From b127ab769bf5986c3da2c4cfa79c6f83c64a372b Mon Sep 17 00:00:00 2001 From: Santhosh <52504160+santacodes@users.noreply.github.com> Date: Thu, 15 Aug 2024 20:08:39 +0530 Subject: [PATCH] Added `pybamm-cookiecutter` CLI (#36) ## Additions + Added a basic `cli` with an optional `--path` argument defining the path generated project should reside within. + Added basic documentation for `cli` usage in `cli.py` and `README.md`. ## Removals - Removed `models`, `entry points`, and `parameter sets` from the project as they are now populated in the template. - Removed the `project-tests` from `noxfile` and updated workflow to only test the template. - Removed the entry points from `pyproject.toml` and `pybamm` as a dependency in the project. ## Usage To test this, check out this branch and inside the repository and do a local `pipx` installation on your machine. ```bash git clone https://github.com/santacodes/pybamm-cookiecutter -b cli cd pybamm-cookiecutter/ pipx install . ``` After installation, the `CLI` will be available systemwide and can be accessed using the `pybamm-cookiecutter` command. For help references add the `-h` or `--help` flag, e.g. `pybamm-cookiecutter --help` which would prompt with all the available arguments. Sub-task #26 --------- Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> --- .github/workflows/test_on_push.yml | 54 ++- LICENSES-bundled.txt | 7 - README.md | 26 +- cookiecutter.json | 50 --- copier.yml | 12 +- noxfile.py | 5 - pyproject.toml | 20 +- src/pybamm_cookiecutter/__init__.py | 11 +- src/pybamm_cookiecutter/__main__.py | 4 + src/pybamm_cookiecutter/cli.py | 60 +++ src/pybamm_cookiecutter/entry_point.py | 154 -------- src/pybamm_cookiecutter/models/input/SPM.py | 214 ---------- .../parameters/input/Chen2020.py | 369 ------------------ .../models/input/BasicReservoir.py | 200 ---------- .../.copier-answers.yml.jinja | 0 template/{ => {{ project_name }}}/.gitignore | 0 .../.pre-commit-config.yaml.jinja | 0 .../{ => {{ project_name }}}/.readthedocs.yml | 0 .../LICENSES-bundled.txt.jinja | 10 + .../{ => {{ project_name }}}/README.md.jinja | 0 .../docs/_static/.gitkeep | 0 .../docs/conf.py.jinja | 0 .../docs/index.md.jinja | 0 .../{ => {{ project_name }}}/docs/refs.bib | 0 .../{ => {{ project_name }}}/noxfile.py.jinja | 0 .../pyproject.toml.jinja | 0 .../src/{{project_slug}}/__init__.py.jinja | 2 +- .../src/{{project_slug}}/entry_point.py.jinja | 0 .../models/input/BasicReservoir.py | 0 .../src/{{project_slug}}/models/input/SPM.py | 0 .../parameters/input/Chen2020.py | 0 .../{% if mypy %}py.typed{% endif %} | 0 .../{% if vcs %}_version.py{% endif %} | 0 .../test_entry_points.py.jinja | 0 .../tests/user_tests/test_package.py.jinja | 0 ...cs == false %}MANIFEST.in{% endif %}.jinja | 0 .../dependabot.yml | 0 .../workflows/test_on_push.yml | 0 ...'gitlab' %}.gitlab-ci.yml{% endif %}.jinja | 0 ...cense=='Apache' %}LICENSE{% endif %}.jinja | 0 ... license=='BSD' %}LICENSE{% endif %}.jinja | 0 ... license=='MIT' %}LICENSE{% endif %}.jinja | 0 ... true %}.git_archival.txt{% endif %}.jinja | 0 ...true %}.gitattributes.txt{% endif %}.jinja | 0 tests/project_tests/test_entry_points.py | 43 -- .../template_tests/test_project_generation.py | 20 +- 46 files changed, 182 insertions(+), 1079 deletions(-) delete mode 100644 LICENSES-bundled.txt delete mode 100644 cookiecutter.json create mode 100644 src/pybamm_cookiecutter/__main__.py create mode 100644 src/pybamm_cookiecutter/cli.py delete mode 100644 src/pybamm_cookiecutter/entry_point.py delete mode 100644 src/pybamm_cookiecutter/models/input/SPM.py delete mode 100644 src/pybamm_cookiecutter/parameters/input/Chen2020.py delete mode 100644 template/src/{{project_slug}}/models/input/BasicReservoir.py rename template/{ => {{ project_name }}}/.copier-answers.yml.jinja (100%) rename template/{ => {{ project_name }}}/.gitignore (100%) rename template/{ => {{ project_name }}}/.pre-commit-config.yaml.jinja (100%) rename template/{ => {{ project_name }}}/.readthedocs.yml (100%) create mode 100644 template/{{ project_name }}/LICENSES-bundled.txt.jinja rename template/{ => {{ project_name }}}/README.md.jinja (100%) rename template/{ => {{ project_name }}}/docs/_static/.gitkeep (100%) rename template/{ => {{ project_name }}}/docs/conf.py.jinja (100%) rename template/{ => {{ project_name }}}/docs/index.md.jinja (100%) rename template/{ => {{ project_name }}}/docs/refs.bib (100%) rename template/{ => {{ project_name }}}/noxfile.py.jinja (100%) rename template/{ => {{ project_name }}}/pyproject.toml.jinja (100%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/__init__.py.jinja (88%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/entry_point.py.jinja (100%) rename {src/pybamm_cookiecutter => template/{{ project_name }}/src/{{project_slug}}}/models/input/BasicReservoir.py (100%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/models/input/SPM.py (100%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/parameters/input/Chen2020.py (100%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/{% if mypy %}py.typed{% endif %} (100%) rename template/{ => {{ project_name }}}/src/{{project_slug}}/{% if vcs %}_version.py{% endif %} (100%) rename template/{ => {{ project_name }}}/tests/generated_project_tests/test_entry_points.py.jinja (100%) rename template/{ => {{ project_name }}}/tests/user_tests/test_package.py.jinja (100%) rename template/{ => {{ project_name }}}/{% if backend == 'setuptools' and vcs == false %}MANIFEST.in{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if ci == 'github' %}.github{% endif %}/dependabot.yml (100%) rename template/{ => {{ project_name }}}/{% if ci == 'github' %}.github{% endif %}/workflows/test_on_push.yml (100%) rename template/{ => {{ project_name }}}/{% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if license=='Apache' %}LICENSE{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if license=='BSD' %}LICENSE{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if license=='MIT' %}LICENSE{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if vcs == true %}.git_archival.txt{% endif %}.jinja (100%) rename template/{ => {{ project_name }}}/{% if vcs == true %}.gitattributes.txt{% endif %}.jinja (100%) delete mode 100644 tests/project_tests/test_entry_points.py diff --git a/.github/workflows/test_on_push.yml b/.github/workflows/test_on_push.yml index 9cad665..c0a5702 100644 --- a/.github/workflows/test_on_push.yml +++ b/.github/workflows/test_on_push.yml @@ -55,12 +55,14 @@ jobs: - name: Install nox run: uv pip install nox[uv] + - name: Set up Git + run: | + git config --global user.name "pybamm user" + git config --global user.email "pybammuser@pybamm.org" + - name: Test template generation run: nox -s template-tests - - name: Test project - run: nox -s project-tests - - name: Run coverage tests if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' run: nox -s coverage @@ -98,7 +100,6 @@ jobs: - name: Check if the documentation can be built run: nox -s docs - generated_project_tests: needs: [template_test] runs-on: ${{ matrix.os }} @@ -121,12 +122,39 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: yezz123/setup-uv@v4 + with: + uv-venv: ".venv" + - name: Install copier and jinja2_time - run: python -m pip install copier jinja2_time + run: uv pip install copier jinja2-time - name: Generate project run: | - copier copy . ../pybamm-${{ matrix.backend }}-${{ matrix.vcs }} --data project_name=pybamm-${{ matrix.backend }}-${{ matrix.vcs }} --data project_slug=pybamm_${{ matrix.backend }}_${{ matrix.vcs }} --data backend=${{ matrix.backend }} --data vcs=${{ matrix.vcs }} --trust --defaults + copier copy . ../ --data project_name=pybamm-${{ matrix.backend }}-${{ matrix.vcs }} --data project_slug=pybamm_${{ matrix.backend }}_${{ matrix.vcs }} --data backend=${{ matrix.backend }} --data vcs=${{ matrix.vcs }} --trust --defaults + - name: Install nox + uses: wntrblm/nox@2024.04.15 + + - name: Test the generated project + working-directory: ../pybamm-${{ matrix.backend }}-${{ matrix.vcs }} + run: nox -s generated-project-tests + + run_generated_project_doctests: + needs: [template_test] + runs-on: ubuntu-latest + name: Generated Project Doctests (ubuntu-latest / Python 3.12) + + steps: + - name: Check out pybamm-cookiecutter repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 - name: Set up uv uses: yezz123/setup-uv@v4 @@ -136,6 +164,14 @@ jobs: - name: Install nox uses: wntrblm/nox@2024.04.15 - - name: Test the generated project - working-directory: ../pybamm-${{ matrix.backend }}-${{ matrix.vcs }} - run: nox -s generated-project-tests + - name: Install copier and jinja2_time and generate a template with default values + run: | + uv pip install copier jinja2-time + copier copy . . --trust --defaults + + - name: Check if the documentation can be built + working-directory: ./pybamm-example-project + run: | + git add . + git commit -am "initial commit" + nox -s docs diff --git a/LICENSES-bundled.txt b/LICENSES-bundled.txt deleted file mode 100644 index d97adec..0000000 --- a/LICENSES-bundled.txt +++ /dev/null @@ -1,7 +0,0 @@ -This project and source distributions bundle several libraries that are -compatibly licensed. - - -Name: PyBaMM -Files: src/pybamm_cookiecutter/parameters/* -License: BSD-3-Clause diff --git a/README.md b/README.md index 56eede4..f548c95 100644 --- a/README.md +++ b/README.md @@ -26,24 +26,42 @@ [![Powered by NumFOCUS](https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A)](http://numfocus.org) [![Copier](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-red.json)](https://github.com/copier-org/copier) +[![Test template and generated project](https://github.com/pybamm-team/pybamm-cookie/actions/workflows/test_on_push.yml/badge.svg)](https://github.com/pybamm-team/pybamm-cookie/actions/workflows/test_on_push.yml) This repository contains a `copier` template for battery modeling projects using PyBaMM, released under the [BSD-3-Clause license](https://github.com/pybamm-team/pybamm-cookiecutter/blob/main/LICENSE). Currently under active development. ## 📄 Using `pybamm-cookiecutter` -### Generating projects with `pybamm-cookiecutter` manually using copier +### Generating projects with `pybamm-cookiecutter` -This template is not on PyPI yet, so it cannot be installed through `pip` until the first release. Meanwhile, it can be used by cloning this repository and using `copier` to generate a project with this template. +#### Manually using copier Install `copier` and `jinja2_time` extension using `pip`. ```bash pip install copier jinja2-time ``` Generate a project from the `pybamm-cookiecutter` template. -**Note:** This requires an internet connection. You could manually clone the git repository and run the copy command, or just execute the copy command with the URL to the git repository. ```bash -copier copy https://github.com/pybamm-team/pybamm-cookiecutter.git name_of_your_project/ --trust +copier copy https://github.com/pybamm-team/pybamm-cookiecutter.git . --trust +# this will generate the project in the current working directory +copier copy https://github.com/pybamm-team/pybamm-cookiecutter.git path_to_copy_to/ --trust +# this will generate the project in the specified path +``` +#### Using pipx (recommended) + +You can generate a project by executing the `pipx run` command which doesn't need any package installations. +```bash +pipx run pybamm-cookiecutter --path /path_to_copy_to +``` + +Or if you wish to install the `pybamm-cookiecutter` package and then generate a project, you could do so with the help of following commands. +```bash +pipx install pybamm-cookiecutter # or pip install pybamm-cookiecutter +``` +Navigate into the directory you want your project directory to reside in, or use `--path` argument to explicitly mention the path where you want your project to be generated. +```bash +pybamm-cookiecutter --path /path_to_copy_to ``` Copier will prompt you with various configurations and you may choose the ones that suit your use case. diff --git a/cookiecutter.json b/cookiecutter.json deleted file mode 100644 index 854d841..0000000 --- a/cookiecutter.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "project_name": "pybamm-example-project", - "platform": [ - "github", - "gitlab" - ], - "org": "pybamm-team", - "url": "https://{{ cookiecutter.platform }}.com/{{ cookiecutter.org }}/{{ cookiecutter.project_name }}", - "branch": "main", - "full_name": "John Doe", - "email": "john@doe.com", - "project_short_description": "A template for creating battery modeling projects based on PyBaMM", - "license": [ - "BSD", - "Apache", - "MIT" - ], - "backend": [ - "hatch" - ], - "vcs": false, - "mypy": false, - "__year": "{% now 'utc', '%Y' %}", - "__project_slug": "{{ cookiecutter.project_name | lower | replace('-', '_') | replace('.', '_') }}", - "_copy_without_render": ["*.yml"], - "__type": "{{ 'pure' }}", - "__answers": "", - "__ci": "{{ cookiecutter.platform }}", - "__prompts__": { - "project_name": "The name of your project", - "platform": { - "__prompt__": "The platform you are using to host your project", - "github": "GitHub", - "gitlab": "GitLab" - }, - "org": "The name of your organisation (or username, if you are not part of an organisation)", - "url": "The Origin URL to your repository", - "branch": "The default branch of your repository", - "full_name": "Your name", - "email": "Your email", - "project_short_description": "A short description of your project", - "license": "Select a license", - "backend": { - "__prompt__": "Choose a build backend", - "hatch": "Hatchling (recommended for pure Python projects)", - "vcs": "Obtain the version from your version control system (Git/Mercurial/SVN)?" - }, - "mypy": "Use mypy and static types" - } -} diff --git a/copier.yml b/copier.yml index c2316cc..026e18f 100644 --- a/copier.yml +++ b/copier.yml @@ -87,7 +87,13 @@ _jinja_extensions: _subdirectory: template +_exclude: + - copier.yml + _tasks: - - git init -b {{branch}} - - git config user.name "{{full_name}}" - - git config user.email "{{email}}" + - command: git init -b {{branch}} + working_directory: "{{project_name}}" + - command: git config user.name {{full_name}} + working_directory: "{{project_name}}" + - command: git config user.email {{email}} + working_directory: "{{project_name}}" diff --git a/noxfile.py b/noxfile.py index 2b236a1..721d245 100644 --- a/noxfile.py +++ b/noxfile.py @@ -46,11 +46,6 @@ def run_template_generation(session): """Run tests for the template generation.""" install_and_run_tests(session, "tests/template_tests") -@nox.session(name="project-tests") -def run_project_tests(session): - """Run the tests for testing project units""" - install_and_run_tests(session, "tests/project_tests") - @nox.session(name="coverage") def run_coverage(session): """Run the coverage tests and generate an XML report.""" diff --git a/pyproject.toml b/pyproject.toml index ddf23e3..bd36195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ build-backend = "hatchling.build" name = "pybamm-cookiecutter" authors = [ { name = "Agriya Khetarpal", email = "agriyakhetarpal@outlook.com" }, + { name = "Santhosh Sundaram", email = "santhoshsundaram9650@gmail.com" }, ] maintainers = [ { name = "PyBaMM Team", email = "pybamm@pybamm.org" }, @@ -30,7 +31,7 @@ classifiers = [ "Typing :: Typed", ] dynamic = ["version"] -dependencies = ["pybamm", "copier", "jinja2-time"] +dependencies = ["copier", "jinja2-time", "colorama"] [project.optional-dependencies] dev = [ @@ -55,19 +56,18 @@ docs = [ "sphinx-gallery", ] +[project.scripts] +pybamm-cookiecutter = "pybamm_cookiecutter.cli:pybamm_cookiecutter_cli" + +[project.entry-points."pipx.run"] +pybamm-cookiecutter = "pybamm_cookiecutter.cli:pybamm_cookiecutter_cli" + [project.urls] Homepage = "https://github.com/pybamm-team/pybamm-cookiecutter" "Bug Tracker" = "https://github.com/pybamm-team/pybamm-cookiecutter/issues" Discussions = "https://github.com/pybamm-team/pybamm-cookiecutter/discussions" Changelog = "https://github.com/pybamm-team/pybamm-cookiecutter/releases" -[project.entry-points."parameter_sets"] -Chen2020 = "pybamm_cookiecutter.parameters.input.Chen2020:get_parameter_values" - -[project.entry-points."models"] -SPM = "pybamm_cookiecutter.models.input.SPM:SPM" -BasicReservoir = "pybamm_cookiecutter.models.input.BasicReservoir:BasicReservoir" - [tool.hatch] version.source = "vcs" build.hooks.vcs.version-file = "src/pybamm_cookiecutter/_version.py" @@ -75,6 +75,10 @@ envs.default.dependencies = [ "pybamm", ] +[tool.hatch.build.targets.wheel] +only-include = ["src/pybamm_cookiecutter", "/template", "copier.yml", "LICENSE", "LICENSES-bundled.txt"] +sources = ["src"] + [tool.mypy] packages = [ "src/pybamm_cookiecutter", diff --git a/src/pybamm_cookiecutter/__init__.py b/src/pybamm_cookiecutter/__init__.py index 6e69f35..aca1fa6 100644 --- a/src/pybamm_cookiecutter/__init__.py +++ b/src/pybamm_cookiecutter/__init__.py @@ -5,15 +5,10 @@ """ from __future__ import annotations -import pybamm - -from ._version import version as __version__ -from .entry_point import Model, parameter_sets, models +from pybamm_cookiecutter.cli import pybamm_cookiecutter_cli +from pybamm_cookiecutter._version import __version__ __all__ : list[str] = [ "__version__", - "pybamm", - "parameter_sets", - "Model", - "models", + "pybamm_cookiecutter_cli", ] diff --git a/src/pybamm_cookiecutter/__main__.py b/src/pybamm_cookiecutter/__main__.py new file mode 100644 index 0000000..36ef9ca --- /dev/null +++ b/src/pybamm_cookiecutter/__main__.py @@ -0,0 +1,4 @@ +if __name__ == "__main__": + from pybamm_cookiecutter.cli import pybamm_cookiecutter_cli + + pybamm_cookiecutter_cli() diff --git a/src/pybamm_cookiecutter/cli.py b/src/pybamm_cookiecutter/cli.py new file mode 100644 index 0000000..2891d32 --- /dev/null +++ b/src/pybamm_cookiecutter/cli.py @@ -0,0 +1,60 @@ +import copier +import argparse +from pathlib import Path +from colorama import Fore +import os + +project_root = Path(__file__).resolve().parent.parent +TEMPLATE = str(project_root) + +def pybamm_cookiecutter_cli(): + """ + Command Line Interface (CLI) for generating PyBaMM based projects using copier. + + Parameters + ---------- + --path: pathlib.Path + + Examples + ------- + $ pybamm-cookiecutter + Generates a project in the current working directory of the terminal. + $ pybamm-cookiecutter --path /myproject + Generates a project in the `myproject` directory. + """ + try: + parser = argparse.ArgumentParser(description = "A copier template generator for PyBaMM based projects") + parser.add_argument( + "--path", type = str, + required = False, + default = os.getcwd(), + help = "The destination path for project generation. The default is the current working directory" + ) + + from pybamm_cookiecutter import __version__ as version + parser.add_argument( + '--version', + action='version', + version=f'PyBaMM Cookiecutter CLI Version - {version}' + ) + + parser.add_argument( + "--defaults", + action="store_true", + help="Whether to use default options for generating the template" + ) + args = parser.parse_args() + destination_path = Path(args.path) + + with copier.Worker(src_path = TEMPLATE, dst_path = destination_path, unsafe = True, defaults = args.defaults) as worker: + worker.run_copy() + + except KeyboardInterrupt: + print(Fore.RED + "Execution stopped by the user" + Fore.RESET) + except Exception as error: + print(Fore.RED + "Error caused by an exception: " + Fore.RESET, error) + print(Fore.CYAN + "If you are unsure what the error is, feel free to open an issue at" + Fore.YELLOW +" - https://github.com/pybamm-team/pybamm-cookiecutter/issues" + Fore.RESET) + +if __name__ == '__main__': + + pybamm_cookiecutter_cli() diff --git a/src/pybamm_cookiecutter/entry_point.py b/src/pybamm_cookiecutter/entry_point.py deleted file mode 100644 index 691cd86..0000000 --- a/src/pybamm_cookiecutter/entry_point.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -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/src/pybamm_cookiecutter/models/input/SPM.py b/src/pybamm_cookiecutter/models/input/SPM.py deleted file mode 100644 index b6c0526..0000000 --- a/src/pybamm_cookiecutter/models/input/SPM.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -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/src/pybamm_cookiecutter/parameters/input/Chen2020.py b/src/pybamm_cookiecutter/parameters/input/Chen2020.py deleted file mode 100644 index 0b4a8a4..0000000 --- a/src/pybamm_cookiecutter/parameters/input/Chen2020.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -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"], - } diff --git a/template/src/{{project_slug}}/models/input/BasicReservoir.py b/template/src/{{project_slug}}/models/input/BasicReservoir.py deleted file mode 100644 index 1e8973c..0000000 --- a/template/src/{{project_slug}}/models/input/BasicReservoir.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -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 Reservoir Model -# -import pybamm - -class BasicReservoir(pybamm.lithium_ion.BaseModel): - """Reservoir model of a lithium-ion battery, from - :footcite:t:`Marquis2019`. - - Parameters - ---------- - name : str, optional - The name of the model. - """ - - def __init__(self, name="Reservoir 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 - sto_n = pybamm.Variable( - "Average negative particle stoichiometry", - domain="current collector", - bounds=(0, 1), - ) - sto_p = pybamm.Variable( - "Average positive particle stoichiometry", - domain="current collector", - bounds=(0, 1), - ) - - # 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 - ###################### - - self.rhs[sto_n] = -i_cell / ( - param.n.L * param.n.prim.epsilon_s_av * param.n.prim.c_max * param.F - ) - self.rhs[sto_p] = i_cell / ( - param.p.L * param.p.prim.epsilon_s_av * param.p.prim.c_max * param.F - ) - # c_n_init and c_p_init are functions of r and x, but for the reservoir model - # we take the x-averaged and r-averaged value since there are no x-dependence - # nor r-dependencein the particles - self.initial_conditions[sto_n] = ( - pybamm.x_average(pybamm.r_average(param.n.prim.c_init)) / param.n.prim.c_max - ) - self.initial_conditions[sto_p] = ( - pybamm.x_average(pybamm.r_average(param.p.prim.c_init)) / param.p.prim.c_max - ) - - self.events += [ - pybamm.Event( - "Minimum negative particle surface stoichiometry", - sto_n - 0.01, - ), - pybamm.Event( - "Maximum negative particle surface stoichiometry", - (1 - 0.01) - sto_n, - ), - pybamm.Event( - "Minimum positive particle surface stoichiometry", - sto_p - 0.01, - ), - pybamm.Event( - "Maximum positive particle surface stoichiometry", - (1 - 0.01) - sto_p, - ), - ] - - # Note that the reservoir model 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, sto_n * param.n.prim.c_max, T) - j0_p = param.p.prim.j0(param.c_e_init_av, sto_p * param.p.prim.c_max, 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_n, T) - phi_s_p = eta_p + phi_e + param.p.prim.U(sto_p, T) - V = phi_s_p - num_cells = pybamm.Parameter( - "Number of cells connected in series to make a battery" - ) - c_s_n = sto_n * param.n.prim.c_max - c_s_p = sto_p * param.p.prim.c_max - - 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]": pybamm.PrimaryBroadcast( - c_s_n, "negative particle" - ), - "Negative particle surface " - "concentration [mol.m-3]": pybamm.PrimaryBroadcast( - c_s_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]": pybamm.PrimaryBroadcast( - c_s_p, "positive particle" - ), - "Positive particle surface " - "concentration [mol.m-3]": pybamm.PrimaryBroadcast( - c_s_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/template/.copier-answers.yml.jinja b/template/{{ project_name }}/.copier-answers.yml.jinja similarity index 100% rename from template/.copier-answers.yml.jinja rename to template/{{ project_name }}/.copier-answers.yml.jinja diff --git a/template/.gitignore b/template/{{ project_name }}/.gitignore similarity index 100% rename from template/.gitignore rename to template/{{ project_name }}/.gitignore diff --git a/template/.pre-commit-config.yaml.jinja b/template/{{ project_name }}/.pre-commit-config.yaml.jinja similarity index 100% rename from template/.pre-commit-config.yaml.jinja rename to template/{{ project_name }}/.pre-commit-config.yaml.jinja diff --git a/template/.readthedocs.yml b/template/{{ project_name }}/.readthedocs.yml similarity index 100% rename from template/.readthedocs.yml rename to template/{{ project_name }}/.readthedocs.yml diff --git a/template/{{ project_name }}/LICENSES-bundled.txt.jinja b/template/{{ project_name }}/LICENSES-bundled.txt.jinja new file mode 100644 index 0000000..563ff54 --- /dev/null +++ b/template/{{ project_name }}/LICENSES-bundled.txt.jinja @@ -0,0 +1,10 @@ +This project and source distributions bundle several libraries that are +compatibly licensed. + + +Name: PyBaMM +Files: - src/{{ project_slug }}/parameters/input/Chen2020.py + - src/{{ project_slug }}/models/input/BasicReservoir.py + - src/{{ project_slug }}/models/input/SPM.py + - src/{{ project_slug }}/entry_point.py +License: BSD-3-Clause diff --git a/template/README.md.jinja b/template/{{ project_name }}/README.md.jinja similarity index 100% rename from template/README.md.jinja rename to template/{{ project_name }}/README.md.jinja diff --git a/template/docs/_static/.gitkeep b/template/{{ project_name }}/docs/_static/.gitkeep similarity index 100% rename from template/docs/_static/.gitkeep rename to template/{{ project_name }}/docs/_static/.gitkeep diff --git a/template/docs/conf.py.jinja b/template/{{ project_name }}/docs/conf.py.jinja similarity index 100% rename from template/docs/conf.py.jinja rename to template/{{ project_name }}/docs/conf.py.jinja diff --git a/template/docs/index.md.jinja b/template/{{ project_name }}/docs/index.md.jinja similarity index 100% rename from template/docs/index.md.jinja rename to template/{{ project_name }}/docs/index.md.jinja diff --git a/template/docs/refs.bib b/template/{{ project_name }}/docs/refs.bib similarity index 100% rename from template/docs/refs.bib rename to template/{{ project_name }}/docs/refs.bib diff --git a/template/noxfile.py.jinja b/template/{{ project_name }}/noxfile.py.jinja similarity index 100% rename from template/noxfile.py.jinja rename to template/{{ project_name }}/noxfile.py.jinja diff --git a/template/pyproject.toml.jinja b/template/{{ project_name }}/pyproject.toml.jinja similarity index 100% rename from template/pyproject.toml.jinja rename to template/{{ project_name }}/pyproject.toml.jinja diff --git a/template/src/{{project_slug}}/__init__.py.jinja b/template/{{ project_name }}/src/{{project_slug}}/__init__.py.jinja similarity index 88% rename from template/src/{{project_slug}}/__init__.py.jinja rename to template/{{ project_name }}/src/{{project_slug}}/__init__.py.jinja index 9ef3253..821026b 100644 --- a/template/src/{{project_slug}}/__init__.py.jinja +++ b/template/{{ project_name }}/src/{{project_slug}}/__init__.py.jinja @@ -17,7 +17,7 @@ __version__ = "0.1.0" {%- endif %} import pybamm -from .entry_point import Model, parameter_sets, models +from {{project_slug}}.entry_point import Model, parameter_sets, models {# keep this line here for newline #} {%- if mypy %} __all__: list[str] = [ diff --git a/template/src/{{project_slug}}/entry_point.py.jinja b/template/{{ project_name }}/src/{{project_slug}}/entry_point.py.jinja similarity index 100% rename from template/src/{{project_slug}}/entry_point.py.jinja rename to template/{{ project_name }}/src/{{project_slug}}/entry_point.py.jinja diff --git a/src/pybamm_cookiecutter/models/input/BasicReservoir.py b/template/{{ project_name }}/src/{{project_slug}}/models/input/BasicReservoir.py similarity index 100% rename from src/pybamm_cookiecutter/models/input/BasicReservoir.py rename to template/{{ project_name }}/src/{{project_slug}}/models/input/BasicReservoir.py diff --git a/template/src/{{project_slug}}/models/input/SPM.py b/template/{{ project_name }}/src/{{project_slug}}/models/input/SPM.py similarity index 100% rename from template/src/{{project_slug}}/models/input/SPM.py rename to template/{{ project_name }}/src/{{project_slug}}/models/input/SPM.py diff --git a/template/src/{{project_slug}}/parameters/input/Chen2020.py b/template/{{ project_name }}/src/{{project_slug}}/parameters/input/Chen2020.py similarity index 100% rename from template/src/{{project_slug}}/parameters/input/Chen2020.py rename to template/{{ project_name }}/src/{{project_slug}}/parameters/input/Chen2020.py diff --git a/template/src/{{project_slug}}/{% if mypy %}py.typed{% endif %} b/template/{{ project_name }}/src/{{project_slug}}/{% if mypy %}py.typed{% endif %} similarity index 100% rename from template/src/{{project_slug}}/{% if mypy %}py.typed{% endif %} rename to template/{{ project_name }}/src/{{project_slug}}/{% if mypy %}py.typed{% endif %} diff --git a/template/src/{{project_slug}}/{% if vcs %}_version.py{% endif %} b/template/{{ project_name }}/src/{{project_slug}}/{% if vcs %}_version.py{% endif %} similarity index 100% rename from template/src/{{project_slug}}/{% if vcs %}_version.py{% endif %} rename to template/{{ project_name }}/src/{{project_slug}}/{% if vcs %}_version.py{% endif %} diff --git a/template/tests/generated_project_tests/test_entry_points.py.jinja b/template/{{ project_name }}/tests/generated_project_tests/test_entry_points.py.jinja similarity index 100% rename from template/tests/generated_project_tests/test_entry_points.py.jinja rename to template/{{ project_name }}/tests/generated_project_tests/test_entry_points.py.jinja diff --git a/template/tests/user_tests/test_package.py.jinja b/template/{{ project_name }}/tests/user_tests/test_package.py.jinja similarity index 100% rename from template/tests/user_tests/test_package.py.jinja rename to template/{{ project_name }}/tests/user_tests/test_package.py.jinja diff --git a/template/{% if backend == 'setuptools' and vcs == false %}MANIFEST.in{% endif %}.jinja b/template/{{ project_name }}/{% if backend == 'setuptools' and vcs == false %}MANIFEST.in{% endif %}.jinja similarity index 100% rename from template/{% if backend == 'setuptools' and vcs == false %}MANIFEST.in{% endif %}.jinja rename to template/{{ project_name }}/{% if backend == 'setuptools' and vcs == false %}MANIFEST.in{% endif %}.jinja diff --git a/template/{% if ci == 'github' %}.github{% endif %}/dependabot.yml b/template/{{ project_name }}/{% if ci == 'github' %}.github{% endif %}/dependabot.yml similarity index 100% rename from template/{% if ci == 'github' %}.github{% endif %}/dependabot.yml rename to template/{{ project_name }}/{% if ci == 'github' %}.github{% endif %}/dependabot.yml diff --git a/template/{% if ci == 'github' %}.github{% endif %}/workflows/test_on_push.yml b/template/{{ project_name }}/{% if ci == 'github' %}.github{% endif %}/workflows/test_on_push.yml similarity index 100% rename from template/{% if ci == 'github' %}.github{% endif %}/workflows/test_on_push.yml rename to template/{{ project_name }}/{% if ci == 'github' %}.github{% endif %}/workflows/test_on_push.yml diff --git a/template/{% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja b/template/{{ project_name }}/{% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja similarity index 100% rename from template/{% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja rename to template/{{ project_name }}/{% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja diff --git a/template/{% if license=='Apache' %}LICENSE{% endif %}.jinja b/template/{{ project_name }}/{% if license=='Apache' %}LICENSE{% endif %}.jinja similarity index 100% rename from template/{% if license=='Apache' %}LICENSE{% endif %}.jinja rename to template/{{ project_name }}/{% if license=='Apache' %}LICENSE{% endif %}.jinja diff --git a/template/{% if license=='BSD' %}LICENSE{% endif %}.jinja b/template/{{ project_name }}/{% if license=='BSD' %}LICENSE{% endif %}.jinja similarity index 100% rename from template/{% if license=='BSD' %}LICENSE{% endif %}.jinja rename to template/{{ project_name }}/{% if license=='BSD' %}LICENSE{% endif %}.jinja diff --git a/template/{% if license=='MIT' %}LICENSE{% endif %}.jinja b/template/{{ project_name }}/{% if license=='MIT' %}LICENSE{% endif %}.jinja similarity index 100% rename from template/{% if license=='MIT' %}LICENSE{% endif %}.jinja rename to template/{{ project_name }}/{% if license=='MIT' %}LICENSE{% endif %}.jinja diff --git a/template/{% if vcs == true %}.git_archival.txt{% endif %}.jinja b/template/{{ project_name }}/{% if vcs == true %}.git_archival.txt{% endif %}.jinja similarity index 100% rename from template/{% if vcs == true %}.git_archival.txt{% endif %}.jinja rename to template/{{ project_name }}/{% if vcs == true %}.git_archival.txt{% endif %}.jinja diff --git a/template/{% if vcs == true %}.gitattributes.txt{% endif %}.jinja b/template/{{ project_name }}/{% if vcs == true %}.gitattributes.txt{% endif %}.jinja similarity index 100% rename from template/{% if vcs == true %}.gitattributes.txt{% endif %}.jinja rename to template/{{ project_name }}/{% if vcs == true %}.gitattributes.txt{% endif %}.jinja diff --git a/tests/project_tests/test_entry_points.py b/tests/project_tests/test_entry_points.py deleted file mode 100644 index 923c2d9..0000000 --- a/tests/project_tests/test_entry_points.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest -import pybamm_cookiecutter -import importlib.util -import sys -from pathlib import Path - -def test_parameter_sets_entry_points(): - """Test if the parameter_sets via entry points are loaded correctly.""" - - entry_points = list(pybamm_cookiecutter.parameter_sets).sort() - parameter_sets = Path("src/pybamm_cookiecutter/parameters/input/").glob("*.py") - # Making a list of parameter sets in the parameters/input directory - parameter_sets = [x.stem for x in parameter_sets].sort() - - assert parameter_sets == entry_points, "Entry points missing either in pyproject.toml or in the input directory" - -def test_parameter_sets_entry_point_load(): - """Testing if the values get loaded via parameter entry points and are equal when loaded through entry points""" - # Loading parameter_sets through entry points - parameters = pybamm_cookiecutter.parameter_sets['Chen2020'] - # Loading parameter sets through the source file by dynamically loading Chen2020.py as a module - spec = importlib.util.spec_from_file_location("Chen2020mod", "src/pybamm_cookiecutter/parameters/input/Chen2020.py") - chen_module = importlib.util.module_from_spec(spec) - sys.modules["Chen2020mod"] = chen_module - spec.loader.exec_module(chen_module) - parameters_from_file = chen_module.get_parameter_values() - assert parameters.keys() == parameters_from_file.keys(), f"The keys in the module and local input file are not the same, expected {parameters.keys} got {parameters_from_file.keys()}" - -def test_model_entry_points(): - """Test if the models via entry points are loaded correctly.""" - - entry_points = list(pybamm_cookiecutter.models).sort() - models = Path("src/pybamm_cookiecutter/models/input/").glob("*.py") - # Making a list Parameter sets in the parameters/input directory - models = [x.stem for x in models].sort() - - assert models == entry_points, "Entry points missing either in pyproject.toml or in the input directory" - -def test_model_entry_point_load(): - """Testing if the model gets initialised and returned.""" - # Loading parameter_sets through entry points - model_instance = pybamm_cookiecutter.Model("SPM") - assert model_instance is not None diff --git a/tests/template_tests/test_project_generation.py b/tests/template_tests/test_project_generation.py index 2f6d959..8bcec51 100644 --- a/tests/template_tests/test_project_generation.py +++ b/tests/template_tests/test_project_generation.py @@ -1,5 +1,8 @@ import pybamm_cookiecutter as m import pytest +import os +import subprocess +import shutil def test_version() -> None: assert m.__version__ @@ -13,8 +16,8 @@ def test_bake_project(copie): # codespell:ignore copie assert result.exit_code == 0, f"Exited with code {result.exit_code}, expected 0" assert result.exception is None, result.exception - assert result.project_dir.is_dir(), f"Project directory {result.project_path} not found" - with open(result.project_dir / "README.md") as f: + assert result.project_dir.is_dir(), f"Project directory {result.project_dir} not found" + with open(result.project_dir / "pybamm-example-project" / "README.md") as f: assert f.readline() == "# pybamm-example-project\n", f"{f.readline()} is not the same as # pybamm-example-project\n" @@ -33,6 +36,15 @@ def test_template_with_extra_answers(copie): # codespell:ignore copie assert result.exit_code == 0, f"Exited with code {result.exit_code}, expected 0" assert result.exception is None, result.exception - assert result.project_dir.is_dir(), f"Project directory {result.project_path} not found" - with open(result.project_dir / "README.md") as f: + assert result.project_dir.is_dir(), f"Project directory {result.project_dir} not found" + with open(result.project_dir / extra_context["project_name"] / "README.md") as f: assert f.readline() == f"# {extra_context['project_name']}\n", f"{f.readline()} is not the same as {extra_context['project_name']}\n" + +def test_cli(): + """ + Testing if the CLI works and returns a successful exit code on execution + """ + os.mkdir("testcli") + return_code = subprocess.run(["pybamm-cookiecutter", "--defaults"], cwd = "./testcli") + shutil.rmtree("testcli") + assert return_code