From f4e413592f42477cf355fac939c60b8dfaba0a92 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Wed, 7 Jul 2021 12:24:41 +0100 Subject: [PATCH 01/16] add many_size_distribution submodels --- .../fast_many_distributions.rst | 2 +- .../fickian_many_distributions.rst | 2 +- .../particle/size_distribution/index.rst | 4 +- .../full_battery_models/lithium_ion/dfn.py | 82 +++++++++---------- .../particle/size_distribution/__init__.py | 4 +- .../fast_many_distributions.py | 1 + .../fickian_many_distributions.py | 1 + 7 files changed, 49 insertions(+), 47 deletions(-) diff --git a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst index 97f0feed64..4d1facd628 100644 --- a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst +++ b/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst @@ -1,4 +1,4 @@ -Fast Many Size Distributions +#Fast Many Size Distributions ============================ .. autoclass:: pybamm.particle.FastManySizeDistributions diff --git a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst index 7b927b52cc..b88d09e742 100644 --- a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst +++ b/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst @@ -1,4 +1,4 @@ -Fickian Many Size Distributions +#Fickian Many Size Distributions =============================== .. autoclass:: pybamm.particle.FickianManySizeDistributions diff --git a/docs/source/models/submodels/particle/size_distribution/index.rst b/docs/source/models/submodels/particle/size_distribution/index.rst index 1efda8f7cd..0b825f54cf 100644 --- a/docs/source/models/submodels/particle/size_distribution/index.rst +++ b/docs/source/models/submodels/particle/size_distribution/index.rst @@ -6,6 +6,6 @@ Particle Size Distribution base_distribution fickian_single_distribution - fickian_many_distributions + #fickian_many_distributions fast_single_distribution - fast_many_distributions + #fast_many_distributions diff --git a/pybamm/models/full_battery_models/lithium_ion/dfn.py b/pybamm/models/full_battery_models/lithium_ion/dfn.py index 1bea5a821c..12ec7085cc 100644 --- a/pybamm/models/full_battery_models/lithium_ion/dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/dfn.py @@ -76,47 +76,47 @@ def set_interfacial_submodel(self): def set_particle_submodel(self): - if isinstance(self.options["particle"], str): - particle_left = self.options["particle"] - particle_right = self.options["particle"] - else: - particle_left, particle_right = self.options["particle"] - for particle_side, domain in [ - [particle_left, "Negative"], - [particle_right, "Positive"], - ]: - if self.options["particle size"] == "single": - if particle_side == "Fickian diffusion": - self.submodels[ - domain.lower() + " particle" - ] = pybamm.particle.FickianManyParticles(self.param, domain) - elif particle_side in [ - "uniform profile", - "quadratic profile", - "quartic profile", - ]: - self.submodels[ - domain.lower() + " particle" - ] = pybamm.particle.PolynomialManyParticles( - self.param, domain, particle_side - ) - # remove when merging - elif self.options["particle size"] == "distribution": - if particle_side == "Fickian diffusion": - raise pybamm.OptionError( - "Fickian diffusion not yet compatible with" - + " particle size distributions." - ) - elif particle_side in [ - "uniform profile", - "quadratic profile", - "quartic profile", - ]: - self.submodels[ - domain.lower() + " particle" - ] = pybamm.particle.FastManySizeDistributions( - self.param, domain - ) + # if isinstance(self.options["particle"], str): + # particle_left = self.options["particle"] + # particle_right = self.options["particle"] + # else: + # particle_left, particle_right = self.options["particle"] + # for particle_side, domain in [ + # [particle_left, "Negative"], + # [particle_right, "Positive"], + # ]: + # if self.options["particle size"] == "single": + # if particle_side == "Fickian diffusion": + # self.submodels[ + # domain.lower() + " particle" + # ] = pybamm.particle.FickianManyParticles(self.param, domain) + # elif particle_side in [ + # "uniform profile", + # "quadratic profile", + # "quartic profile", + # ]: + # self.submodels[ + # domain.lower() + " particle" + # ] = pybamm.particle.PolynomialManyParticles( + # self.param, domain, particle_side + # ) + # # remove when merging + # elif self.options["particle size"] == "distribution": + # if particle_side == "Fickian diffusion": + # raise pybamm.OptionError( + # "Fickian diffusion not yet compatible with" + # + " particle size distributions." + # ) + # elif particle_side in [ + # "uniform profile", + # "quadratic profile", + # "quartic profile", + # ]: + # self.submodels[ + # domain.lower() + " particle" + # ] = pybamm.particle.FastManySizeDistributions( + # self.param, domain + # ) def set_solid_submodel(self): diff --git a/pybamm/models/submodels/particle/size_distribution/__init__.py b/pybamm/models/submodels/particle/size_distribution/__init__.py index 6ef02d5297..d7e6e4ae06 100644 --- a/pybamm/models/submodels/particle/size_distribution/__init__.py +++ b/pybamm/models/submodels/particle/size_distribution/__init__.py @@ -1,5 +1,5 @@ from .base_distribution import BaseSizeDistribution -from .fickian_many_distributions import FickianManySizeDistributions +#from .fickian_many_distributions import FickianManySizeDistributions from .fickian_single_distribution import FickianSingleSizeDistribution -from .fast_many_distributions import FastManySizeDistributions +#from .fast_many_distributions import FastManySizeDistributions from .fast_single_distribution import FastSingleSizeDistribution diff --git a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py index 6815af81fd..faf377d0d4 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py @@ -3,6 +3,7 @@ # x location of the electrode, with fast diffusion (uniform concentration in r) # within particles # +# import pybamm from .base_distribution import BaseSizeDistribution diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py index 2d80455c70..ca4d1e29cf 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py @@ -2,6 +2,7 @@ # Class for many particle-size distributions, one distribution at every # x location of the electrode, and Fickian diffusion within each particle # +# import pybamm from .base_distribution import BaseSizeDistribution From 808d1166e7f890fac5ba53b574f35e0f00621237 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Wed, 7 Jul 2021 12:49:13 +0100 Subject: [PATCH 02/16] fix typos --- .../particle/size_distribution/fast_many_distributions.py | 3 +-- .../particle/size_distribution/fickian_many_distributions.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py index faf377d0d4..d4e9632064 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py @@ -3,7 +3,6 @@ # x location of the electrode, with fast diffusion (uniform concentration in r) # within particles # -# import pybamm from .base_distribution import BaseSizeDistribution @@ -160,7 +159,7 @@ def set_events(self, variables): self.events.append( pybamm.Event( - "Minumum " + self.domain.lower() + " particle surface concentration", + "Minimum " + self.domain.lower() + " particle surface concentration", pybamm.min(c_s_surf_distribution) - tol, pybamm.EventType.TERMINATION, ) diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py index ca4d1e29cf..179cead7aa 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py @@ -2,7 +2,6 @@ # Class for many particle-size distributions, one distribution at every # x location of the electrode, and Fickian diffusion within each particle # -# import pybamm from .base_distribution import BaseSizeDistribution @@ -240,7 +239,7 @@ def set_events(self, variables): self.events.append( pybamm.Event( - "Minumum " + self.domain.lower() + " particle surface concentration", + "Minimum " + self.domain.lower() + " particle surface concentration", pybamm.min(c_s_surf_distribution) - tol, pybamm.EventType.TERMINATION, ) From 3aa189eec08b6021ce43031051c01779b0c57fe2 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Wed, 7 Jul 2021 18:08:53 +0100 Subject: [PATCH 03/16] add particle size domains to domain_size --- pybamm/expression_tree/symbol.py | 2 ++ pybamm/models/full_battery_models/lithium_ion/dfn.py | 7 ++++--- .../size_distribution/fickian_many_distributions.py | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pybamm/expression_tree/symbol.py b/pybamm/expression_tree/symbol.py index 680c0077bf..ca98aca539 100644 --- a/pybamm/expression_tree/symbol.py +++ b/pybamm/expression_tree/symbol.py @@ -32,6 +32,8 @@ def domain_size(domain): "positive electrode": 17, "working electrode": 19, "working particle": 23, + "negative particle size": 29, + "positive particle size": 31, } if isinstance(domain, str): domain = [domain] diff --git a/pybamm/models/full_battery_models/lithium_ion/dfn.py b/pybamm/models/full_battery_models/lithium_ion/dfn.py index 9f71a75ce5..7c627513bc 100644 --- a/pybamm/models/full_battery_models/lithium_ion/dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/dfn.py @@ -102,9 +102,10 @@ def set_particle_submodel(self): ) elif self.options["particle size"] == "distribution": if particle_side == "Fickian diffusion": - raise pybamm.OptionError( - "Fickian diffusion not yet compatible with" - + " particle size distributions." + self.submodels[ + domain.lower() + " particle" + ] = pybamm.particle.FickianManySizeDistributions( + self.param, domain ) elif particle_side in [ "uniform profile", diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py index 179cead7aa..2890ab4d81 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py @@ -39,7 +39,7 @@ def get_fundamental_variables(self): }, bounds=(0, 1), ) - R_variable = pybamm.standard_spatial_vars.R_n + R = pybamm.standard_spatial_vars.R_n elif self.domain == "Positive": # distribution variables @@ -69,7 +69,7 @@ def get_fundamental_variables(self): f_v_dist = variables[ self.domain + " volume-weighted particle-size distribution" ] - c_s = pybamm.Integral(f_v_dist * c_s_distribution, R_variable) + c_s = pybamm.Integral(f_v_dist * c_s_distribution, R) c_s_xav = pybamm.x_average(c_s) variables.update(self._get_standard_concentration_variables(c_s, c_s_xav)) From 75d9e7766d853c95b19d3e2786c5b25ed09b7698 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 9 Aug 2021 14:05:15 +0100 Subject: [PATCH 04/16] add size distribution output vars in x --- .../size_distribution/base_distribution.py | 436 +++++++++--------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/pybamm/models/submodels/particle/size_distribution/base_distribution.py b/pybamm/models/submodels/particle/size_distribution/base_distribution.py index 379480d503..69189eded9 100644 --- a/pybamm/models/submodels/particle/size_distribution/base_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/base_distribution.py @@ -23,242 +23,242 @@ class BaseSizeDistribution(BaseParticle): def __init__(self, param, domain): super().__init__(param, domain) - def _get_distribution_variables(self, R): - """ - Forms the particle-size distributions and mean radii given a spatial variable - R. The domains of R will be different depending on the submodel, e.g. for the - `SingleSizeDistribution` classes R does not have an "electrode" domain. - """ - if self.domain == "Negative": - R_typ = self.param.R_n_typ - # Particle-size distribution (area-weighted) - f_a_dist = self.param.f_a_dist_n(R) - elif self.domain == "Positive": - R_typ = self.param.R_p_typ - # Particle-size distribution (area-weighted) - f_a_dist = self.param.f_a_dist_p(R) + # def _get_distribution_variables(self, R): + # """ + # Forms the particle-size distributions and mean radii given a spatial variable + # R. The domains of R will be different depending on the submodel, e.g. for the + # `SingleSizeDistribution` classes R does not have an "electrode" domain. + # """ + # if self.domain == "Negative": + # R_typ = self.param.R_n_typ + # # Particle-size distribution (area-weighted) + # f_a_dist = self.param.f_a_dist_n(R) + # elif self.domain == "Positive": + # R_typ = self.param.R_p_typ + # # Particle-size distribution (area-weighted) + # f_a_dist = self.param.f_a_dist_p(R) - # Ensure the distribution is normalised, irrespective of discretisation - # or user input - f_a_dist = f_a_dist / pybamm.Integral(f_a_dist, R) + # # Ensure the distribution is normalised, irrespective of discretisation + # # or user input + # f_a_dist = f_a_dist / pybamm.Integral(f_a_dist, R) - # Volume-weighted particle-size distribution - f_v_dist = R * f_a_dist / pybamm.Integral(R * f_a_dist, R) + # # Volume-weighted particle-size distribution + # f_v_dist = R * f_a_dist / pybamm.Integral(R * f_a_dist, R) - # Number-based particle-size distribution - f_num_dist = (f_a_dist / R ** 2) / pybamm.Integral(f_a_dist / R ** 2, R) + # # Number-based particle-size distribution + # f_num_dist = (f_a_dist / R ** 2) / pybamm.Integral(f_a_dist / R ** 2, R) - # True mean radii and standard deviations, calculated from the f_a_dist that - # was given - R_num_mean = pybamm.Integral(R * f_num_dist, R) - R_a_mean = pybamm.Integral(R * f_a_dist, R) - R_v_mean = pybamm.Integral(R * f_v_dist, R) - sd_num = pybamm.sqrt(pybamm.Integral((R - R_num_mean) ** 2 * f_num_dist, R)) - sd_a = pybamm.sqrt(pybamm.Integral((R - R_a_mean) ** 2 * f_a_dist, R)) - sd_v = pybamm.sqrt(pybamm.Integral((R - R_v_mean) ** 2 * f_v_dist, R)) + # # True mean radii and standard deviations, calculated from the f_a_dist that + # # was given + # R_num_mean = pybamm.Integral(R * f_num_dist, R) + # R_a_mean = pybamm.Integral(R * f_a_dist, R) + # R_v_mean = pybamm.Integral(R * f_v_dist, R) + # sd_num = pybamm.sqrt(pybamm.Integral((R - R_num_mean) ** 2 * f_num_dist, R)) + # sd_a = pybamm.sqrt(pybamm.Integral((R - R_a_mean) ** 2 * f_a_dist, R)) + # sd_v = pybamm.sqrt(pybamm.Integral((R - R_v_mean) ** 2 * f_v_dist, R)) - # X-average the means and standard deviations to give scalars - # (to remove the "electrode" domain, if present) - R_num_mean = pybamm.x_average(R_num_mean) - R_a_mean = pybamm.x_average(R_a_mean) - R_v_mean = pybamm.x_average(R_v_mean) - sd_num = pybamm.x_average(sd_num) - sd_a = pybamm.x_average(sd_a) - sd_v = pybamm.x_average(sd_v) + # # X-average the means and standard deviations to give scalars + # # (to remove the "electrode" domain, if present) + # R_num_mean = pybamm.x_average(R_num_mean) + # R_a_mean = pybamm.x_average(R_a_mean) + # R_v_mean = pybamm.x_average(R_v_mean) + # sd_num = pybamm.x_average(sd_num) + # sd_a = pybamm.x_average(sd_a) + # sd_v = pybamm.x_average(sd_v) - # X-averaged distributions, or broadcast - if R.auxiliary_domains["secondary"] == [self.domain.lower() + " electrode"]: - f_a_dist_xav = pybamm.x_average(f_a_dist) - f_v_dist_xav = pybamm.x_average(f_v_dist) - f_num_dist_xav = pybamm.x_average(f_num_dist) - else: - f_a_dist_xav = f_a_dist - f_v_dist_xav = f_v_dist - f_num_dist_xav = f_num_dist + # # X-averaged distributions, or broadcast + # if R.auxiliary_domains["secondary"] == [self.domain.lower() + " electrode"]: + # f_a_dist_xav = pybamm.x_average(f_a_dist) + # f_v_dist_xav = pybamm.x_average(f_v_dist) + # f_num_dist_xav = pybamm.x_average(f_num_dist) + # else: + # f_a_dist_xav = f_a_dist + # f_v_dist_xav = f_v_dist + # f_num_dist_xav = f_num_dist - # broadcast - f_a_dist = pybamm.SecondaryBroadcast( - f_a_dist_xav, [self.domain.lower() + " electrode"] - ) - f_v_dist = pybamm.SecondaryBroadcast( - f_v_dist_xav, [self.domain.lower() + " electrode"] - ) - f_num_dist = pybamm.SecondaryBroadcast( - f_num_dist_xav, [self.domain.lower() + " electrode"] - ) + # # broadcast + # f_a_dist = pybamm.SecondaryBroadcast( + # f_a_dist_xav, [self.domain.lower() + " electrode"] + # ) + # f_v_dist = pybamm.SecondaryBroadcast( + # f_v_dist_xav, [self.domain.lower() + " electrode"] + # ) + # f_num_dist = pybamm.SecondaryBroadcast( + # f_num_dist_xav, [self.domain.lower() + " electrode"] + # ) - variables = { - self.domain + " particle sizes": R, - self.domain + " particle sizes [m]": R * R_typ, - self.domain + " area-weighted particle-size" - + " distribution": f_a_dist, - self.domain + " area-weighted particle-size" - + " distribution [m-1]": f_a_dist / R_typ, - self.domain + " volume-weighted particle-size" - + " distribution": f_v_dist, - self.domain + " volume-weighted particle-size" - + " distribution [m-1]": f_v_dist / R_typ, - self.domain + " number-based particle-size" - + " distribution": f_num_dist, - self.domain + " number-based particle-size" - + " distribution [m-1]": f_num_dist / R_typ, - self.domain + " area-weighted" - + " mean particle radius": R_a_mean, - self.domain + " area-weighted" - + " mean particle radius [m]": R_a_mean * R_typ, - self.domain + " volume-weighted" - + " mean particle radius": R_v_mean, - self.domain + " volume-weighted" - + " mean particle radius [m]": R_v_mean * R_typ, - self.domain + " number-based" - + " mean particle radius": R_num_mean, - self.domain + " number-based" - + " mean particle radius [m]": R_num_mean * R_typ, - self.domain + " area-weighted particle-size" - + " standard deviation": sd_a, - self.domain + " area-weighted particle-size" - + " standard deviation [m]": sd_a * R_typ, - self.domain + " volume-weighted particle-size" - + " standard deviation": sd_v, - self.domain + " volume-weighted particle-size" - + " standard deviation [m]": sd_v * R_typ, - self.domain + " number-based particle-size" - + " standard deviation": sd_num, - self.domain + " number-based particle-size" - + " standard deviation [m]": sd_num * R_typ, - # X-averaged distributions - "X-averaged " + self.domain.lower() + - " area-weighted particle-size distribution": f_a_dist_xav, - "X-averaged " + self.domain.lower() + - " area-weighted particle-size distribution [m-1]": f_a_dist_xav / R_typ, - "X-averaged " + self.domain.lower() + - " volume-weighted particle-size distribution": f_v_dist_xav, - "X-averaged " + self.domain.lower() + - " volume-weighted particle-size distribution [m-1]": f_v_dist_xav / R_typ, - "X-averaged " + self.domain.lower() + - " number-based particle-size distribution": f_num_dist_xav, - "X-averaged " + self.domain.lower() + - " number-based particle-size distribution [m-1]": f_num_dist_xav / R_typ, - } + # variables = { + # self.domain + " particle sizes": R, + # self.domain + " particle sizes [m]": R * R_typ, + # self.domain + " area-weighted particle-size" + # + " distribution": f_a_dist, + # self.domain + " area-weighted particle-size" + # + " distribution [m-1]": f_a_dist / R_typ, + # self.domain + " volume-weighted particle-size" + # + " distribution": f_v_dist, + # self.domain + " volume-weighted particle-size" + # + " distribution [m-1]": f_v_dist / R_typ, + # self.domain + " number-based particle-size" + # + " distribution": f_num_dist, + # self.domain + " number-based particle-size" + # + " distribution [m-1]": f_num_dist / R_typ, + # self.domain + " area-weighted" + # + " mean particle radius": R_a_mean, + # self.domain + " area-weighted" + # + " mean particle radius [m]": R_a_mean * R_typ, + # self.domain + " volume-weighted" + # + " mean particle radius": R_v_mean, + # self.domain + " volume-weighted" + # + " mean particle radius [m]": R_v_mean * R_typ, + # self.domain + " number-based" + # + " mean particle radius": R_num_mean, + # self.domain + " number-based" + # + " mean particle radius [m]": R_num_mean * R_typ, + # self.domain + " area-weighted particle-size" + # + " standard deviation": sd_a, + # self.domain + " area-weighted particle-size" + # + " standard deviation [m]": sd_a * R_typ, + # self.domain + " volume-weighted particle-size" + # + " standard deviation": sd_v, + # self.domain + " volume-weighted particle-size" + # + " standard deviation [m]": sd_v * R_typ, + # self.domain + " number-based particle-size" + # + " standard deviation": sd_num, + # self.domain + " number-based particle-size" + # + " standard deviation [m]": sd_num * R_typ, + # # X-averaged distributions + # "X-averaged " + self.domain.lower() + + # " area-weighted particle-size distribution": f_a_dist_xav, + # "X-averaged " + self.domain.lower() + + # " area-weighted particle-size distribution [m-1]": f_a_dist_xav / R_typ, + # "X-averaged " + self.domain.lower() + + # " volume-weighted particle-size distribution": f_v_dist_xav, + # "X-averaged " + self.domain.lower() + + # " volume-weighted particle-size distribution [m-1]": f_v_dist_xav / R_typ, + # "X-averaged " + self.domain.lower() + + # " number-based particle-size distribution": f_num_dist_xav, + # "X-averaged " + self.domain.lower() + + # " number-based particle-size distribution [m-1]": f_num_dist_xav / R_typ, + # } - return variables + # return variables - def _get_standard_concentration_distribution_variables(self, c_s): - """ - Forms standard concentration variables that depend on particle size R given - the fundamental concentration distribution variable c_s from the submodel. - """ - if self.domain == "Negative": - c_scale = self.param.c_n_max - elif self.domain == "Positive": - c_scale = self.param.c_p_max + # def _get_standard_concentration_distribution_variables(self, c_s): + # """ + # Forms standard concentration variables that depend on particle size R given + # the fundamental concentration distribution variable c_s from the submodel. + # """ + # if self.domain == "Negative": + # c_scale = self.param.c_n_max + # elif self.domain == "Positive": + # c_scale = self.param.c_p_max - # Broadcast and x-average when necessary - if c_s.domain == [ - self.domain.lower() + " particle size" - ] and c_s.auxiliary_domains["secondary"] != [ - self.domain.lower() + " electrode" - ]: - # X-avg concentration distribution - c_s_xav_distribution = pybamm.PrimaryBroadcast( - c_s, [self.domain.lower() + " particle"] - ) + # # Broadcast and x-average when necessary + # if c_s.domain == [ + # self.domain.lower() + " particle size" + # ] and c_s.auxiliary_domains["secondary"] != [ + # self.domain.lower() + " electrode" + # ]: + # # X-avg concentration distribution + # c_s_xav_distribution = pybamm.PrimaryBroadcast( + # c_s, [self.domain.lower() + " particle"] + # ) - # Surface concentration distribution variables - c_s_surf_xav_distribution = c_s - c_s_surf_distribution = pybamm.SecondaryBroadcast( - c_s_surf_xav_distribution, [self.domain.lower() + " electrode"] - ) + # # Surface concentration distribution variables + # c_s_surf_xav_distribution = c_s + # c_s_surf_distribution = pybamm.SecondaryBroadcast( + # c_s_surf_xav_distribution, [self.domain.lower() + " electrode"] + # ) - # Concentration distribution in all domains. - # NOTE: currently variables can only have 3 domains, so current collector - # is excluded, i.e. pushed off domain list - c_s_distribution = pybamm.PrimaryBroadcast( - c_s_surf_distribution, [self.domain.lower() + " particle"] - ) - elif c_s.domain == [self.domain.lower() + " particle"] and ( - c_s.auxiliary_domains["tertiary"] != [self.domain.lower() + " electrode"] - ): - # X-avg concentration distribution - c_s_xav_distribution = c_s + # # Concentration distribution in all domains. + # # NOTE: currently variables can only have 3 domains, so current collector + # # is excluded, i.e. pushed off domain list + # c_s_distribution = pybamm.PrimaryBroadcast( + # c_s_surf_distribution, [self.domain.lower() + " particle"] + # ) + # elif c_s.domain == [self.domain.lower() + " particle"] and ( + # c_s.auxiliary_domains["tertiary"] != [self.domain.lower() + " electrode"] + # ): + # # X-avg concentration distribution + # c_s_xav_distribution = c_s - # Surface concentration distribution variables - c_s_surf_xav_distribution = pybamm.surf(c_s_xav_distribution) - c_s_surf_distribution = pybamm.SecondaryBroadcast( - c_s_surf_xav_distribution, [self.domain.lower() + " electrode"] - ) + # # Surface concentration distribution variables + # c_s_surf_xav_distribution = pybamm.surf(c_s_xav_distribution) + # c_s_surf_distribution = pybamm.SecondaryBroadcast( + # c_s_surf_xav_distribution, [self.domain.lower() + " electrode"] + # ) - # Concentration distribution in all domains. - # NOTE: requires broadcast to "tertiary" domain, which is - # not implemented. Fill with zeros instead as placeholder - c_s_distribution = pybamm.FullBroadcast( - 0, - [self.domain.lower() + " particle"], - { - "secondary": self.domain.lower() + " particle size", - "tertiary": self.domain.lower() + " electrode", - }, - ) - elif c_s.domain == [ - self.domain.lower() + " particle size" - ] and c_s.auxiliary_domains["secondary"] == [ - self.domain.lower() + " electrode" - ]: - # Surface concentration distribution variables - c_s_surf_distribution = c_s - c_s_surf_xav_distribution = pybamm.x_average(c_s) + # # Concentration distribution in all domains. + # # NOTE: requires broadcast to "tertiary" domain, which is + # # not implemented. Fill with zeros instead as placeholder + # c_s_distribution = pybamm.FullBroadcast( + # 0, + # [self.domain.lower() + " particle"], + # { + # "secondary": self.domain.lower() + " particle size", + # "tertiary": self.domain.lower() + " electrode", + # }, + # ) + # elif c_s.domain == [ + # self.domain.lower() + " particle size" + # ] and c_s.auxiliary_domains["secondary"] == [ + # self.domain.lower() + " electrode" + # ]: + # # Surface concentration distribution variables + # c_s_surf_distribution = c_s + # c_s_surf_xav_distribution = pybamm.x_average(c_s) - # X-avg concentration distribution - c_s_xav_distribution = pybamm.PrimaryBroadcast( - c_s_surf_xav_distribution, [self.domain.lower() + " particle"] - ) + # # X-avg concentration distribution + # c_s_xav_distribution = pybamm.PrimaryBroadcast( + # c_s_surf_xav_distribution, [self.domain.lower() + " particle"] + # ) - # Concentration distribution in all domains. - # NOTE: currently variables can only have 3 domains, so current collector - # is excluded, i.e. pushed off domain list - c_s_distribution = pybamm.PrimaryBroadcast( - c_s_surf_distribution, [self.domain.lower() + " particle"] - ) - else: - c_s_distribution = c_s + # # Concentration distribution in all domains. + # # NOTE: currently variables can only have 3 domains, so current collector + # # is excluded, i.e. pushed off domain list + # c_s_distribution = pybamm.PrimaryBroadcast( + # c_s_surf_distribution, [self.domain.lower() + " particle"] + # ) + # else: + # c_s_distribution = c_s - # x-average the *tertiary* domain. Do manually using Integral - x = pybamm.SpatialVariable("x", domain=[self.domain.lower() + " electrode"]) - v = pybamm.ones_like(c_s) - l = pybamm.Integral(v, x) - c_s_xav_distribution = pybamm.Integral(c_s, x) / l + # # x-average the *tertiary* domain. Do manually using Integral + # x = pybamm.SpatialVariable("x", domain=[self.domain.lower() + " electrode"]) + # v = pybamm.ones_like(c_s) + # l = pybamm.Integral(v, x) + # c_s_xav_distribution = pybamm.Integral(c_s, x) / l - # Surface concentration distribution variables - c_s_surf_distribution = pybamm.surf(c_s) - c_s_surf_xav_distribution = pybamm.x_average(c_s_surf_distribution) + # # Surface concentration distribution variables + # c_s_surf_distribution = pybamm.surf(c_s) + # c_s_surf_xav_distribution = pybamm.x_average(c_s_surf_distribution) - variables = { - self.domain - + " particle concentration distribution": c_s_distribution, - self.domain - + " particle concentration distribution " - + "[mol.m-3]": c_scale * c_s_distribution, - "X-averaged " - + self.domain.lower() - + " particle concentration distribution": c_s_xav_distribution, - "X-averaged " - + self.domain.lower() - + " particle concentration distribution " - + "[mol.m-3]": c_scale * c_s_xav_distribution, - "X-averaged " - + self.domain.lower() - + " particle surface concentration" - + " distribution": c_s_surf_xav_distribution, - "X-averaged " - + self.domain.lower() - + " particle surface concentration distribution " - + "[mol.m-3]": c_scale * c_s_surf_xav_distribution, - self.domain - + " particle surface concentration" - + " distribution": c_s_surf_distribution, - self.domain - + " particle surface concentration" - + " distribution [mol.m-3]": c_scale * c_s_surf_distribution, - } - return variables + # variables = { + # self.domain + # + " particle concentration distribution": c_s_distribution, + # self.domain + # + " particle concentration distribution " + # + "[mol.m-3]": c_scale * c_s_distribution, + # "X-averaged " + # + self.domain.lower() + # + " particle concentration distribution": c_s_xav_distribution, + # "X-averaged " + # + self.domain.lower() + # + " particle concentration distribution " + # + "[mol.m-3]": c_scale * c_s_xav_distribution, + # "X-averaged " + # + self.domain.lower() + # + " particle surface concentration" + # + " distribution": c_s_surf_xav_distribution, + # "X-averaged " + # + self.domain.lower() + # + " particle surface concentration distribution " + # + "[mol.m-3]": c_scale * c_s_surf_xav_distribution, + # self.domain + # + " particle surface concentration" + # + " distribution": c_s_surf_distribution, + # self.domain + # + " particle surface concentration" + # + " distribution [mol.m-3]": c_scale * c_s_surf_distribution, + # } + # return variables From 9673d2c5f7ea37a57c4247e57dd44ce400122139 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 9 Aug 2021 16:23:58 +0100 Subject: [PATCH 05/16] fix size distribution submodels --- .../size_distribution/base_distribution.py | 37 ++++----- .../fast_many_distributions.py | 23 ------ .../fickian_many_distributions.py | 79 +++++-------------- .../fickian_single_distribution.py | 6 +- 4 files changed, 40 insertions(+), 105 deletions(-) diff --git a/pybamm/models/submodels/particle/size_distribution/base_distribution.py b/pybamm/models/submodels/particle/size_distribution/base_distribution.py index 379480d503..d452032557 100644 --- a/pybamm/models/submodels/particle/size_distribution/base_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/base_distribution.py @@ -171,8 +171,6 @@ def _get_standard_concentration_distribution_variables(self, c_s): ) # Concentration distribution in all domains. - # NOTE: currently variables can only have 3 domains, so current collector - # is excluded, i.e. pushed off domain list c_s_distribution = pybamm.PrimaryBroadcast( c_s_surf_distribution, [self.domain.lower() + " particle"] ) @@ -189,15 +187,8 @@ def _get_standard_concentration_distribution_variables(self, c_s): ) # Concentration distribution in all domains. - # NOTE: requires broadcast to "tertiary" domain, which is - # not implemented. Fill with zeros instead as placeholder - c_s_distribution = pybamm.FullBroadcast( - 0, - [self.domain.lower() + " particle"], - { - "secondary": self.domain.lower() + " particle size", - "tertiary": self.domain.lower() + " electrode", - }, + c_s_distribution = pybamm.TertiaryBroadcast( + c_s_xav_distribution, [self.domain.lower() + " electrode"] ) elif c_s.domain == [ self.domain.lower() + " particle size" @@ -214,19 +205,29 @@ def _get_standard_concentration_distribution_variables(self, c_s): ) # Concentration distribution in all domains. - # NOTE: currently variables can only have 3 domains, so current collector - # is excluded, i.e. pushed off domain list c_s_distribution = pybamm.PrimaryBroadcast( c_s_surf_distribution, [self.domain.lower() + " particle"] ) else: c_s_distribution = c_s - # x-average the *tertiary* domain. Do manually using Integral - x = pybamm.SpatialVariable("x", domain=[self.domain.lower() + " electrode"]) - v = pybamm.ones_like(c_s) - l = pybamm.Integral(v, x) - c_s_xav_distribution = pybamm.Integral(c_s, x) / l + # x-average the *tertiary* domain. + # NOTE: not yet implemented + # x = pybamm.SpatialVariable( + # "x", domain=[self.domain.lower() + " electrode"] + # ) + # v = pybamm.ones_like(c_s) + # l = pybamm.Integral(v, x) + # c_s_xav_distribution = pybamm.Integral(c_s, x) / l + c_s_xav_distribution = pybamm.FullBroadcast( + 0, + [self.domain.lower() + " particle"], + { + "secondary": self.domain.lower() + " particle size", + "tertiary": self.domain.lower() + " electrode", + "quaternary": "current collector" + }, + ) # Surface concentration distribution variables c_s_surf_distribution = pybamm.surf(c_s) diff --git a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py index d4e9632064..abe0eef576 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py @@ -149,26 +149,3 @@ def set_initial_conditions(self, variables): c_init = self.param.c_p_init(x_p) self.initial_conditions = {c_s_surf_distribution: c_init} - - def set_events(self, variables): - c_s_surf_distribution = variables[ - self.domain - + " particle surface concentration distribution" - ] - tol = 1e-4 - - self.events.append( - pybamm.Event( - "Minimum " + self.domain.lower() + " particle surface concentration", - pybamm.min(c_s_surf_distribution) - tol, - pybamm.EventType.TERMINATION, - ) - ) - - self.events.append( - pybamm.Event( - "Maximum " + self.domain.lower() + " particle surface concentration", - (1 - tol) - pybamm.max(c_s_surf_distribution), - pybamm.EventType.TERMINATION, - ) - ) diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py index 2890ab4d81..ecf9c077a9 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py @@ -36,6 +36,7 @@ def get_fundamental_variables(self): auxiliary_domains={ "secondary": "negative particle size", "tertiary": "negative electrode", + "quaternary": "current collector" }, bounds=(0, 1), ) @@ -49,6 +50,7 @@ def get_fundamental_variables(self): auxiliary_domains={ "secondary": "positive particle size", "tertiary": "positive electrode", + "quaternary": "current collector" }, bounds=(0, 1), ) @@ -79,54 +81,33 @@ def get_coupled_variables(self, variables): c_s_distribution = variables[ self.domain + " particle concentration distribution" ] - R_spatial_variable = variables[self.domain + " particle sizes"] - R = pybamm.PrimaryBroadcast( - R_spatial_variable, [self.domain.lower() + " particle"] - ) + R = variables[self.domain + " particle sizes"] T_k = variables[self.domain + " electrode temperature"] - # Variables can currently only have 3 domains, so remove "current collector" - # from T_k. If T_k was broadcast to "electrode", take orphan, average - # over "current collector", then broadcast to "particle", "particle-size" - # and "electrode" - if isinstance(T_k, pybamm.Broadcast): - T_k = pybamm.yz_average(T_k.orphans[0]) - T_k = pybamm.FullBroadcast( - T_k, self.domain.lower() + " particle", - { - "secondary": self.domain.lower() + " particle size", - "tertiary": self.domain.lower() + " electrode" - } - ) - else: - # broadcast to "particle size" domain then again into "particle" - T_k = pybamm.PrimaryBroadcast( - T_k, - [self.domain.lower() + " particle size"], - ) - T_k = pybamm.PrimaryBroadcast( - T_k, [self.domain.lower() + " particle"], - ) + # broadcast to "particle size" domain then again into "particle" + T_k = pybamm.PrimaryBroadcast( + T_k, + [self.domain.lower() + " particle size"], + ) + T_k = pybamm.PrimaryBroadcast( + T_k, [self.domain.lower() + " particle"], + ) if self.domain == "Negative": N_s_distribution = ( -self.param.D_n(c_s_distribution, T_k) * pybamm.grad(c_s_distribution) - / R ) - f_a_dist = self.param.f_a_dist_n(R_spatial_variable) + f_a_dist = self.param.f_a_dist_n(R) elif self.domain == "Positive": N_s_distribution = ( -self.param.D_p(c_s_distribution, T_k) * pybamm.grad(c_s_distribution) - / R ) - f_a_dist = self.param.f_a_dist_p(R_spatial_variable) + f_a_dist = self.param.f_a_dist_p(R) - # Standard R-averaged flux variables - # Use R_spatial_variable, since "R" is a broadcast - N_s = pybamm.Integral(f_a_dist * N_s_distribution, R_spatial_variable) + N_s = pybamm.Integral(f_a_dist * N_s_distribution, R) variables.update(self._get_standard_flux_variables(N_s, N_s)) # Standard distribution flux variables (R-dependent) @@ -153,13 +134,13 @@ def set_rhs(self, variables): self.rhs = { c_s_distribution: -(1 / self.param.C_n) * pybamm.div(N_s_distribution) - / R + / R ** 2 } elif self.domain == "Positive": self.rhs = { c_s_distribution: -(1 / self.param.C_p) * pybamm.div(N_s_distribution) - / R + / R ** 2 } def set_boundary_conditions(self, variables): @@ -173,7 +154,7 @@ def set_boundary_conditions(self, variables): j_distribution = variables[ self.domain + " electrode interfacial current density distribution" ] - R_variable = variables[self.domain + " particle size"] + R = variables[self.domain + " particle sizes"] # Extract T and broadcast to particle size domain T_k = variables[self.domain + " electrode temperature"] @@ -185,7 +166,7 @@ def set_boundary_conditions(self, variables): if self.domain == "Negative": rbc = ( -self.param.C_n - * R_variable + * R * j_distribution / self.param.a_R_n / self.param.D_n(c_s_surf_distribution, T_k) @@ -194,7 +175,7 @@ def set_boundary_conditions(self, variables): elif self.domain == "Positive": rbc = ( -self.param.C_p - * R_variable + * R * j_distribution / self.param.a_R_p / self.param.gamma_p @@ -230,25 +211,3 @@ def set_initial_conditions(self, variables): c_init = self.param.c_p_init(x_p) self.initial_conditions = {c_s_distribution: c_init} - - def set_events(self, variables): - c_s_surf_distribution = variables[ - self.domain + " particle surface concentration distribution" - ] - tol = 1e-5 - - self.events.append( - pybamm.Event( - "Minimum " + self.domain.lower() + " particle surface concentration", - pybamm.min(c_s_surf_distribution) - tol, - pybamm.EventType.TERMINATION, - ) - ) - - self.events.append( - pybamm.Event( - "Maximum " + self.domain.lower() + " particle surface concentration", - (1 - tol) - pybamm.max(c_s_surf_distribution), - pybamm.EventType.TERMINATION, - ) - ) diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py b/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py index 84d71e32e9..09e5261a7e 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py @@ -97,7 +97,7 @@ def get_coupled_variables(self, variables): c_s_xav_distribution = variables[ "X-averaged " + self.domain.lower() + " particle concentration distribution" ] - R_spatial_variable = variables[self.domain + " particle sizes"] + R = variables[self.domain + " particle sizes"] # broadcast to "particle size" domain then again into "particle" T_k_xav = pybamm.PrimaryBroadcast( @@ -129,9 +129,7 @@ def get_coupled_variables(self, variables): f_a_dist, [self.domain.lower() + " particle"], ) - # must use "R_spatial_variable" as integration variable, since "R" is a - # broadcast - N_s_xav = pybamm.Integral(f_a_dist * N_s_xav_distribution, R_spatial_variable) + N_s_xav = pybamm.Integral(f_a_dist * N_s_xav_distribution, R) N_s = pybamm.SecondaryBroadcast(N_s_xav, [self.domain.lower() + " electrode"]) variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) From e4ac212ce50970108fd32d066aa0f8267d10c220 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 9 Aug 2021 18:31:32 +0100 Subject: [PATCH 06/16] clean up distribution flux variables --- .../size_distribution/base_distribution.py | 65 +++++++++++++++++-- .../fast_many_distributions.py | 35 +++++----- .../fast_single_distribution.py | 31 ++++----- .../fickian_many_distributions.py | 39 +++++------ .../fickian_single_distribution.py | 30 ++++----- 5 files changed, 116 insertions(+), 84 deletions(-) diff --git a/pybamm/models/submodels/particle/size_distribution/base_distribution.py b/pybamm/models/submodels/particle/size_distribution/base_distribution.py index d452032557..e1e82e1168 100644 --- a/pybamm/models/submodels/particle/size_distribution/base_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/base_distribution.py @@ -212,13 +212,7 @@ def _get_standard_concentration_distribution_variables(self, c_s): c_s_distribution = c_s # x-average the *tertiary* domain. - # NOTE: not yet implemented - # x = pybamm.SpatialVariable( - # "x", domain=[self.domain.lower() + " electrode"] - # ) - # v = pybamm.ones_like(c_s) - # l = pybamm.Integral(v, x) - # c_s_xav_distribution = pybamm.Integral(c_s, x) / l + # NOTE: not yet implemented. Fill with zeros c_s_xav_distribution = pybamm.FullBroadcast( 0, [self.domain.lower() + " particle"], @@ -263,3 +257,60 @@ def _get_standard_concentration_distribution_variables(self, c_s): } return variables + def _get_standard_flux_distribution_variables(self, N_s): + """ + Forms standard flux variables that depend on particle size R given + the flux variable N_s from the distribution submodel. + """ + + if [self.domain.lower() + " electrode"] in N_s.auxiliary_domains.values(): + # N_s depends on x + + N_s_distribution = N_s + # x-av the *tertiary* domain + # NOTE: not yet implemented. Fill with zeros instead + N_s_xav_distribution = pybamm.FullBroadcast( + 0, + [self.domain.lower() + " particle"], + { + "secondary": self.domain.lower() + " particle size", + "tertiary": self.domain.lower() + " electrode", + "quaternary": "current collector" + }, + ) + elif isinstance(N_s, pybamm.Scalar): + # N_s is a constant (zero), as in "fast" submodels + + N_s_distribution = pybamm.FullBroadcastToEdges( + 0, + [self.domain.lower() + " particle"], + auxiliary_domains={ + "secondary": self.domain.lower() + " particle size", + "tertiary": self.domain.lower() + " electrode", + "quaternary": "current collector", + }, + ) + N_s_xav_distribution = pybamm.FullBroadcastToEdges( + 0, + [self.domain.lower() + " particle"], + auxiliary_domains={ + "secondary": self.domain.lower() + " particle size", + "tertiary": "current collector", + }, + ) + else: + N_s_xav_distribution = N_s + N_s_distribution = pybamm.TertiaryBroadcast( + N_s, [self.domain.lower() + " electrode"] + ) + + variables = { + "X-averaged " + + self.domain.lower() + + " particle flux distribution": N_s_xav_distribution, + self.domain + + " particle flux distribution": N_s_distribution, + } + + return variables + diff --git a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py index abe0eef576..9262d42ca2 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py @@ -58,30 +58,20 @@ def get_fundamental_variables(self): ) R = pybamm.standard_spatial_vars.R_p - # Distribution variables variables = self._get_distribution_variables(R) - # Flux variables (zero) - N_s = pybamm.FullBroadcastToEdges( - 0, - [self.domain.lower() + " particle"], - auxiliary_domains={ - "secondary": self.domain.lower() + " electrode", - "tertiary": "current collector", - }, - ) - N_s_xav = pybamm.FullBroadcast( - 0, self.domain.lower() + " electrode", "current collector" - ) - - # Standard concentration distribution variables (R-dependent) + # Standard concentration distribution variables (size-dependent) variables.update( self._get_standard_concentration_distribution_variables( c_s_surf_distribution ) ) + # Flux variables (size-dependent) + variables.update( + self._get_standard_flux_distribution_variables(pybamm.Scalar(0)) + ) - # Standard R-averaged variables. Average concentrations using + # Standard size-averaged variables. Average concentrations using # the volume-weighted distribution since they are volume-based # quantities. Necessary for output variables "Total lithium in # negative electrode [mol]", etc, to be calculated correctly @@ -94,6 +84,19 @@ def get_fundamental_variables(self): ) c_s_xav = pybamm.x_average(c_s) variables.update(self._get_standard_concentration_variables(c_s, c_s_xav)) + + # Size-averaged flux variables + N_s = pybamm.FullBroadcastToEdges( + 0, + [self.domain.lower() + " particle"], + auxiliary_domains={ + "secondary": self.domain.lower() + " electrode", + "tertiary": "current collector", + }, + ) + N_s_xav = pybamm.FullBroadcastToEdges( + 0, self.domain.lower() + " particle", "current collector" + ) variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) return variables diff --git a/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py b/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py index 9f24f4139a..9985121fb6 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py @@ -32,7 +32,6 @@ def get_fundamental_variables(self): # can just use the surface value. if self.domain == "Negative": - # distribution variables c_s_surf_xav_distribution = pybamm.Variable( "X-averaged negative particle surface concentration distribution", domain="negative particle size", @@ -50,7 +49,6 @@ def get_fundamental_variables(self): ) elif self.domain == "Positive": - # distribution variables c_s_surf_xav_distribution = pybamm.Variable( "X-averaged positive particle surface concentration distribution", domain="positive particle size", @@ -67,30 +65,20 @@ def get_fundamental_variables(self): coord_sys="cartesian", ) - # Distribution variables variables = self._get_distribution_variables(R) - # Flux variables (zero) - N_s = pybamm.FullBroadcastToEdges( - 0, - [self.domain.lower() + " particle"], - auxiliary_domains={ - "secondary": self.domain.lower() + " electrode", - "tertiary": "current collector", - }, - ) - N_s_xav = pybamm.FullBroadcast( - 0, self.domain.lower() + " electrode", "current collector" - ) - - # Standard distribution variables (R-dependent) + # Standard distribution variables (size-dependent) variables.update( self._get_standard_concentration_distribution_variables( c_s_surf_xav_distribution ) ) + # Flux variables (size-dependent) + variables.update( + self._get_standard_flux_distribution_variables(pybamm.Scalar(0)) + ) - # Standard R-averaged variables. Average concentrations using + # Standard size-averaged variables. Average concentrations using # the volume-weighted distribution since they are volume-based # quantities. Necessary for output variables "Total lithium in # negative electrode [mol]", etc, to be calculated correctly @@ -105,7 +93,12 @@ def get_fundamental_variables(self): ) c_s = pybamm.SecondaryBroadcast(c_s_xav, [self.domain.lower() + " electrode"]) variables.update(self._get_standard_concentration_variables(c_s, c_s_xav)) - variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) + + # Size-averaged flux variables + N_s_xav = pybamm.FullBroadcastToEdges( + 0, self.domain.lower() + " particle", "current collector" + ) + variables.update(self._get_standard_flux_variables(N_s_xav, N_s_xav)) return variables def get_coupled_variables(self, variables): diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py index ecf9c077a9..cea5957142 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py @@ -29,7 +29,6 @@ def __init__(self, param, domain): def get_fundamental_variables(self): if self.domain == "Negative": - # distribution variables c_s_distribution = pybamm.Variable( "Negative particle concentration distribution", domain="negative particle", @@ -43,7 +42,6 @@ def get_fundamental_variables(self): R = pybamm.standard_spatial_vars.R_n elif self.domain == "Positive": - # distribution variables c_s_distribution = pybamm.Variable( "Positive particle concentration distribution", domain="positive particle", @@ -56,15 +54,14 @@ def get_fundamental_variables(self): ) R = pybamm.standard_spatial_vars.R_p - # Distribution variables variables = self._get_distribution_variables(R) - # Standard distribution variables (R-dependent) + # Standard concentration distribution variables (size-dependent) variables.update( self._get_standard_concentration_distribution_variables(c_s_distribution) ) - # Standard R-averaged variables. Average concentrations using + # Standard size-averaged variables. Average concentrations using # the volume-weighted distribution since they are volume-based # quantities. Necessary for output variables "Total lithium in # negative electrode [mol]", etc, to be calculated correctly @@ -82,11 +79,10 @@ def get_coupled_variables(self, variables): self.domain + " particle concentration distribution" ] R = variables[self.domain + " particle sizes"] - T_k = variables[self.domain + " electrode temperature"] - # broadcast to "particle size" domain then again into "particle" + # broadcast T to "particle size" domain then again into "particle" T_k = pybamm.PrimaryBroadcast( - T_k, + variables[self.domain + " electrode temperature"], [self.domain.lower() + " particle size"], ) T_k = pybamm.PrimaryBroadcast( @@ -99,7 +95,6 @@ def get_coupled_variables(self, variables): * pybamm.grad(c_s_distribution) ) f_a_dist = self.param.f_a_dist_n(R) - elif self.domain == "Positive": N_s_distribution = ( -self.param.D_p(c_s_distribution, T_k) @@ -107,14 +102,16 @@ def get_coupled_variables(self, variables): ) f_a_dist = self.param.f_a_dist_p(R) - N_s = pybamm.Integral(f_a_dist * N_s_distribution, R) - variables.update(self._get_standard_flux_variables(N_s, N_s)) - - # Standard distribution flux variables (R-dependent) + # Size-dependent flux variables variables.update( - {self.domain + " particle flux distribution": N_s_distribution} + self._get_standard_flux_distribution_variables(N_s_distribution) ) + # Size-averaged flux variables (perform area-weighted avg manually as flux + # evals on edges) + N_s = pybamm.Integral(f_a_dist * N_s_distribution, R) + variables.update(self._get_standard_flux_variables(N_s, N_s)) + variables.update(self._get_total_concentration_variables(variables)) return variables @@ -122,12 +119,10 @@ def set_rhs(self, variables): c_s_distribution = variables[ self.domain + " particle concentration distribution" ] - N_s_distribution = variables[self.domain + " particle flux distribution"] - - R_spatial_variable = variables[self.domain + " particle sizes"] R = pybamm.PrimaryBroadcast( - R_spatial_variable, [self.domain.lower() + " particle"] + variables[self.domain + " particle sizes"], + [self.domain.lower() + " particle"] ) if self.domain == "Negative": @@ -155,11 +150,9 @@ def set_boundary_conditions(self, variables): self.domain + " electrode interfacial current density distribution" ] R = variables[self.domain + " particle sizes"] - - # Extract T and broadcast to particle size domain - T_k = variables[self.domain + " electrode temperature"] T_k = pybamm.PrimaryBroadcast( - T_k, [self.domain.lower() + " particle size"] + variables[self.domain + " electrode temperature"], + [self.domain.lower() + " particle size"] ) # Set surface Neumann boundary values @@ -171,7 +164,6 @@ def set_boundary_conditions(self, variables): / self.param.a_R_n / self.param.D_n(c_s_surf_distribution, T_k) ) - elif self.domain == "Positive": rbc = ( -self.param.C_p @@ -181,7 +173,6 @@ def set_boundary_conditions(self, variables): / self.param.gamma_p / self.param.D_p(c_s_surf_distribution, T_k) ) - self.boundary_conditions = { c_s_distribution: { "left": (pybamm.Scalar(0), "Neumann"), diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py b/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py index 09e5261a7e..3decc7d5a3 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py @@ -118,31 +118,25 @@ def get_coupled_variables(self, variables): c_s_xav_distribution, T_k_xav ) * pybamm.grad(c_s_xav_distribution) - # Standard R-averaged flux variables. Average using the area-weighted - # distribution - f_a_dist = variables[ - "X-averaged " - + self.domain.lower() - + " area-weighted particle-size distribution" - ] + # Size-dependent flux variables + variables.update( + self._get_standard_flux_distribution_variables(N_s_xav_distribution) + ) + + # Size-averaged flux variables (perform area-weighted avg manually as flux + # evals on edges) f_a_dist = pybamm.PrimaryBroadcast( - f_a_dist, + variables[ + "X-averaged " + + self.domain.lower() + + " area-weighted particle-size distribution" + ], [self.domain.lower() + " particle"], ) N_s_xav = pybamm.Integral(f_a_dist * N_s_xav_distribution, R) N_s = pybamm.SecondaryBroadcast(N_s_xav, [self.domain.lower() + " electrode"]) variables.update(self._get_standard_flux_variables(N_s, N_s_xav)) - # Standard distribution flux variables (R-dependent) - # (Cannot currently broadcast to "x" as cannot have 4 domains) - variables.update( - { - "X-averaged " - + self.domain.lower() - + " particle flux distribution": N_s_xav_distribution, - } - ) - variables.update(self._get_total_concentration_variables(variables)) return variables From 7f4a2df6f29a2ee622ce748c8a10becf4521aefe Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 9 Aug 2021 19:41:11 +0100 Subject: [PATCH 07/16] add DFN tests with size distribution --- .../size_distribution/base_distribution.py | 7 ++- .../test_models/standard_output_tests.py | 34 ++++++++++---- .../test_lithium_ion/test_dfn.py | 47 +++++++++++++++++++ .../test_lithium_ion/test_dfn.py | 12 +++++ 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/pybamm/models/submodels/particle/size_distribution/base_distribution.py b/pybamm/models/submodels/particle/size_distribution/base_distribution.py index e1e82e1168..b48491aea9 100644 --- a/pybamm/models/submodels/particle/size_distribution/base_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/base_distribution.py @@ -212,14 +212,13 @@ def _get_standard_concentration_distribution_variables(self, c_s): c_s_distribution = c_s # x-average the *tertiary* domain. - # NOTE: not yet implemented. Fill with zeros + # NOTE: not yet implemented. Make 0.5 everywhere c_s_xav_distribution = pybamm.FullBroadcast( - 0, + 0.5, [self.domain.lower() + " particle"], { "secondary": self.domain.lower() + " particle size", - "tertiary": self.domain.lower() + " electrode", - "quaternary": "current collector" + "tertiary": "current collector" }, ) diff --git a/tests/integration/test_models/standard_output_tests.py b/tests/integration/test_models/standard_output_tests.py index 02a8b2895c..c02a07d0f1 100644 --- a/tests/integration/test_models/standard_output_tests.py +++ b/tests/integration/test_models/standard_output_tests.py @@ -316,18 +316,23 @@ def test_concentration_increase_decrease(self): pos_end_vs_start = self.c_s_p_rav(t[-1], x_p) - self.c_s_p_rav(t[0], x_p) elif self.model.options["particle size"] == "distribution": R_n, R_p = self.R_n, self.R_p - # Test the concentration variables that depend on particle size - neg_diff = self.c_s_n_dist(t[1:], r=r_n, R=R_n) - self.c_s_n_dist( - t[:-1], r=r_n, R=R_n + # Test the concentration variables that depend on x-R (surface values only, + # as 3D vars not implemented) + neg_diff = ( + self.c_s_n_surf_dist(t[1:], x=x_n, R=R_n) + - self.c_s_n_surf_dist(t[:-1], x=x_n, R=R_n) ) - pos_diff = self.c_s_p_dist(t[1:], r=r_p, R=R_p) - self.c_s_p_dist( - t[:-1], r=r_p, R=R_p + pos_diff = ( + self.c_s_p_surf_dist(t[1:], x=x_p, R=R_p) + - self.c_s_p_surf_dist(t[:-1], x=x_p, R=R_p) ) - neg_end_vs_start = self.c_s_n_dist(t[-1], r=r_n, R=R_n) - self.c_s_n_dist( - t[0], r=r_n, R=R_n + neg_end_vs_start = ( + self.c_s_n_surf_dist(t[-1], x=x_n, R=R_n) + - self.c_s_n_surf_dist(t[0], x=x_n, R=R_n) ) - pos_end_vs_start = self.c_s_p_dist(t[-1], r=r_p, R=R_p) - self.c_s_p_dist( - t[0], r=r_p, R=R_p + pos_end_vs_start = ( + self.c_s_p_surf_dist(t[-1], x=x_p, R=R_p) + - self.c_s_p_surf_dist(t[0], x=x_p, R=R_p) ) tol = 1e-15 else: @@ -363,12 +368,23 @@ def test_concentration_limits(self): np.testing.assert_array_less(self.c_s_p(t, x_p, r_p), 1) if self.model.options["particle size"] == "distribution": R_n, R_p = self.R_n, self.R_p + # Cannot have 3D processed variables, so test concs that depend on + # r-R and x-R + + # r-R (x-averaged) np.testing.assert_array_less(-self.c_s_n_dist(t, r=r_n, R=R_n), 0) np.testing.assert_array_less(-self.c_s_p_dist(t, r=r_p, R=R_p), 0) np.testing.assert_array_less(self.c_s_n_dist(t, r=r_n, R=R_n), 1) np.testing.assert_array_less(self.c_s_p_dist(t, r=r_p, R=R_p), 1) + # x-R (surface concentrations) + np.testing.assert_array_less(-self.c_s_n_surf_dist(t, x=x_n, R=R_n), 0) + np.testing.assert_array_less(-self.c_s_p_surf_dist(t, x=x_p, R=R_p), 0) + + np.testing.assert_array_less(self.c_s_n_surf_dist(t, x=x_n, R=R_n), 1) + np.testing.assert_array_less(self.c_s_p_surf_dist(t, x=x_p, R=R_p), 1) + def test_conservation(self): """Test amount of lithium stored across all particles and in SEI layers is constant.""" diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index b0b65673d6..09c45f407d 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -257,6 +257,53 @@ def test_well_posed_both_swelling_only(self): modeltest.test_all() +class TestDFNWithSizeDistribution(unittest.TestCase): + def setUp(self): + params = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Marquis2019) + self.params = pybamm.get_size_distribution_parameters(params) + + var = pybamm.standard_spatial_vars + self.var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.R_n: 5, + var.R_p: 5, + var.y: 5, + var.z: 5, + } + + def test_basic_processing(self): + options = {"particle size": "distribution"} + model = pybamm.lithium_ion.DFN(options) + modeltest = tests.StandardModelTest( + model, parameter_values=self.params, var_pts=self.var_pts + ) + modeltest.test_all() + + def test_uniform_profile(self): + options = {"particle size": "distribution", "particle": "uniform profile"} + model = pybamm.lithium_ion.DFN(options) + modeltest = tests.StandardModelTest( + model, parameter_values=self.params, var_pts=self.var_pts + ) + modeltest.test_all() + + def test_4D_basic_processing(self): + options = { + "particle size": "distribution", + "current collector": "potential pair", + "dimensionality": "1" + } + model = pybamm.lithium_ion.DFN(options) + modeltest = tests.StandardModelTest( + model, parameter_values=self.params, var_pts=self.var_pts + ) + modeltest.test_all(skip_output_tests=True) + + if __name__ == "__main__": print("Add -v for more debug output") import sys diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index 5600bec60f..957deb045d 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -241,6 +241,18 @@ def test_well_posed_irreversible_plating(self): model.check_well_posedness() +class TestDFNWithSizeDistribution(unittest.TestCase): + def test_well_posed(self): + options = {"particle size": "distribution"} + model = pybamm.lithium_ion.DFN(options) + model.check_well_posedness() + + def test_uniform_profile(self): + options = {"particle size": "distribution", "particle": "uniform profile"} + model = pybamm.lithium_ion.DFN(options) + model.check_well_posedness() + + if __name__ == "__main__": print("Add -v for more debug output") import sys From 2c48f7c7017fe5f5a23157359a9e4617c791c37a Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 9 Aug 2021 19:41:33 +0100 Subject: [PATCH 08/16] more tests --- .../test_lithium_ion/test_dfn.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index 09c45f407d..9b559ac605 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -269,8 +269,8 @@ def setUp(self): var.x_p: 5, var.r_n: 5, var.r_p: 5, - var.R_n: 5, - var.R_p: 5, + var.R_n: 3, + var.R_p: 3, var.y: 5, var.z: 5, } @@ -291,11 +291,12 @@ def test_uniform_profile(self): ) modeltest.test_all() - def test_4D_basic_processing(self): + def test_basic_processing_4D(self): + # 4 dimensions: particle, particle size, electrode, current collector options = { "particle size": "distribution", "current collector": "potential pair", - "dimensionality": "1" + "dimensionality": 1 } model = pybamm.lithium_ion.DFN(options) modeltest = tests.StandardModelTest( From 47dfc038682b7ac4143c881ef0206785dc8df1c9 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Tue, 10 Aug 2021 17:41:36 +0100 Subject: [PATCH 09/16] add MPDFN notebook and example --- examples/scripts/DFN_size_distributions.py | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 examples/scripts/DFN_size_distributions.py diff --git a/examples/scripts/DFN_size_distributions.py b/examples/scripts/DFN_size_distributions.py new file mode 100644 index 0000000000..3bba8e0219 --- /dev/null +++ b/examples/scripts/DFN_size_distributions.py @@ -0,0 +1,67 @@ +# +# Compare the DFN with particle size distributions (MP-DFN) +# to the DFN with a single particle size, and the MPM +# +import pybamm + +pybamm.set_logging_level("INFO") + +# load models +models = [ + pybamm.lithium_ion.DFN(options={"particle size": "distribution"}, name="MP-DFN"), + pybamm.lithium_ion.MPM(name="MPM"), + pybamm.lithium_ion.DFN(name="DFN") +] + +# parameter set +params = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Marquis2019) + +# add distribution params (lognormals) with custom standard deviations +params = pybamm.get_size_distribution_parameters(params, sd_n=0.2, sd_p=0.4) + +# discharge and relaxation: define current function +t_cutoff = 3450 # [s] +t_rest = 3600 # [s] +I_typ = params["Typical current [A]"] # current for 1C + + +def current(t): + return I_typ * pybamm.EqualHeaviside(t, t_cutoff) + + +params.update({"Current function [A]": current}) +t_eval = [0, t_cutoff + t_rest] + +# solve +solver = pybamm.CasadiSolver(mode="fast") +sims = [] +for model in models: + sim = pybamm.Simulation( + model, + parameter_values=params, + solver=solver + ) + sims.append(sim) + +for sim in sims: + sim.solve(t_eval=t_eval) + +# plot MP-DFN variables +output_variables = [ + "Negative particle surface concentration distribution", + "Positive particle surface concentration distribution", + "Current [A]", + "X-averaged negative area-weighted particle-size distribution", + "X-averaged positive area-weighted particle-size distribution", + "Terminal voltage [V]", +] +sims[0].plot(output_variables) + +# compare models (size-averaged concentrations) +output_variables = [ + "Negative particle surface concentration", + "Positive particle surface concentration", + "Current [A]", + "Terminal voltage [V]", +] +pybamm.dynamic_plot(sims, output_variables=output_variables) From d660865c9577e6a52f3e36cecb556a143d30564c Mon Sep 17 00:00:00 2001 From: tobykirk Date: Tue, 10 Aug 2021 17:59:45 +0100 Subject: [PATCH 10/16] add MPDFN notebook --- ...DFN-with-particle-size-distributions.ipynb | 497 ++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 examples/notebooks/models/DFN-with-particle-size-distributions.ipynb diff --git a/examples/notebooks/models/DFN-with-particle-size-distributions.ipynb b/examples/notebooks/models/DFN-with-particle-size-distributions.ipynb new file mode 100644 index 0000000000..b0ed3811c6 --- /dev/null +++ b/examples/notebooks/models/DFN-with-particle-size-distributions.ipynb @@ -0,0 +1,497 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Doyle-Fuller-Newman Model with particle-size distributions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook demonstrates the extension of the Doyle-Fuller-Newman (DFN) model to include a distribution of particle sizes at every macroscale location (e.g. through-cell coordinate $x$) within the electrodes. This model, referred to here as the Many-Particle DFN (or MP-DFN), also extends the ideas of the Many-Particle Model or MPM (an $x$-averaged model) into the DFN framework. Note: this differs from a \"size distribution in x\", where the particle size is a function of $x$ but with only a single size at any given value of $x$.\n", + "\n", + "The [MPM notebook](https://github.com/pybamm-team/PyBaMM/blob/develop/examples/notebooks/models/MPM.ipynb) describes how the particle-size distributions are implemented in PyBaMM, and how to input parameters and plot some relevant output variables. The model equations for the MP-DFN, which allow for variations in the through-cell $x$ direction and therefore include electrolyte dynamics, are similar to those of the MPM and DFN, and are available in [[5]](#References).\n", + "\n", + "By default per electrode, the DFN has 1 microscale dimension (the radial coordinate within the active particles, $r_{\\mathrm{k}}$, $\\mathrm{k}=\\mathrm{n,p}$) and 1 macroscale dimension (the through-cell coordinate $x$), and is commonly called \"pseudo-2D\". The MP-DFN adds another microscale variation with particle size $R_{\\mathrm{k}}$, $\\mathrm{k}=\\mathrm{n,p}$ over an interval $[R_{\\mathrm{k,min}}, R_{\\mathrm{k,max}}]$, $\\mathrm{k}=\\mathrm{n,p}$, and thus can be thought of as \"pseudo-3D\". See [[5]](#References) for more details. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example solving the MP-DFN" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING: You are using pip version 21.1.2; however, version 21.2.3 is available.\n", + "You should consider upgrading via the '/home/user/PyBaMM/.tox/dev/bin/python -m pip install --upgrade pip' command.\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install pybamm -q # install PyBaMM if it is not installed\n", + "import pybamm\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MP-DFN can be accessed from the `DFN` model in PyBaMM, and specifying the `\"particle size\"` option. The default option for `\"particle size\"` is `\"single\"`. Let's change this to `\"distribution\"` and pass to the `DFN` when loading the model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# choose option(s)\n", + "options = {\n", + " \"particle size\": \"distribution\"\n", + "}\n", + "\n", + "# load model\n", + "model = pybamm.lithium_ion.DFN(options=options, name=\"MP-DFN\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adding size distribution parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to input and set the particle-size distributions and the minimum and maximum sizes for each electrode. Values for these are not currently in the default parameter set for the DFN, but they are easily added (to any parameter set of choice) - see the [MPM notebook](https://github.com/pybamm-team/PyBaMM/blob/develop/examples/notebooks/models/MPM.ipynb) for more details.\n", + "\n", + "Here, we start from the Marquis et al. (2019) [[6]](#References) parameter set and use the convenience method `pybamm.get_size_distribution_parameters()`, which adds lognormals for the area-weighted size distribution in each electrode. By default, it chooses the `\"Negative particle radius [m]\"` and `\"Positive particle radius [m]\"` values already in the parameter set to be the mean of the lognormals, and sets the standard deviations to be 0.3 times the mean. (All parameters can be overwritten with keyword arguments.)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Base parameter set (no distribution parameters by default)\n", + "params = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Marquis2019)\n", + "\n", + "# Add distribution parameters to the set, with default values (lognormals)\n", + "params = pybamm.get_size_distribution_parameters(params)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solve" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Solve for a 1C discharge" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# load parameter values into simuluation\n", + "solver = pybamm.CasadiSolver(mode=\"fast\")\n", + "sim = pybamm.Simulation(model, parameter_values=params, solver=solver)\n", + "\n", + "# solve\n", + "sim.solve(t_eval=[0, 3500])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fe7860b9192848bab44903e273a83031", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=3500.0, step=35.0), Output()), _dom_classes=…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# plot some variables that depend on particle size\n", + "output_variables = [\n", + " \"Negative particle surface concentration distribution\",\n", + " \"Positive particle surface concentration distribution\",\n", + " \"X-averaged negative particle surface concentration distribution\",\n", + " \"Negative area-weighted particle-size distribution\",\n", + " \"Positive area-weighted particle-size distribution\",\n", + " \"Terminal voltage [V]\",\n", + "]\n", + "\n", + "sim.plot(output_variables=output_variables)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Variables that depend on particle size have names that end in `\"distribution\"`, e.g. `\"Negative particle surface concentration distribution\"`. The same variables without the `\"distribution\"` have been averaged over particle size, and can be compared to the corresponding variable with the same name from other models that have a single particle size (e.g. SPM).\n", + "\n", + "The particle size distributions do not vary with $x$, so you can use the \"X-averaged\" versions to better visualize the discretized distribution in $R$ that PyBaMM uses." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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axvcQDZr41auXMmAAQP41re+xaBBgzj11FPZvHkzY8aMofJHGdauXUvfvn0BuPvuuznrrLM45ZRT6Nu3L//1X//Fz372M4YOHcrIkSNZt25dVd733nsvpaWlHH744SxYsACATZs2MXHiREaMGMHQoUN55JFHqvI988wzOfHEEznppJOqlfHiiy/m0UcfBeDss89m4sSJAEybNo0rrrgCgPvuu48RI0ZQWlrKhRdeyPbt2wHo27cva9euBeDaa6+lf//+HHfccZx77rlMnjy5ahu//OUvGTFiBIcddhjz5s1j27ZtXHXVVcyYMYPS0lJmzJhRa9k3b97MOeecw4ABAzj77LPZvHlz0x0gMzNrcdxANmsHli9fzsUXX8yrr75Kjx49mDVrVp3pX3nlFR5++GFeeuklrrjiCvbYYw9efvlljjnmGO65556qdB9++CFlZWXceuutVY3a66+/nhNPPJEFCxbwzDPPMGnSJDZt2gTAn/70J2bOnMncuXOrbW/UqFHMmzcPgLfffpulS5cCMG/ePEaPHs2yZcuYMWMGzz//PGVlZZSUlDB9+vRqebz00kvMmjWLxYsX8+STT5L7i2wff/wxCxYs4Oabb+bqq6+mU6dOXHPNNYwfP56ysjLGjx9fa9lvu+029thjD5YtW8bVV1/NokWLGnEUzMystfAYZLN2oF+/fpSWlgJw1FFHsXLlyjrTn3DCCXTr1o1u3brRvXt3zjjjDAAGDx7MkiVLqtKde+65AIwePZoPPviA8vJyZs+ezaOPPlrVe7tlyxbefPNNAE455RT23nvvnbY3atQobr75ZpYuXcrAgQNZv349q1evZv78+dxyyy384he/YNGiRQwfPhxIenT333//ank8//zzfP7zn6dLly506dKlqsyVvvCFL9Rb/9rK/uyzz3LJJZcAMGTIEIYMGVLn/jMzs9bNDWSzdqBz585V70tKSti8eTO77bYbFRUVQNIQrC19hw4dqqY7dOhQbfywpGrrSSIimDVrFv3796+27MUXX2TPPfesen/hhRcCcM0113DmmWdSXl7Ob3/7W0aPHs26det46KGH6Nq1K926dSMiOO+88/jpT3+6y/ugpKSk1jHQtZXdzMzaFw+xMGun+vbtWzVUYObMmY3KY8aMGQA899xzdO/ene7duzN27FimTJlCRADw8ssv77Te0UcfTVlZGWVlZZx55pkAjBw5kptvvpnRo0czatQoJk+ezKhRowA46aSTmDlzJu+99x4A69at44033qiW57HHHstjjz3Gli1b2LhxI48//ni95e/WrRsbNmyomq6t7KNHj676GexXXnmlWi+6mZm1Pe5BNmtGxXwsW67LLruML3/5y0ydOpXPfe5zjcqjS5cuDB06lI8++ohp06YBcOWVV3LppZcyZMgQKioq6NevX16N1VGjRjF79mwOOeQQ+vTpw7p166oayAMHDuS6667j1FNPpaKigo4dO/Lzn/+cPn36VK0/fPhwzjzzTIYMGcIBBxzA4MGD6d69e53bPOGEE7jhhhsoLS3l8ssvr7XsF110Eeeffz4DBgxgwIABHHXUUY3aX2Zm1jqosqekvRs2bFjk3tRjO2vK5/i2pMZiIXz00UcAdOzYscglaT82btxI165d+fDDDxk9ejRTp07lyCOPbJZt13a8JS2KCD8wOQ+Ow/lzLDZrmIbGYvcgm1mbccEFF7B06VK2bNnCeeed12yNYzMza1uK3kCWNAG4AAjguxHxp8yyzwB3AIcCh0TEW+n8u4EjgPeBNRHxpXT+acCP09V/EhG/a6ZqmFkLUDlO2BrOsdjMbIeiNpAl9QQuAUYCBwP3AsdlkrwKHAPUNIDxuxHxXCavEuBGYHQ6a66kpyJieyHKbmbWVjgWm5lVV+ynWIwA5kXEtohYAXSTVPV8qYh4PyI21rLuzyTNkzQ+nT4EWBER5RFRDqxM55mZWd0ci83MMoo9xGIfYH1muhzYG1hdz3qXRcRaSXsDf5D0Uh151UrSBSSXFOndu3eDCm5m1oYULRY7DptZS1TsBvI6oEdmuns6r04RsTb9u07S70nGwC1raF4RMRWYCsnd0w0od5vUlHdFN9X2fHe1WbMoWix2HN6ZY7FZ8RW7gfwicJ2kjsAngI0RsbW+lST1iIhySZ2AY4FfAMuBfpL2SpP1A/5SoHJbE1i8qrzeNMfVm6J1mbNqTpPmN6bXmCbLa+XKlZx++um88sorTZZnoSxcuJB77rmHW265pdY0ddXn7rvv5tRTT+Wggw7Ke5utaf80gmNxO9YeY7FZfYraQI6I9ZJuBeaS3Dn9PUmlwCkRcZOkw4BbSXolHpB0f0TcBsyQ1BXoCNwXEa8CSLocqLxb+nLfFGLWNg0bNoxhwxr/aOG7776bww8/vEEN5LbMsdjMrLpi36RHREyLiM9ExLERsTAiyiLipnTZnyPi5IjoGRGj0oBMRIxN04+IiFsyeT0REcekryeKVSezluIHP/gBP//5z6umf/KTn3DTTTcxadIkDj/8cAYPHlz1c9FZd999N9/5zneqpk8//XTmzJkDQNeuXZk0aRKDBg3i5JNPZsGCBYwZM4ZPfepTPProowBs376dSZMmMXz4cIYMGcIdd9yx0za2b99Ov379iAjKy8spKSnh2WefBZKfdl6+fDmbNm1i4sSJjBgxgqFDh/LII48AMGfOHE4//XQA1qxZwymnnMKgQYP45je/SZ8+fVi7dm3VNr71rW8xaNAgTj31VDZv3szMmTNZuHAhX/nKVygtLWXz5s0sWrSI448/nqOOOoqxY8eyenUy9HbRokUcccQRHHHEEdX2Y1vkWGxmtkPRG8hmVjjjx4/noYceqpp+6KGH2H///SkrK2Px4sU89dRTTJo0qapBmI9NmzZx4okn8uqrr9KtWzd+9KMf8fvf/55f/epXXHXVVQDcdddddO/enZdeeomXXnqJO++8kxUrVlTLp6SkhP79+7N06VKee+45jjzySObNm8fWrVtZtWoVhx56KNdffz0nnngiCxYs4JlnnmHSpEls2rSpWj5XX311VXm++MUv8uabb1YtW758ORdffDGvvvoqPXr0YNasWXzxi19k2LBhTJ8+nbKyMnbbbTe++93vMnPmTBYtWsTEiRO54oorADj//POZMmUKixcvbvC+NzOz1qvYY5DNrICGDh3Ke++9xzvvvMOaNWvo2bMnZWVlnHvuuZSUlHDAAQdw/PHH89JLLzFkyJC88uzUqROnnXYaAIMHD6Zz58507NiRwYMHs3LlSgBmz57NkiVLmDlzJgDvv/8+y5cvp1+/ftXyGjVqFM8++ywrVqzg8ssv58477+T4449n+PDhVfk8+uijTJ48GYAtW7ZUawADPPfcc/zqV78C4LTTTqNnz55Vy/r160dpaSkARx11VFX5sl5//XVeeeUVTjnlFCDpdf7EJz5BeXk55eXljB6dPM73a1/7Gk8++WRe+8jMzFo3N5DN2rgvfelLzJw5k7/97W+MHz9+p57cmuy2225UVFRUTW/ZsqXqfceOHZEEQIcOHejcuXPV+48//hiAiGDKlCmMHTu2Wr5XXHEFv/nNbwAoKytj9OjR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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# The discrete sizes or \"bins\" used\n", + "R_p = sim.solution[\"Positive particle sizes [m]\"].entries[:,0,0] # const in the x and current collector direction\n", + "R_n = sim.solution[\"Negative particle sizes [m]\"].entries[:,0,0]\n", + "\n", + "# The distributions (number, area, and volume-weighted)\n", + "f_a_p = sim.solution[\"X-averaged positive area-weighted particle-size distribution [m-1]\"].entries[:,0]\n", + "f_num_p = sim.solution[\"X-averaged positive number-based particle-size distribution [m-1]\"].entries[:,0]\n", + "f_v_p = sim.solution[\"X-averaged positive volume-weighted particle-size distribution [m-1]\"].entries[:,0]\n", + "f_a_n = sim.solution[\"X-averaged negative area-weighted particle-size distribution [m-1]\"].entries[:,0]\n", + "f_num_n = sim.solution[\"X-averaged negative number-based particle-size distribution [m-1]\"].entries[:,0]\n", + "f_v_n = sim.solution[\"X-averaged negative volume-weighted particle-size distribution [m-1]\"].entries[:,0]\n", + "\n", + "# plot\n", + "f, axs = plt.subplots(1, 2 ,figsize=(10,4))\n", + "\n", + "# negative electrode\n", + "width_n = (R_n[-1] - R_n[-2])/ 1e-6\n", + "axs[0].bar(R_n / 1e-6, f_a_n * 1e-6, width=width_n, alpha=0.3, color=\"tab:blue\",\n", + " label=\"area-weighted\")\n", + "axs[0].bar(R_n / 1e-6, f_num_n * 1e-6, width=width_n, alpha=0.3, color=\"tab:red\",\n", + " label=\"number-weighted\")\n", + "axs[0].bar(R_n / 1e-6, f_v_n * 1e-6, width=width_n, alpha=0.3, color=\"tab:green\",\n", + " label=\"volume-weighted\")\n", + "axs[0].set_xlim((0,25))\n", + "axs[0].set_xlabel(\"Particle size $R_{\\mathrm{n}}$ [$\\mu$m]\", fontsize=12)\n", + "axs[0].set_ylabel(\"[$\\mu$m$^{-1}$]\", fontsize=12)\n", + "axs[0].legend(fontsize=10)\n", + "axs[0].set_title(\"Discretized distributions (histograms) in negative electrode\")\n", + "\n", + "# positive electrode\n", + "width_p = (R_p[-1] - R_p[-2])/ 1e-6\n", + "axs[1].bar(R_p / 1e-6, f_a_p * 1e-6, width=width_p, alpha=0.3, color=\"tab:blue\",\n", + " label=\"area-weighted\")\n", + "axs[1].bar(R_p / 1e-6, f_num_p * 1e-6, width=width_p, alpha=0.3, color=\"tab:red\",\n", + " label=\"number-weighted\")\n", + "axs[1].bar(R_p / 1e-6, f_v_p * 1e-6, width=width_p, alpha=0.3, color=\"tab:green\",\n", + " label=\"volume-weighted\")\n", + "axs[1].set_xlim((0,25))\n", + "axs[1].set_xlabel(\"Particle size $R_{\\mathrm{p}}$ [$\\mu$m]\", fontsize=12)\n", + "axs[1].set_ylabel(\"[$\\mu$m$^{-1}$]\", fontsize=12)\n", + "axs[1].set_title(\"Positive electrode\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Custom size distributions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we show how to change the distribution parameters, or set any distribution of choice. We will set the distribution parameters for the positive distribution (leaving the one for the negative electrode as the default lognormal). We refer to the [MPM notebook](https://github.com/pybamm-team/PyBaMM/blob/develop/examples/notebooks/models/MPM.ipynb) for more examples." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Set the area-weighted mean radius to be the reference value from the parameter set\n", + "R_av_p_dim = params[\"Positive particle radius [m]\"]\n", + "\n", + "# Standard deviation (dimensional)\n", + "sd_p_dim = 0.6 * R_av_p_dim\n", + "\n", + "# Minimum and maximum particle sizes (dimensional)\n", + "R_min_p = 0\n", + "R_max_p = 3 * R_av_p_dim\n", + "\n", + "# Set the area-weighted particle-size distribution.\n", + "# Choose a lognormal (but any pybamm function could be used)\n", + "def f_a_dist_p_dim(R):\n", + " return pybamm.lognormal(R, R_av_p_dim, sd_p_dim)\n", + "\n", + "# Note: the only argument must be the particle size R" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# input params to the dictionary\n", + "distribution_params = {\n", + " \"Positive minimum particle radius [m]\": R_min_p,\n", + " \"Positive maximum particle radius [m]\": R_max_p,\n", + " \"Positive area-weighted \"\n", + " + \"particle-size distribution [m-1]\": f_a_dist_p_dim,\n", + "}\n", + "params.update(distribution_params, check_already_exists=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Solve and plot " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# load parameter values into simulation\n", + "sim_custom = pybamm.Simulation(model, parameter_values=params, solver=solver)\n", + "\n", + "# solve\n", + "sim_custom.solve(t_eval=[0, 3500])\n", + "\n", + "# plot\n", + "output_variables = [\n", + " \"X-averaged negative area-weighted particle-size distribution\",\n", + " \"X-averaged positive area-weighted particle-size distribution\",\n", + " \"Terminal voltage [V]\"\n", + "]\n", + "quickplot = pybamm.QuickPlot(\n", + " [sim, sim_custom], output_variables=output_variables, labels=[\"default lognormals\", \"custom\"]\n", + ")\n", + "quickplot.plot(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare MP-DFN to MPM and DFN models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MP-DFN is an extension of the MPM (size distributions, but no x variation) and DFN models (x variation, but no size distribution). Here we compare the three for the same parameter values, for a discharge followed by a relaxation. Note: this is implemented here by specifying the current function to be a Heaviside, not using the `Experiment` class." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "models = [\n", + " pybamm.lithium_ion.DFN(options={\"particle size\": \"distribution\"}, name=\"MP-DFN\"),\n", + " pybamm.lithium_ion.MPM(name=\"MPM\"),\n", + " pybamm.lithium_ion.DFN(name=\"DFN\")\n", + "]\n", + "\n", + "# parameters\n", + "params = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Marquis2019)\n", + "params = pybamm.get_size_distribution_parameters(params) \n", + "\n", + "# define current function\n", + "t_cutoff = 3450 # [s]\n", + "t_rest = 3600 # [s]\n", + "I_typ = params[\"Typical current [A]\"] # current for 1C\n", + "\n", + "def current(t):\n", + " return I_typ * pybamm.EqualHeaviside(t, t_cutoff)\n", + "\n", + "params.update({\"Current function [A]\": current})\n", + "t_eval = [0, t_cutoff + t_rest]\n", + "\n", + "# solve\n", + "sims = []\n", + "for model in models:\n", + " sim = pybamm.Simulation(model, parameter_values=params, solver=solver)\n", + " sim.solve(t_eval=t_eval)\n", + " sims.append(sim)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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1FdeeSWx/PzWrVt3q4047VF3Y4wxxtSYbt265ZU/7j5z5sz6F110Ubvx48dvHTx4cPaMGTM2lZdbu3ZtvaOOOqrg0UcfbTp8+PCssLDa64+xnh9jjDHG+MWpp566v3PnzvlLliyJrex406ZNi7t375732muvJdZmXNbzY4wxxgS5xo0b96hs/6mnnpr1+uuvbwYYP358owkTJjSrrNyHH364rm/fvvmVHatKy5Ytizp06FA4bdq0xn369OkEMGXKlI3R0dEKcM8992w/99xzU4cPH551JNc/Epb8GGOMMcZvtm7dWi8uLq60stteAKmpqcU9evTIe/XVVxNrKyZLfowxxpggt2vXrpVVlRk7duzusWPH7q6qXHXMnj07ds2aNTFXXnnl3o0bN0Ydqtzdd9+9/ZxzzkmtyboPx5IfY4wxxtSYVatWxfbt27djYWFhWHJycsnrr7/+644dOyIPd05qampxr1698ubMmRNfGzGKqtZGPcYYY4ypBcuWLWvWrVu3z6KioordjqUuKCwsjFy1atXQ3r17by/fZ097GWOMMSakWPJjjDHGmJBiyY8xxhhjQoolP8YYY0xwyS4pKclzO4i6wvO7yPbeZwOejTHGmCCzdevWq5OSkq6LiIiodGblUFFSUpKXmZk5pWXLltO891vyY4wxxgShZcuWxQIJbsfhsuzevXv/rhfMkh9jjDHGhBQb82OMMcaYkGLJjzHGGGNCyv8DKrwEeUHWehAAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# plot current, terminal voltage \n", + "qp = pybamm.QuickPlot(sims, output_variables=[\"Current [A]\", \"Terminal voltage [V]\"])\n", + "qp.plot(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "During discharge, the MPM overpredicts the voltage since it neglects the transport (and therefore resistances) through the electrolyte. During the relaxation portion, the DFN overpredicts the rate of relaxation to equilibrium compared to the MP-DFN and MPM. However, the slower relaxation of the size distribution models has been shown to agree better with experiment [[5]](#References)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "The relevant papers for this notebook are:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n", + "[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n", + "[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n", + "[4] Toby L. Kirk, Jack Evans, Colin P. Please, and S. Jonathan Chapman. Modelling electrode heterogeneity in lithium-ion batteries: unimodal and bimodal particle-size distributions. arXiv:2006.12208, 2020. URL: https://arxiv.org/abs/2006.12208, arXiv:2006.12208.\n", + "[5] Toby L. Kirk, Colin P. Please, and S. Jon Chapman. Physical modelling of the slow voltage relaxation phenomenon in lithium-ion batteries. Journal of The Electrochemical Society, 168(6):060554, jun 2021. URL: https://doi.org/10.1149/1945-7111/ac0bf7, doi:10.1149/1945-7111/ac0bf7.\n", + "[6] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n", + "[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n", + "\n" + ] + } + ], + "source": [ + "pybamm.print_citations()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 9e408854c2c7cd02a87b6659e2f14d3c52320193 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Tue, 10 Aug 2021 18:00:24 +0100 Subject: [PATCH 11/16] add more tests DFN size distributions --- .../test_lithium_ion/test_dfn.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index 9b559ac605..932d06a0cf 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -304,6 +304,45 @@ def test_basic_processing_4D(self): ) modeltest.test_all(skip_output_tests=True) + def test_conservation_each_electrode(self): + # Test that surface areas are being calculated from the distribution correctly + # for any discretization in the size domain. + # We test that the amount of lithium removed or added to each electrode + # is the same as for the standard DFN with the same parameters + models = [ + pybamm.lithium_ion.DFN(), + pybamm.lithium_ion.DFN(options={"particle size": "distribution"}) + ] + var = pybamm.standard_spatial_vars + + # reduce number of particle sizes, for a crude discretization + var_pts = { + var.R_n: 3, + var.R_p: 3, + } + solver = pybamm.CasadiSolver(mode="fast") + + # solve + neg_Li = [] + pos_Li = [] + for model in models: + sim = pybamm.Simulation( + model, + parameter_values=self.params, + var_pts=self.var_pts, + solver=solver + ) + sim.var_pts.update(var_pts) + solution = sim.solve([0, 3500]) + neg = solution["Total lithium in negative electrode [mol]"].entries[-1] + pos = solution["Total lithium in positive electrode [mol]"].entries[-1] + neg_Li.append(neg) + pos_Li.append(pos) + + # compare + np.testing.assert_array_almost_equal(neg_Li[0], neg_Li[1], decimal=12) + np.testing.assert_array_almost_equal(pos_Li[0], pos_Li[1], decimal=12) + if __name__ == "__main__": print("Add -v for more debug output") From 5a963eda8445700a127123941385e38965344302 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Tue, 10 Aug 2021 18:06:35 +0100 Subject: [PATCH 12/16] changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c8418962..e67d7b69b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM) ## Features - +- Added submodels and functionality for particle-size distributions in the DFN model, including an +example notebook ([#1601](https://github.com/pybamm-team/PyBaMM/pull/1601)) - Added capability for `quaternary` domains (in addition to `primary`, `secondary` and `tertiary`), increasing the maximum number of domains that a `Symbol` can have to 4. ([#1580](https://github.com/pybamm-team/PyBaMM/pull/1580)) - Tabs can now be placed at the bottom of the cell in 1+1D thermal models ([#1581](https://github.com/pybamm-team/PyBaMM/pull/1581)) - Added temperature dependence on electrode electronic conductivity ([#1570](https://github.com/pybamm-team/PyBaMM/pull/1570)) From 4d27e462a1caf25eaedb44db1d304a915ae84530 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Wed, 11 Aug 2021 09:39:12 +0100 Subject: [PATCH 13/16] fix docs --- .../particle/size_distribution/fast_many_distributions.rst | 2 +- .../particle/size_distribution/fickian_many_distributions.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst index 4d1facd628..97f0feed64 100644 --- a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst +++ b/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst @@ -1,4 +1,4 @@ -#Fast Many Size Distributions +Fast Many Size Distributions ============================ .. autoclass:: pybamm.particle.FastManySizeDistributions diff --git a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst index b88d09e742..7b927b52cc 100644 --- a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst +++ b/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst @@ -1,4 +1,4 @@ -#Fickian Many Size Distributions +Fickian Many Size Distributions =============================== .. autoclass:: pybamm.particle.FickianManySizeDistributions From c38ac70033913c13dad9c548326cac7be209b072 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 23 Aug 2021 16:00:28 +0100 Subject: [PATCH 14/16] rename particle submodels --- .../particle/fickian_many_particles.rst | 7 --- .../particle/fickian_single_particle.rst | 6 -- .../models/submodels/particle/index.rst | 5 +- .../no_distribution/fickian_diffusion.rst | 7 +++ .../particle/no_distribution/index.rst | 10 ++++ .../no_distribution/polynomial_profile.rst | 5 ++ .../x_averaged_fickian_diffusion.rst | 6 ++ .../x_averaged_polynomial_profile.rst | 5 ++ .../particle/polynomial_many_particles.rst | 5 -- .../particle/polynomial_single_particle.rst | 5 -- .../size_distribution/base_distribution.rst | 2 +- .../fast_many_distributions.rst | 5 -- .../fast_single_distribution.rst | 5 -- .../size_distribution/fickian_diffusion.rst | 7 +++ .../fickian_many_distributions.rst | 7 --- .../fickian_single_distribution.rst | 6 -- .../particle/size_distribution/index.rst | 8 +-- .../size_distribution/uniform_profile.rst | 5 ++ .../x_averaged_fickian_diffusion.rst | 6 ++ .../x_averaged_uniform_profile.rst | 5 ++ examples/scripts/custom_model.py | 6 +- .../full_battery_models/lithium_ion/dfn.py | 10 ++-- .../full_battery_models/lithium_ion/mpm.py | 8 +-- .../lithium_ion/newman_tobias.py | 10 ++-- .../full_battery_models/lithium_ion/spm.py | 6 +- .../full_battery_models/lithium_ion/spme.py | 6 +- pybamm/models/submodels/particle/__init__.py | 6 +- .../submodels/particle/base_particle.py | 4 +- .../particle/no_distribution/__init__.py | 4 ++ .../fickian_diffusion.py} | 10 ++-- .../polynomial_profile.py} | 9 +-- .../x_averaged_fickian_diffusion.py} | 15 ++--- .../x_averaged_polynomial_profile.py} | 8 +-- .../particle/size_distribution/__init__.py | 8 +-- .../size_distribution/base_distribution.py | 5 +- ..._distributions.py => fickian_diffusion.py} | 13 +++-- ...ny_distributions.py => uniform_profile.py} | 18 +++--- ...ion.py => x_averaged_fickian_diffusion.py} | 15 ++--- ...ution.py => x_averaged_uniform_profile.py} | 13 ++--- tests/unit/test_citations.py | 8 ++- .../test_lithium_ion/test_spm.py | 3 +- .../test_fickian_many_particles.py | 4 +- .../test_fickian_single_particle.py | 8 ++- .../test_polynomial_many_particles.py | 12 ++-- .../test_polynomial_single_particle.py | 12 ++-- .../test_size_distribution/__init__.py | 0 .../test_base_distribution.py | 32 ----------- .../test_fast_many_distributions.py | 57 ------------------- .../test_fast_single_distribution.py | 50 ---------------- .../test_fickian_single_distribution.py | 44 -------------- 50 files changed, 185 insertions(+), 336 deletions(-) delete mode 100644 docs/source/models/submodels/particle/fickian_many_particles.rst delete mode 100644 docs/source/models/submodels/particle/fickian_single_particle.rst create mode 100644 docs/source/models/submodels/particle/no_distribution/fickian_diffusion.rst create mode 100644 docs/source/models/submodels/particle/no_distribution/index.rst create mode 100644 docs/source/models/submodels/particle/no_distribution/polynomial_profile.rst create mode 100644 docs/source/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.rst create mode 100644 docs/source/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.rst delete mode 100644 docs/source/models/submodels/particle/polynomial_many_particles.rst delete mode 100644 docs/source/models/submodels/particle/polynomial_single_particle.rst delete mode 100644 docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst delete mode 100644 docs/source/models/submodels/particle/size_distribution/fast_single_distribution.rst create mode 100644 docs/source/models/submodels/particle/size_distribution/fickian_diffusion.rst delete mode 100644 docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst delete mode 100644 docs/source/models/submodels/particle/size_distribution/fickian_single_distribution.rst create mode 100644 docs/source/models/submodels/particle/size_distribution/uniform_profile.rst create mode 100644 docs/source/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.rst create mode 100644 docs/source/models/submodels/particle/size_distribution/x_averaged_uniform_profile.rst create mode 100644 pybamm/models/submodels/particle/no_distribution/__init__.py rename pybamm/models/submodels/particle/{fickian_many_particles.py => no_distribution/fickian_diffusion.py} (90%) rename pybamm/models/submodels/particle/{polynomial_many_particles.py => no_distribution/polynomial_profile.py} (98%) rename pybamm/models/submodels/particle/{fickian_single_particle.py => no_distribution/x_averaged_fickian_diffusion.py} (89%) rename pybamm/models/submodels/particle/{polynomial_single_particle.py => no_distribution/x_averaged_polynomial_profile.py} (98%) rename pybamm/models/submodels/particle/size_distribution/{fickian_many_distributions.py => fickian_diffusion.py} (93%) rename pybamm/models/submodels/particle/size_distribution/{fast_many_distributions.py => uniform_profile.py} (90%) rename pybamm/models/submodels/particle/size_distribution/{fickian_single_distribution.py => x_averaged_fickian_diffusion.py} (94%) rename pybamm/models/submodels/particle/size_distribution/{fast_single_distribution.py => x_averaged_uniform_profile.py} (92%) delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_size_distribution/__init__.py delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_base_distribution.py delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_many_distributions.py delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_single_distribution.py delete mode 100644 tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fickian_single_distribution.py diff --git a/docs/source/models/submodels/particle/fickian_many_particles.rst b/docs/source/models/submodels/particle/fickian_many_particles.rst deleted file mode 100644 index c9834bc5d9..0000000000 --- a/docs/source/models/submodels/particle/fickian_many_particles.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fickian Many Particles -====================== - -.. autoclass:: pybamm.particle.FickianManyParticles - :members: - - diff --git a/docs/source/models/submodels/particle/fickian_single_particle.rst b/docs/source/models/submodels/particle/fickian_single_particle.rst deleted file mode 100644 index 008b2e8b48..0000000000 --- a/docs/source/models/submodels/particle/fickian_single_particle.rst +++ /dev/null @@ -1,6 +0,0 @@ -Fickian Single Particle -======================= - -.. autoclass:: pybamm.particle.FickianSingleParticle - :members: - diff --git a/docs/source/models/submodels/particle/index.rst b/docs/source/models/submodels/particle/index.rst index ad9676f4dd..1ed77b6e0d 100644 --- a/docs/source/models/submodels/particle/index.rst +++ b/docs/source/models/submodels/particle/index.rst @@ -5,8 +5,5 @@ Particle :maxdepth: 1 base_particle - fickian_single_particle - fickian_many_particles - polynomial_single_particle - polynomial_many_particles + no_distribution/index size_distribution/index diff --git a/docs/source/models/submodels/particle/no_distribution/fickian_diffusion.rst b/docs/source/models/submodels/particle/no_distribution/fickian_diffusion.rst new file mode 100644 index 0000000000..3e934580a2 --- /dev/null +++ b/docs/source/models/submodels/particle/no_distribution/fickian_diffusion.rst @@ -0,0 +1,7 @@ +Fickian Diffusion +================= + +.. autoclass:: pybamm.particle.no_distribution.FickianDiffusion + :members: + + diff --git a/docs/source/models/submodels/particle/no_distribution/index.rst b/docs/source/models/submodels/particle/no_distribution/index.rst new file mode 100644 index 0000000000..732d55f2dc --- /dev/null +++ b/docs/source/models/submodels/particle/no_distribution/index.rst @@ -0,0 +1,10 @@ +No Particle-Size Distribution +============================= + +.. toctree:: + :maxdepth: 1 + + fickian_diffusion + x_averaged_fickian_diffusion + polynomial_profile + x_averaged_polynomial_profile diff --git a/docs/source/models/submodels/particle/no_distribution/polynomial_profile.rst b/docs/source/models/submodels/particle/no_distribution/polynomial_profile.rst new file mode 100644 index 0000000000..0775f90194 --- /dev/null +++ b/docs/source/models/submodels/particle/no_distribution/polynomial_profile.rst @@ -0,0 +1,5 @@ +Polynomial Profile +================== + +.. autoclass:: pybamm.particle.no_distribution.PolynomialProfile + :members: diff --git a/docs/source/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.rst b/docs/source/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.rst new file mode 100644 index 0000000000..33aa8a4384 --- /dev/null +++ b/docs/source/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.rst @@ -0,0 +1,6 @@ +X-averaged Fickian Diffusion +============================ + +.. autoclass:: pybamm.particle.no_distribution.XAveragedFickianDiffusion + :members: + diff --git a/docs/source/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.rst b/docs/source/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.rst new file mode 100644 index 0000000000..fe6a3fcf0d --- /dev/null +++ b/docs/source/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.rst @@ -0,0 +1,5 @@ +X-averaged Polynomial Profile +============================= + +.. autoclass:: pybamm.particle.no_distribution.XAveragedPolynomialProfile + :members: diff --git a/docs/source/models/submodels/particle/polynomial_many_particles.rst b/docs/source/models/submodels/particle/polynomial_many_particles.rst deleted file mode 100644 index 4a3fe1e138..0000000000 --- a/docs/source/models/submodels/particle/polynomial_many_particles.rst +++ /dev/null @@ -1,5 +0,0 @@ -Polynomial Many Particles -========================= - -.. autoclass:: pybamm.particle.PolynomialManyParticles - :members: diff --git a/docs/source/models/submodels/particle/polynomial_single_particle.rst b/docs/source/models/submodels/particle/polynomial_single_particle.rst deleted file mode 100644 index 5cd1747e0e..0000000000 --- a/docs/source/models/submodels/particle/polynomial_single_particle.rst +++ /dev/null @@ -1,5 +0,0 @@ -Polynomial Single Particle -=========================== - -.. autoclass:: pybamm.particle.PolynomialSingleParticle - :members: diff --git a/docs/source/models/submodels/particle/size_distribution/base_distribution.rst b/docs/source/models/submodels/particle/size_distribution/base_distribution.rst index 43182a4531..308423364d 100644 --- a/docs/source/models/submodels/particle/size_distribution/base_distribution.rst +++ b/docs/source/models/submodels/particle/size_distribution/base_distribution.rst @@ -1,6 +1,6 @@ Particle Size Distribution Base Model ===================================== -.. autoclass:: pybamm.particle.BaseSizeDistribution +.. autoclass:: pybamm.particle.size_distribution.BaseSizeDistribution :members: diff --git a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst deleted file mode 100644 index 97f0feed64..0000000000 --- a/docs/source/models/submodels/particle/size_distribution/fast_many_distributions.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fast Many Size Distributions -============================ - -.. autoclass:: pybamm.particle.FastManySizeDistributions - :members: diff --git a/docs/source/models/submodels/particle/size_distribution/fast_single_distribution.rst b/docs/source/models/submodels/particle/size_distribution/fast_single_distribution.rst deleted file mode 100644 index 22d281f56c..0000000000 --- a/docs/source/models/submodels/particle/size_distribution/fast_single_distribution.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fast Single Size Distribution -============================= - -.. autoclass:: pybamm.particle.FastSingleSizeDistribution - :members: diff --git a/docs/source/models/submodels/particle/size_distribution/fickian_diffusion.rst b/docs/source/models/submodels/particle/size_distribution/fickian_diffusion.rst new file mode 100644 index 0000000000..45d999da00 --- /dev/null +++ b/docs/source/models/submodels/particle/size_distribution/fickian_diffusion.rst @@ -0,0 +1,7 @@ +Fickian Diffusion +================= + +.. autoclass:: pybamm.particle.size_distribution.FickianDiffusion + :members: + + diff --git a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst b/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst deleted file mode 100644 index 7b927b52cc..0000000000 --- a/docs/source/models/submodels/particle/size_distribution/fickian_many_distributions.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fickian Many Size Distributions -=============================== - -.. autoclass:: pybamm.particle.FickianManySizeDistributions - :members: - - diff --git a/docs/source/models/submodels/particle/size_distribution/fickian_single_distribution.rst b/docs/source/models/submodels/particle/size_distribution/fickian_single_distribution.rst deleted file mode 100644 index 6ed09a1d7c..0000000000 --- a/docs/source/models/submodels/particle/size_distribution/fickian_single_distribution.rst +++ /dev/null @@ -1,6 +0,0 @@ -Fickian Single Size Distribution -================================ - -.. autoclass:: pybamm.particle.FickianSingleSizeDistribution - :members: - diff --git a/docs/source/models/submodels/particle/size_distribution/index.rst b/docs/source/models/submodels/particle/size_distribution/index.rst index 1efda8f7cd..24e7d3fcf6 100644 --- a/docs/source/models/submodels/particle/size_distribution/index.rst +++ b/docs/source/models/submodels/particle/size_distribution/index.rst @@ -5,7 +5,7 @@ Particle Size Distribution :maxdepth: 1 base_distribution - fickian_single_distribution - fickian_many_distributions - fast_single_distribution - fast_many_distributions + fickian_diffusion + x_averaged_fickian_diffusion + uniform_profile + x_averaged_uniform_profile diff --git a/docs/source/models/submodels/particle/size_distribution/uniform_profile.rst b/docs/source/models/submodels/particle/size_distribution/uniform_profile.rst new file mode 100644 index 0000000000..8a07b17bd8 --- /dev/null +++ b/docs/source/models/submodels/particle/size_distribution/uniform_profile.rst @@ -0,0 +1,5 @@ +Uniform Profile +=============== + +.. autoclass:: pybamm.particle.size_distribution.UniformProfile + :members: diff --git a/docs/source/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.rst b/docs/source/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.rst new file mode 100644 index 0000000000..925002614d --- /dev/null +++ b/docs/source/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.rst @@ -0,0 +1,6 @@ +X-averaged Fickian Diffusion +============================ + +.. autoclass:: pybamm.particle.size_distribution.XAveragedFickianDiffusion + :members: + diff --git a/docs/source/models/submodels/particle/size_distribution/x_averaged_uniform_profile.rst b/docs/source/models/submodels/particle/size_distribution/x_averaged_uniform_profile.rst new file mode 100644 index 0000000000..0ee9d08de1 --- /dev/null +++ b/docs/source/models/submodels/particle/size_distribution/x_averaged_uniform_profile.rst @@ -0,0 +1,5 @@ +X-averaged Uniform Profile +========================== + +.. autoclass:: pybamm.particle.size_distribution.XAveragedUniformProfile + :members: diff --git a/examples/scripts/custom_model.py b/examples/scripts/custom_model.py index 98ddb8389d..0c95919c95 100644 --- a/examples/scripts/custom_model.py +++ b/examples/scripts/custom_model.py @@ -29,12 +29,14 @@ model.submodels["positive electrode potential"] = pybamm.electrode.ohm.LeadingOrder( model.param, "Positive" ) -model.submodels["negative particle"] = pybamm.particle.PolynomialSingleParticle( +particle_n = pybamm.particle.no_distribution.XAveragedPolynomialProfile( model.param, "Negative", "uniform profile" ) -model.submodels["positive particle"] = pybamm.particle.PolynomialSingleParticle( +model.submodels["negative particle"] = particle_n +particle_p = pybamm.particle.no_distribution.XAveragedPolynomialProfile( model.param, "Positive", "uniform profile" ) +model.submodels["positive particle"] = particle_p model.submodels["negative interface"] = pybamm.interface.InverseButlerVolmer( model.param, "Negative", "lithium-ion main", options=model.options ) diff --git a/pybamm/models/full_battery_models/lithium_ion/dfn.py b/pybamm/models/full_battery_models/lithium_ion/dfn.py index 7c627513bc..d0551eb4c8 100644 --- a/pybamm/models/full_battery_models/lithium_ion/dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/dfn.py @@ -89,7 +89,9 @@ def set_particle_submodel(self): if particle_side == "Fickian diffusion": self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.FickianManyParticles(self.param, domain) + ] = pybamm.particle.no_distribution.FickianDiffusion( + self.param, domain + ) elif particle_side in [ "uniform profile", "quadratic profile", @@ -97,14 +99,14 @@ def set_particle_submodel(self): ]: self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.PolynomialManyParticles( + ] = pybamm.particle.no_distribution.PolynomialProfile( self.param, domain, particle_side ) elif self.options["particle size"] == "distribution": if particle_side == "Fickian diffusion": self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.FickianManySizeDistributions( + ] = pybamm.particle.size_distribution.FickianDiffusion( self.param, domain ) elif particle_side in [ @@ -114,7 +116,7 @@ def set_particle_submodel(self): ]: self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.FastManySizeDistributions( + ] = pybamm.particle.size_distribution.UniformProfile( self.param, domain ) diff --git a/pybamm/models/full_battery_models/lithium_ion/mpm.py b/pybamm/models/full_battery_models/lithium_ion/mpm.py index 600fbce9a6..eda4c64340 100644 --- a/pybamm/models/full_battery_models/lithium_ion/mpm.py +++ b/pybamm/models/full_battery_models/lithium_ion/mpm.py @@ -69,17 +69,17 @@ def __init__( def set_particle_submodel(self): if self.options["particle"] == "Fickian diffusion": - submod_n = pybamm.particle.FickianSingleSizeDistribution( + submod_n = pybamm.particle.size_distribution.XAveragedFickianDiffusion( self.param, "Negative" ) - submod_p = pybamm.particle.FickianSingleSizeDistribution( + submod_p = pybamm.particle.size_distribution.XAveragedFickianDiffusion( self.param, "Positive" ) elif self.options["particle"] == "uniform profile": - submod_n = pybamm.particle.FastSingleSizeDistribution( + submod_n = pybamm.particle.size_distribution.XAveragedUniformProfile( self.param, "Negative" ) - submod_p = pybamm.particle.FastSingleSizeDistribution( + submod_p = pybamm.particle.size_distribution.XAveragedUniformProfile( self.param, "Positive" ) self.submodels["negative particle"] = submod_n diff --git a/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py b/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py index 4606d9de42..5f6961c8ba 100644 --- a/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py +++ b/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py @@ -58,12 +58,14 @@ def __init__(self, options=None, name="Newman-Tobias model", build=True): def set_particle_submodel(self): if self.options["particle"] == "Fickian diffusion": - self.submodels["negative particle"] = pybamm.particle.FickianSingleParticle( + submod_n = pybamm.particle.no_distribution.XAveragedFickianDiffusion( self.param, "Negative" ) - self.submodels["positive particle"] = pybamm.particle.FickianSingleParticle( + self.submodels["negative particle"] = submod_n + submod_p = pybamm.particle.no_distribution.XAveragedFickianDiffusion( self.param, "Positive" ) + self.submodels["positive particle"] = submod_p elif self.options["particle"] in [ "uniform profile", "quadratic profile", @@ -71,12 +73,12 @@ def set_particle_submodel(self): ]: self.submodels[ "negative particle" - ] = pybamm.particle.PolynomialSingleParticle( + ] = pybamm.particle.no_distribution.XAveragedPolynomialProfile( self.param, "Negative", self.options["particle"] ) self.submodels[ "positive particle" - ] = pybamm.particle.PolynomialSingleParticle( + ] = pybamm.particle.no_distribution.XAveragedPolynomialProfile( self.param, "Positive", self.options["particle"] ) diff --git a/pybamm/models/full_battery_models/lithium_ion/spm.py b/pybamm/models/full_battery_models/lithium_ion/spm.py index 706c6c3471..5686a1625f 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spm.py +++ b/pybamm/models/full_battery_models/lithium_ion/spm.py @@ -109,7 +109,9 @@ def set_particle_submodel(self): if particle_side == "Fickian diffusion": self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.FickianSingleParticle(self.param, domain) + ] = pybamm.particle.no_distribution.XAveragedFickianDiffusion( + self.param, domain + ) elif particle_side in [ "uniform profile", "quadratic profile", @@ -117,7 +119,7 @@ def set_particle_submodel(self): ]: self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.PolynomialSingleParticle( + ] = pybamm.particle.no_distribution.XAveragedPolynomialProfile( self.param, domain, particle_side ) diff --git a/pybamm/models/full_battery_models/lithium_ion/spme.py b/pybamm/models/full_battery_models/lithium_ion/spme.py index d8bed59d43..ad0accd59c 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spme.py +++ b/pybamm/models/full_battery_models/lithium_ion/spme.py @@ -119,7 +119,9 @@ def set_particle_submodel(self): if particle_side == "Fickian diffusion": self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.FickianSingleParticle(self.param, domain) + ] = pybamm.particle.no_distribution.XAveragedFickianDiffusion( + self.param, domain + ) elif particle_side in [ "uniform profile", "quadratic profile", @@ -127,7 +129,7 @@ def set_particle_submodel(self): ]: self.submodels[ domain.lower() + " particle" - ] = pybamm.particle.PolynomialSingleParticle( + ] = pybamm.particle.no_distribution.XAveragedPolynomialProfile( self.param, domain, particle_side ) diff --git a/pybamm/models/submodels/particle/__init__.py b/pybamm/models/submodels/particle/__init__.py index b60cdfde22..dab3275365 100644 --- a/pybamm/models/submodels/particle/__init__.py +++ b/pybamm/models/submodels/particle/__init__.py @@ -1,6 +1,2 @@ from .base_particle import BaseParticle -from .fickian_many_particles import FickianManyParticles -from .fickian_single_particle import FickianSingleParticle -from .polynomial_single_particle import PolynomialSingleParticle -from .polynomial_many_particles import PolynomialManyParticles -from .size_distribution import * +from . import no_distribution, size_distribution \ No newline at end of file diff --git a/pybamm/models/submodels/particle/base_particle.py b/pybamm/models/submodels/particle/base_particle.py index 67f044a811..cdbda45a31 100644 --- a/pybamm/models/submodels/particle/base_particle.py +++ b/pybamm/models/submodels/particle/base_particle.py @@ -27,8 +27,8 @@ def _get_standard_concentration_variables( """ All particle submodels must provide the particle concentration as an argument to this method. Some submodels solve for quantities other than the concentration - itself, for example the 'FickianSingleParticle' models solves for the x-averaged - concentration. In such cases the variables being solved for (set in + itself, for example the 'XAveragedFickianDiffusion' models solves for the + x-averaged concentration. In such cases the variables being solved for (set in 'get_fundamental_variables') must also be passed as keyword arguments. If not passed as keyword arguments, the various average concentrations and surface concentration are computed automatically from the particle concentration. diff --git a/pybamm/models/submodels/particle/no_distribution/__init__.py b/pybamm/models/submodels/particle/no_distribution/__init__.py new file mode 100644 index 0000000000..5036e9c5e7 --- /dev/null +++ b/pybamm/models/submodels/particle/no_distribution/__init__.py @@ -0,0 +1,4 @@ +from .fickian_diffusion import FickianDiffusion +from .x_averaged_fickian_diffusion import XAveragedFickianDiffusion +from .x_averaged_polynomial_profile import XAveragedPolynomialProfile +from .polynomial_profile import PolynomialProfile \ No newline at end of file diff --git a/pybamm/models/submodels/particle/fickian_many_particles.py b/pybamm/models/submodels/particle/no_distribution/fickian_diffusion.py similarity index 90% rename from pybamm/models/submodels/particle/fickian_many_particles.py rename to pybamm/models/submodels/particle/no_distribution/fickian_diffusion.py index 5cb8b3f99f..ff9ee7ca34 100644 --- a/pybamm/models/submodels/particle/fickian_many_particles.py +++ b/pybamm/models/submodels/particle/no_distribution/fickian_diffusion.py @@ -1,13 +1,15 @@ # -# Class for many particles with Fickian diffusion +# Class for particles with Fickian diffusion and x-dependence # import pybamm -from .base_particle import BaseParticle +from ..base_particle import BaseParticle -class FickianManyParticles(BaseParticle): +class FickianDiffusion(BaseParticle): """ - Class for molar conservation in many particles which employs Fick's law. + Class for molar conservation in particles, employing Fick's law, and allowing + variation in the electrode domain. I.e., the concentration varies with r + (internal coordinate) and x (electrode coordinate). Parameters ---------- diff --git a/pybamm/models/submodels/particle/polynomial_many_particles.py b/pybamm/models/submodels/particle/no_distribution/polynomial_profile.py similarity index 98% rename from pybamm/models/submodels/particle/polynomial_many_particles.py rename to pybamm/models/submodels/particle/no_distribution/polynomial_profile.py index c451ef7219..9217ee1e7c 100644 --- a/pybamm/models/submodels/particle/polynomial_many_particles.py +++ b/pybamm/models/submodels/particle/no_distribution/polynomial_profile.py @@ -3,13 +3,14 @@ # import pybamm -from .base_particle import BaseParticle +from ..base_particle import BaseParticle -class PolynomialManyParticles(BaseParticle): +class PolynomialProfile(BaseParticle): """ - Class for molar conservation in many particles with an assumed polynomial - concentration profile in r. Model equations from [1]_. + Class for molar conservation in particles, assuming a polynomial + concentration profile in r, and allowing variation in the electrode domain. + Model equations from [1]_. Parameters ---------- diff --git a/pybamm/models/submodels/particle/fickian_single_particle.py b/pybamm/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.py similarity index 89% rename from pybamm/models/submodels/particle/fickian_single_particle.py rename to pybamm/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.py index acc64ad49e..ea4370af6d 100644 --- a/pybamm/models/submodels/particle/fickian_single_particle.py +++ b/pybamm/models/submodels/particle/no_distribution/x_averaged_fickian_diffusion.py @@ -1,15 +1,16 @@ # -# Class for a single particle with Fickian diffusion +# Class for a single x-averaged particle with Fickian diffusion # import pybamm -from .base_particle import BaseParticle +from ..base_particle import BaseParticle -class FickianSingleParticle(BaseParticle): +class XAveragedFickianDiffusion(BaseParticle): """ - Class for molar conservation in a single x-averaged particle which employs - Fick's law. + Class for molar conservation in a single x-averaged particle, employing Fick's + law. I.e., the concentration varies with r (internal spherical coordinate) + but not x (electrode coordinate). Parameters ---------- @@ -108,8 +109,8 @@ def set_boundary_conditions(self, variables): def set_initial_conditions(self, variables): """ - For single particle models, initial conditions can't depend on x so we - arbitrarily set the initial values of the single particles to be given + For single or x-averaged particle models, initial conditions can't depend on x + so we arbitrarily set the initial values of the single particles to be given by the values at x=0 in the negative electrode and x=1 in the positive electrode. Typically, supplied initial conditions are uniform x. diff --git a/pybamm/models/submodels/particle/polynomial_single_particle.py b/pybamm/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.py similarity index 98% rename from pybamm/models/submodels/particle/polynomial_single_particle.py rename to pybamm/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.py index 1dc4bfad19..c94d566854 100644 --- a/pybamm/models/submodels/particle/polynomial_single_particle.py +++ b/pybamm/models/submodels/particle/no_distribution/x_averaged_polynomial_profile.py @@ -3,10 +3,10 @@ # import pybamm -from .base_particle import BaseParticle +from ..base_particle import BaseParticle -class PolynomialSingleParticle(BaseParticle): +class XAveragedPolynomialProfile(BaseParticle): """ Class for molar conservation in a single x-averaged particle with an assumed polynomial concentration profile in r. Model equations from [1]_. @@ -344,8 +344,8 @@ def set_rhs(self, variables): def set_initial_conditions(self, variables): """ - For single particle models, initial conditions can't depend on x so we - arbitrarily evaluate them at x=0 in the negative electrode and x=1 in the + For single or x-averaged particle models, initial conditions can't depend on x + so we arbitrarily evaluate them at x=0 in the negative electrode and x=1 in the positive electrode (they will usually be constant) """ c_s_rxav = variables[ diff --git a/pybamm/models/submodels/particle/size_distribution/__init__.py b/pybamm/models/submodels/particle/size_distribution/__init__.py index 6ef02d5297..aaa0aeb734 100644 --- a/pybamm/models/submodels/particle/size_distribution/__init__.py +++ b/pybamm/models/submodels/particle/size_distribution/__init__.py @@ -1,5 +1,5 @@ from .base_distribution import BaseSizeDistribution -from .fickian_many_distributions import FickianManySizeDistributions -from .fickian_single_distribution import FickianSingleSizeDistribution -from .fast_many_distributions import FastManySizeDistributions -from .fast_single_distribution import FastSingleSizeDistribution +from .fickian_diffusion import FickianDiffusion +from .x_averaged_fickian_diffusion import XAveragedFickianDiffusion +from .uniform_profile import UniformProfile +from .x_averaged_uniform_profile import XAveragedUniformProfile diff --git a/pybamm/models/submodels/particle/size_distribution/base_distribution.py b/pybamm/models/submodels/particle/size_distribution/base_distribution.py index b48491aea9..4ba2fe391e 100644 --- a/pybamm/models/submodels/particle/size_distribution/base_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/base_distribution.py @@ -1,5 +1,5 @@ # -# Base class for particles +# Base class for particle-size distributions # import pybamm @@ -273,8 +273,7 @@ def _get_standard_flux_distribution_variables(self, N_s): [self.domain.lower() + " particle"], { "secondary": self.domain.lower() + " particle size", - "tertiary": self.domain.lower() + " electrode", - "quaternary": "current collector" + "tertiary": "current collector" }, ) elif isinstance(N_s, pybamm.Scalar): diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/fickian_diffusion.py similarity index 93% rename from pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py rename to pybamm/models/submodels/particle/size_distribution/fickian_diffusion.py index cea5957142..a5f29177de 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/fickian_diffusion.py @@ -1,5 +1,5 @@ # -# Class for many particle-size distributions, one distribution at every +# Class for particle-size distributions, one distribution at every # x location of the electrode, and Fickian diffusion within each particle # import pybamm @@ -7,10 +7,11 @@ from .base_distribution import BaseSizeDistribution -class FickianManySizeDistributions(BaseSizeDistribution): - """Class for molar conservation in many particle-size - distributions, one distribution at every x location of the electrode, - with Fickian diffusion within each particle. +class FickianDiffusion(BaseSizeDistribution): + """Class for molar conservation in particle-size distributions, one + distribution at every x location of the electrode, + with Fickian diffusion within each particle. Concentration varies with + r (spherical coordinate), R (particle size), and x (electrode coordinate). Parameters ---------- @@ -20,7 +21,7 @@ class FickianManySizeDistributions(BaseSizeDistribution): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.BaseSizeDistribution` + **Extends:** :class:`pybamm.particle.size_distribution.BaseSizeDistribution` """ def __init__(self, param, domain): diff --git a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py b/pybamm/models/submodels/particle/size_distribution/uniform_profile.py similarity index 90% rename from pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py rename to pybamm/models/submodels/particle/size_distribution/uniform_profile.py index 9262d42ca2..9d6e3c89d9 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_many_distributions.py +++ b/pybamm/models/submodels/particle/size_distribution/uniform_profile.py @@ -1,17 +1,19 @@ # -# Class for many particle-size distributions, one distribution at every -# x location of the electrode, with fast diffusion (uniform concentration in r) -# within particles +# Class for particle-size distributions, one distribution at every +# x location of the electrode, with uniform concentration in each +# particle # import pybamm from .base_distribution import BaseSizeDistribution -class FastManySizeDistributions(BaseSizeDistribution): - """Class for molar conservation in many particle-size - distributions, one distribution at every x location of the electrode, - with fast diffusion (uniform concentration in r) within the particles +class UniformProfile(BaseSizeDistribution): + """ + Class for molar conservation in particle-size distributions, one + distribution at every x location of the electrode, + with a uniform concentration within each particle (in r). Concentration varies + with R (particle size), and x (electrode coordinate). Parameters ---------- @@ -21,7 +23,7 @@ class FastManySizeDistributions(BaseSizeDistribution): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.BaseSizeDistribution` + **Extends:** :class:`pybamm.particle.size_distribution.BaseSizeDistribution` """ def __init__(self, param, domain): diff --git a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py b/pybamm/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.py similarity index 94% rename from pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py rename to pybamm/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.py index 3decc7d5a3..76c97d7c35 100644 --- a/pybamm/models/submodels/particle/size_distribution/fickian_single_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/x_averaged_fickian_diffusion.py @@ -1,15 +1,16 @@ # -# Class for a single particle-size distribution representing an -# electrode, with Fickian diffusion within each particle +# Class for a particle-size distribution averaged in the x direction, +# with Fickian diffusion within each particle # import pybamm from .base_distribution import BaseSizeDistribution -class FickianSingleSizeDistribution(BaseSizeDistribution): - """Class for molar conservation in a single (i.e., x-averaged) particle-size - distribution with Fickian diffusion within each particle. +class XAveragedFickianDiffusion(BaseSizeDistribution): + """Class for molar conservation in an x-averaged particle-size + distribution with Fickian diffusion in each particle. Concentration + varies with r (spherical coordinate), R (particle size) but not x. Parameters ---------- @@ -19,7 +20,7 @@ class FickianSingleSizeDistribution(BaseSizeDistribution): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.BaseSizeDistribution` + **Extends:** :class:`pybamm.particle.size_distribution.BaseSizeDistribution` """ def __init__(self, param, domain): @@ -223,7 +224,7 @@ def set_boundary_conditions(self, variables): def set_initial_conditions(self, variables): """ - For single particle-size distribution models, initial conditions can't + For x-averaged particle-size distribution models, initial conditions can't depend on x so we arbitrarily set the initial values of the single particles to be given by the values at x=0 in the negative electrode and x=1 in the positive electrode. Typically, supplied initial diff --git a/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py b/pybamm/models/submodels/particle/size_distribution/x_averaged_uniform_profile.py similarity index 92% rename from pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py rename to pybamm/models/submodels/particle/size_distribution/x_averaged_uniform_profile.py index 9985121fb6..8fa3cd49bd 100644 --- a/pybamm/models/submodels/particle/size_distribution/fast_single_distribution.py +++ b/pybamm/models/submodels/particle/size_distribution/x_averaged_uniform_profile.py @@ -1,16 +1,15 @@ # -# Class for a single particle-size distribution representing an -# electrode, with fast diffusion (uniform concentration in r) within particles +# Class for a x-averaged particle-size distribution, with uniform concentration +# profile in each particle # import pybamm from .base_distribution import BaseSizeDistribution -class FastSingleSizeDistribution(BaseSizeDistribution): - """Class for molar conservation in a single (i.e., x-averaged) particle-size - distribution) with fast diffusion within each particle - (uniform concentration in r). +class XAveragedUniformProfile(BaseSizeDistribution): + """Class for molar conservation in an x-averaged particle-size + distribution with uniform concentration in each particle. Parameters ---------- @@ -20,7 +19,7 @@ class FastSingleSizeDistribution(BaseSizeDistribution): The domain of the model either 'Negative' or 'Positive' - **Extends:** :class:`pybamm.particle.BaseSizeDistribution` + **Extends:** :class:`pybamm.particle.size_distribution.BaseSizeDistribution` """ def __init__(self, param, domain): diff --git a/tests/unit/test_citations.py b/tests/unit/test_citations.py index 095789f1bb..c688648bdf 100644 --- a/tests/unit/test_citations.py +++ b/tests/unit/test_citations.py @@ -123,12 +123,16 @@ def test_subramanian_2005(self): citations._reset() self.assertNotIn("Subramanian2005", citations._papers_to_cite) - pybamm.particle.PolynomialSingleParticle(None, "Negative", "quadratic profile") + pybamm.particle.no_distribution.XAveragedPolynomialProfile( + None, "Negative", "quadratic profile" + ) self.assertIn("Subramanian2005", citations._papers_to_cite) citations._reset() self.assertNotIn("Subramanian2005", citations._papers_to_cite) - pybamm.particle.PolynomialManyParticles(None, "Negative", "quadratic profile") + pybamm.particle.no_distribution.PolynomialProfile( + None, "Negative", "quadratic profile" + ) self.assertIn("Subramanian2005", citations._papers_to_cite) def test_brosaplanella_2020(self): diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py index f2dd194eb8..af05cad0f1 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spm.py @@ -162,9 +162,10 @@ def test_new_model(self): # with custom submodels model = pybamm.lithium_ion.SPM({"thermal": "x-full"}, build=False) - model.submodels["negative particle"] = pybamm.particle.PolynomialSingleParticle( + particle_n = pybamm.particle.no_distribution.XAveragedPolynomialProfile( model.param, "Negative", "quadratic profile" ) + model.submodels["negative particle"] = particle_n model.build_model() new_model = model.new_copy() new_model_cs_eqn = list(new_model.rhs.values())[1] diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py index 20d0db88a6..f423a0a298 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fickian_many_particles.py @@ -26,7 +26,7 @@ def test_public_functions(self): "Negative particle radius": a_n, } - submodel = pybamm.particle.FickianManyParticles(param, "Negative") + submodel = pybamm.particle.no_distribution.FickianDiffusion(param, "Negative") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() @@ -37,7 +37,7 @@ def test_public_functions(self): "Positive electrode surface area to volume ratio": a_p, "Positive particle radius": a_p, } - submodel = pybamm.particle.FickianManyParticles(param, "Positive") + submodel = pybamm.particle.no_distribution.FickianDiffusion(param, "Positive") std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py index 6b11753550..d3287bf6ea 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_fickian_single_particle.py @@ -19,7 +19,9 @@ def test_public_functions(self): "Negative electrode surface area to volume ratio": a, } - submodel = pybamm.particle.FickianSingleParticle(param, "Negative") + submodel = pybamm.particle.no_distribution.XAveragedFickianDiffusion( + param, "Negative" + ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() @@ -29,7 +31,9 @@ def test_public_functions(self): "Positive electrode active material volume fraction": a, "Positive electrode surface area to volume ratio": a, } - submodel = pybamm.particle.FickianSingleParticle(param, "Positive") + submodel = pybamm.particle.no_distribution.XAveragedFickianDiffusion( + param, "Positive" + ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_polynomial_many_particles.py b/tests/unit/test_models/test_submodels/test_particle/test_polynomial_many_particles.py index 718fa91c50..10b129da0c 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_polynomial_many_particles.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_polynomial_many_particles.py @@ -26,19 +26,19 @@ def test_public_functions(self): "Negative particle radius": a_n, } - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Negative", "uniform profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Negative", "quadratic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Negative", "quartic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) @@ -52,19 +52,19 @@ def test_public_functions(self): "Positive particle radius": a_p, } - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Positive", "uniform profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Positive", "quadratic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialManyParticles( + submodel = pybamm.particle.no_distribution.PolynomialProfile( param, "Positive", "quartic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) diff --git a/tests/unit/test_models/test_submodels/test_particle/test_polynomial_single_particle.py b/tests/unit/test_models/test_submodels/test_particle/test_polynomial_single_particle.py index d949027aa0..9c4b64e46e 100644 --- a/tests/unit/test_models/test_submodels/test_particle/test_polynomial_single_particle.py +++ b/tests/unit/test_models/test_submodels/test_particle/test_polynomial_single_particle.py @@ -21,19 +21,19 @@ def test_public_functions(self): "Negative electrode surface area to volume ratio": a, } - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Negative", "uniform profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Negative", "quadratic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Negative", "quartic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) @@ -47,19 +47,19 @@ def test_public_functions(self): "Positive electrode surface area to volume ratio": a, } - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Positive", "uniform profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Positive", "quadratic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() - submodel = pybamm.particle.PolynomialSingleParticle( + submodel = pybamm.particle.no_distribution.XAveragedPolynomialProfile( param, "Positive", "quartic profile" ) std_tests = tests.StandardSubModelTests(submodel, variables) diff --git a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/__init__.py b/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_base_distribution.py b/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_base_distribution.py deleted file mode 100644 index 576e2b9748..0000000000 --- a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_base_distribution.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Test base particle size distribution submodel -# - -import pybamm -import tests -import unittest - - -class TestBaseSizeDistribution(unittest.TestCase): - def test_public_functions(self): - variables = { - "Negative particle surface concentration": 0, - "Positive particle surface concentration": 0, - } - submodel = pybamm.particle.BaseSizeDistribution(None, "Negative") - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - submodel = pybamm.particle.BaseSizeDistribution(None, "Positive") - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_many_distributions.py b/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_many_distributions.py deleted file mode 100644 index 2552dbe4ad..0000000000 --- a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_many_distributions.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Test many size distributions of particles with internal uniform profile -# - -import pybamm -import tests -import unittest - - -class TestManySizeDistributions(unittest.TestCase): - def test_public_functions(self): - param = pybamm.LithiumIonParameters() - - a_n = pybamm.FullBroadcast( - pybamm.Scalar(0), "negative electrode", {"secondary": "current collector"} - ) - a_p = pybamm.FullBroadcast( - pybamm.Scalar(0), "positive electrode", {"secondary": "current collector"} - ) - - variables = { - "Negative electrode interfacial current density distribution": a_n, - "Negative electrode temperature": a_n, - "Negative electrode active material volume fraction": a_n, - "Negative electrode surface area to volume ratio": a_n, - "Negative particle radius": a_n, - } - - submodel = pybamm.particle.FastManySizeDistributions( - param, "Negative" - ) - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - variables = { - "Positive electrode interfacial current density distribution": a_p, - "Positive electrode temperature": a_p, - "Positive electrode active material volume fraction": a_p, - "Positive electrode surface area to volume ratio": a_p, - "Positive particle radius": a_p, - } - - submodel = pybamm.particle.FastManySizeDistributions( - param, "Positive" - ) - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_single_distribution.py b/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_single_distribution.py deleted file mode 100644 index 86ea45f9db..0000000000 --- a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fast_single_distribution.py +++ /dev/null @@ -1,50 +0,0 @@ -# -# Test single size distribution of particles with uniform internal profile -# - -import pybamm -import tests -import unittest - - -class TestSingleSizeDistribution(unittest.TestCase): - def test_public_functions(self): - param = pybamm.LithiumIonParameters() - - a = pybamm.PrimaryBroadcast(pybamm.Scalar(0), "current collector") - - variables = { - "X-averaged negative electrode interfacial current density distribution": a, - "X-averaged negative electrode temperature": a, - "Negative electrode active material volume fraction": a, - "Negative electrode surface area to volume ratio": a, - } - - submodel = pybamm.particle.FastSingleSizeDistribution( - param, "Negative" - ) - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - variables = { - "X-averaged positive electrode interfacial current density distribution": a, - "X-averaged positive electrode temperature": a, - "Positive electrode active material volume fraction": a, - "Positive electrode surface area to volume ratio": a, - } - - submodel = pybamm.particle.FastSingleSizeDistribution( - param, "Positive" - ) - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fickian_single_distribution.py b/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fickian_single_distribution.py deleted file mode 100644 index 9699ffe6f3..0000000000 --- a/tests/unit/test_models/test_submodels/test_particle/test_size_distribution/test_fickian_single_distribution.py +++ /dev/null @@ -1,44 +0,0 @@ -# -# Test single size distribution of fickian particles -# - -import pybamm -import tests -import unittest - - -class TestSingleSizeDistribution(unittest.TestCase): - def test_public_functions(self): - param = pybamm.LithiumIonParameters() - - a = pybamm.PrimaryBroadcast(pybamm.Scalar(0), "current collector") - variables = { - "X-averaged negative electrode interfacial current density distribution": a, - "X-averaged negative electrode temperature": a, - "Negative electrode active material volume fraction": a, - "Negative electrode surface area to volume ratio": a, - } - - submodel = pybamm.particle.FickianSingleSizeDistribution(param, "Negative") - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - variables = { - "X-averaged positive electrode interfacial current density distribution": a, - "X-averaged positive electrode temperature": a, - "Positive electrode active material volume fraction": a, - "Positive electrode surface area to volume ratio": a, - } - submodel = pybamm.particle.FickianSingleSizeDistribution(param, "Positive") - std_tests = tests.StandardSubModelTests(submodel, variables) - std_tests.test_all() - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() From ee674d4bfe48669d0912baad08dabd7004cdab73 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 23 Aug 2021 16:07:32 +0100 Subject: [PATCH 15/16] fix notebook --- examples/notebooks/using-submodels.ipynb | 221 ++++++++++++++--------- 1 file changed, 136 insertions(+), 85 deletions(-) diff --git a/examples/notebooks/using-submodels.ipynb b/examples/notebooks/using-submodels.ipynb index 5dd216d669..07c091d012 100644 --- a/examples/notebooks/using-submodels.ipynb +++ b/examples/notebooks/using-submodels.ipynb @@ -27,6 +27,8 @@ "name": "stdout", "output_type": "stream", "text": [ + "\u001b[33mWARNING: You are using pip version 21.1.2; however, version 21.2.4 is available.\n", + "You should consider upgrading via the '/home/user/PyBaMM/.tox/dev/bin/python -m pip install --upgrade pip' command.\u001b[0m\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } @@ -68,32 +70,32 @@ "name": "stdout", "output_type": "stream", "text": [ - "external circuit \n", - "porosity \n", - "negative active material \n", - "positive active material \n", - "electrolyte tortuosity \n", - "electrode tortuosity \n", - "through-cell convection \n", - "transverse convection \n", - "negative interface \n", - "positive interface \n", - "negative interface current \n", - "positive interface current \n", - "negative oxygen interface \n", - "positive oxygen interface \n", - "negative particle \n", - "positive particle \n", - "negative electrode potential \n", - "leading-order electrolyte conductivity \n", - "electrolyte diffusion \n", - "positive electrode potential \n", - "thermal \n", - "current collector \n", - "negative SEI \n", - "positive SEI \n", - "negative lithium plating \n", - "positive lithium plating \n" + "external circuit \n", + "porosity \n", + "negative active material \n", + "positive active material \n", + "electrolyte tortuosity \n", + "electrode tortuosity \n", + "through-cell convection \n", + "transverse convection \n", + "negative interface \n", + "positive interface \n", + "negative interface current \n", + "positive interface current \n", + "negative oxygen interface \n", + "positive oxygen interface \n", + "negative particle \n", + "positive particle \n", + "negative electrode potential \n", + "leading-order electrolyte conductivity \n", + "electrolyte diffusion \n", + "positive electrode potential \n", + "thermal \n", + "current collector \n", + "negative sei \n", + "positive sei \n", + "negative lithium plating \n", + "positive lithium plating \n" ] } ], @@ -122,16 +124,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This collects all of the submodels which make up the SPM, but doesn't build the model. Now you are free to swap out one submodel for another. For instance, you may want to assume that diffusion within the negative particles is infinitely fast, so that the PDE describing diffusion is replaced with an ODE for the uniform particle concentration. To change a submodel you simply update the dictionary entry, in this case to the `PolynomialSingleParticle` submodel" + "This collects all of the submodels which make up the SPM, but doesn't build the model. Now you are free to swap out one submodel for another. For instance, you may want to assume that diffusion within the negative particles is infinitely fast, so that the PDE describing diffusion is replaced with an ODE for the uniform particle concentration. To change a submodel you simply update the dictionary entry, in this case to the `XAveragedPolynomialProfile` submodel" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "model.submodels[\"negative particle\"] = pybamm.particle.PolynomialSingleParticle(model.param, \"Negative\",\"uniform profile\")" + "model.submodels[\"negative particle\"] = pybamm.particle.no_distribution.XAveragedPolynomialProfile(model.param, \"Negative\",\"uniform profile\")" ] }, { @@ -150,39 +152,39 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "external circuit \n", - "porosity \n", - "negative active material \n", - "positive active material \n", - "electrolyte tortuosity \n", - "electrode tortuosity \n", - "through-cell convection \n", - "transverse convection \n", - "negative interface \n", - "positive interface \n", - "negative interface current \n", - "positive interface current \n", - "negative oxygen interface \n", - "positive oxygen interface \n", - "negative particle \n", - "positive particle \n", - "negative electrode potential \n", - "leading-order electrolyte conductivity \n", - "electrolyte diffusion \n", - "positive electrode potential \n", - "thermal \n", - "current collector \n", - "negative SEI \n", - "positive SEI \n", - "negative lithium plating \n", - "positive lithium plating \n" + "external circuit \n", + "porosity \n", + "negative active material \n", + "positive active material \n", + "electrolyte tortuosity \n", + "electrode tortuosity \n", + "through-cell convection \n", + "transverse convection \n", + "negative interface \n", + "positive interface \n", + "negative interface current \n", + "positive interface current \n", + "negative oxygen interface \n", + "positive oxygen interface \n", + "negative particle \n", + "positive particle \n", + "negative electrode potential \n", + "leading-order electrolyte conductivity \n", + "electrolyte diffusion \n", + "positive electrode potential \n", + "thermal \n", + "current collector \n", + "negative sei \n", + "positive sei \n", + "negative lithium plating \n", + "positive lithium plating \n" ] } ], @@ -200,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -209,7 +211,7 @@ "{}" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -227,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -243,18 +245,18 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{Variable(0x1d3d8267a7036811, Discharge capacity [A.h], children=[], domain=[], auxiliary_domains={}): Division(0x399fd4d160b7703e, /, children=['Current function [A] * 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) / absolute(Typical current [A] / (Number of electrodes connected in parallel to make a cell * Electrode width [m] * Electrode height [m]))', '3600.0'], domain=[], auxiliary_domains={}),\n", - " Variable(0x589c7aa33de3f696, R-X-averaged negative particle concentration, children=[], domain=['current collector'], auxiliary_domains={}): Division(-0x4f4bdccab703f79a, /, children=[\"-3.0 * integral dx_n ['negative electrode'](broadcast(broadcast((Current function [A] / Typical current [A]) * sign(Typical current [A])) / (Negative electrode thickness [m] / (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]))) - broadcast(0.0)) / (Negative electrode thickness [m] / (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]))\", '(3.0 * Negative electrode active material volume fraction / Negative particle radius [m]) * Negative particle radius [m]'], domain=['current collector'], auxiliary_domains={}),\n", - " Variable(0x50534afe93ecb7c7, X-averaged positive particle concentration, children=[], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"}): Multiplication(-0x5c162e6ae732e796, *, children=['-1.0 / ((Positive particle radius [m] ** 2.0) / Positive electrode diffusivity [m2.s-1] / (96485.33212 * Maximum concentration in negative electrode [mol.m-3] * (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) / absolute(Typical current [A] / (Number of electrodes connected in parallel to make a cell * Electrode width [m] * Electrode height [m]))))', 'div(-Positive electrode diffusivity [m2.s-1] / Positive electrode diffusivity [m2.s-1] * grad(X-averaged positive particle concentration))'], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"})}" + "{Variable(0x1a8c98a3b8ed5152, Discharge capacity [A.h], children=[], domain=[], auxiliary_domains={}): Division(0x6028ff89b15eddee, /, children=['Current function [A] * 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) / absolute(Typical current [A] / (Number of electrodes connected in parallel to make a cell * Electrode width [m] * Electrode height [m]))', '3600.0'], domain=[], auxiliary_domains={}),\n", + " Variable(0x6965fac1966e5544, R-X-averaged negative particle concentration, children=[], domain=['current collector'], auxiliary_domains={}): MatrixMultiplication(0x3af8bf3c293d48fe, @, children=['mass(R-X-averaged negative particle concentration)', 'broadcast(-3.0 * (Current function [A] / Typical current [A]) * sign(Typical current [A]) / (Negative electrode thickness [m] / (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m])) / ((3.0 * Negative electrode active material volume fraction / Negative particle radius [m]) * Negative particle radius [m]))'], domain=['current collector'], auxiliary_domains={}),\n", + " Variable(0x5533a0a0f0f095be, X-averaged positive particle concentration, children=[], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"}): Multiplication(0x5ecbd5fdd62c5e56, *, children=['1.0 / ((Positive particle radius [m] ** 2.0) / Positive electrode diffusivity [m2.s-1] / (96485.33212 * Maximum concentration in negative electrode [mol.m-3] * (Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) / absolute(Typical current [A] / (Number of electrodes connected in parallel to make a cell * Electrode width [m] * Electrode height [m]))))', 'div((Positive electrode diffusivity [m2.s-1] / Positive electrode diffusivity [m2.s-1]) * grad(X-averaged positive particle concentration))'], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"})}" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -272,13 +274,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "31aa4e91557f4256aec2ccd929b2f475", + "model_id": "844a8fd358ea4d9b90e1fc27ccfc393b", "version_major": 2, "version_minor": 0 }, @@ -288,6 +290,16 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -308,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -326,7 +338,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -342,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -366,7 +378,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -387,14 +399,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "model.submodels[\"negative particle\"] = pybamm.particle.PolynomialSingleParticle(\n", + "model.submodels[\"negative particle\"] = pybamm.particle.no_distribution.PolynomialProfile(\n", " model.param, \"Negative\", \"uniform profile\"\n", ")\n", - "model.submodels[\"positive particle\"] = pybamm.particle.PolynomialSingleParticle(\n", + "model.submodels[\"positive particle\"] = pybamm.particle.no_distribution.PolynomialProfile(\n", " model.param, \"Positive\", \"uniform profile\"\n", ")" ] @@ -408,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -443,7 +455,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -462,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -483,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -499,13 +511,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e3e6d85ba0134678840311ec2bef25cd", + "model_id": "f9c3d3e4f2764a62b720e598cca82c95", "version_major": 2, "version_minor": 0 }, @@ -515,6 +527,16 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -534,24 +556,53 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n", + "[2] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n", + "[3] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n", + "[4] Venkat R. Subramanian, Vinten D. Diwakar, and Deepak Tapriyal. Efficient macro-micro scale coupled modeling of batteries. Journal of The Electrochemical Society, 152(10):A2002, 2005. doi:10.1149/1.2032427.\n", + "[5] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n", + "\n" + ] + } + ], "source": [ "pybamm.print_citations()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2.7.17 64-bit", - "name": "python375jvsc74a57bd0fd69f43f58546b570e94fd7eba7b65e6bcc7a5bbc4eab0408017d18902915d69" + "display_name": "Python 3", + "language": "python", + "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "version": "" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" } }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From baa75ef68118db0c8eb9fa6e4e58d4fd99e1bab7 Mon Sep 17 00:00:00 2001 From: tobykirk Date: Mon, 23 Aug 2021 16:13:46 +0100 Subject: [PATCH 16/16] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 212f7fdebc..30d6d401dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ example notebook ([#1602](https://github.com/pybamm-team/PyBaMM/pull/1602)) - Made parameters importable through pybamm ([#1475](https://github.com/pybamm-team/PyBaMM/pull/1475)) ## Breaking changes - +- Refactored the `particle` submodel module, with the models having no size distribution now found in `particle.no_distribution`, and those with a size distribution in `particle.size_distribution`. Renamed submodels to indicate the transport model (Fickian diffusion, polynomial profile) and if they are "x-averaged". E.g., `FickianManyParticles` and `FickianSingleParticle` are now `no_distribution.FickianDiffusion` and `no_distribution.XAveragedFickianDiffusion` ([#1602](https://github.com/pybamm-team/PyBaMM/pull/1602)) - The `Yang2017` parameter set has been removed as the complete parameter set is not publicly available in the literature ([#1577](https://github.com/pybamm-team/PyBaMM/pull/1577)) - Changed how options are specified for the "loss of active material" and "particle cracking" submodels. "loss of active material" can now be one of "none", "stress-driven", or "reaction-driven", or a 2-tuple for different options in negative and positive electrode. Similarly "particle cracking" (now called "particle mechanics") can now be "none", "swelling only", "swelling and cracking", or a 2-tuple ([#1490](https://github.com/pybamm-team/PyBaMM/pull/1490)) - Changed the variable in the full diffusion model from "Electrolyte concentration" to "Porosity times concentration" ([#1476](https://github.com/pybamm-team/PyBaMM/pull/1476))