diff --git a/.all-contributorsrc b/.all-contributorsrc index 4366246007..00b86f3474 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -960,6 +960,15 @@ "code", "test" ] + }, + { + "login": "medha-14", + "name": "Medha Bhardwaj", + "avatar_url": "https://avatars.githubusercontent.com/u/143182673?v=4", + "profile": "https://github.com/medha-14", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/periodic_benchmarks.yml b/.github/workflows/periodic_benchmarks.yml index 641627c0ba..b99e2ab7b0 100644 --- a/.github/workflows/periodic_benchmarks.yml +++ b/.github/workflows/periodic_benchmarks.yml @@ -51,7 +51,7 @@ jobs: LD_LIBRARY_PATH: $HOME/.local/lib - name: Upload results as artifact - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: asv_periodic_results path: results diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 9ca277b653..5f68bd2b45 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -92,7 +92,7 @@ jobs: python -c "import pybamm; print(pybamm.IDAKLUSolver())" python -m pytest -m cibw {project}/tests/unit - name: Upload Windows wheels - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: wheels_windows path: ./wheelhouse/*.whl @@ -129,7 +129,7 @@ jobs: python -m pytest -m cibw {project}/tests/unit - name: Upload wheels for Linux - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: wheels_manylinux path: ./wheelhouse/*.whl @@ -261,7 +261,7 @@ jobs: python -m pytest -m cibw {project}/tests/unit - name: Upload wheels for macOS (amd64, arm64) - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: wheels_${{ matrix.os }} path: ./wheelhouse/*.whl @@ -281,7 +281,7 @@ jobs: run: pipx run build --sdist - name: Upload SDist - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: sdist path: ./dist/*.tar.gz diff --git a/.github/workflows/run_benchmarks_over_history.yml b/.github/workflows/run_benchmarks_over_history.yml index d01564b210..71687a8b02 100644 --- a/.github/workflows/run_benchmarks_over_history.yml +++ b/.github/workflows/run_benchmarks_over_history.yml @@ -46,7 +46,7 @@ jobs: ${{ github.event.inputs.commit_start }}..${{ github.event.inputs.commit_end }} - name: Upload results as artifact - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4.4.0 with: name: asv_over_history_results path: results diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8b33553737..784ccebc4f 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: SARIF file path: results.sarif @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/work_precision_sets.yml b/.github/workflows/work_precision_sets.yml index fafc5b1738..c167bf5747 100644 --- a/.github/workflows/work_precision_sets.yml +++ b/.github/workflows/work_precision_sets.yml @@ -27,7 +27,7 @@ jobs: python benchmarks/work_precision_sets/time_vs_reltols.py python benchmarks/work_precision_sets/time_vs_abstols.py - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: delete-branch: true branch-suffix: short-commit-hash diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e61a1b795..57c2f71af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ ## Features -- Added a lithium ion equivalent circuit model with split open circuit voltages for each electrode. ([#4330](https://github.com/pybamm-team/PyBaMM/pull/4330)) +- Added a lithium ion equivalent circuit model with split open circuit voltages for each electrode (`SplitOCVR`). ([#4330](https://github.com/pybamm-team/PyBaMM/pull/4330)) + +## Optimizations + +- Removed the `start_step_offset` setting and disabled minimum `dt` warnings for drive cycles with the (`IDAKLUSolver`). ([#4416](https://github.com/pybamm-team/PyBaMM/pull/4416)) + +## Breaking changes + +- Renamed `set_events` function to `add_events_from` to better reflect its purpose. ([#4421](https://github.com/pybamm-team/PyBaMM/pull/4421)) + # [v24.9.0](https://github.com/pybamm-team/PyBaMM/tree/v24.9.0) - 2024-09-03 @@ -10,6 +19,7 @@ - Added additional user-configurable options to the (`IDAKLUSolver`) and adjusted the default values to improve performance. ([#4282](https://github.com/pybamm-team/PyBaMM/pull/4282)) - Added the diffusion element to be used in the Thevenin model. ([#4254](https://github.com/pybamm-team/PyBaMM/pull/4254)) +- Added lumped surface thermal model ([#4203](https://github.com/pybamm-team/PyBaMM/pull/4203)) ## Optimizations diff --git a/README.md b/README.md index a904e5a67c..611d8adcfe 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/pybamm-team/PyBaMM/badge)](https://scorecard.dev/viewer/?uri=github.com/pybamm-team/PyBaMM) -[![All Contributors](https://img.shields.io/badge/all_contributors-90-orange.svg)](#-contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-91-orange.svg)](#-contributors) diff --git a/all_contributors.md b/all_contributors.md index 9bb1e373d5..49a5f1d1ef 100644 --- a/all_contributors.md +++ b/all_contributors.md @@ -120,6 +120,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Ubham16
Ubham16

💻 Mehrdad Babazadeh
Mehrdad Babazadeh

💻 ⚠️ Pip Liggins
Pip Liggins

💻 ⚠️ + Medha Bhardwaj
Medha Bhardwaj

💻 diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index a5958b327b..dab8247ddf 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -54,6 +54,7 @@ The notebooks are organised into subfolders, and can be viewed in the galleries notebooks/models/DFN-with-particle-size-distributions.ipynb notebooks/models/DFN.ipynb notebooks/models/electrode-state-of-health.ipynb + notebooks/models/graded-electrodes.ipynb notebooks/models/half-cell.ipynb notebooks/models/jelly-roll-model.ipynb notebooks/models/latexify.ipynb diff --git a/docs/source/examples/notebooks/models/graded-electrodes.ipynb b/docs/source/examples/notebooks/models/graded-electrodes.ipynb new file mode 100644 index 0000000000..adb316ba2d --- /dev/null +++ b/docs/source/examples/notebooks/models/graded-electrodes.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simulating graded electrodes\n", + "\n", + "In this notebook we explore how to simulate the effect of graded electrodes in the performance of a battery. Graded electrodes have a composition that varies along the thickness of the electrode, typically active material volume fraction and particle size. This variation can be used to improve the performance of the battery, for example, by increasing the power density.\n", + "\n", + "As usual, we start by importing PyBaMM." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.\n" + ] + } + ], + "source": [ + "%pip install \"pybamm[plot,cite]\" -q # install PyBaMM if it is not installed\n", + "import pybamm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use the DFN model for the simulations and the Chen2020 parameter set. Note that we will need to modify the default Chen2020 parameter set to describe graded electrodes." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "model = pybamm.lithium_ion.DFN()\n", + "parameter_values = pybamm.ParameterValues(\"Chen2020\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will vary the porosity in both electrodes and we will try three different scenarios: constant porosity, one where lower porosity occurs near the separator and one where lower porosity occurs near the current collector. All other parameters are kept constant. The varying porosity is defined to be linear centered around the default value and with a variation of $\\pm$ 10%.\n", + "\n", + "We define the varying porosities and store them in a list so we can loop over when solving the model." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "L_n = parameter_values[\"Negative electrode thickness [m]\"]\n", + "L_s = parameter_values[\"Separator thickness [m]\"]\n", + "L_p = parameter_values[\"Positive electrode thickness [m]\"]\n", + "\n", + "eps_n_0 = parameter_values[\"Negative electrode porosity\"]\n", + "eps_p_0 = parameter_values[\"Positive electrode porosity\"]\n", + "\n", + "eps_ns = [\n", + " eps_n_0,\n", + " lambda x: eps_n_0 * (1.1 - 0.2 * (x / L_n)),\n", + " lambda x: eps_n_0 * (0.9 + 0.2 * (x / L_n)),\n", + "]\n", + "\n", + "eps_ps = [\n", + " eps_p_0,\n", + " lambda x: eps_p_0 * (0.9 - 0.2 / L_p * (L_n + L_s) + 0.2 * (x / L_p)),\n", + " lambda x: eps_p_0 * (1.1 + 0.2 / L_p * (L_n + L_s) - 0.2 * (x / L_p)),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the distance through the electrode is computed from the negative electrode, so parameter need to be defined accordingly. Next, we can just solve the models for the various parameter sets. We apply a fairly high C-rate to see the effect of the graded electrodes on the discharge capacity." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "solutions = []\n", + "\n", + "experiment = pybamm.Experiment([\"Discharge at 3C until 2.5 V\"])\n", + "\n", + "for eps_n, eps_p in zip(eps_ns, eps_ps):\n", + " parameter_values[\"Negative electrode porosity\"] = eps_n\n", + " parameter_values[\"Positive electrode porosity\"] = eps_p\n", + " sim = pybamm.Simulation(\n", + " model,\n", + " parameter_values=parameter_values,\n", + " experiment=experiment,\n", + " solver=pybamm.IDAKLUSolver(),\n", + " )\n", + " sol = sim.solve()\n", + " solutions.append(sol)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And plot the results:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "99f847ca09da40cba550dd02dd8281a2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=673.9136958613059, step=6.7391369586130585),…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pybamm.dynamic_plot(\n", + " solutions,\n", + " labels=[\n", + " \"Constant porosity\",\n", + " \"Low porosity at separator\",\n", + " \"High porosity at separator\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We observe that, even though the average porosity is the same for the three cases the discharge capacity is much higher with the graded electrode where porosity is higher near the separator. This is because the higher porosity near the separator facilitates the ion transport and the better utilisation of the active material.\n", + "\n", + "As a sanity check we can plot the porosity profiles for the three cases and see they match what we intended." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "829b68c6b3e04e0ebe5537daabec2278", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=673.9136958613059, step=6.7391369586130585),…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pybamm.dynamic_plot(\n", + " solutions,\n", + " output_variables=[\"Negative electrode porosity\", \"Positive electrode porosity\"],\n", + " labels=[\n", + " \"Constant porosity\",\n", + " \"Low porosity at separator\",\n", + " \"High porosity at separator\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n", + "[2] Von DAG Bruggeman. Berechnung verschiedener physikalischer konstanten von heterogenen substanzen. i. dielektrizitätskonstanten und leitfähigkeiten der mischkörper aus isotropen substanzen. Annalen der physik, 416(7):636–664, 1935.\n", + "[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.\n", + "[4] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n", + "[5] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n", + "[6] Alan C. Hindmarsh. The PVODE and IDA algorithms. Technical Report, Lawrence Livermore National Lab., CA (US), 2000. doi:10.2172/802599.\n", + "[7] Alan C. Hindmarsh, Peter N. Brown, Keith E. Grant, Steven L. Lee, Radu Serban, Dan E. Shumaker, and Carol S. Woodward. SUNDIALS: Suite of nonlinear and differential/algebraic equation solvers. ACM Transactions on Mathematical Software (TOMS), 31(3):363–396, 2005. doi:10.1145/1089014.1089020.\n", + "[8] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.\n", + "[9] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n", + "[10] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.\n", + "[11] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.\n", + "\n" + ] + } + ], + "source": [ + "pybamm.print_citations()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/pybamm/models/base_model.py b/src/pybamm/models/base_model.py index 0d4638e178..989465e375 100644 --- a/src/pybamm/models/base_model.py +++ b/src/pybamm/models/base_model.py @@ -757,7 +757,7 @@ def build_model_equations(self): f"Setting initial conditions for {submodel_name} submodel ({self.name})" ) submodel.set_initial_conditions(self.variables) - submodel.set_events(self.variables) + submodel.add_events_from(self.variables) pybamm.logger.verbose(f"Updating {submodel_name} submodel ({self.name})") self.update(submodel) self.check_no_repeated_keys() diff --git a/src/pybamm/models/full_battery_models/base_battery_model.py b/src/pybamm/models/full_battery_models/base_battery_model.py index ccda594b14..e0ade7b429 100644 --- a/src/pybamm/models/full_battery_models/base_battery_model.py +++ b/src/pybamm/models/full_battery_models/base_battery_model.py @@ -1033,7 +1033,7 @@ def build_model_equations(self): f"Setting initial conditions for {submodel_name} submodel ({self.name})" ) submodel.set_initial_conditions(self.variables) - submodel.set_events(self.variables) + submodel.add_events_from(self.variables) pybamm.logger.verbose(f"Updating {submodel_name} submodel ({self.name})") self.update(submodel) self.check_no_repeated_keys() diff --git a/src/pybamm/models/submodels/base_submodel.py b/src/pybamm/models/submodels/base_submodel.py index e120691edd..d5e313e153 100644 --- a/src/pybamm/models/submodels/base_submodel.py +++ b/src/pybamm/models/submodels/base_submodel.py @@ -221,7 +221,7 @@ def set_initial_conditions(self, variables): """ pass - def set_events(self, variables): + def add_events_from(self, variables): """ A method to set events related to the state of submodel variable. Note: this method modifies the state of self.events. Unless overwritten by a submodel, the diff --git a/src/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py b/src/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py index eee441446f..006619e8bb 100644 --- a/src/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py +++ b/src/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py @@ -76,6 +76,6 @@ def set_boundary_conditions(self, variables): } } - def set_events(self, variables): + def add_events_from(self, variables): # No event since the concentration is constant pass diff --git a/src/pybamm/models/submodels/equivalent_circuit_elements/diffusion_element.py b/src/pybamm/models/submodels/equivalent_circuit_elements/diffusion_element.py index c7f1b4bcd5..bf9a183875 100644 --- a/src/pybamm/models/submodels/equivalent_circuit_elements/diffusion_element.py +++ b/src/pybamm/models/submodels/equivalent_circuit_elements/diffusion_element.py @@ -102,7 +102,7 @@ def set_initial_conditions(self, variables): z = variables["Distributed SoC"] self.initial_conditions = {z: self.param.initial_soc} - def set_events(self, variables): + def add_events_from(self, variables): z_surf = variables["Surface SoC"] self.events += [ pybamm.Event("Minimum surface SoC", z_surf), diff --git a/src/pybamm/models/submodels/equivalent_circuit_elements/ocv_element.py b/src/pybamm/models/submodels/equivalent_circuit_elements/ocv_element.py index 9d1adf3d57..901b63cf74 100644 --- a/src/pybamm/models/submodels/equivalent_circuit_elements/ocv_element.py +++ b/src/pybamm/models/submodels/equivalent_circuit_elements/ocv_element.py @@ -54,7 +54,7 @@ def set_initial_conditions(self, variables): soc = variables["SoC"] self.initial_conditions = {soc: self.param.initial_soc} - def set_events(self, variables): + def add_events_from(self, variables): soc = variables["SoC"] self.events += [ pybamm.Event("Minimum SoC", soc), diff --git a/src/pybamm/models/submodels/equivalent_circuit_elements/voltage_model.py b/src/pybamm/models/submodels/equivalent_circuit_elements/voltage_model.py index 380902fca5..89f8904f32 100644 --- a/src/pybamm/models/submodels/equivalent_circuit_elements/voltage_model.py +++ b/src/pybamm/models/submodels/equivalent_circuit_elements/voltage_model.py @@ -54,7 +54,7 @@ def x_not_zero(x): return variables - def set_events(self, variables): + def add_events_from(self, variables): voltage = variables["Voltage [V]"] # Add voltage events diff --git a/src/pybamm/models/submodels/interface/interface_utilisation/current_driven_utilisation.py b/src/pybamm/models/submodels/interface/interface_utilisation/current_driven_utilisation.py index bbd9af4fb6..e785204882 100644 --- a/src/pybamm/models/submodels/interface/interface_utilisation/current_driven_utilisation.py +++ b/src/pybamm/models/submodels/interface/interface_utilisation/current_driven_utilisation.py @@ -89,7 +89,7 @@ def set_initial_conditions(self, variables): self.initial_conditions = {u: u_init} - def set_events(self, variables): + def add_events_from(self, variables): domain, Domain = self.domain_Domain if self.reaction_loc == "full electrode": diff --git a/src/pybamm/models/submodels/particle_mechanics/crack_propagation.py b/src/pybamm/models/submodels/particle_mechanics/crack_propagation.py index 3bb9ecb7eb..b51f1d1ebd 100644 --- a/src/pybamm/models/submodels/particle_mechanics/crack_propagation.py +++ b/src/pybamm/models/submodels/particle_mechanics/crack_propagation.py @@ -102,7 +102,7 @@ def set_initial_conditions(self, variables): l_cr_0 = pybamm.PrimaryBroadcast(l_cr_0, f"{domain} electrode") self.initial_conditions = {l_cr: l_cr_0} - def set_events(self, variables): + def add_events_from(self, variables): domain, Domain = self.domain_Domain if self.x_average is True: diff --git a/src/pybamm/models/submodels/porosity/constant_porosity.py b/src/pybamm/models/submodels/porosity/constant_porosity.py index 0b9f3c0da4..6d6b93d76f 100644 --- a/src/pybamm/models/submodels/porosity/constant_porosity.py +++ b/src/pybamm/models/submodels/porosity/constant_porosity.py @@ -27,6 +27,6 @@ def get_fundamental_variables(self): return variables - def set_events(self, variables): + def add_events_from(self, variables): # No events since porosity is constant pass diff --git a/src/pybamm/models/submodels/porosity/reaction_driven_porosity.py b/src/pybamm/models/submodels/porosity/reaction_driven_porosity.py index fc69d0f1fd..989c90cd9c 100644 --- a/src/pybamm/models/submodels/porosity/reaction_driven_porosity.py +++ b/src/pybamm/models/submodels/porosity/reaction_driven_porosity.py @@ -64,7 +64,7 @@ def get_coupled_variables(self, variables): return variables - def set_events(self, variables): + def add_events_from(self, variables): eps_p = variables["Positive electrode porosity"] self.events.append( pybamm.Event( diff --git a/src/pybamm/models/submodels/porosity/reaction_driven_porosity_ode.py b/src/pybamm/models/submodels/porosity/reaction_driven_porosity_ode.py index 476845f054..f82a73ae68 100644 --- a/src/pybamm/models/submodels/porosity/reaction_driven_porosity_ode.py +++ b/src/pybamm/models/submodels/porosity/reaction_driven_porosity_ode.py @@ -94,7 +94,7 @@ def set_initial_conditions(self, variables): eps = variables["Porosity"] self.initial_conditions = {eps: self.param.epsilon_init} - def set_events(self, variables): + def add_events_from(self, variables): for domain in self.options.whole_cell_domains: if domain == "separator": continue diff --git a/src/pybamm/settings.py b/src/pybamm/settings.py index d190eaf47e..6b7a628195 100644 --- a/src/pybamm/settings.py +++ b/src/pybamm/settings.py @@ -12,7 +12,6 @@ class Settings: _abs_smoothing = "exact" max_words_in_line = 4 max_y_value = 1e5 - step_start_offset = 1e-9 tolerances = { "D_e__c_e": 10, # dimensional "kappa_e__c_e": 10, # dimensional diff --git a/src/pybamm/simulation.py b/src/pybamm/simulation.py index 5b999d6c83..0aa85d1c20 100644 --- a/src/pybamm/simulation.py +++ b/src/pybamm/simulation.py @@ -8,7 +8,6 @@ import numpy as np import hashlib import warnings -import sys from functools import lru_cache from datetime import timedelta from pybamm.util import import_optional_dependency @@ -464,9 +463,9 @@ def solve( # the time data (to ensure the resolution of t_eval is fine enough). # We only raise a warning here as users may genuinely only want # the solution returned at some specified points. - elif ( - set(np.round(time_data, 12)).issubset(set(np.round(t_eval, 12))) - ) is False: + elif not isinstance(solver, pybamm.IDAKLUSolver) and not set( + np.round(time_data, 12) + ).issubset(set(np.round(t_eval, 12))): warnings.warn( """ t_eval does not contain all of the time points in the data @@ -478,7 +477,7 @@ def solve( ) dt_data_min = np.min(np.diff(time_data)) dt_eval_max = np.max(np.diff(t_eval)) - if dt_eval_max > dt_data_min + sys.float_info.epsilon: + if dt_eval_max > np.nextafter(dt_data_min, np.inf): warnings.warn( f""" The largest timestep in t_eval ({dt_eval_max}) is larger than @@ -581,7 +580,7 @@ def solve( + timedelta(seconds=float(current_solution.t[-1])) ) ).total_seconds() - if rest_time > pybamm.settings.step_start_offset: + if rest_time > 0: # logs["step operating conditions"] = "Initial rest for padding" # callbacks.on_step_start(logs) @@ -738,7 +737,7 @@ def solve( + timedelta(seconds=float(step_solution.t[-1])) ) ).total_seconds() - if rest_time > pybamm.settings.step_start_offset: + if rest_time > 0: logs["step number"] = (step_num, cycle_length) logs["step operating conditions"] = "Rest for padding" callbacks.on_step_start(logs) diff --git a/src/pybamm/solvers/base_solver.py b/src/pybamm/solvers/base_solver.py index efef7e9357..9c0d94f1a9 100644 --- a/src/pybamm/solvers/base_solver.py +++ b/src/pybamm/solvers/base_solver.py @@ -1142,12 +1142,9 @@ def step( # Make sure model isn't empty self._check_empty_model(model) - # Make sure dt is greater than the offset - step_start_offset = pybamm.settings.step_start_offset - if dt <= step_start_offset: - raise pybamm.SolverError( - f"Step time must be at least {pybamm.TimerTime(step_start_offset)}" - ) + # Make sure dt is greater than zero + if dt <= 0: + raise pybamm.SolverError("Step time must be >0") # Raise deprecation warning for npts and convert it to t_eval if npts is not None: @@ -1176,11 +1173,11 @@ def step( if t_start == 0: t_start_shifted = t_start else: - # offset t_start by t_start_offset (default 1 ns) + # find the next largest floating point value for t_start # to avoid repeated times in the solution # from having the same time at the end of the previous step and # the start of the next step - t_start_shifted = t_start + step_start_offset + t_start_shifted = np.nextafter(t_start, np.inf) t_eval[0] = t_start_shifted if t_interp.size > 0 and t_interp[0] == t_start: t_interp[0] = t_start_shifted diff --git a/tests/unit/test_models/test_base_model.py b/tests/unit/test_models/test_base_model.py index 4d5f71201a..d50cd66d2d 100644 --- a/tests/unit/test_models/test_base_model.py +++ b/tests/unit/test_models/test_base_model.py @@ -231,7 +231,7 @@ def set_initial_conditions(self, variables): u = variables["u"] self.initial_conditions = {u: c} - def set_events(self, variables): + def add_events_from(self, variables): e = pybamm.InputParameter("e") u = variables["u"] self.events = [pybamm.Event("u=e", u - e)] @@ -1178,7 +1178,7 @@ def set_initial_conditions(self, variables): v = variables["v"] self.initial_conditions = {u: 0, v: 0} - def set_events(self, variables): + def add_events_from(self, variables): u = variables["u"] self.events.append( pybamm.Event( diff --git a/tests/unit/test_solvers/test_base_solver.py b/tests/unit/test_solvers/test_base_solver.py index a4b43e1dd2..9a1e87acec 100644 --- a/tests/unit/test_solvers/test_base_solver.py +++ b/tests/unit/test_solvers/test_base_solver.py @@ -70,10 +70,8 @@ def test_nonmonotonic_teval(self): solver.solve(model, np.array([1, 2, 3, 2])) # Check stepping with step size too small - dt = 1e-9 - with self.assertRaisesRegex( - pybamm.SolverError, "Step time must be at least 1.000 ns" - ): + dt = -1e-9 + with self.assertRaisesRegex(pybamm.SolverError, "Step time must be >0"): solver.step(None, model, dt) # Checking if array t_eval lies within range diff --git a/tests/unit/test_solvers/test_casadi_solver.py b/tests/unit/test_solvers/test_casadi_solver.py index eaaeedf0d0..76f5a6c9dc 100644 --- a/tests/unit/test_solvers/test_casadi_solver.py +++ b/tests/unit/test_solvers/test_casadi_solver.py @@ -289,7 +289,7 @@ def test_model_step(self): # Step again (return 5 points) step_sol_2 = solver.step(step_sol, model, dt, npts=5) np.testing.assert_array_equal( - step_sol_2.t, np.array([0, 1, 1 + 1e-9, 1.25, 1.5, 1.75, 2]) + step_sol_2.t, np.array([0, 1, np.nextafter(1, np.inf), 1.25, 1.5, 1.75, 2]) ) np.testing.assert_array_almost_equal( step_sol_2.y.full()[0], np.exp(0.1 * step_sol_2.t) diff --git a/tests/unit/test_solvers/test_idaklu_solver.py b/tests/unit/test_solvers/test_idaklu_solver.py index 5d71b5f945..3f9dcf0508 100644 --- a/tests/unit/test_solvers/test_idaklu_solver.py +++ b/tests/unit/test_solvers/test_idaklu_solver.py @@ -1079,7 +1079,7 @@ def test_interpolate_time_step_start_offset(self): sim = pybamm.Simulation(model, experiment=experiment, solver=solver) sol = sim.solve() np.testing.assert_equal( - sol.sub_solutions[0].t[-1] + pybamm.settings.step_start_offset, + np.nextafter(sol.sub_solutions[0].t[-1], np.inf), sol.sub_solutions[1].t[0], ) diff --git a/tests/unit/test_solvers/test_scipy_solver.py b/tests/unit/test_solvers/test_scipy_solver.py index 446206e95c..dfaa6c7201 100644 --- a/tests/unit/test_solvers/test_scipy_solver.py +++ b/tests/unit/test_solvers/test_scipy_solver.py @@ -181,7 +181,7 @@ def test_model_step_python(self): # Step again (return 5 points) step_sol_2 = solver.step(step_sol, model, dt, npts=5) np.testing.assert_array_equal( - step_sol_2.t, np.array([0, 1, 1 + 1e-9, 1.25, 1.5, 1.75, 2]) + step_sol_2.t, np.array([0, 1, np.nextafter(1, np.inf), 1.25, 1.5, 1.75, 2]) ) np.testing.assert_array_almost_equal( step_sol_2.y[0], np.exp(0.1 * step_sol_2.t)