From 8aa4ae707a1b52584f111609f09ec546340f4302 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Thu, 5 Oct 2023 14:58:54 +0530 Subject: [PATCH 01/14] Bump to v23.9rc0 --- CHANGELOG.md | 2 ++ CITATION.cff | 2 +- pybamm/version.py | 2 +- vcpkg.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c400cc31ea..a8518ba639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) +# [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 + ## Features - The parameter "Ambient temperature [K]" can now be given as a function of position `(y,z)` and time `t`. The "edge" and "current collector" heat transfer coefficient parameters can also depend on `(y,z)` ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) - Spherical and cylindrical shell domains can now be solved with any boundary conditions ([#3237](https://github.com/pybamm-team/PyBaMM/pull/3237)) diff --git a/CITATION.cff b/CITATION.cff index f5d6fe4911..5a9e1e2ddc 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -24,6 +24,6 @@ keywords: - "expression tree" - "python" - "symbolic differentiation" -version: "23.5" +version: "23.9rc0" repository-code: "https://github.com/pybamm-team/PyBaMM" title: "Python Battery Mathematical Modelling (PyBaMM)" diff --git a/pybamm/version.py b/pybamm/version.py index 0e8c575aea..c8d63f83e1 100644 --- a/pybamm/version.py +++ b/pybamm/version.py @@ -1 +1 @@ -__version__ = "23.5" +__version__ = "23.9rc0" diff --git a/vcpkg.json b/vcpkg.json index 2609370382..6877dfa094 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "pybamm", - "version-string": "23.5", + "version-string": "23.9rc0", "dependencies": [ "casadi", { From 50be8a5a90ef683b97a434c056c27bc12e6e284c Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Thu, 5 Oct 2023 19:47:55 +0530 Subject: [PATCH 02/14] Merge pull request #3412 from agriyakhetarpal/drop-i686-manylinux2014-support Drop support for i686 manylinux --- .github/workflows/publish_pypi.yml | 18 +++++++++--------- CHANGELOG.md | 1 + build_manylinux_wheels/install_sundials.sh | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 919a00d6ef..a009828e6f 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -102,10 +102,10 @@ jobs: - name: Build wheels on Linux and MacOS run: python -m cibuildwheel --output-dir wheelhouse env: - # TODO: openblas no longer available on centos 7 i686 image, use blas instead for now + CIBW_ARCHS_LINUX: x86_64 CIBW_BEFORE_ALL_LINUX: > - yum -y install blas-devel lapack-devel && - bash build_manylinux_wheels/install_sundials.sh 5.8.1 6.5.0 + yum -y install openblas-devel lapack-devel && + bash build_manylinux_wheels/install_sundials.sh 6.0.3 6.5.0 CIBW_BEFORE_BUILD_LINUX: "python -m pip install cmake casadi numpy" CIBW_BEFORE_BUILD_MACOS: > @@ -135,13 +135,13 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.11 - name: Install dependencies - run: pip install wheel + run: pip install --upgrade pip setuptools wheel build - name: Build sdist - run: python setup.py sdist --formats=gztar + run: python -m build --sdist - name: Upload sdist uses: actions/upload-artifact@v3 @@ -171,7 +171,7 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - packages_dir: files/ + packages-dir: files/ - name: Publish on TestPyPI if: github.event.inputs.target == 'testpypi' @@ -179,5 +179,5 @@ jobs: with: user: __token__ password: ${{ secrets.TESTPYPI_TOKEN }} - packages_dir: files/ - repository_url: https://test.pypi.org/legacy/ + packages-dir: files/ + repository-url: https://test.pypi.org/legacy/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a8518ba639..421d3bfa29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ ## Breaking changes +- Dropped support for i686 (32-bit) architectures on GNU/Linux distributions ([#3412](https://github.com/pybamm-team/PyBaMM/pull/3412)) - The class `pybamm.thermal.OneDimensionalX` has been moved to `pybamm.thermal.pouch_cell.OneDimensionalX` to reflect the fact that the model formulation implicitly assumes a pouch cell geometry ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) - The "lumped" thermal option now always used the parameters "Cell cooling surface area [m2]", "Cell volume [m3]" and "Total heat transfer coefficient [W.m-2.K-1]" to compute the cell cooling regardless of the chosen "cell geometry" option. The user must now specify the correct values for these parameters instead of them being calculated based on e.g. a pouch cell. An `OptionWarning` is raised to let users know to update their parameters ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) - Numpy functions now work with PyBaMM symbols (e.g. `np.exp(pybamm.Symbol("a"))` returns `pybamm.Exp(pybamm.Symbol("a"))`). This means that parameter functions can be specified using numpy functions instead of pybamm functions. Additionally, combining numpy arrays with pybamm objects now works (the numpy array is converted to a pybamm array) ([#3205](https://github.com/pybamm-team/PyBaMM/pull/3205)) diff --git a/build_manylinux_wheels/install_sundials.sh b/build_manylinux_wheels/install_sundials.sh index 709d9c13c7..3d14bde7c7 100644 --- a/build_manylinux_wheels/install_sundials.sh +++ b/build_manylinux_wheels/install_sundials.sh @@ -65,8 +65,8 @@ yum -y install openblas-devel mkdir -p build_sundials cd build_sundials -KLU_INCLUDE_DIR=/usr/include -KLU_LIBRARY_DIR=/usr/lib +KLU_INCLUDE_DIR=/usr/local/include +KLU_LIBRARY_DIR=/usr/local/lib SUNDIALS_DIR=sundials-$SUNDIALS_VERSION cmake -DENABLE_LAPACK=ON\ -DSUNDIALS_INDEX_SIZE=32\ From e00be74376969b1c689062c7fb2760472a507e94 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Thu, 5 Oct 2023 20:41:16 +0530 Subject: [PATCH 03/14] Merge pull request #3413 from Saransh-cpp/improve-release-workflow Improve release workflow, add a note, bump version manually --- .github/release_workflow.md | 25 +++++++++++++++---------- .github/workflows/update_version.yml | 10 ++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/release_workflow.md b/.github/release_workflow.md index 04f0667773..1af23fca25 100644 --- a/.github/release_workflow.md +++ b/.github/release_workflow.md @@ -4,7 +4,7 @@ This file contains the workflow required to make a `PyBaMM` release on GitHub an ## rc0 releases (automated) -1. The `update_version.yml` workflow will run on every 1st of January, May and September, updating incrementing the version to `YY.MMrc0` by running `scripts/update_version.py` in the following files - +1. The `update_version.yml` workflow will run on every 1st of January, May and September, updating incrementing the version to `vYY.MMrc0` by running `scripts/update_version.py` in the following files - - `pybamm/version.py` - `docs/conf.py` @@ -13,9 +13,9 @@ This file contains the workflow required to make a `PyBaMM` release on GitHub an - `docs/_static/versions.json` - `CHANGELOG.md` - These changes will be automatically pushed to a new branch `YY.MM`. + These changes will be automatically pushed to a new branch `vYY.MM` and a PR from `vvYY.MM` to `develop` will be created (to sync the branches). -2. Create a new GitHub _pre-release_ with the tag `YY.MMrc0` from the `YY.MM` branch and a description copied from `CHANGELOG.md`. +2. Create a new GitHub _pre-release_ with the tag `vYY.MMrc0` from the `vYY.MM` branch and a description copied from `CHANGELOG.md`. 3. This release will automatically trigger `publish_pypi.yml` and create a _pre-release_ on PyPI. @@ -23,11 +23,11 @@ This file contains the workflow required to make a `PyBaMM` release on GitHub an If a new release candidate is required after the release of `rc0` - -1. Fix a bug in `YY.MM` (no new features should be added to `YY.MM` once `rc0` is released) and `develop` individually. +1. Fix a bug in `vYY.MM` (no new features should be added to `vYY.MM` once `rc0` is released) and `develop` individually. 2. Run `update_version.yml` manually while using `append_to_tag` to specify the release candidate version number (`rc1`, `rc2`, ...). -3. This will increment the version to `YY.MMrcX` by running `scripts/update_version.py` in the following files - +3. This will increment the version to `vYY.MMrcX` by running `scripts/update_version.py` in the following files - - `pybamm/version.py` - `docs/conf.py` @@ -36,9 +36,9 @@ If a new release candidate is required after the release of `rc0` - - `docs/_static/versions.json` - `CHANGELOG.md` - These changes will be automatically pushed to the existing branch `YY.MM`. + These changes will be automatically pushed to the existing `vYY.MM` branch and a PR from `vvYY.MM` to `develop` will be created (to sync the branches). -4. Create a new GitHub _pre-release_ with the same tag (`YY.MMrcX`) from the `YY.MM` branch and a description copied from `CHANGELOG.md`. +4. Create a new GitHub _pre-release_ with the same tag (`vYY.MMrcX`) from the `vYY.MM` branch and a description copied from `CHANGELOG.md`. 5. This release will automatically trigger `publish_pypi.yml` and create a _pre-release_ on PyPI. @@ -48,7 +48,7 @@ Once satisfied with the release candidates - 1. Run `update_version.yml` manually, leaving the `append_to_tag` field blank ("") for an actual release. -2. This will increment the version to `YY.MMrcX` by running `scripts/update_version.py` in the following files - +2. This will increment the version to `vYY.MMrcX` by running `scripts/update_version.py` in the following files - - `pybamm/version.py` - `docs/conf.py` @@ -57,9 +57,9 @@ Once satisfied with the release candidates - - `docs/_static/versions.json` - `CHANGELOG.md` - These changes will be automatically pushed to the existing branch `YY.MM`. + These changes will be automatically pushed to the existing `vYY.MM` branch and a PR from `vvYY.MM` to `develop` will be created (to sync the branches). -3. Next, a PR from `YY.MM` to `main` will be generated that should be merged once all the tests pass. +3. Next, a PR from `vYY.MM` to `main` will be generated that should be merged once all the tests pass. 4. Create a new GitHub _release_ with the same tag from the `main` branch and a description copied from `CHANGELOG.md`. @@ -72,3 +72,8 @@ Some other essential things to check throughout the release process - - If updating our custom vcpkg registory entries [pybamm-team/sundials-vcpkg-registry](https://github.com/pybamm-team/sundials-vcpkg-registry) or [pybamm-team/casadi-vcpkg-registry](https://github.com/pybamm-team/casadi-vcpkg-registry) (used to build Windows wheels), make sure to update the baseline of the registories in vcpkg-configuration.json to the latest commit id. - Update jax and jaxlib to the latest version in `pybamm.util` and `setup.py`, fixing any bugs that arise - Make sure the URLs in `docs/_static/versions.json` are valid +- As the release workflow is initiated by the `release` event, it's important to note that the default `GITHUB_REF` used by `actions/checkout` during the checkout process will correspond to the tag created during the release process. Consequently, the workflows will consistently build PyBaMM based on the commit associated with this tag. Should new commits be introduced to the `vYY.MM` branch, such as those addressing build issues, it becomes necessary to manually update this tag to point to the most recent commit - + ``` + git tag -f + git push origin # can only be carried out by the maintainers + ``` diff --git a/.github/workflows/update_version.yml b/.github/workflows/update_version.yml index 472de06f0e..0d63e68007 100644 --- a/.github/workflows/update_version.yml +++ b/.github/workflows/update_version.yml @@ -63,7 +63,17 @@ jobs: with: message: 'Bump to ${{ env.VERSION }}' + - name: Make a PR from ${{ env.NON_RC_VERSION }} to develop + uses: repo-sync/pull-request@v2 + with: + source_branch: '${{ env.NON_RC_VERSION }}' + destination_branch: "develop" + pr_title: "Sync ${{ env.NON_RC_VERSION }} and develop" + pr_body: "**Merge as soon as possible to avoid potential conflicts.**" + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Make a PR from ${{ env.NON_RC_VERSION }} to main + id: release_pr if: github.event_name == 'workflow_dispatch' && !startsWith(github.event.inputs.append_to_tag, 'rc') uses: repo-sync/pull-request@v2 with: From 9da889278d4adb6cba9ca8cd548987b6744244fc Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Wed, 11 Oct 2023 23:07:14 +0530 Subject: [PATCH 04/14] Merge pull request #3436 from Saransh-cpp/fortnightly-wheels Build wheels on the 1st and 15th of every month --- .github/release_reminder.md | 1 + .github/wheel_failure.md | 6 ++++++ .github/workflows/publish_pypi.yml | 22 +++++++++++++++++++--- 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 .github/wheel_failure.md diff --git a/.github/release_reminder.md b/.github/release_reminder.md index 69696d03ec..94066e80c8 100644 --- a/.github/release_reminder.md +++ b/.github/release_reminder.md @@ -1,5 +1,6 @@ --- title: Create {{ date | date('YY.MM') }} (final or rc0) release +labels: priority:high --- Quarterly reminder to create a - diff --git a/.github/wheel_failure.md b/.github/wheel_failure.md new file mode 100644 index 0000000000..107b4dd6d6 --- /dev/null +++ b/.github/wheel_failure.md @@ -0,0 +1,6 @@ +--- +title: Fortnightly build for wheels failed +labels: priority:high, bug +--- + +The build is failing with the following logs - {{ env.LOGS }} diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index a009828e6f..25fbafc0af 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -3,6 +3,9 @@ name: Build and publish package to PyPI on: release: types: [published] + schedule: + # Run at 10 am UTC on day-of-month 1 and 15. + - cron: "0 10 1,15 * *" workflow_dispatch: inputs: target: @@ -151,6 +154,7 @@ jobs: if-no-files-found: error publish_pypi: + if: github.event_name != 'schedule' name: Upload package to PyPI needs: [build_wheels, build_windows_wheels, build_sdist] runs-on: ubuntu-latest @@ -164,9 +168,7 @@ jobs: mv windows_wheels/* wheels/* sdist/* files/ - name: Publish on PyPI - if: | - github.event.inputs.target == 'pypi' || - (github.event_name == 'push' && github.ref == 'refs/heads/main') + if: github.event.inputs.target == 'pypi' || github.event_name == 'release' uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ @@ -181,3 +183,17 @@ jobs: password: ${{ secrets.TESTPYPI_TOKEN }} packages-dir: files/ repository-url: https://test.pypi.org/legacy/ + + open_failure_issue: + needs: [build_windows_wheels, build_wheels, build_sdist] + name: Open an issue if build fails + if: ${{ always() && contains(needs.*.result, 'failure') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LOGS: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + filename: .github/wheel_failure.md From 211778b101246bb6c8e2843755348757f21aa6f6 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Mon, 16 Oct 2023 15:49:19 -0400 Subject: [PATCH 05/14] Merge pull request #3445 from pybamm-team/issue-3428-rename-exchange #3428 exchange-current density error --- CHANGELOG.md | 2 ++ pybamm/parameters/parameter_values.py | 20 ++++++++++++++++++- .../test_parameters/test_parameter_values.py | 13 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 421d3bfa29..d9583ee31c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 ## Features + - The parameter "Ambient temperature [K]" can now be given as a function of position `(y,z)` and time `t`. The "edge" and "current collector" heat transfer coefficient parameters can also depend on `(y,z)` ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) - Spherical and cylindrical shell domains can now be solved with any boundary conditions ([#3237](https://github.com/pybamm-team/PyBaMM/pull/3237)) - Processed variables now get the spatial variables automatically, allowing plotting of more generic models ([#3234](https://github.com/pybamm-team/PyBaMM/pull/3234)) @@ -44,6 +45,7 @@ ## Breaking changes +- The parameter "Exchange-current density for lithium plating [A.m-2]" has been renamed to "Exchange-current density for lithium metal electrode [A.m-2]" when referring to the lithium plating reaction on the surface of a lithium metal electrode ([#3445](https://github.com/pybamm-team/PyBaMM/pull/3445)) - Dropped support for i686 (32-bit) architectures on GNU/Linux distributions ([#3412](https://github.com/pybamm-team/PyBaMM/pull/3412)) - The class `pybamm.thermal.OneDimensionalX` has been moved to `pybamm.thermal.pouch_cell.OneDimensionalX` to reflect the fact that the model formulation implicitly assumes a pouch cell geometry ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) - The "lumped" thermal option now always used the parameters "Cell cooling surface area [m2]", "Cell volume [m3]" and "Total heat transfer coefficient [W.m-2.K-1]" to compute the cell cooling regardless of the chosen "cell geometry" option. The user must now specify the correct values for these parameters instead of them being calculated based on e.g. a pouch cell. An `OptionWarning` is raised to let users know to update their parameters ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 136d9737aa..e69291035d 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -119,7 +119,25 @@ def create_from_bpx(filename, target_soc=1): return pybamm.ParameterValues(pybamm_dict) def __getitem__(self, key): - return self._dict_items[key] + try: + return self._dict_items[key] + except KeyError as err: + if ( + "Exchange-current density for lithium metal electrode [A.m-2]" + in err.args[0] + and "Exchange-current density for plating [A.m-2]" in self._dict_items + ): + raise KeyError( + "'Exchange-current density for plating [A.m-2]' has been renamed " + "to 'Exchange-current density for lithium metal electrode [A.m-2]' " + "when referring to the reaction at the surface of a lithium metal " + "electrode. This is to avoid confusion with the exchange-current " + "density for the lithium plating reaction in a porous negative " + "electrode. To avoid this error, change your parameter file to use " + "the new name." + ) + else: + raise err def get(self, key, default=None): """Return item corresponding to key if it exists, otherwise return default""" diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py index c6a4831e86..cc1f954686 100644 --- a/tests/unit/test_parameters/test_parameter_values.py +++ b/tests/unit/test_parameters/test_parameter_values.py @@ -977,6 +977,19 @@ def test_evaluate(self): with self.assertRaises(ValueError): parameter_values.evaluate(y) + def test_exchange_current_density_plating(self): + parameter_values = pybamm.ParameterValues( + {"Exchange-current density for plating [A.m-2]": 1} + ) + param = pybamm.Parameter( + "Exchange-current density for lithium metal electrode [A.m-2]" + ) + with self.assertRaisesRegex( + KeyError, + "referring to the reaction at the surface of a lithium metal electrode", + ): + parameter_values.evaluate(param) + if __name__ == "__main__": print("Add -v for more debug output") From 320b2e5e669b6041d2b6212db932fc0b8cf0cb68 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Tue, 17 Oct 2023 09:03:16 +0100 Subject: [PATCH 06/14] Merge pull request #3449 from pybamm-team/i3431-windows-wheels Fix failing windows wheel builds --- CMakeLists.txt | 5 ++++- pybamm/solvers/c_solvers/idaklu/CasadiSolverOpenMP.cpp | 2 +- pybamm/solvers/c_solvers/idaklu/casadi_functions.hpp | 2 +- .../solvers/c_solvers/idaklu/casadi_sundials_functions.cpp | 6 +++--- vcpkg-configuration.json | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38e3d4976c..2a78ee9d62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_POSITION_INDEPENDENT_CODE ON) - +if(NOT MSVC) + # MSVC does not support variable length arrays (vla) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=vla") +endif() # casadi seems to compile without the newer versions of std::string add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) diff --git a/pybamm/solvers/c_solvers/idaklu/CasadiSolverOpenMP.cpp b/pybamm/solvers/c_solvers/idaklu/CasadiSolverOpenMP.cpp index c1c71a967d..ad51eda4e1 100644 --- a/pybamm/solvers/c_solvers/idaklu/CasadiSolverOpenMP.cpp +++ b/pybamm/solvers/c_solvers/idaklu/CasadiSolverOpenMP.cpp @@ -275,7 +275,7 @@ Solution CasadiSolverOpenMP::solve( // set inputs auto p_inputs = inputs.unchecked<2>(); - for (uint i = 0; i < functions->inputs.size(); i++) + for (int i = 0; i < functions->inputs.size(); i++) functions->inputs[i] = p_inputs(i, 0); // set initial conditions diff --git a/pybamm/solvers/c_solvers/idaklu/casadi_functions.hpp b/pybamm/solvers/c_solvers/idaklu/casadi_functions.hpp index d29cb7c961..1a33b957f8 100644 --- a/pybamm/solvers/c_solvers/idaklu/casadi_functions.hpp +++ b/pybamm/solvers/c_solvers/idaklu/casadi_functions.hpp @@ -21,7 +21,7 @@ */ template void csc_csr(const realtype f[], const T1 c[], const T1 r[], realtype nf[], T2 nc[], T2 nr[], int N, int cols) { - int nn[cols+1]; + std::vector nn(cols+1); std::vector rr(N); for (int i=0; i newjac(SUNSparseMatrix_NNZ(JJ)); sunindextype *jac_ptrs = SUNSparseMatrix_IndexPointers(JJ); sunindextype *jac_vals = SUNSparseMatrix_IndexValues(JJ); @@ -229,7 +229,7 @@ int jacobian_casadi(realtype tt, realtype cj, N_Vector yy, N_Vector yp, p_python_functions->jac_times_cjmass.m_arg[2] = p_python_functions->inputs.data(); p_python_functions->jac_times_cjmass.m_arg[3] = &cj; - p_python_functions->jac_times_cjmass.m_res[0] = newjac; + p_python_functions->jac_times_cjmass.m_res[0] = newjac.data(); p_python_functions->jac_times_cjmass(); // convert (casadi's) CSC format to CSR @@ -237,7 +237,7 @@ int jacobian_casadi(realtype tt, realtype cj, N_Vector yy, N_Vector yp, std::remove_pointer_tjac_times_cjmass_rowvals.data())>, std::remove_pointer_t >( - newjac, + newjac.data(), p_python_functions->jac_times_cjmass_rowvals.data(), p_python_functions->jac_times_cjmass_colptrs.data(), jac_data, diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index 27c1b0bcb1..8ab4e738fc 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -7,7 +7,7 @@ { "kind": "git", "repository": "https://github.com/pybamm-team/sundials-vcpkg-registry.git", - "baseline": "2aaffb6bba7bc0b50cb74ddad636832d673851a1", + "baseline": "af9f5e4bc730bf2361c47f809dcfb733e7951faa", "packages": ["sundials"] }, { From a94130768b9b5100e296ac05fad88cded2cb30a0 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Wed, 18 Oct 2023 20:31:24 -0400 Subject: [PATCH 07/14] Merge pull request #3456 from abillscmu/issue-3224-initial_soc make initial soc work with half cell models --- CHANGELOG.md | 1 + .../full_battery_models/base_battery_model.py | 2 +- .../lithium_ion/__init__.py | 5 +- .../lithium_ion/electrode_soh_half_cell.py | 91 +++++++++++++++++++ pybamm/parameters/parameter_values.py | 33 +++++++ pybamm/simulation.py | 19 +++- .../test_lithium_ion/test_electrode_soh.py | 20 ++++ .../test_parameters/test_parameter_values.py | 51 +++++++++++ tests/unit/test_simulation.py | 27 ++++++ 9 files changed, 242 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9583ee31c..008cad125f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Error generated when invalid parameter values are passed ([#3132](https://github.com/pybamm-team/PyBaMM/pull/3132)) - Parameters in `Prada2013` have been updated to better match those given in the paper, which is a 2.3 Ah cell, instead of the mix-and-match with the 1.1 Ah cell from Lain2019 ([#3096](https://github.com/pybamm-team/PyBaMM/pull/3096)) - The `OneDimensionalX` thermal model has been updated to account for edge/tab cooling and account for the current collector volumetric heat capacity. It now gives the correct behaviour compared with a lumped model with the correct total heat transfer coefficient and surface area for cooling. ([#3042](https://github.com/pybamm-team/PyBaMM/pull/3042)) +- Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) ## Optimizations diff --git a/pybamm/models/full_battery_models/base_battery_model.py b/pybamm/models/full_battery_models/base_battery_model.py index 79e135123f..971bd1a880 100644 --- a/pybamm/models/full_battery_models/base_battery_model.py +++ b/pybamm/models/full_battery_models/base_battery_model.py @@ -551,7 +551,7 @@ def __init__(self, extra_options): ) if options["working electrode"] == "negative": raise pybamm.OptionError( - "The 'negative' working elecrtrode option has been removed because " + "The 'negative' working electrode option has been removed because " "the voltage - and therefore the energy stored - would be negative." "Use the 'positive' working electrode option instead and set whatever " "would normally be the negative electrode as the positive electrode." diff --git a/pybamm/models/full_battery_models/lithium_ion/__init__.py b/pybamm/models/full_battery_models/lithium_ion/__init__.py index 95a5059f5a..4afb23f493 100644 --- a/pybamm/models/full_battery_models/lithium_ion/__init__.py +++ b/pybamm/models/full_battery_models/lithium_ion/__init__.py @@ -9,7 +9,10 @@ get_initial_ocps, get_min_max_ocps, ) -from .electrode_soh_half_cell import ElectrodeSOHHalfCell +from .electrode_soh_half_cell import ( + ElectrodeSOHHalfCell, + get_initial_stoichiometry_half_cell +) from .spm import SPM from .spme import SPMe from .dfn import DFN diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index d938fdc769..1e237e73c8 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -56,3 +56,94 @@ def __init__(self, name="Electrode-specific SOH model"): def default_solver(self): # Use AlgebraicSolver as CasadiAlgebraicSolver gives unnecessary warnings return pybamm.AlgebraicSolver() + + +def get_initial_stoichiometry_half_cell( + initial_value, + parameter_values, + param=None, + known_value="cyclable lithium capacity", + options=None, +): + """ + Calculate initial stoichiometry to start off the simulation at a particular + state of charge, given voltage limits, open-circuit potential, etc defined by + parameter_values + + Parameters + ---------- + initial_value : float + Target initial value. + If integer, interpreted as SOC, must be between 0 and 1. + If string e.g. "4 V", interpreted as voltage, + must be between V_min and V_max. + parameter_values : pybamm.ParameterValues + The parameter values to use in the calculation + + Returns + ------- + x + The initial stoichiometry that give the desired initial state of charge + """ + param = pybamm.LithiumIonParameters(options) + x_0, x_100 = get_min_max_stoichiometries(parameter_values) + + if isinstance(initial_value, str) and initial_value.endswith("V"): + V_init = float(initial_value[:-1]) + V_min = parameter_values.evaluate(param.voltage_low_cut) + V_max = parameter_values.evaluate(param.voltage_high_cut) + + if not V_min < V_init < V_max: + raise ValueError( + f"Initial voltage {V_init}V is outside the voltage limits " + f"({V_min}, {V_max})" + ) + + # Solve simple model for initial soc based on target voltage + soc_model = pybamm.BaseModel() + soc = pybamm.Variable("soc") + Up = param.p.prim.U + T_ref = parameter_values["Reference temperature [K]"] + x = x_0 + soc * (x_100 - x_0) + + soc_model.algebraic[soc] = Up(x, T_ref) - V_init + # initial guess for soc linearly interpolates between 0 and 1 + # based on V linearly interpolating between V_max and V_min + soc_model.initial_conditions[soc] = (V_init - V_min) / (V_max - V_min) + soc_model.variables["soc"] = soc + parameter_values.process_model(soc_model) + initial_soc = pybamm.AlgebraicSolver().solve(soc_model, [0])["soc"].data[0] + elif isinstance(initial_value, (int, float)): + initial_soc = initial_value + if not 0 <= initial_soc <= 1: + raise ValueError("Initial SOC should be between 0 and 1") + + else: + raise ValueError( + "Initial value must be a float between 0 and 1, " + "or a string ending in 'V'" + ) + + x = x_0 + initial_soc * (x_100 - x_0) + + return x + + +def get_min_max_stoichiometries( + parameter_values, options={"working electrode": "positive"} +): + """ + Get the minimum and maximum stoichiometries from the parameter values + + Parameters + ---------- + parameter_values : pybamm.ParameterValues + The parameter values to use in the calculation + """ + esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell(options) + param = pybamm.LithiumIonParameters(options) + esoh_sim = pybamm.Simulation(esoh_model, parameter_values=parameter_values) + Q_w = parameter_values.evaluate(param.p.Q_init) + esoh_sol = esoh_sim.solve([0], inputs={"Q_w": Q_w}) + x_0, x_100 = esoh_sol["x_0"].data[0], esoh_sol["x_100"].data[0] + return x_0, x_100 diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index e69291035d..d5f12f362f 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -271,6 +271,39 @@ def update(self, values, check_conflict=False, check_already_exists=True, path=" # reset processed symbols self._processed_symbols = {} + def set_initial_stoichiometry_half_cell( + self, + initial_value, + param=None, + known_value="cyclable lithium capacity", + inplace=True, + options=None, + ): + """ + Set the initial stoichiometry of the working electrode, based on the initial + SOC or voltage + """ + param = param or pybamm.LithiumIonParameters(options) + x = pybamm.lithium_ion.get_initial_stoichiometry_half_cell( + initial_value, self, param=param, known_value=known_value, options=options + ) + if inplace: + parameter_values = self + else: + parameter_values = self.copy() + + c_max = self.evaluate(param.p.prim.c_max) + + parameter_values.update( + { + "Initial concentration in {} electrode [mol.m-3]".format( + options["working electrode"] + ): x + * c_max + } + ) + return parameter_values + def set_initial_stoichiometries( self, initial_value, diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 04a373b436..8805e925d0 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -290,9 +290,10 @@ def update_new_model_events(self, new_model, op): # figure out whether the voltage event is greater than the starting # voltage (charge) or less (discharge) and set the sign of the # event accordingly - if (isinstance(op.value, pybamm.Interpolant) or - isinstance(op.value, pybamm.Multiplication)): - inpt = {"start time":0} + if isinstance(op.value, pybamm.Interpolant) or isinstance( + op.value, pybamm.Multiplication + ): + inpt = {"start time": 0} init_curr = op.value.evaluate(t=0, inputs=inpt).flatten()[0] sign = np.sign(init_curr) else: @@ -373,8 +374,16 @@ def set_initial_soc(self, initial_soc): options = self.model.options param = self._model.param if options["open-circuit potential"] == "MSMR": - self._parameter_values = self._unprocessed_parameter_values.set_initial_ocps( # noqa: E501 - initial_soc, param=param, inplace=False, options=options + self._parameter_values = ( + self._unprocessed_parameter_values.set_initial_ocps( # noqa: E501 + initial_soc, param=param, inplace=False, options=options + ) + ) + elif options["working electrode"] == "positive": + self._parameter_values = ( + self._unprocessed_parameter_values.set_initial_stoichiometry_half_cell( + initial_soc, param=param, inplace=False, options=options + ) ) else: self._parameter_values = ( diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index 49f7a5d855..628017d5d8 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -346,6 +346,9 @@ def test_initial_soc_cell_capacity(self): def test_error(self): parameter_values = pybamm.ParameterValues("Chen2020") + parameter_values_half_cell = pybamm.lithium_ion.DFN( + {"working electrode": "positive"} + ).default_parameter_values with self.assertRaisesRegex( ValueError, "Initial SOC should be between 0 and 1" @@ -358,6 +361,23 @@ def test_error(self): with self.assertRaisesRegex(ValueError, "must be a float"): pybamm.lithium_ion.get_initial_stoichiometries("5 A", parameter_values) + with self.assertRaisesRegex(ValueError, "outside the voltage limits"): + pybamm.lithium_ion.get_initial_stoichiometry_half_cell( + "1 V", parameter_values_half_cell + ) + + with self.assertRaisesRegex(ValueError, "must be a float"): + pybamm.lithium_ion.get_initial_stoichiometry_half_cell( + "5 A", parameter_values_half_cell + ) + + with self.assertRaisesRegex( + ValueError, "Initial SOC should be between 0 and 1" + ): + pybamm.lithium_ion.get_initial_stoichiometry_half_cell( + 2, parameter_values_half_cell + ) + class TestGetInitialOCP(TestCase): def test_get_initial_ocp(self): diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py index cc1f954686..fa6e2398ee 100644 --- a/tests/unit/test_parameters/test_parameter_values.py +++ b/tests/unit/test_parameters/test_parameter_values.py @@ -15,6 +15,7 @@ lico2_ocp_Dualfoil1998, lico2_diffusivity_Dualfoil1998, ) +from pybamm.expression_tree.exceptions import OptionError import casadi @@ -119,6 +120,56 @@ def test_set_initial_stoichiometries(self): y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + def test_set_initial_stoichiometry_half_cell(self): + param = pybamm.lithium_ion.DFN( + {"working electrode": "positive"} + ).default_parameter_values + param = param.set_initial_stoichiometry_half_cell( + 0.4, inplace=False, options={"working electrode": "positive"} + ) + param_0 = param.set_initial_stoichiometry_half_cell( + 0, inplace=False, options={"working electrode": "positive"} + ) + param_100 = param.set_initial_stoichiometry_half_cell( + 1, inplace=False, options={"working electrode": "positive"} + ) + + y = param["Initial concentration in positive electrode [mol.m-3]"] + y_0 = param_0["Initial concentration in positive electrode [mol.m-3]"] + y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] + self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + + # inplace for 100% coverage + param_t = pybamm.lithium_ion.DFN( + {"working electrode": "positive"} + ).default_parameter_values + param_t.set_initial_stoichiometry_half_cell( + 0.4, inplace=True, options={"working electrode": "positive"} + ) + y = param_t["Initial concentration in positive electrode [mol.m-3]"] + param_0 = pybamm.lithium_ion.DFN( + {"working electrode": "positive"} + ).default_parameter_values + param_0.set_initial_stoichiometry_half_cell( + 0, inplace=True, options={"working electrode": "positive"} + ) + y_0 = param_0["Initial concentration in positive electrode [mol.m-3]"] + param_100 = pybamm.lithium_ion.DFN( + {"working electrode": "positive"} + ).default_parameter_values + param_100.set_initial_stoichiometry_half_cell( + 1, inplace=True, options={"working electrode": "positive"} + ) + y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] + self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + + # test error + param = pybamm.ParameterValues("Chen2020") + with self.assertRaisesRegex(OptionError, "working electrode"): + param.set_initial_stoichiometry_half_cell( + 0.1, options={"working electrode": "negative"} + ) + def test_set_initial_ocps(self): options = { "open-circuit potential": "MSMR", diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index f008c7ff2a..c50b03b9ab 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -203,6 +203,33 @@ def test_solve_with_initial_soc(self): sim.build(initial_soc=0.5) self.assertEqual(sim._built_initial_soc, 0.5) + # Test whether initial_soc works with half cell (solve) + options = {"working electrode": "positive"} + model = pybamm.lithium_ion.DFN(options) + sim = pybamm.Simulation(model) + sim.solve([0,1], initial_soc = 0.9) + self.assertEqual(sim._built_initial_soc, 0.9) + + # Test whether initial_soc works with half cell (build) + options = {"working electrode": "positive"} + model = pybamm.lithium_ion.DFN(options) + sim = pybamm.Simulation(model) + sim.build(initial_soc = 0.9) + self.assertEqual(sim._built_initial_soc, 0.9) + + # Test whether initial_soc works with half cell when it is a voltage + model = pybamm.lithium_ion.SPM({"working electrode": "positive"}) + parameter_values = model.default_parameter_values + ucv = parameter_values["Open-circuit voltage at 100% SOC [V]"] + parameter_values["Open-circuit voltage at 100% SOC [V]"] = ucv + 1e-12 + parameter_values["Upper voltage cut-off [V]"] = ucv + 1e-12 + options = {"working electrode": "positive"} + parameter_values["Current function [A]"] = 0.0 + sim = pybamm.Simulation(model, parameter_values=parameter_values) + sol = sim.solve([0,1], initial_soc = "{} V".format(ucv)) + voltage = sol["Terminal voltage [V]"].entries + self.assertAlmostEqual(voltage[0], ucv, places=5) + # test with MSMR model = pybamm.lithium_ion.MSMR({"number of MSMR reactions": ("6", "4")}) param = pybamm.ParameterValues("MSMR_Example") From 37dfe895467b594bc4d0501e4b6f1fa4d6c9a8a0 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Wed, 25 Oct 2023 18:44:24 -0700 Subject: [PATCH 08/14] Merge pull request #3467 from abillscmu/bugfix/initial_soh --- .../lithium_ion/electrode_soh_half_cell.py | 4 ++-- pybamm/simulation.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py index 1e237e73c8..8c22cf2ada 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh_half_cell.py @@ -21,7 +21,7 @@ class ElectrodeSOHHalfCell(pybamm.BaseModel): """ - def __init__(self, name="Electrode-specific SOH model"): + def __init__(self, name="ElectrodeSOH model"): pybamm.citations.register("Mohtat2019") super().__init__(name) param = pybamm.LithiumIonParameters({"working electrode": "positive"}) @@ -140,7 +140,7 @@ def get_min_max_stoichiometries( parameter_values : pybamm.ParameterValues The parameter values to use in the calculation """ - esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell(options) + esoh_model = pybamm.lithium_ion.ElectrodeSOHHalfCell("ElectrodeSOH") param = pybamm.LithiumIonParameters(options) esoh_sim = pybamm.Simulation(esoh_model, parameter_values=parameter_values) Q_w = parameter_values.evaluate(param.p.Q_init) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 8805e925d0..380105d215 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -552,7 +552,7 @@ def solve( ) if ( self.operating_mode == "without experiment" - or self._model.name == "ElectrodeSOH model" + or "ElectrodeSOH" in self._model.name ): if t_eval is None: raise pybamm.SolverError( From e8e2ac5c77eb43b3218120cbcef03a6a4457d25b Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Tue, 31 Oct 2023 14:55:14 +0530 Subject: [PATCH 09/14] Merge pull request #3423 from jsbrittain/jax_gpu JaxSolver fails when using GPU support with no input parameters --- CHANGELOG.md | 4 ++++ docs/conf.py | 1 + pybamm/solvers/jax_solver.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 008cad125f..b02df8ed4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) +## Bug fixes + +- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) + # [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 ## Features diff --git a/docs/conf.py b/docs/conf.py index a96d139b12..abc56b5b63 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -154,6 +154,7 @@ "navbar_end": ["theme-switcher", "navbar-icon-links"], # add Algolia to the persistent navbar, this removes the default search icon "navbar_persistent": "algolia-searchbox", + "navigation_with_keys": False, "use_edit_page_button": True, "pygment_light_style": "xcode", "pygment_dark_style": "monokai", diff --git a/pybamm/solvers/jax_solver.py b/pybamm/solvers/jax_solver.py index b1928fa82a..4c9759008a 100644 --- a/pybamm/solvers/jax_solver.py +++ b/pybamm/solvers/jax_solver.py @@ -215,7 +215,7 @@ def _integrate(self, model, t_eval, inputs=None): y = [] platform = jax.lib.xla_bridge.get_backend().platform.casefold() - if platform.startswith("cpu"): + if len(inputs) <= 1 or platform.startswith("cpu"): # cpu execution runs faster when multithreaded async def solve_model_for_inputs(): async def solve_model_async(inputs_v): From 32ec45853366b49578a789ce0d24dc664fdc45b5 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Tue, 31 Oct 2023 15:21:23 +0530 Subject: [PATCH 10/14] Fix changelog --- CHANGELOG.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b02df8ed4c..1181515a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,4 @@ -# [Unreleased](https://github.com/pybamm-team/PyBaMM/) - -## Bug fixes - -- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) - -# [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 +# [v23.9rc1](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 ## Features @@ -23,6 +17,7 @@ ## Bug fixes +- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) - Fixed a bug where empty lists passed to QuickPlot resulted in an IndexError and did not return a meaningful error message ([#3359](https://github.com/pybamm-team/PyBaMM/pull/3359)) - Fixed a bug where there was a missing thermal conductivity in the thermal pouch cell models ([#3330](https://github.com/pybamm-team/PyBaMM/pull/3330)) - Fixed a bug that caused incorrect results of “{Domain} electrode thickness change [m]” due to the absence of dimension for the variable `electrode_thickness_change`([#3329](https://github.com/pybamm-team/PyBaMM/pull/3329)). From 49cd4a876f8792c225bd7792005701cbcea4ca9a Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Wed, 15 Nov 2023 01:05:56 +0530 Subject: [PATCH 11/14] Merge pull request #3475 from arjxn-py/fix-default-imports Resolve default imports for optional dependencies --- CHANGELOG.md | 17 +++++-- CONTRIBUTING.md | 50 ++++++++++++++----- docs/source/user_guide/installation/index.rst | 1 + pybamm/__init__.py | 2 +- pybamm/citations.py | 9 ++-- pybamm/expression_tree/array.py | 3 +- pybamm/expression_tree/binary_operators.py | 6 ++- pybamm/expression_tree/concatenations.py | 3 +- pybamm/expression_tree/functions.py | 9 ++-- .../expression_tree/independent_variable.py | 5 +- pybamm/expression_tree/operations/latexify.py | 6 ++- pybamm/expression_tree/parameter.py | 4 +- .../printing/sympy_overrides.py | 2 - pybamm/expression_tree/scalar.py | 4 +- pybamm/expression_tree/symbol.py | 9 ++-- pybamm/expression_tree/unary_operators.py | 11 ++-- pybamm/expression_tree/variable.py | 3 +- pybamm/meshes/scikit_fem_submeshes.py | 5 +- pybamm/models/base_model.py | 39 +++++++++++++-- pybamm/plotting/plot.py | 3 +- pybamm/plotting/plot2D.py | 3 +- pybamm/plotting/plot_summary_variables.py | 3 +- pybamm/plotting/plot_voltage_components.py | 4 +- pybamm/plotting/quick_plot.py | 18 ++++--- pybamm/simulation.py | 17 ++++--- .../spatial_methods/scikit_finite_element.py | 10 +++- pybamm/util.py | 23 +++++++++ setup.py | 1 + .../test_binary_operators.py | 3 +- .../test_concatenations.py | 3 +- .../test_expression_tree/test_functions.py | 3 +- .../test_independent_variable.py | 3 +- .../test_operations/test_latexify.py | 4 -- .../test_expression_tree/test_parameter.py | 5 +- .../test_printing/test_sympy_overrides.py | 4 +- .../unit/test_expression_tree/test_symbol.py | 3 +- .../test_unary_operators.py | 11 ++-- .../test_expression_tree/test_variable.py | 3 +- tests/unit/test_util.py | 26 ++++++++++ 39 files changed, 254 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1181515a94..7446e9203c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ +# [Unreleased](https://github.com/pybamm-team/PyBaMM/) + +## Bug fixes + +- Fixed bug in calculation of theoretical energy that made it very slow ([#3506](https://github.com/pybamm-team/PyBaMM/pull/3506)) + # [v23.9rc1](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 +- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) +- Make pybamm importable with minimal dependencies ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044), [#3475](https://github.com/pybamm-team/PyBaMM/pull/3475)) +- Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) + + +# [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 + ## Features - The parameter "Ambient temperature [K]" can now be given as a function of position `(y,z)` and time `t`. The "edge" and "current collector" heat transfer coefficient parameters can also depend on `(y,z)` ([#3257](https://github.com/pybamm-team/PyBaMM/pull/3257)) @@ -17,7 +30,6 @@ ## Bug fixes -- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) - Fixed a bug where empty lists passed to QuickPlot resulted in an IndexError and did not return a meaningful error message ([#3359](https://github.com/pybamm-team/PyBaMM/pull/3359)) - Fixed a bug where there was a missing thermal conductivity in the thermal pouch cell models ([#3330](https://github.com/pybamm-team/PyBaMM/pull/3330)) - Fixed a bug that caused incorrect results of “{Domain} electrode thickness change [m]” due to the absence of dimension for the variable `electrode_thickness_change`([#3329](https://github.com/pybamm-team/PyBaMM/pull/3329)). @@ -36,7 +48,6 @@ - Error generated when invalid parameter values are passed ([#3132](https://github.com/pybamm-team/PyBaMM/pull/3132)) - Parameters in `Prada2013` have been updated to better match those given in the paper, which is a 2.3 Ah cell, instead of the mix-and-match with the 1.1 Ah cell from Lain2019 ([#3096](https://github.com/pybamm-team/PyBaMM/pull/3096)) - The `OneDimensionalX` thermal model has been updated to account for edge/tab cooling and account for the current collector volumetric heat capacity. It now gives the correct behaviour compared with a lumped model with the correct total heat transfer coefficient and surface area for cooling. ([#3042](https://github.com/pybamm-team/PyBaMM/pull/3042)) -- Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) ## Optimizations @@ -56,7 +67,7 @@ - Added option to use an empirical hysteresis model for the diffusivity and exchange-current density ([#3194](https://github.com/pybamm-team/PyBaMM/pull/3194)) - Double-layer capacity can now be provided as a function of temperature ([#3174](https://github.com/pybamm-team/PyBaMM/pull/3174)) - `pybamm_install_jax` is deprecated. It is now replaced with `pip install pybamm[jax]` ([#3163](https://github.com/pybamm-team/PyBaMM/pull/3163)) -- PyBaMM now has optional dependencies that can be installed with the pattern `pip install pybamm[option]` e.g. `pybamm[plot]` ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044)) +- PyBaMM now has optional dependencies that can be installed with the pattern `pip install pybamm[option]` e.g. `pybamm[plot]` ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044), [#3475](https://github.com/pybamm-team/PyBaMM/pull/3475)) # [v23.5](https://github.com/pybamm-team/PyBaMM/tree/v23.5) - 2023-06-18 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6aa6987c2..ff5de8eceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,21 +100,52 @@ On the other hand... We _do_ want to compare several tools, to generate document Only 'core pybamm' is installed by default. The others have to be specified explicitly when running the installation command. -### Matplotlib +### Managing Optional Dependencies and Their Imports -We use Matplotlib in PyBaMM, but with two caveats: +PyBaMM utilizes optional dependencies to allow users to choose which additional libraries they want to use. Managing these optional dependencies and their imports is essential to provide flexibility to PyBaMM users. -First, Matplotlib should only be used in plotting methods, and these should _never_ be called by other PyBaMM methods. So users who don't like Matplotlib will not be forced to use it in any way. Use in notebooks is OK and encouraged. +PyBaMM provides a utility function `have_optional_dependency`, to check for the availability of optional dependencies within methods. This function can be used to conditionally import optional dependencies only if they are available. Here's how to use it: -Second, Matplotlib should never be imported at the module level, but always inside methods. For example: +Optional dependencies should never be imported at the module level, but always inside methods. For example: ``` -def plot_great_things(self, x, y, z): - import matplotlib.pyplot as pl +def use_pybtex(x,y,z): + pybtex = have_optional_dependency("pybtex") ... ``` -This allows people to (1) use PyBaMM without ever importing Matplotlib and (2) configure Matplotlib's back-end in their scripts, which _must_ be done before e.g. `pyplot` is first imported. +While importing a specific module instead of an entire package/library: + +```python +def use_parse_file(x, y, z): + parse_file = have_optional_dependency("pybtex.database", "parse_file") + ... +``` + +This allows people to (1) use PyBaMM without importing optional dependencies by default and (2) configure module-dependent functionalities in their scripts, which _must_ be done before e.g. `print_citations` method is first imported. + +**Writing Tests for Optional Dependencies** + +Whenever a new optional dependency is added for optional functionality, it is recommended to write a corresponding unit test in `test_util.py`. This ensures that an error is raised upon the absence of said dependency. Here's an example: + +```python +from tests import TestCase +import pybamm + + +class TestUtil(TestCase): + def test_optional_dependency(self): + # Test that an error is raised when pybtex is not available + with self.assertRaisesRegex( + ModuleNotFoundError, "Optional dependency pybtex is not available" + ): + sys.modules["pybtex"] = None + pybamm.function_using_pybtex(x, y, z) + + # Test that the function works when pybtex is available + sys.modules["pybtex"] = pybamm.util.have_optional_dependency("pybtex") + pybamm.function_using_pybtex(x, y, z) +``` ## Testing @@ -266,7 +297,6 @@ This also means that, if you can't fix the bug yourself, it will be much easier ``` This will start the debugger at the point where the `ValueError` was raised, and allow you to investigate further. Sometimes, it is more informative to put the try-except block further up the call stack than exactly where the error is raised. - 2. Warnings. If functions are raising warnings instead of errors, it can be hard to pinpoint where this is coming from. Here, you can use the `warnings` module to convert warnings to errors: ```python @@ -276,7 +306,6 @@ This also means that, if you can't fix the bug yourself, it will be much easier ``` Then you can use a try-except block, as in a., but with, for example, `RuntimeWarning` instead of `ValueError`. - 3. Stepping through the expression tree. Most calls in PyBaMM are operations on [expression trees](https://github.com/pybamm-team/PyBaMM/blob/develop/docs/source/examples/notebooks/expression_tree/expression-tree.ipynb). To view an expression tree in ipython, you can use the `render` command: ```python @@ -284,11 +313,8 @@ This also means that, if you can't fix the bug yourself, it will be much easier ``` You can then step through the expression tree, using the `children` attribute, to pinpoint exactly where a bug is coming from. For example, if `expression_tree.jac(y)` is failing, you can check `expression_tree.children[0].jac(y)`, then `expression_tree.children[0].children[0].jac(y)`, etc. - 3. To isolate whether a bug is in a model, its Jacobian or its simplified version, you can set the `use_jacobian` and/or `use_simplify` attributes of the model to `False` (they are both `True` by default for most models). - 4. If a model isn't giving the answer you expect, you can try comparing it to other models. For example, you can investigate parameter limits in which two models should give the same answer by setting some parameters to be small or zero. The `StandardOutputComparison` class can be used to compare some standard outputs from battery models. - 5. To get more information about what is going on under the hood, and hence understand what is causing the bug, you can set the [logging](https://realpython.com/python-logging/) level to `DEBUG` by adding the following line to your test or script: ```python3 diff --git a/docs/source/user_guide/installation/index.rst b/docs/source/user_guide/installation/index.rst index 6338323e79..2cf61093be 100644 --- a/docs/source/user_guide/installation/index.rst +++ b/docs/source/user_guide/installation/index.rst @@ -66,6 +66,7 @@ Package Minimum support `SciPy `__ 2.8.2 `CasADi `__ 3.6.0 `Xarray `__ 2023.04.0 +`Anytree `__ 2.4.3 ================================================================ ========================== .. _install.optional_dependencies: diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 9aa1ca79a0..07d8a1c0ea 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -47,13 +47,13 @@ get_parameters_filepath, have_jax, install_jax, + have_optional_dependency, is_jax_compatible, get_git_commit_info, ) from .logger import logger, set_logging_level, get_new_logger from .settings import settings from .citations import Citations, citations, print_citations - # # Classes for the Expression Tree # diff --git a/pybamm/citations.py b/pybamm/citations.py index da619062e0..b72262989b 100644 --- a/pybamm/citations.py +++ b/pybamm/citations.py @@ -6,10 +6,8 @@ import pybamm import os import warnings -import pybtex from sys import _getframe -from pybtex.database import parse_file, parse_string, Entry -from pybtex.scanner import PybtexError +from pybamm.util import have_optional_dependency class Citations: @@ -76,6 +74,7 @@ def read_citations(self): """Reads the citations in `pybamm.CITATIONS.bib`. Other works can be cited by passing a BibTeX citation to :meth:`register`. """ + parse_file = have_optional_dependency("pybtex.database", "parse_file") citations_file = os.path.join(pybamm.root_dir(), "pybamm", "CITATIONS.bib") bib_data = parse_file(citations_file, bib_format="bibtex") for key, entry in bib_data.entries.items(): @@ -86,6 +85,7 @@ def _add_citation(self, key, entry): previous entry is overwritten """ + Entry = have_optional_dependency("pybtex.database", "Entry") # Check input types are correct if not isinstance(key, str) or not isinstance(entry, Entry): raise TypeError() @@ -151,6 +151,8 @@ def _parse_citation(self, key): key: str A BibTeX formatted citation """ + PybtexError = have_optional_dependency("pybtex.scanner", "PybtexError") + parse_string = have_optional_dependency("pybtex.database", "parse_string") try: # Parse string as a bibtex citation, and check that a citation was found bib_data = parse_string(key, bib_format="bibtex") @@ -217,6 +219,7 @@ def print(self, filename=None, output_format="text", verbose=False): """ # Parse citations that were not known keys at registration, but do not # fail if they cannot be parsed + pybtex = have_optional_dependency("pybtex") try: for key in self._unknown_citations: self._parse_citation(key) diff --git a/pybamm/expression_tree/array.py b/pybamm/expression_tree/array.py index a9141041b3..2736886d95 100644 --- a/pybamm/expression_tree/array.py +++ b/pybamm/expression_tree/array.py @@ -2,10 +2,10 @@ # NumpyArray class # import numpy as np -import sympy from scipy.sparse import csr_matrix, issparse import pybamm +from pybamm.util import have_optional_dependency class Array(pybamm.Symbol): @@ -125,6 +125,7 @@ def is_constant(self): def to_equation(self): """Returns the value returned by the node when evaluated.""" + sympy = have_optional_dependency("sympy") entries_list = self.entries.tolist() return sympy.Array(entries_list) diff --git a/pybamm/expression_tree/binary_operators.py b/pybamm/expression_tree/binary_operators.py index 749384e9bc..9fc6d2642e 100644 --- a/pybamm/expression_tree/binary_operators.py +++ b/pybamm/expression_tree/binary_operators.py @@ -4,11 +4,11 @@ import numbers import numpy as np -import sympy from scipy.sparse import csr_matrix, issparse import functools import pybamm +from pybamm.util import have_optional_dependency def _preprocess_binary(left, right): @@ -147,6 +147,7 @@ def _sympy_operator(self, left, right): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: @@ -323,6 +324,7 @@ def _binary_evaluate(self, left, right): def _sympy_operator(self, left, right): """Override :meth:`pybamm.BinaryOperator._sympy_operator`""" + sympy = have_optional_dependency("sympy") left = sympy.Matrix(left) right = sympy.Matrix(right) return left * right @@ -626,6 +628,7 @@ def _binary_new_copy(self, left, right): def _sympy_operator(self, left, right): """Override :meth:`pybamm.BinaryOperator._sympy_operator`""" + sympy = have_optional_dependency("sympy") return sympy.Min(left, right) @@ -662,6 +665,7 @@ def _binary_new_copy(self, left, right): def _sympy_operator(self, left, right): """Override :meth:`pybamm.BinaryOperator._sympy_operator`""" + sympy = have_optional_dependency("sympy") return sympy.Max(left, right) diff --git a/pybamm/expression_tree/concatenations.py b/pybamm/expression_tree/concatenations.py index 2185a0fad6..1c82aff122 100644 --- a/pybamm/expression_tree/concatenations.py +++ b/pybamm/expression_tree/concatenations.py @@ -5,10 +5,10 @@ from collections import defaultdict import numpy as np -import sympy from scipy.sparse import issparse, vstack import pybamm +from pybamm.util import have_optional_dependency class Concatenation(pybamm.Symbol): @@ -135,6 +135,7 @@ def is_constant(self): def _sympy_operator(self, *children): """Apply appropriate SymPy operators.""" + sympy = have_optional_dependency("sympy") self.concat_latex = tuple(map(sympy.latex, children)) if self.print_name is not None: diff --git a/pybamm/expression_tree/functions.py b/pybamm/expression_tree/functions.py index 80c2848ad9..0c7e98b508 100644 --- a/pybamm/expression_tree/functions.py +++ b/pybamm/expression_tree/functions.py @@ -3,13 +3,11 @@ # import numbers -import autograd import numpy as np -import sympy from scipy import special import pybamm - +from pybamm.util import have_optional_dependency class Function(pybamm.Symbol): """ @@ -96,6 +94,7 @@ def _function_diff(self, children, idx): Derivative with respect to child number 'idx'. See :meth:`pybamm.Symbol._diff()`. """ + autograd = have_optional_dependency("autograd") # Store differentiated function, needed in case we want to convert to CasADi if self.derivative == "autograd": return Function( @@ -202,6 +201,7 @@ def _sympy_operator(self, child): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: @@ -250,6 +250,7 @@ def _function_new_copy(self, children): def _sympy_operator(self, child): """Apply appropriate SymPy operators.""" + sympy = have_optional_dependency("sympy") class_name = self.__class__.__name__.lower() sympy_function = getattr(sympy, class_name) return sympy_function(child) @@ -267,6 +268,7 @@ def _function_diff(self, children, idx): def _sympy_operator(self, child): """Override :meth:`pybamm.Function._sympy_operator`""" + sympy = have_optional_dependency("sympy") return sympy.asinh(child) @@ -287,6 +289,7 @@ def _function_diff(self, children, idx): def _sympy_operator(self, child): """Override :meth:`pybamm.Function._sympy_operator`""" + sympy = have_optional_dependency("sympy") return sympy.atan(child) diff --git a/pybamm/expression_tree/independent_variable.py b/pybamm/expression_tree/independent_variable.py index efeb73f8bc..2f30da9a5e 100644 --- a/pybamm/expression_tree/independent_variable.py +++ b/pybamm/expression_tree/independent_variable.py @@ -1,9 +1,8 @@ # # IndependentVariable class # -import sympy - import pybamm +from pybamm.util import have_optional_dependency KNOWN_COORD_SYS = ["cartesian", "cylindrical polar", "spherical polar"] @@ -44,6 +43,7 @@ def _jac(self, variable): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: @@ -77,6 +77,7 @@ def _evaluate_for_shape(self): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") return sympy.Symbol("t") diff --git a/pybamm/expression_tree/operations/latexify.py b/pybamm/expression_tree/operations/latexify.py index 67e0199656..9f2949069e 100644 --- a/pybamm/expression_tree/operations/latexify.py +++ b/pybamm/expression_tree/operations/latexify.py @@ -5,10 +5,9 @@ import re import warnings -import sympy - import pybamm from pybamm.expression_tree.printing.sympy_overrides import custom_print_func +from pybamm.util import have_optional_dependency def get_rng_min_max_name(rng, min_or_max): @@ -88,6 +87,7 @@ def _get_bcs_displays(self, var): Returns a list of boundary condition equations with ranges in front of the equations. """ + sympy = have_optional_dependency("sympy") bcs_eqn_list = [] bcs = self.model.boundary_conditions.get(var, None) @@ -118,6 +118,7 @@ def _get_bcs_displays(self, var): def _get_param_var(self, node): """Returns a list of parameters and a list of variables.""" + sympy = have_optional_dependency("sympy") param_list = [] var_list = [] dfs_nodes = [node] @@ -160,6 +161,7 @@ def _get_param_var(self, node): return param_list, var_list def latexify(self, output_variables=None): + sympy = have_optional_dependency("sympy") # Voltage is the default output variable if it exists if output_variables is None: if "Voltage [V]" in self.model.variables: diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py index 10addae464..eebe77ad2f 100644 --- a/pybamm/expression_tree/parameter.py +++ b/pybamm/expression_tree/parameter.py @@ -5,9 +5,9 @@ import sys import numpy as np -import sympy import pybamm +from pybamm.util import have_optional_dependency class Parameter(pybamm.Symbol): @@ -44,6 +44,7 @@ def is_constant(self): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: @@ -217,6 +218,7 @@ def _evaluate_for_shape(self): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: diff --git a/pybamm/expression_tree/printing/sympy_overrides.py b/pybamm/expression_tree/printing/sympy_overrides.py index a96aa19729..1898822ea8 100644 --- a/pybamm/expression_tree/printing/sympy_overrides.py +++ b/pybamm/expression_tree/printing/sympy_overrides.py @@ -8,11 +8,9 @@ class CustomPrint(LatexPrinter): """Override SymPy methods to match PyBaMM's requirements""" - def _print_Derivative(self, expr): """Override :meth:`sympy.printing.latex.LatexPrinter._print_Derivative`""" eqn = super()._print_Derivative(expr) - if getattr(expr, "force_partial", False) and "partial" not in eqn: var1, var2 = re.findall(r"^\\frac{(\w+)}{(\w+) .+", eqn)[0] eqn = eqn.replace(var1, "\partial").replace(var2, "\partial") diff --git a/pybamm/expression_tree/scalar.py b/pybamm/expression_tree/scalar.py index 3149bf7bee..0209c02a8e 100644 --- a/pybamm/expression_tree/scalar.py +++ b/pybamm/expression_tree/scalar.py @@ -2,10 +2,9 @@ # Scalar class # import numpy as np -import sympy import pybamm - +from pybamm.util import have_optional_dependency class Scalar(pybamm.Symbol): """ @@ -70,6 +69,7 @@ def is_constant(self): def to_equation(self): """Returns the value returned by the node when evaluated.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: diff --git a/pybamm/expression_tree/symbol.py b/pybamm/expression_tree/symbol.py index 5d28884ed5..8f1608e7ba 100644 --- a/pybamm/expression_tree/symbol.py +++ b/pybamm/expression_tree/symbol.py @@ -3,14 +3,12 @@ # import numbers -import anytree import numpy as np -import sympy -from anytree.exporter import DotExporter from scipy.sparse import csr_matrix, issparse from functools import lru_cache, cached_property import pybamm +from pybamm.util import have_optional_dependency from pybamm.expression_tree.printing.print_name import prettify_print_name DOMAIN_LEVELS = ["primary", "secondary", "tertiary", "quaternary"] @@ -442,6 +440,7 @@ def render(self): # pragma: no cover """ Print out a visual representation of the tree (this node and its children) """ + anytree = have_optional_dependency("anytree") for pre, _, node in anytree.RenderTree(self): if isinstance(node, pybamm.Scalar) and node.name != str(node.value): print("{}{} = {}".format(pre, node.name, node.value)) @@ -460,6 +459,7 @@ def visualise(self, filename): filename to output, must end in ".png" """ + DotExporter = have_optional_dependency("anytree.exporter", "DotExporter") # check that filename ends in .png. if filename[-4:] != ".png": raise ValueError("filename should end in .png") @@ -479,6 +479,7 @@ def relabel_tree(self, symbol, counter): Finds all children of a symbol and assigns them a new id so that they can be visualised properly using the graphviz output """ + anytree = have_optional_dependency("anytree") name = symbol.name if name == "div": name = "∇⋅" @@ -522,6 +523,7 @@ def pre_order(self): a b """ + anytree = have_optional_dependency("anytree") return anytree.PreOrderIter(self) def __str__(self): @@ -984,4 +986,5 @@ def print_name(self, name): self._print_name = prettify_print_name(name) def to_equation(self): + sympy = have_optional_dependency("sympy") return sympy.Symbol(str(self.name)) diff --git a/pybamm/expression_tree/unary_operators.py b/pybamm/expression_tree/unary_operators.py index 7f9c45775c..e7412e381b 100644 --- a/pybamm/expression_tree/unary_operators.py +++ b/pybamm/expression_tree/unary_operators.py @@ -4,11 +4,9 @@ import numbers import numpy as np -import sympy from scipy.sparse import csr_matrix, issparse -from sympy.vector.operators import Divergence as sympy_Divergence -from sympy.vector.operators import Gradient as sympy_Gradient import pybamm +from pybamm.util import have_optional_dependency class UnaryOperator(pybamm.Symbol): @@ -83,6 +81,7 @@ def _sympy_operator(self, child): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: @@ -368,6 +367,7 @@ def _unary_new_copy(self, child): def _sympy_operator(self, child): """Override :meth:`pybamm.UnaryOperator._sympy_operator`""" + sympy_Gradient = have_optional_dependency("sympy.vector.operators", "Gradient") return sympy_Gradient(child) @@ -403,6 +403,9 @@ def _unary_new_copy(self, child): def _sympy_operator(self, child): """Override :meth:`pybamm.UnaryOperator._sympy_operator`""" + sympy_Divergence = have_optional_dependency( + "sympy.vector.operators", "Divergence" + ) return sympy_Divergence(child) @@ -579,6 +582,7 @@ def _evaluates_on_edges(self, dimension): def _sympy_operator(self, child): """Override :meth:`pybamm.UnaryOperator._sympy_operator`""" + sympy = have_optional_dependency("sympy") return sympy.Integral(child, sympy.Symbol("xn")) @@ -889,6 +893,7 @@ def _unary_new_copy(self, child): def _sympy_operator(self, child): """Override :meth:`pybamm.UnaryOperator._sympy_operator`""" + sympy = have_optional_dependency("sympy") if ( self.child.domain[0] in ["negative particle", "positive particle"] and self.side == "right" diff --git a/pybamm/expression_tree/variable.py b/pybamm/expression_tree/variable.py index f9f7d94efc..0d1e1fd424 100644 --- a/pybamm/expression_tree/variable.py +++ b/pybamm/expression_tree/variable.py @@ -3,9 +3,9 @@ # import numpy as np -import sympy import numbers import pybamm +from pybamm.util import have_optional_dependency class VariableBase(pybamm.Symbol): @@ -124,6 +124,7 @@ def _evaluate_for_shape(self): def to_equation(self): """Convert the node and its subtree into a SymPy equation.""" + sympy = have_optional_dependency("sympy") if self.print_name is not None: return sympy.Symbol(self.print_name) else: diff --git a/pybamm/meshes/scikit_fem_submeshes.py b/pybamm/meshes/scikit_fem_submeshes.py index f25dce80b1..23c024dbbb 100644 --- a/pybamm/meshes/scikit_fem_submeshes.py +++ b/pybamm/meshes/scikit_fem_submeshes.py @@ -3,10 +3,10 @@ # import pybamm from .meshes import SubMesh - -import skfem import numpy as np +from pybamm.util import have_optional_dependency + class ScikitSubMesh2D(SubMesh): """ @@ -27,6 +27,7 @@ class ScikitSubMesh2D(SubMesh): """ def __init__(self, edges, coord_sys, tabs): + skfem = have_optional_dependency("skfem") self.edges = edges self.nodes = dict.fromkeys(["y", "z"]) for var in self.nodes.keys(): diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 41192dbe1f..08890757b7 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -9,7 +9,7 @@ import numpy as np import pybamm -from pybamm.expression_tree.operations.latexify import Latexify +from pybamm.util import have_optional_dependency class BaseModel: @@ -1055,14 +1055,43 @@ def generate( C.generate() def latexify(self, filename=None, newline=True, output_variables=None): - # For docstring, see pybamm.expression_tree.operations.latexify.Latexify + """ + Converts all model equations in latex. + + Parameters + ---------- + filename: str (optional) + Accepted file formats - any image format, pdf and tex + Default is None, When None returns all model equations in latex + If not None, returns all model equations in given file format. + + newline: bool (optional) + Default is True, If True, returns every equation in a new line. + If False, returns the list of all the equations. + + Load model + >>> model = pybamm.lithium_ion.SPM() + + This will returns all model equations in png + >>> model.latexify("equations.png") + + This will return all the model equations in latex + >>> model.latexify() + + This will return the list of all the model equations + >>> model.latexify(newline=False) + + This will return first five model equations + >>> model.latexify(newline=False)[1:5] + """ + sympy = have_optional_dependency("sympy") + if sympy: + from pybamm.expression_tree.operations.latexify import Latexify + return Latexify(self, filename, newline).latexify( output_variables=output_variables ) - # Set :meth:`latexify` docstring from :class:`Latexify` - latexify.__doc__ = Latexify.__doc__ - def process_parameters_and_discretise(self, symbol, parameter_values, disc): """ Process parameters and discretise a symbol using supplied parameter values diff --git a/pybamm/plotting/plot.py b/pybamm/plotting/plot.py index 19aa9dc5e0..88c8dfe442 100644 --- a/pybamm/plotting/plot.py +++ b/pybamm/plotting/plot.py @@ -3,6 +3,7 @@ # import pybamm from .quick_plot import ax_min, ax_max +from pybamm.util import have_optional_dependency def plot(x, y, ax=None, testing=False, **kwargs): @@ -25,7 +26,7 @@ def plot(x, y, ax=None, testing=False, **kwargs): Keyword arguments, passed to plt.plot """ - import matplotlib.pyplot as plt + plt = have_optional_dependency("matplotlib.pyplot") if not isinstance(x, pybamm.Array): raise TypeError("x must be 'pybamm.Array'") diff --git a/pybamm/plotting/plot2D.py b/pybamm/plotting/plot2D.py index 80bb5d0ee2..d4f6d31e3a 100644 --- a/pybamm/plotting/plot2D.py +++ b/pybamm/plotting/plot2D.py @@ -3,6 +3,7 @@ # import pybamm from .quick_plot import ax_min, ax_max +from pybamm.util import have_optional_dependency def plot2D(x, y, z, ax=None, testing=False, **kwargs): @@ -25,7 +26,7 @@ def plot2D(x, y, z, ax=None, testing=False, **kwargs): Whether to actually make the plot (turned off for unit tests) """ - import matplotlib.pyplot as plt + plt = have_optional_dependency("matplotlib.pyplot") if not isinstance(x, pybamm.Array): raise TypeError("x must be 'pybamm.Array'") diff --git a/pybamm/plotting/plot_summary_variables.py b/pybamm/plotting/plot_summary_variables.py index 6fe71518db..e50f38fddf 100644 --- a/pybamm/plotting/plot_summary_variables.py +++ b/pybamm/plotting/plot_summary_variables.py @@ -3,6 +3,7 @@ # import numpy as np import pybamm +from pybamm.util import have_optional_dependency def plot_summary_variables( @@ -25,7 +26,7 @@ def plot_summary_variables( Keyword arguments, passed to plt.subplots. """ - import matplotlib.pyplot as plt + plt = have_optional_dependency("matplotlib.pyplot") if isinstance(solutions, pybamm.Solution): solutions = [solutions] diff --git a/pybamm/plotting/plot_voltage_components.py b/pybamm/plotting/plot_voltage_components.py index ad0e9a8b71..a681094bea 100644 --- a/pybamm/plotting/plot_voltage_components.py +++ b/pybamm/plotting/plot_voltage_components.py @@ -3,6 +3,8 @@ # import numpy as np +from pybamm.util import have_optional_dependency + def plot_voltage_components( solution, @@ -32,7 +34,7 @@ def plot_voltage_components( Keyword arguments, passed to ax.fill_between """ - import matplotlib.pyplot as plt + plt = have_optional_dependency("matplotlib.pyplot") # Set a default value for alpha, the opacity kwargs_fill = {"alpha": 0.6, **kwargs_fill} diff --git a/pybamm/plotting/quick_plot.py b/pybamm/plotting/quick_plot.py index 5e9c9ef941..ff657ee375 100644 --- a/pybamm/plotting/quick_plot.py +++ b/pybamm/plotting/quick_plot.py @@ -5,6 +5,7 @@ import numpy as np import pybamm from collections import defaultdict +from pybamm.util import have_optional_dependency class LoopList(list): @@ -46,7 +47,7 @@ def split_long_string(title, max_words=None): def close_plots(): """Close all open figures""" - import matplotlib.pyplot as plt + plt = have_optional_dependency("matplotlib.pyplot") plt.close("all") @@ -469,9 +470,10 @@ def plot(self, t, dynamic=False): Dimensional time (in 'time_units') at which to plot. """ - import matplotlib.pyplot as plt - import matplotlib.gridspec as gridspec - from matplotlib import cm, colors + plt = have_optional_dependency("matplotlib.pyplot") + gridspec = have_optional_dependency("matplotlib.gridspec") + cm = have_optional_dependency("matplotlib", "cm") + colors = have_optional_dependency("matplotlib", "colors") t_in_seconds = t * self.time_scaling_factor self.fig = plt.figure(figsize=self.figsize) @@ -668,8 +670,8 @@ def dynamic_plot(self, testing=False, step=None): continuous_update=False, ) else: - import matplotlib.pyplot as plt - from matplotlib.widgets import Slider + plt = have_optional_dependency("matplotlib.pyplot") + Slider = have_optional_dependency("matplotlib.widgets", "Slider") # create an initial plot at time self.min_t self.plot(self.min_t, dynamic=True) @@ -773,8 +775,8 @@ def create_gif(self, number_of_images=80, duration=0.1, output_filename="plot.gi Name of the generated GIF file. """ - import imageio.v2 as imageio - import matplotlib.pyplot as plt + imageio = have_optional_dependency("imageio.v2") + plt = have_optional_dependency("matplotlib.pyplot") # time stamps at which the images/plots will be created time_array = np.linspace(self.min_t, self.max_t, num=number_of_images) diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 380105d215..42bda08e31 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -8,7 +8,7 @@ import sys from functools import lru_cache from datetime import timedelta -import tqdm +from pybamm.util import have_optional_dependency def is_notebook(): @@ -726,13 +726,18 @@ def solve( # Update _solution self._solution = current_solution - for cycle_num, cycle_length in enumerate( - # tqdm is the progress bar. - tqdm.tqdm( + # check if a user has tqdm installed + if showprogress: + tqdm = have_optional_dependency("tqdm") + cycle_lengths = tqdm.tqdm( self.experiment.cycle_lengths, - disable=(not showprogress), desc="Cycling", - ), + ) + else: + cycle_lengths = self.experiment.cycle_lengths + + for cycle_num, cycle_length in enumerate( + cycle_lengths, start=1, ): logs["cycle number"] = ( diff --git a/pybamm/spatial_methods/scikit_finite_element.py b/pybamm/spatial_methods/scikit_finite_element.py index 0f0a42bbcb..2d51e16c32 100644 --- a/pybamm/spatial_methods/scikit_finite_element.py +++ b/pybamm/spatial_methods/scikit_finite_element.py @@ -6,7 +6,8 @@ from scipy.sparse import csr_matrix, csc_matrix from scipy.sparse.linalg import inv import numpy as np -import skfem + +from pybamm.util import have_optional_dependency class ScikitFiniteElement(pybamm.SpatialMethod): @@ -87,6 +88,7 @@ def gradient(self, symbol, discretised_symbol, boundary_conditions): to the y-component of the gradient and the second column corresponds to the z component of the gradient. """ + skfem = have_optional_dependency("skfem") domain = symbol.domain[0] mesh = self.mesh[domain] @@ -142,6 +144,7 @@ def gradient_matrix(self, symbol, boundary_conditions): :class:`pybamm.Matrix` The (sparse) finite element gradient matrix for the domain """ + skfem = have_optional_dependency("skfem") # get primary domain mesh domain = symbol.domain[0] mesh = self.mesh[domain] @@ -187,6 +190,7 @@ def laplacian(self, symbol, discretised_symbol, boundary_conditions): Contains the result of acting the discretised gradient on the child discretised_symbol """ + skfem = have_optional_dependency("skfem") domain = symbol.domain[0] mesh = self.mesh[domain] @@ -258,6 +262,7 @@ def stiffness_matrix(self, symbol, boundary_conditions): :class:`pybamm.Matrix` The (sparse) finite element stiffness matrix for the domain """ + skfem = have_optional_dependency("skfem") # get primary domain mesh domain = symbol.domain[0] mesh = self.mesh[domain] @@ -320,6 +325,7 @@ def definite_integral_matrix(self, child, vector_type="row"): :class:`pybamm.Matrix` The finite element integral vector for the domain """ + skfem = have_optional_dependency("skfem") # get primary domain mesh domain = child.domain[0] mesh = self.mesh[domain] @@ -381,6 +387,7 @@ def boundary_integral_vector(self, domain, region): :class:`pybamm.Matrix` The finite element integral vector for the domain """ + skfem = have_optional_dependency("skfem") # get primary domain mesh mesh = self.mesh[domain[0]] @@ -498,6 +505,7 @@ def assemble_mass_form(self, symbol, boundary_conditions, region="interior"): :class:`pybamm.Matrix` The (sparse) mass matrix for the spatial method. """ + skfem = have_optional_dependency("skfem") # get primary domain mesh domain = symbol.domain[0] mesh = self.mesh[domain] diff --git a/pybamm/util.py b/pybamm/util.py index 562352bfac..4822396e92 100644 --- a/pybamm/util.py +++ b/pybamm/util.py @@ -345,3 +345,26 @@ def install_jax(arguments=None): # pragma: no cover f"jaxlib>={JAXLIB_VERSION}", ] ) + +# https://docs.pybamm.org/en/latest/source/user_guide/contributing.html#managing-optional-dependencies-and-their-imports +def have_optional_dependency(module_name, attribute=None): + err_msg = f"Optional dependency {module_name} is not available. See https://docs.pybamm.org/en/latest/source/user_guide/installation/index.html#optional-dependencies for more details." # noqa: E501 + try: + # Attempt to import the specified module + module = importlib.import_module(module_name) + + if attribute: + # If an attribute is specified, check if it's available + if hasattr(module, attribute): + imported_attribute = getattr(module, attribute) + return imported_attribute # Return the imported attribute + else: + # Raise an ModuleNotFoundError if the attribute is not available + raise ModuleNotFoundError(err_msg) # pragma: no cover + else: + # Return the entire module if no attribute is specified + return module + + except ModuleNotFoundError: + # Raise an ModuleNotFoundError if the module or attribute is not available + raise ModuleNotFoundError(err_msg) diff --git a/setup.py b/setup.py index f6fd37f75c..fca5b83de8 100644 --- a/setup.py +++ b/setup.py @@ -207,6 +207,7 @@ def compile_KLU(): "scipy>=1.3", "casadi>=3.6.0", "xarray", + "anytree>=2.4.3", ], extras_require={ "docs": [ diff --git a/tests/unit/test_expression_tree/test_binary_operators.py b/tests/unit/test_expression_tree/test_binary_operators.py index 6acd7c41b0..225f8e93c9 100644 --- a/tests/unit/test_expression_tree/test_binary_operators.py +++ b/tests/unit/test_expression_tree/test_binary_operators.py @@ -5,10 +5,10 @@ import unittest import numpy as np -import sympy from scipy.sparse import coo_matrix import pybamm +from pybamm.util import have_optional_dependency class TestBinaryOperators(TestCase): @@ -746,6 +746,7 @@ def test_inner_simplifications(self): self.assertEqual(pybamm.inner(a3, a3).evaluate(), 9) def test_to_equation(self): + sympy = have_optional_dependency("sympy") # Test print_name pybamm.Addition.print_name = "test" self.assertEqual(pybamm.Addition(1, 2).to_equation(), sympy.Symbol("test")) diff --git a/tests/unit/test_expression_tree/test_concatenations.py b/tests/unit/test_expression_tree/test_concatenations.py index df5add0f98..4b07b09fea 100644 --- a/tests/unit/test_expression_tree/test_concatenations.py +++ b/tests/unit/test_expression_tree/test_concatenations.py @@ -5,9 +5,9 @@ from tests import TestCase import numpy as np -import sympy import pybamm +from pybamm.util import have_optional_dependency from tests import get_discretisation_for_testing, get_mesh_for_testing @@ -370,6 +370,7 @@ def test_numpy_concatenation(self): ) def test_to_equation(self): + sympy = have_optional_dependency("sympy") a = pybamm.Symbol("a", domain="test a") b = pybamm.Symbol("b", domain="test b") func_symbol = sympy.Symbol(r"\begin{cases}a\\b\end{cases}") diff --git a/tests/unit/test_expression_tree/test_functions.py b/tests/unit/test_expression_tree/test_functions.py index ac5410d9e1..6d22571a01 100644 --- a/tests/unit/test_expression_tree/test_functions.py +++ b/tests/unit/test_expression_tree/test_functions.py @@ -5,10 +5,10 @@ import unittest import numpy as np -import sympy from scipy import special import pybamm +from pybamm.util import have_optional_dependency def test_function(arg): @@ -120,6 +120,7 @@ def test_function_unnamed(self): self.assertEqual(fun.name, "function (cos)") def test_to_equation(self): + sympy = have_optional_dependency("sympy") a = pybamm.Symbol("a", domain="test") # Test print_name diff --git a/tests/unit/test_expression_tree/test_independent_variable.py b/tests/unit/test_expression_tree/test_independent_variable.py index 95141f0f03..b748a6fbe9 100644 --- a/tests/unit/test_expression_tree/test_independent_variable.py +++ b/tests/unit/test_expression_tree/test_independent_variable.py @@ -4,9 +4,9 @@ from tests import TestCase import unittest -import sympy import pybamm +from pybamm.util import have_optional_dependency class TestIndependentVariable(TestCase): @@ -64,6 +64,7 @@ def test_spatial_variable_edge(self): self.assertTrue(x.evaluates_on_edges("primary")) def test_to_equation(self): + sympy = have_optional_dependency("sympy") # Test print_name func = pybamm.IndependentVariable("a") func.print_name = "test" diff --git a/tests/unit/test_expression_tree/test_operations/test_latexify.py b/tests/unit/test_expression_tree/test_operations/test_latexify.py index be7cc21115..7e0703534e 100644 --- a/tests/unit/test_expression_tree/test_operations/test_latexify.py +++ b/tests/unit/test_expression_tree/test_operations/test_latexify.py @@ -8,7 +8,6 @@ import uuid import pybamm -from pybamm.expression_tree.operations.latexify import Latexify class TestLatexify(TestCase): @@ -19,9 +18,6 @@ def test_latexify(self): model_spme = pybamm.lithium_ion.SPMe() func_spme = str(model_spme.latexify()) - # Test docstring - self.assertEqual(pybamm.BaseModel.latexify.__doc__, Latexify.__doc__) - # Test model name self.assertIn("Single Particle Model with electrolyte Equations", func_spme) diff --git a/tests/unit/test_expression_tree/test_parameter.py b/tests/unit/test_expression_tree/test_parameter.py index f67ee2dd62..d9a756b45d 100644 --- a/tests/unit/test_expression_tree/test_parameter.py +++ b/tests/unit/test_expression_tree/test_parameter.py @@ -5,9 +5,8 @@ import numbers import unittest -import sympy - import pybamm +from pybamm.util import have_optional_dependency class TestParameter(TestCase): @@ -21,6 +20,7 @@ def test_evaluate_for_shape(self): self.assertIsInstance(a.evaluate_for_shape(), numbers.Number) def test_to_equation(self): + sympy = have_optional_dependency("sympy") func = pybamm.Parameter("test_string") func1 = pybamm.Parameter("test_name") @@ -98,6 +98,7 @@ def _myfun(x): self.assertEqual(_myfun(x).print_name, None) def test_function_parameter_to_equation(self): + sympy = have_optional_dependency("sympy") func = pybamm.FunctionParameter("test", {"x": pybamm.Scalar(1)}) func1 = pybamm.FunctionParameter("func", {"var": pybamm.Variable("var")}) diff --git a/tests/unit/test_expression_tree/test_printing/test_sympy_overrides.py b/tests/unit/test_expression_tree/test_printing/test_sympy_overrides.py index b5ae229ae5..de3ff08c43 100644 --- a/tests/unit/test_expression_tree/test_printing/test_sympy_overrides.py +++ b/tests/unit/test_expression_tree/test_printing/test_sympy_overrides.py @@ -4,14 +4,14 @@ from tests import TestCase import unittest -import sympy - import pybamm from pybamm.expression_tree.printing.sympy_overrides import custom_print_func +from pybamm.util import have_optional_dependency class TestCustomPrint(TestCase): def test_print_Derivative(self): + sympy = have_optional_dependency("sympy") # Test force_partial der1 = sympy.Derivative("y", "x") der1.force_partial = True diff --git a/tests/unit/test_expression_tree/test_symbol.py b/tests/unit/test_expression_tree/test_symbol.py index 3a74375ce7..a9c219edda 100644 --- a/tests/unit/test_expression_tree/test_symbol.py +++ b/tests/unit/test_expression_tree/test_symbol.py @@ -7,10 +7,10 @@ import numpy as np from scipy.sparse import csr_matrix, coo_matrix -import sympy import pybamm from pybamm.expression_tree.binary_operators import _Heaviside +from pybamm.util import have_optional_dependency class TestSymbol(TestCase): @@ -480,6 +480,7 @@ def test_test_shape(self): (y1 + y2).test_shape() def test_to_equation(self): + sympy = have_optional_dependency("sympy") self.assertEqual(pybamm.Symbol("test").to_equation(), sympy.Symbol("test")) def test_numpy_array_ufunc(self): diff --git a/tests/unit/test_expression_tree/test_unary_operators.py b/tests/unit/test_expression_tree/test_unary_operators.py index b0513c974b..5c85e64b4d 100644 --- a/tests/unit/test_expression_tree/test_unary_operators.py +++ b/tests/unit/test_expression_tree/test_unary_operators.py @@ -5,12 +5,10 @@ from tests import TestCase import numpy as np -import sympy from scipy.sparse import diags -from sympy.vector.operators import Divergence as sympy_Divergence -from sympy.vector.operators import Gradient as sympy_Gradient import pybamm +from pybamm.util import have_optional_dependency class TestUnaryOperators(TestCase): @@ -613,6 +611,13 @@ def test_not_constant(self): self.assertFalse((2 * a).is_constant()) def test_to_equation(self): + + sympy = have_optional_dependency("sympy") + sympy_Divergence = have_optional_dependency( + "sympy.vector.operators", "Divergence" + ) + sympy_Gradient = have_optional_dependency("sympy.vector.operators", "Gradient") + a = pybamm.Symbol("a", domain="negative particle") b = pybamm.Symbol("b", domain="current collector") c = pybamm.Symbol("c", domain="test") diff --git a/tests/unit/test_expression_tree/test_variable.py b/tests/unit/test_expression_tree/test_variable.py index be791903e2..583008f882 100644 --- a/tests/unit/test_expression_tree/test_variable.py +++ b/tests/unit/test_expression_tree/test_variable.py @@ -5,9 +5,9 @@ import unittest import numpy as np -import sympy import pybamm +from pybamm.util import have_optional_dependency class TestVariable(TestCase): @@ -55,6 +55,7 @@ def test_variable_bounds(self): pybamm.Variable("var", bounds=(1, 1)) def test_to_equation(self): + sympy = have_optional_dependency("sympy") # Test print_name func = pybamm.Variable("test_string") func.print_name = "test" diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index c5060e65a6..4aa8fb557d 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -10,7 +10,9 @@ import unittest from unittest.mock import patch from io import StringIO +from tempfile import TemporaryDirectory +anytree = sys.modules['anytree'] class TestUtil(TestCase): """ @@ -29,6 +31,7 @@ def test_rmse(self): pybamm.rmse(np.ones(5), np.zeros(3)) def test_is_constant_and_can_evaluate(self): + sys.modules['anytree'] = anytree symbol = pybamm.PrimaryBroadcast(0, "negative electrode") self.assertEqual(False, pybamm.is_constant_and_can_evaluate(symbol)) symbol = pybamm.StateVector(slice(0, 1)) @@ -88,6 +91,29 @@ def test_git_commit_info(self): self.assertIsInstance(git_commit_info, str) self.assertEqual(git_commit_info[:2], "v2") + def test_have_optional_dependency(self): + with self.assertRaisesRegex( + ModuleNotFoundError, "Optional dependency pybtex is not available." + ): + pybtex = sys.modules['pybtex'] + sys.modules['pybtex'] = None + pybamm.print_citations() + with self.assertRaisesRegex( + ModuleNotFoundError, "Optional dependency anytree is not available." + ): + with TemporaryDirectory() as dir_name: + sys.modules['anytree'] = None + test_stub = os.path.join(dir_name, "test_visualize") + test_name = f"{test_stub}.png" + c = pybamm.Variable("c", "negative electrode") + d = pybamm.Variable("d", "negative electrode") + sym = pybamm.div(c * pybamm.grad(c)) + (c / d + c - d) ** 5 + sym.visualise(test_name) + + sys.modules['pybtex'] = pybtex + pybamm.util.have_optional_dependency("pybtex") + pybamm.print_citations() + class TestSearch(TestCase): def test_url_gets_to_stdout(self): From 1694cb82ca86bbfb010d86b2990ee98cff99e0e7 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Wed, 15 Nov 2023 01:21:30 +0530 Subject: [PATCH 12/14] Bump - `v23.9rc1` --- CITATION.cff | 2 +- pybamm/version.py | 2 +- vcpkg.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 5a9e1e2ddc..b7f68164fc 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -24,6 +24,6 @@ keywords: - "expression tree" - "python" - "symbolic differentiation" -version: "23.9rc0" +version: "23.9rc1" repository-code: "https://github.com/pybamm-team/PyBaMM" title: "Python Battery Mathematical Modelling (PyBaMM)" diff --git a/pybamm/version.py b/pybamm/version.py index c8d63f83e1..e5cfaa0882 100644 --- a/pybamm/version.py +++ b/pybamm/version.py @@ -1 +1 @@ -__version__ = "23.9rc0" +__version__ = "23.9rc1" diff --git a/vcpkg.json b/vcpkg.json index 6877dfa094..de71a5a87d 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "pybamm", - "version-string": "23.9rc0", + "version-string": "23.9rc1", "dependencies": [ "casadi", { From fbcf22bea430e03420e82ffab46539ce51b1522f Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Wed, 15 Nov 2023 01:22:53 +0530 Subject: [PATCH 13/14] Fix date in CHANGELOG --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7446e9203c..82b3824272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,12 @@ - Fixed bug in calculation of theoretical energy that made it very slow ([#3506](https://github.com/pybamm-team/PyBaMM/pull/3506)) -# [v23.9rc1](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 +# [v23.9rc1](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-11-15 - Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) - Make pybamm importable with minimal dependencies ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044), [#3475](https://github.com/pybamm-team/PyBaMM/pull/3475)) - Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) - # [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 ## Features From e00b0d566632225491f3b6dabf73ca2fed66d8d2 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Mon, 20 Nov 2023 17:37:01 +0530 Subject: [PATCH 14/14] Bump version to v23.9 --- CHANGELOG.md | 11 ++++------- CITATION.cff | 2 +- docs/_static/versions.json | 5 +++++ pybamm/version.py | 2 +- vcpkg.json | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b3824272..7adb07939b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,7 @@ - Fixed bug in calculation of theoretical energy that made it very slow ([#3506](https://github.com/pybamm-team/PyBaMM/pull/3506)) -# [v23.9rc1](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-11-15 - -- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) -- Make pybamm importable with minimal dependencies ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044), [#3475](https://github.com/pybamm-team/PyBaMM/pull/3475)) -- Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) - -# [v23.9rc0](https://github.com/pybamm-team/PyBaMM/tree/v23.9rc0) - 2023-10-31 +# [v23.9](https://github.com/pybamm-team/PyBaMM/tree/v23.9) - 2023-10-31 ## Features @@ -29,6 +23,9 @@ ## Bug fixes +- Fixed a bug where the JaxSolver would fails when using GPU support with no input parameters ([#3423](https://github.com/pybamm-team/PyBaMM/pull/3423)) +- Make pybamm importable with minimal dependencies ([#3044](https://github.com/pybamm-team/PyBaMM/pull/3044), [#3475](https://github.com/pybamm-team/PyBaMM/pull/3475)) +- Fixed a bug where supplying an initial soc did not work with half cell models ([#3456](https://github.com/pybamm-team/PyBaMM/pull/3456)) - Fixed a bug where empty lists passed to QuickPlot resulted in an IndexError and did not return a meaningful error message ([#3359](https://github.com/pybamm-team/PyBaMM/pull/3359)) - Fixed a bug where there was a missing thermal conductivity in the thermal pouch cell models ([#3330](https://github.com/pybamm-team/PyBaMM/pull/3330)) - Fixed a bug that caused incorrect results of “{Domain} electrode thickness change [m]” due to the absence of dimension for the variable `electrode_thickness_change`([#3329](https://github.com/pybamm-team/PyBaMM/pull/3329)). diff --git a/CITATION.cff b/CITATION.cff index b7f68164fc..44f1c5d407 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -24,6 +24,6 @@ keywords: - "expression tree" - "python" - "symbolic differentiation" -version: "23.9rc1" +version: "23.9" repository-code: "https://github.com/pybamm-team/PyBaMM" title: "Python Battery Mathematical Modelling (PyBaMM)" diff --git a/docs/_static/versions.json b/docs/_static/versions.json index 5c9bba7c17..675ecbcf88 100644 --- a/docs/_static/versions.json +++ b/docs/_static/versions.json @@ -9,6 +9,11 @@ "version": "stable", "url": "https://docs.pybamm.org/en/stable/" }, + { + "name": "v23.9", + "version": "23.9", + "url": "https://docs.pybamm.org/en/v23.9_a/" + }, { "name": "v23.5", "version": "23.5", diff --git a/pybamm/version.py b/pybamm/version.py index e5cfaa0882..970be77f66 100644 --- a/pybamm/version.py +++ b/pybamm/version.py @@ -1 +1 @@ -__version__ = "23.9rc1" +__version__ = "23.9" diff --git a/vcpkg.json b/vcpkg.json index de71a5a87d..f62c18ddd2 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "pybamm", - "version-string": "23.9rc1", + "version-string": "23.9", "dependencies": [ "casadi", {