From c9704e1c129e77fd4577516a2c543f3b33ca2b13 Mon Sep 17 00:00:00 2001 From: Santhosh Sundaram Date: Fri, 19 Jul 2024 10:31:54 +0530 Subject: [PATCH 1/5] migrated entry points --- {{cookiecutter.project_name}}/pyproject.toml | 83 ++-- .../__init__.py | 18 +- .../entry_point.py | 154 ++++++++ .../models/input/SPM.py | 214 ++++++++++ .../parameters/input/Chen2020.py | 369 ++++++++++++++++++ 5 files changed, 802 insertions(+), 36 deletions(-) create mode 100644 {{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/entry_point.py create mode 100644 {{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py create mode 100644 {{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index a14b122..cdb91d3 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -47,14 +47,15 @@ classifiers = [ {%- if cookiecutter.backend == "hatch" %} dynamic = ["version"] {%- endif %} -dependencies = ["pybamm"] +dependencies = ["pybamm", "cookiecutter"] [project.optional-dependencies] dev = [ "pytest >=6", "pytest-cov >=3", - "nox", + "nox[uv]", "pre-commit", + "pytest-cookies", ] docs = [ "sphinx", @@ -76,6 +77,13 @@ Homepage = "{{ cookiecutter.url }}" "Bug Tracker" = "{{ cookiecutter.url }}/issues" Discussions = "{{ cookiecutter.url }}/discussions" Changelog = "{{ cookiecutter.url }}/releases" + +[project.entry-points."parameter_sets"] +Chen2020 = "{{ cookiecutter.__project_slug }}.parameters.input.Chen2020:get_parameter_values" + +[project.entry-points."models"] +SPM = "{{ cookiecutter.__project_slug }}.models.input.SPM:SPM" + {# keep this line here for newline #} {%- if cookiecutter.backend == "hatch" %} [tool.hatch] @@ -88,8 +96,8 @@ envs.default.dependencies = [ {# keep this line here for newline #} {%- if cookiecutter.mypy %} [tool.mypy] -python_version = "3.8" -strict = true +python_version = "3.11" +strict = false warn_return_any = false show_error_codes = true enable_error_code = [ @@ -99,6 +107,9 @@ enable_error_code = [ ] disallow_untyped_defs = false disallow_untyped_calls = false +ignore_missing_imports = true +allow_redefinition = true +disable_error_code = ["call-overload", "operator"] {%- endif %} [tool.coverage] @@ -108,36 +119,40 @@ port.exclude_lines = [ ] [tool.ruff] -select = [ - "E", "F", "W", # flake8 - "B", # flake8-bugbear - "I", # isort - "ARG", # flake8-unused-arguments - "C4", # flake8-comprehensions - "EM", # flake8-errmsg - "ICN", # flake8-import-conventions - "ISC", # flake8-implicit-str-concat - "G", # flake8-logging-format - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-use-pathlib - "RET", # flake8-return - "RUF", # Ruff-specific - "SIM", # flake8-simplify - "T20", # flake8-print - "UP", # pyupgrade - "YTT", # flake8-2020 - "EXE", # flake8-executable - "NPY", # NumPy specific rules - "PD", # pandas-vet -] +extend-include = ["*.ipynb"] +extend-exclude = ["__init__.py"] src = ["src"] -unfixable = [ - "T20", # Removes print statements - "F841", # Removes unused variables -] -line-length = 100 exclude = [] +isort.required-imports = ["from __future__ import annotations"] flake8-unused-arguments.ignore-variadic-names = true + + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "RUF", # Ruff-specific + "UP", # pyupgrade + "YTT", # flake8-2020 + "TID252", # relative-imports +] +ignore = [ + "E741", # Ambiguous variable name + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "SIM108", # Use ternary operator + "ARG001", # Unused function argument: + "ARG002", # Unused method arguments + "PLR2004", # Magic value used in comparison + "PLR0915", # Too many statements + "PLR0913", # Too many arguments + "PLR0912", # Too many branches + "RET504", # Unnecessary assignment + "RET505", # Unnecessary `else` + "RET506", # Unnecessary `elif` + "B018", # Found useless expression + "RUF002", # Docstring contains ambiguous + "UP007", # For pyupgrade +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["T20"] +"docs/*" = ["T20"] diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py index cd28a1a..4b10162 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py @@ -8,9 +8,23 @@ {%- endif %} from ._version import version as __version__ +import pybamm +from .entry_point import Model, parameter_sets, models {# keep this line here for newline #} {%- if cookiecutter.mypy %} -__all__: tuple[str] = ("__version__",) +__all__: list[str] = [ + "__version__", + "pybamm", + "parameter_sets", + "Model", + "models", +] {%- else %} -__all__ = ("__version__",) +__all__ = [ + "__version__", + "pybamm", + "parameter_sets", + "Model", + "models", +] {%- endif %} diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/entry_point.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/entry_point.py new file mode 100644 index 0000000..691cd86 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/entry_point.py @@ -0,0 +1,154 @@ +""" +This code is adopted from the PyBaMM project under the BSD-3-Clause + +Copyright (c) 2018-2024, the PyBaMM team. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +import importlib.metadata +import sys +import textwrap +from collections.abc import Mapping +from typing import Callable + +class EntryPoint(Mapping): + """ + Dict-like interface for accessing parameter sets and models through entry points in cookiecutter template. + Access via :py:data:`pybamm_cookiecutter.parameter_sets` for parameter_sets + Access via :py:data:`pybamm_cookiecutter.Model` for Models + + Examples + -------- + Listing available parameter sets: + >>> import pybamm_cookiecutter + >>> list(pybamm_cookiecutter.parameter_sets) + ['Chen2020', ...] + >>> list(pybamm_cookiecutter.models) + ['SPM', ...] + + Get the docstring for a parameter set/model: + + + >>> print(pybamm_cookiecutter.parameter_sets.get_docstring("Ai2020")) + + Parameters for the Enertech cell (Ai2020), from the papers :footcite:t:`Ai2019`, + :footcite:t:`rieger2016new` and references therein. + ... + + >>> print(pybamm_cookiecutter.models.get_docstring("SPM")) + + Single Particle Model (SPM) model of a lithium-ion battery, from :footcite:t:`Marquis2019`. This class differs from the :class:`pybamm.lithium_ion.SPM` model class in that it shows the whole model in a single class. This comes at the cost of flexibility in combining different physical effects, and in general the main SPM class should be used instead. + ... + See also: :ref:`adding-parameter-sets` + """ + + _instances = 0 + def __init__(self, group): + """Dict of entry points for parameter sets or models, lazily load entry points as""" + if not hasattr(self, 'initialized'): # Ensure __init__ is called once per instance + self.initialized = True + EntryPoint._instances += 1 + self._all_entries = dict() + self.group = group + for entry_point in self.get_entries(self.group): + self._all_entries[entry_point.name] = entry_point + + @staticmethod + def get_entries(group_name): + """Wrapper for the importlib version logic""" + if sys.version_info < (3, 10): # pragma: no cover + return importlib.metadata.entry_points()[group_name] + else: + return importlib.metadata.entry_points(group=group_name) + + def __new__(cls, group): + """Ensure only two instances of entry points exist, one for parameter sets and the other for models""" + if EntryPoint._instances < 2: + cls.instance = super().__new__(cls) + return cls.instance + + def __getitem__(self, key) -> dict: + return self._load_entry_point(key)() + + def _load_entry_point(self, key) -> Callable: + """Check that ``key`` is a registered ``parameter_sets`` or ``models` , + and return the entry point for the parameter set/model, loading it needed.""" + if key not in self._all_entries: + raise KeyError(f"Unknown parameter set or model: {key}") + ps = self._all_entries[key] + try: + ps = self._all_entries[key] = ps.load() + except AttributeError: + pass + return ps + + def __iter__(self): + return self._all_entries.__iter__() + + def __len__(self) -> int: + return len(self._all_entries) + + def get_docstring(self, key): + """Return the docstring for the ``key`` parameter set or model""" + return textwrap.dedent(self._load_entry_point(key).__doc__) + + def __getattribute__(self, name): + try: + return super().__getattribute__(name) + except AttributeError as error: + raise error + +#: Singleton Instance of :class:ParameterSets """ +parameter_sets = EntryPoint(group="parameter_sets") + +#: Singleton Instance of :class:ModelEntryPoints""" +models = EntryPoint(group="models") + +def Model(model:str): + """ + Returns the loaded model object + + Parameters + ---------- + model : str + The model name or author name of the model mentioned at the model entry point. + Returns + ------- + pybamm.model + Model object of the initialised model. + Examples + -------- + Listing available models: + >>> import pybamm_cookiecutter + >>> list(pybamm_cookiecutter.models) + ['SPM', ...] + >>> pybamm_cookiecutter.Model('Author/Year') + + """ + return models[model] diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py new file mode 100644 index 0000000..b6c0526 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py @@ -0,0 +1,214 @@ +""" +This code is adopted from the PyBaMM project under the BSD-3-Clause + +Copyright (c) 2018-2024, the PyBaMM team. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +# +# Basic Single Particle Model (SPM) +# +import pybamm + +class SPM(pybamm.lithium_ion.BaseModel): + """Single Particle Model (SPM) model of a lithium-ion battery, from + :footcite:t:`Marquis2019`. + + This class differs from the :class:`pybamm.lithium_ion.SPM` model class in that it + shows the whole model in a single class. This comes at the cost of flexibility in + combining different physical effects, and in general the main SPM class should be + used instead. + + Parameters + ---------- + name : str, optional + The name of the model. + """ + + def __init__(self, name="Single Particle Model"): + super().__init__({}, name) + pybamm.citations.register("Marquis2019") + # `param` is a class containing all the relevant parameters and functions for + # this model. These are purely symbolic at this stage, and will be set by the + # `ParameterValues` class when the model is processed. + param = self.param + + ###################### + # Variables + ###################### + # Variables that depend on time only are created without a domain + Q = pybamm.Variable("Discharge capacity [A.h]") + # Variables that vary spatially are created with a domain + c_s_n = pybamm.Variable( + "X-averaged negative particle concentration [mol.m-3]", + domain="negative particle", + ) + c_s_p = pybamm.Variable( + "X-averaged positive particle concentration [mol.m-3]", + domain="positive particle", + ) + + # Constant temperature + T = param.T_init + + ###################### + # Other set-up + ###################### + + # Current density + i_cell = param.current_density_with_time + a_n = 3 * param.n.prim.epsilon_s_av / param.n.prim.R_typ + a_p = 3 * param.p.prim.epsilon_s_av / param.p.prim.R_typ + j_n = i_cell / (param.n.L * a_n) + j_p = -i_cell / (param.p.L * a_p) + + ###################### + # State of Charge + ###################### + I = param.current_with_time + # The `rhs` dictionary contains differential equations, with the key being the + # variable in the d/dt + self.rhs[Q] = I / 3600 + # Initial conditions must be provided for the ODEs + self.initial_conditions[Q] = pybamm.Scalar(0) + + ###################### + # Particles + ###################### + + # The div and grad operators will be converted to the appropriate matrix + # multiplication at the discretisation stage + N_s_n = -param.n.prim.D(c_s_n, T) * pybamm.grad(c_s_n) + N_s_p = -param.p.prim.D(c_s_p, T) * pybamm.grad(c_s_p) + self.rhs[c_s_n] = -pybamm.div(N_s_n) + self.rhs[c_s_p] = -pybamm.div(N_s_p) + # Surf takes the surface value of a variable, i.e. its boundary value on the + # right side. This is also accessible via `boundary_value(x, "right")`, with + # "left" providing the boundary value of the left side + c_s_surf_n = pybamm.surf(c_s_n) + c_s_surf_p = pybamm.surf(c_s_p) + # Boundary conditions must be provided for equations with spatial derivatives + self.boundary_conditions[c_s_n] = { + "left": (pybamm.Scalar(0), "Neumann"), + "right": ( + -j_n / (param.F * pybamm.surf(param.n.prim.D(c_s_n, T))), + "Neumann", + ), + } + self.boundary_conditions[c_s_p] = { + "left": (pybamm.Scalar(0), "Neumann"), + "right": ( + -j_p / (param.F * pybamm.surf(param.p.prim.D(c_s_p, T))), + "Neumann", + ), + } + # c_n_init and c_p_init are functions of r and x, but for the SPM we + # take the x-averaged value since there is no x-dependence in the particles + self.initial_conditions[c_s_n] = pybamm.x_average(param.n.prim.c_init) + self.initial_conditions[c_s_p] = pybamm.x_average(param.p.prim.c_init) + # Events specify points at which a solution should terminate + sto_surf_n = c_s_surf_n / param.n.prim.c_max + sto_surf_p = c_s_surf_p / param.p.prim.c_max + self.events += [ + pybamm.Event( + "Minimum negative particle surface stoichiometry", + pybamm.min(sto_surf_n) - 0.01, + ), + pybamm.Event( + "Maximum negative particle surface stoichiometry", + (1 - 0.01) - pybamm.max(sto_surf_n), + ), + pybamm.Event( + "Minimum positive particle surface stoichiometry", + pybamm.min(sto_surf_p) - 0.01, + ), + pybamm.Event( + "Maximum positive particle surface stoichiometry", + (1 - 0.01) - pybamm.max(sto_surf_p), + ), + ] + + # Note that the SPM does not have any algebraic equations, so the `algebraic` + # dictionary remains empty + + ###################### + # (Some) variables + ###################### + # Interfacial reactions + RT_F = param.R * T / param.F + j0_n = param.n.prim.j0(param.c_e_init_av, c_s_surf_n, T) + j0_p = param.p.prim.j0(param.c_e_init_av, c_s_surf_p, T) + eta_n = (2 / param.n.prim.ne) * RT_F * pybamm.arcsinh(j_n / (2 * j0_n)) + eta_p = (2 / param.p.prim.ne) * RT_F * pybamm.arcsinh(j_p / (2 * j0_p)) + phi_s_n = 0 + phi_e = -eta_n - param.n.prim.U(sto_surf_n, T) + phi_s_p = eta_p + phi_e + param.p.prim.U(sto_surf_p, T) + V = phi_s_p + num_cells = pybamm.Parameter( + "Number of cells connected in series to make a battery" + ) + + whole_cell = ["negative electrode", "separator", "positive electrode"] + # The `variables` dictionary contains all variables that might be useful for + # visualising the solution of the model + # Primary broadcasts are used to broadcast scalar quantities across a domain + # into a vector of the right shape, for multiplying with other vectors + self.variables = { + "Time [s]": pybamm.t, + "Discharge capacity [A.h]": Q, + "X-averaged negative particle concentration [mol.m-3]": c_s_n, + "Negative particle surface " + "concentration [mol.m-3]": pybamm.PrimaryBroadcast( + c_s_surf_n, "negative electrode" + ), + "Electrolyte concentration [mol.m-3]": pybamm.PrimaryBroadcast( + param.c_e_init_av, whole_cell + ), + "X-averaged positive particle concentration [mol.m-3]": c_s_p, + "Positive particle surface " + "concentration [mol.m-3]": pybamm.PrimaryBroadcast( + c_s_surf_p, "positive electrode" + ), + "Current [A]": I, + "Current variable [A]": I, # for compatibility with pybamm.Experiment + "Negative electrode potential [V]": pybamm.PrimaryBroadcast( + phi_s_n, "negative electrode" + ), + "Electrolyte potential [V]": pybamm.PrimaryBroadcast(phi_e, whole_cell), + "Positive electrode potential [V]": pybamm.PrimaryBroadcast( + phi_s_p, "positive electrode" + ), + "Voltage [V]": V, + "Battery voltage [V]": V * num_cells, + } + # Events specify points at which a solution should terminate + self.events += [ + pybamm.Event("Minimum voltage [V]", V - param.voltage_low_cut), + pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - V), + ] diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py new file mode 100644 index 0000000..0b4a8a4 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py @@ -0,0 +1,369 @@ +""" +This code is adopted from the PyBaMM project under the BSD-3-Clause + +Copyright (c) 2018-2024, the PyBaMM team. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +from __future__ import annotations + +import numpy as np +import pybamm +def graphite_LGM50_ocp_Chen2020(sto): + """ + LG M50 Graphite open-circuit potential as a function of stochiometry, fit taken + from [1]. + + References + ---------- + .. [1] Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W. + Dhammika Widanage, and Emma Kendrick. "Development of Experimental Techniques for + Parameterization of Multi-scale Lithium-ion Battery Models." Journal of the + Electrochemical Society 167 (2020): 080534. + + Parameters + ---------- + sto: :class:`pybamm.Symbol` + Electrode stochiometry + + Returns + ------- + :class:`pybamm.Symbol` + Open-circuit potential + """ + + u_eq = ( + 1.9793 * np.exp(-39.3631 * sto) + + 0.2482 + - 0.0909 * np.tanh(29.8538 * (sto - 0.1234)) + - 0.04478 * np.tanh(14.9159 * (sto - 0.2769)) + - 0.0205 * np.tanh(30.4444 * (sto - 0.6103)) + ) + + return u_eq + + +def graphite_LGM50_electrolyte_exchange_current_density_Chen2020( + c_e, c_s_surf, c_s_max, T +): + """ + Exchange-current density for Butler-Volmer reactions between graphite and LiPF6 in + EC:DMC. + + References + ---------- + .. [1] Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W. + Dhammika Widanage, and Emma Kendrick. "Development of Experimental Techniques for + Parameterization of Multi-scale Lithium-ion Battery Models." Journal of the + Electrochemical Society 167 (2020): 080534. + + Parameters + ---------- + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_s_surf : :class:`pybamm.Symbol` + Particle concentration [mol.m-3] + c_s_max : :class:`pybamm.Symbol` + Maximum particle concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + + Returns + ------- + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + m_ref = 6.48e-7 # (A/m2)(m3/mol)**1.5 - includes ref concentrations + E_r = 35000 + arrhenius = np.exp(E_r / pybamm.constants.R * (1 / 298.15 - 1 / T)) + + return m_ref * arrhenius * c_e**0.5 * c_s_surf**0.5 * (c_s_max - c_s_surf) ** 0.5 + + +def nmc_LGM50_ocp_Chen2020(sto): + """ + LG M50 NMC open-circuit potential as a function of stochiometry, fit taken + from [1]. + + References + ---------- + .. [1] Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W. + Dhammika Widanage, and Emma Kendrick. "Development of Experimental Techniques for + Parameterization of Multi-scale Lithium-ion Battery Models." Journal of the + Electrochemical Society 167 (2020): 080534. + + Parameters + ---------- + sto: :class:`pybamm.Symbol` + Electrode stochiometry + + Returns + ------- + :class:`pybamm.Symbol` + Open-circuit potential + """ + + u_eq = ( + -0.8090 * sto + + 4.4875 + - 0.0428 * np.tanh(18.5138 * (sto - 0.5542)) + - 17.7326 * np.tanh(15.7890 * (sto - 0.3117)) + + 17.5842 * np.tanh(15.9308 * (sto - 0.3120)) + ) + + return u_eq + + +def nmc_LGM50_electrolyte_exchange_current_density_Chen2020(c_e, c_s_surf, c_s_max, T): + """ + Exchange-current density for Butler-Volmer reactions between NMC and LiPF6 in + EC:DMC. + + References + ---------- + .. [1] Chang-Hui Chen, Ferran Brosa Planella, Kieran O’Regan, Dominika Gastol, W. + Dhammika Widanage, and Emma Kendrick. "Development of Experimental Techniques for + Parameterization of Multi-scale Lithium-ion Battery Models." Journal of the + Electrochemical Society 167 (2020): 080534. + + Parameters + ---------- + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_s_surf : :class:`pybamm.Symbol` + Particle concentration [mol.m-3] + c_s_max : :class:`pybamm.Symbol` + Maximum particle concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + + Returns + ------- + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + m_ref = 3.42e-6 # (A/m2)(m3/mol)**1.5 - includes ref concentrations + E_r = 17800 + arrhenius = np.exp(E_r / pybamm.constants.R * (1 / 298.15 - 1 / T)) + + return m_ref * arrhenius * c_e**0.5 * c_s_surf**0.5 * (c_s_max - c_s_surf) ** 0.5 + + +def electrolyte_diffusivity_Nyman2008(c_e, T): + """ + Diffusivity of LiPF6 in EC:EMC (3:7) as a function of ion concentration. The data + comes from [1] + + References + ---------- + .. [1] A. Nyman, M. Behm, and G. Lindbergh, "Electrochemical characterisation and + modelling of the mass transport phenomena in LiPF6-EC-EMC electrolyte," + Electrochim. Acta, vol. 53, no. 22, pp. 6356–6365, 2008. + + Parameters + ---------- + c_e: :class:`pybamm.Symbol` + Dimensional electrolyte concentration + T: :class:`pybamm.Symbol` + Dimensional temperature + + Returns + ------- + :class:`pybamm.Symbol` + Solid diffusivity + """ + + D_c_e = 8.794e-11 * (c_e / 1000) ** 2 - 3.972e-10 * (c_e / 1000) + 4.862e-10 + + # Nyman et al. (2008) does not provide temperature dependence + + return D_c_e + + +def electrolyte_conductivity_Nyman2008(c_e, T): + """ + Conductivity of LiPF6 in EC:EMC (3:7) as a function of ion concentration. The data + comes from [1]. + + References + ---------- + .. [1] A. Nyman, M. Behm, and G. Lindbergh, "Electrochemical characterisation and + modelling of the mass transport phenomena in LiPF6-EC-EMC electrolyte," + Electrochim. Acta, vol. 53, no. 22, pp. 6356–6365, 2008. + + Parameters + ---------- + c_e: :class:`pybamm.Symbol` + Dimensional electrolyte concentration + T: :class:`pybamm.Symbol` + Dimensional temperature + + Returns + ------- + :class:`pybamm.Symbol` + Solid diffusivity + """ + + sigma_e = ( + 0.1297 * (c_e / 1000) ** 3 - 2.51 * (c_e / 1000) ** 1.5 + 3.329 * (c_e / 1000) + ) + + # Nyman et al. (2008) does not provide temperature dependence + + return sigma_e + + +# Call dict via a function to avoid errors when editing in place +def get_parameter_values(): + """ + Parameters for an LG M50 cell, from the paper :footcite:t:`Chen2020` and references + therein. + + SEI parameters are example parameters for SEI growth from the papers + :footcite:t:`Ramadass2004`, :footcite:t:`ploehn2004solvent`, + :footcite:t:`single2018identifying`, :footcite:t:`safari2008multimodal`, and + :footcite:t:`Yang2017` + + .. note:: + This parameter set does not claim to be representative of the true parameter + values. Instead these are parameter values that were used to fit SEI models to + observed experimental data in the referenced papers. + """ + + return { + "chemistry": "lithium_ion", + # sei + "Ratio of lithium moles to SEI moles": 2.0, + "Inner SEI reaction proportion": 0.5, + "Inner SEI partial molar volume [m3.mol-1]": 9.585e-05, + "Outer SEI partial molar volume [m3.mol-1]": 9.585e-05, + "SEI reaction exchange current density [A.m-2]": 1.5e-07, + "SEI resistivity [Ohm.m]": 200000.0, + "Outer SEI solvent diffusivity [m2.s-1]": 2.5000000000000002e-22, + "Bulk solvent concentration [mol.m-3]": 2636.0, + "Inner SEI open-circuit potential [V]": 0.1, + "Outer SEI open-circuit potential [V]": 0.8, + "Inner SEI electron conductivity [S.m-1]": 8.95e-14, + "Inner SEI lithium interstitial diffusivity [m2.s-1]": 1e-20, + "Lithium interstitial reference concentration [mol.m-3]": 15.0, + "Initial inner SEI thickness [m]": 2.5e-09, + "Initial outer SEI thickness [m]": 2.5e-09, + "EC initial concentration in electrolyte [mol.m-3]": 4541.0, + "EC diffusivity [m2.s-1]": 2e-18, + "SEI kinetic rate constant [m.s-1]": 1e-12, + "SEI open-circuit potential [V]": 0.4, + "SEI growth activation energy [J.mol-1]": 0.0, + "Negative electrode reaction-driven LAM factor [m3.mol-1]": 0.0, + "Positive electrode reaction-driven LAM factor [m3.mol-1]": 0.0, + # cell + "Negative current collector thickness [m]": 1.2e-05, + "Negative electrode thickness [m]": 8.52e-05, + "Separator thickness [m]": 1.2e-05, + "Positive electrode thickness [m]": 7.56e-05, + "Positive current collector thickness [m]": 1.6e-05, + "Electrode height [m]": 0.065, + "Electrode width [m]": 1.58, + "Cell cooling surface area [m2]": 0.00531, + "Cell volume [m3]": 2.42e-05, + "Cell thermal expansion coefficient [m.K-1]": 1.1e-06, + "Negative current collector conductivity [S.m-1]": 58411000.0, + "Positive current collector conductivity [S.m-1]": 36914000.0, + "Negative current collector density [kg.m-3]": 8960.0, + "Positive current collector density [kg.m-3]": 2700.0, + "Negative current collector specific heat capacity [J.kg-1.K-1]": 385.0, + "Positive current collector specific heat capacity [J.kg-1.K-1]": 897.0, + "Negative current collector thermal conductivity [W.m-1.K-1]": 401.0, + "Positive current collector thermal conductivity [W.m-1.K-1]": 237.0, + "Nominal cell capacity [A.h]": 5.0, + "Current function [A]": 5.0, + "Contact resistance [Ohm]": 0, + # negative electrode + "Negative electrode conductivity [S.m-1]": 215.0, + "Maximum concentration in negative electrode [mol.m-3]": 33133.0, + "Negative particle diffusivity [m2.s-1]": 3.3e-14, + "Negative electrode OCP [V]": graphite_LGM50_ocp_Chen2020, + "Negative electrode porosity": 0.25, + "Negative electrode active material volume fraction": 0.75, + "Negative particle radius [m]": 5.86e-06, + "Negative electrode Bruggeman coefficient (electrolyte)": 1.5, + "Negative electrode Bruggeman coefficient (electrode)": 0, + "Negative electrode charge transfer coefficient": 0.5, + "Negative electrode double-layer capacity [F.m-2]": 0.2, + "Negative electrode exchange-current density [A.m-2]" + "": graphite_LGM50_electrolyte_exchange_current_density_Chen2020, + "Negative electrode density [kg.m-3]": 1657.0, + "Negative electrode specific heat capacity [J.kg-1.K-1]": 700.0, + "Negative electrode thermal conductivity [W.m-1.K-1]": 1.7, + "Negative electrode OCP entropic change [V.K-1]": 0.0, + # positive electrode + "Positive electrode conductivity [S.m-1]": 0.18, + "Maximum concentration in positive electrode [mol.m-3]": 63104.0, + "Positive particle diffusivity [m2.s-1]": 4e-15, + "Positive electrode OCP [V]": nmc_LGM50_ocp_Chen2020, + "Positive electrode porosity": 0.335, + "Positive electrode active material volume fraction": 0.665, + "Positive particle radius [m]": 5.22e-06, + "Positive electrode Bruggeman coefficient (electrolyte)": 1.5, + "Positive electrode Bruggeman coefficient (electrode)": 0, + "Positive electrode charge transfer coefficient": 0.5, + "Positive electrode double-layer capacity [F.m-2]": 0.2, + "Positive electrode exchange-current density [A.m-2]" + "": nmc_LGM50_electrolyte_exchange_current_density_Chen2020, + "Positive electrode density [kg.m-3]": 3262.0, + "Positive electrode specific heat capacity [J.kg-1.K-1]": 700.0, + "Positive electrode thermal conductivity [W.m-1.K-1]": 2.1, + "Positive electrode OCP entropic change [V.K-1]": 0.0, + # separator + "Separator porosity": 0.47, + "Separator Bruggeman coefficient (electrolyte)": 1.5, + "Separator density [kg.m-3]": 397.0, + "Separator specific heat capacity [J.kg-1.K-1]": 700.0, + "Separator thermal conductivity [W.m-1.K-1]": 0.16, + # electrolyte + "Initial concentration in electrolyte [mol.m-3]": 1000.0, + "Cation transference number": 0.2594, + "Thermodynamic factor": 1.0, + "Electrolyte diffusivity [m2.s-1]": electrolyte_diffusivity_Nyman2008, + "Electrolyte conductivity [S.m-1]": electrolyte_conductivity_Nyman2008, + # experiment + "Reference temperature [K]": 298.15, + "Total heat transfer coefficient [W.m-2.K-1]": 10.0, + "Ambient temperature [K]": 298.15, + "Number of electrodes connected in parallel to make a cell": 1.0, + "Number of cells connected in series to make a battery": 1.0, + "Lower voltage cut-off [V]": 2.5, + "Upper voltage cut-off [V]": 4.2, + "Open-circuit voltage at 0% SOC [V]": 2.5, + "Open-circuit voltage at 100% SOC [V]": 4.2, + "Initial concentration in negative electrode [mol.m-3]": 29866.0, + "Initial concentration in positive electrode [mol.m-3]": 17038.0, + "Initial temperature [K]": 298.15, + # citations + "notcite": ["Chen2020"], + } From 567d0a92e4b3d12dac57f1d99853a078a88dcbf2 Mon Sep 17 00:00:00 2001 From: Santhosh <52504160+santacodes@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:14:41 +0530 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> --- .../src/{{cookiecutter.__project_slug}}/models/input/SPM.py | 1 + .../parameters/input/Chen2020.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py index b6c0526..e94741a 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/models/input/SPM.py @@ -36,6 +36,7 @@ # import pybamm + class SPM(pybamm.lithium_ion.BaseModel): """Single Particle Model (SPM) model of a lithium-ion battery, from :footcite:t:`Marquis2019`. diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py index 0b4a8a4..23777eb 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/parameters/input/Chen2020.py @@ -35,6 +35,8 @@ import numpy as np import pybamm + + def graphite_LGM50_ocp_Chen2020(sto): """ LG M50 Graphite open-circuit potential as a function of stochiometry, fit taken From b275e0ccb7edb2eeee0b9e5ea5598e9c5e470b3c Mon Sep 17 00:00:00 2001 From: Santhosh Sundaram Date: Sat, 20 Jul 2024 12:05:55 +0530 Subject: [PATCH 3/5] ruff rules updated inside template --- {{cookiecutter.project_name}}/pyproject.toml | 67 ++++++++++---------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index cdb91d3..d98c443 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -119,40 +119,41 @@ port.exclude_lines = [ ] [tool.ruff] -extend-include = ["*.ipynb"] -extend-exclude = ["__init__.py"] +select = [ + "E", "F", "W", # flake8 + "B", # flake8-bugbear + "I", # isort + #"ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + #"RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet +] src = ["src"] -exclude = [] -isort.required-imports = ["from __future__ import annotations"] -flake8-unused-arguments.ignore-variadic-names = true - - -[tool.ruff.lint] -extend-select = [ - "B", # flake8-bugbear - "RUF", # Ruff-specific - "UP", # pyupgrade - "YTT", # flake8-2020 - "TID252", # relative-imports +unfixable = [ + "T20", # Removes print statements + "F841", # Removes unused variables ] ignore = [ - "E741", # Ambiguous variable name - "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` - "SIM108", # Use ternary operator - "ARG001", # Unused function argument: - "ARG002", # Unused method arguments - "PLR2004", # Magic value used in comparison - "PLR0915", # Too many statements - "PLR0913", # Too many arguments - "PLR0912", # Too many branches - "RET504", # Unnecessary assignment - "RET505", # Unnecessary `else` - "RET506", # Unnecessary `elif` - "B018", # Found useless expression - "RUF002", # Docstring contains ambiguous - "UP007", # For pyupgrade + "E741", # Ambiguous variable name + "E501", # Line too long + "PLR2004", # Magic value used in comparison ] - -[tool.ruff.lint.per-file-ignores] -"tests/*" = ["T20"] -"docs/*" = ["T20"] +line-length = 100 +exclude = [] +flake8-unused-arguments.ignore-variadic-names = true From c765dc273e852d9f00790dd866ab225d9e0f5b68 Mon Sep 17 00:00:00 2001 From: Santhosh Sundaram Date: Mon, 22 Jul 2024 16:20:45 +0530 Subject: [PATCH 4/5] changed mypy config logic and removed cookiecutter dependency --- {{cookiecutter.project_name}}/pyproject.toml | 2 +- .../src/{{cookiecutter.__project_slug}}/__init__.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index d98c443..34650f5 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -47,7 +47,7 @@ classifiers = [ {%- if cookiecutter.backend == "hatch" %} dynamic = ["version"] {%- endif %} -dependencies = ["pybamm", "cookiecutter"] +dependencies = ["pybamm",] [project.optional-dependencies] dev = [ diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py index 4b10162..7e3555f 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py @@ -13,18 +13,12 @@ {# keep this line here for newline #} {%- if cookiecutter.mypy %} __all__: list[str] = [ - "__version__", - "pybamm", - "parameter_sets", - "Model", - "models", -] {%- else %} __all__ = [ +{%- endif %} "__version__", "pybamm", "parameter_sets", "Model", "models", ] -{%- endif %} From 510955a1abed07abb8669b977e03f663aac22be1 Mon Sep 17 00:00:00 2001 From: Santhosh <52504160+santacodes@users.noreply.github.com> Date: Mon, 22 Jul 2024 21:48:33 +0530 Subject: [PATCH 5/5] Update {{cookiecutter.project_name}}/pyproject.toml Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> --- {{cookiecutter.project_name}}/pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 34650f5..1a2d993 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -55,7 +55,6 @@ dev = [ "pytest-cov >=3", "nox[uv]", "pre-commit", - "pytest-cookies", ] docs = [ "sphinx",