From e6e8aab63f8ecc11c4611b5c0b20a3fe25609480 Mon Sep 17 00:00:00 2001 From: AgnieszkaZaba <56157996+AgnieszkaZaba@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:16:45 +0100 Subject: [PATCH] add a HOWTO notebook depicting how to manage units in PySDM (#1443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Agnieszka Żaba Co-authored-by: Sylwester Arabas --- PySDM/physics/dimensional_analysis.py | 8 +- README.md | 5 +- docs/templates/index.html.jinja2 | 3 +- .../_HOWTOs/dimensional_analysis.ipynb | 175 ++++++++++++++++++ tests/examples_tests/conftest.py | 1 + 5 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 examples/PySDM_examples/_HOWTOs/dimensional_analysis.ipynb diff --git a/PySDM/physics/dimensional_analysis.py b/PySDM/physics/dimensional_analysis.py index 1da24f0c9..31ca77e75 100644 --- a/PySDM/physics/dimensional_analysis.py +++ b/PySDM/physics/dimensional_analysis.py @@ -1,12 +1,12 @@ """ -A context manager (for use with the `with` statement) -for use in unit tests which disables Numba and enables Pint +A context manager (for use with the `with` statement) which +enables Pint physical-units checks and disables Numba in `PySDM.formulae.Formulae` """ from importlib import reload from PySDM import formulae - +from PySDM import physics from . import constants, constants_defaults from .impl import flag @@ -17,9 +17,11 @@ def __enter__(*_): # pylint: disable=no-method-argument,no-self-argument reload(constants) reload(constants_defaults) reload(formulae) + reload(physics) def __exit__(*_): # pylint: disable=no-method-argument,no-self-argument flag.DIMENSIONAL_ANALYSIS = False reload(constants) reload(constants_defaults) reload(formulae) + reload(physics) diff --git a/README.md b/README.md index e58e49556..28750d7da 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The package features a Pythonic high-performance implementation of the Super-Droplet Method (SDM) Monte-Carlo algorithm for representing collisional growth ([Shima et al. 2009](https://rmets.onlinelibrary.wiley.com/doi/abs/10.1002/qj.441)), hence the name. -There is a growing set of example Jupyter notebooks exemplifying how to perform +There is a growing set of [example Jupyter notebooks](https://open-atmos.github.io/PySDM/PySDM_examples.html) exemplifying how to perform various types of calculations and simulations using PySDM. Most of the example notebooks reproduce results and plot from literature, see below for a list of examples and links to the notebooks (which can be either executed or viewed @@ -149,7 +149,8 @@ To run all pre-commit hooks, run `pre-commit run --all-files`. The `.pre-commit-config.yaml` file can be modified in case new hooks are to be added or existing ones need to be altered. -Further hints addressed at PySDM developers are maintained in the [open-atmos/python-dev-hints Wiki](https://github.com/open-atmos/python-dev-hints/wiki). +Further hints addressed at PySDM developers are maintained in the [open-atmos/python-dev-hints Wiki](https://github.com/open-atmos/python-dev-hints/wiki) + and in [PySDM HOWTOs](https://github.com/open-atmos/PySDM/tree/main/examples/PySDM_examples/_HOWTOS). Issues regarding any incorrect, unintuitive or undocumented bahaviour of PySDM are best to be reported on the [GitHub issue tracker](https://github.com/open-atmos/PySDM/issues/new). diff --git a/docs/templates/index.html.jinja2 b/docs/templates/index.html.jinja2 index f726e6707..1266fe1ea 100644 --- a/docs/templates/index.html.jinja2 +++ b/docs/templates/index.html.jinja2 @@ -91,7 +91,8 @@
git clone https://github.com/open-atmos/PySDM.git
 pip install -e PySDM[tests] -e PySDM/examples[tests]
 pytest PySDM
-

(the above should be a viable way to set up development environment for PySDM, see also our Python dev hints Wiki for further information)

+

(the above should be a viable way to set up development environment for PySDM, see also our Python dev hints Wiki + and PySDM HOWTOs for further information)

PySDM-examples is also available on PyPI and can be installed using pip:

diff --git a/examples/PySDM_examples/_HOWTOs/dimensional_analysis.ipynb b/examples/PySDM_examples/_HOWTOs/dimensional_analysis.ipynb new file mode 100644 index 000000000..dfb6c4627 --- /dev/null +++ b/examples/PySDM_examples/_HOWTOs/dimensional_analysis.ipynb @@ -0,0 +1,175 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c1925b9647eb39e9", + "metadata": {}, + "source": [ + "[![preview notebook](https://img.shields.io/static/v1?label=render%20on&logo=github&color=87ce3e&message=GitHub)](https://github.com/open-atmos/PySDM/blob/main/examples/PySDM_examples/howtos/units.ipynb)\n", + "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/open-atmos/PySDM.git/main?urlpath=lab/tree/examples/PySDM_examples/howtos/units.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-atmos/PySDM/blob/main/examples/PySDM_examples/howtos/units.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "57a5c59c4cb8a1ec", + "metadata": {}, + "source": [ + "### PySDM dimensional analysis HOWTO\n", + "- PySDM depends on the [Pint](https://pint.readthedocs.io/en/stable/) package which offers dimensional analysis (physical units checks) of Python code \n", + "- this improves code readibility with expressions such as `p = 1000 * si.hPa`\n", + "- using [Pint](https://pint.readthedocs.io/en/stable/), `si` is an instance of [`pint.UnitRegistry`](https://pint.readthedocs.io/en/stable/api/base.html#pint.UnitRegistry) \n", + "- however, for performance reasons, by default PySDM uses a custom drop-in-replacement [`FakeUnitRegistry`](https://open-atmos.github.io/PySDM/PySDM/physics/impl/fake_unit_registry.html#FakeUnitRegistry)\n", + "- this way, we keep the readibility advantage, while not incurring any performance overhead\n", + "- moreover, this makes the code potentially Numba JIT-compilable!\n", + "- we also provide a way to leverage the dimensional analysis benefit for testing purposes\n", + "- to this end, the test code can use the [`DimensionalAnalysis`](https://open-atmos.github.io/PySDM/PySDM/physics/dimensional_analysis.html#DimensionalAnalysis) context manager\n", + "- code below demonstrate how a single unit-equipped function can be used with and without unit checks " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "97dab670c5cf8d8b", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-04T13:27:07.009798Z", + "start_time": "2024-12-04T13:27:07.003077Z" + } + }, + "outputs": [], + "source": [ + "import sys\n", + "if 'google.colab' in sys.modules:\n", + " !pip --quiet install open-atmos-jupyter-utils\n", + " from open_atmos_jupyter_utils import pip_install_on_colab\n", + " pip_install_on_colab('PySDM-examples')" + ] + }, + { + "cell_type": "markdown", + "id": "19a8f1dc-a5ea-461b-9019-50d25dfa7bc6", + "metadata": {}, + "source": [ + "#### sample physics-related code" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6ea31783d8e0e849", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-04T13:27:08.240983Z", + "start_time": "2024-12-04T13:27:07.013763Z" + } + }, + "outputs": [], + "source": [ + "from PySDM import physics\n", + "\n", + "def code():\n", + " si = physics.si\n", + "\n", + " p = 1000 * si.hPa\n", + " T = 300 * si.K\n", + " R = 286 * si.J / si.K / si.kg\n", + "\n", + " rho = p / R / T\n", + " return rho" + ] + }, + { + "cell_type": "markdown", + "id": "183bcca3-5546-4676-a6bc-de3a1dd76ccb", + "metadata": {}, + "source": [ + "#### sample unit-unaware usage (default)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "287ae3f109f11bb6", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-04T13:27:08.341648Z", + "start_time": "2024-12-04T13:27:08.339564Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.2\n" + ] + } + ], + "source": [ + "result_unit_unaware = code()\n", + "print(f\"{result_unit_unaware:.2g}\")" + ] + }, + { + "cell_type": "markdown", + "id": "da1669cf-653b-4138-9ff9-8fac303ad8c4", + "metadata": {}, + "source": [ + "#### sample unit-aware usage (e.g., for testing)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "85bf8e7bc2ca98a", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-04T13:27:08.671915Z", + "start_time": "2024-12-04T13:27:08.402327Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.012 hectopascal * kilogram / joule\n", + "1.2 kilogram / meter ** 3\n" + ] + } + ], + "source": [ + "from PySDM.physics.dimensional_analysis import DimensionalAnalysis\n", + "\n", + "with DimensionalAnalysis():\n", + " result_unit_aware = code()\n", + "\n", + "assert result_unit_aware.check(\"[mass] / [volume]\")\n", + "print(f\"{result_unit_aware:.2g}\")\n", + "print(f\"{result_unit_aware.to_base_units():.2g}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/examples_tests/conftest.py b/tests/examples_tests/conftest.py index 969bae90e..3afac82a8 100644 --- a/tests/examples_tests/conftest.py +++ b/tests/examples_tests/conftest.py @@ -61,6 +61,7 @@ def findfiles(path, regex): "Morrison_and_Grabowski_2007", "Szumowski_et_al_1998", "utils", + "_HOWTOs", ], }