From d04ec05fb70a3694d428eb253faffc02575cabb7 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 11:36:48 -0400 Subject: [PATCH 01/26] Add basic requirements.txt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..131db9f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +plasmapy[docs,tests] +nox +uv From 4a4b2fb4e6fe82ff86f26ead4e8642cca2efe736 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 11:44:08 -0400 Subject: [PATCH 02/26] Add src/hack/README.md --- src/hack/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/hack/README.md diff --git a/src/hack/README.md b/src/hack/README.md new file mode 100644 index 0000000..f3f051f --- /dev/null +++ b/src/hack/README.md @@ -0,0 +1,17 @@ +[`src/hack`]: . +[**coding guide**]: https://docs.plasmapy.org/en/latest/contributing/coding_guide.html +[contributor guide]: https://docs.plasmapy.org/en/latest/contributing +[`tests`]: ../../tests +[`docs`]: ../../docs + +# Source code + +The [`src/hack`] directory contains the source code for the `hack` +Python package hosted in this repository. + +The corresponding tests are in the top-level [`tests`] directory, with +documentation in the top-level [`docs`] directory. + +> [!TIP] +> For more coding tips, please see the [**coding guide**] in PlasmaPy's +> [contributor guide]. From fc3bb4ffb50453116a758d815deaa32ac791e251 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 11:44:29 -0400 Subject: [PATCH 03/26] Add .github/workflows/README.md --- .github/workflows/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/README.md diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..c748fe1 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,16 @@ +# GitHub workflows + +[GitHub Actions]: https://docs.github.com/en/actions +[Nox]: https://nox.thea.codes +[YAML]: https://en.wikipedia.org/wiki/YAML + +The [`.github/workflows`](.) directory contains [YAML] files that +describe [GitHub Actions] workflows such as those used to perform +continuous integration tests. + +Several of the workflows invoke [Nox] sessions that are defined in the +top-level [`noxfile.py`](../../noxfile.py). + +Continuous integration (CI) workflows include: + + - [`ci.yml`](./ci.yml) — perform CI checks during pull requests (PRs) From c7f82b8f95af3d389cc3e59135443f33c58cb888 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 11:47:32 -0400 Subject: [PATCH 04/26] Add docs/README.md --- docs/README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e65dc75 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,55 @@ +# Documentation directory + +[**documentation guide**]: https://docs.plasmapy.org/en/latest/contributing/doc_guide.html +[Sphinx]: https://www.sphinx-doc.org +[Nox]: https://nox.thea.codes +[Read the Docs]: https://about.readthedocs.com +[reStructuredText]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-primer +[`docs`]: . +[`docs/source/conf.py`]: conf.py +[install graphviz]: https://graphviz.org/download +[install pandoc]: https://pandoc.org/installing.html + +> [!TIP] +> To learn more about writing documentation, please check out the +> [**documentation guide**]. + +PlasmaPy's documentation is written in [reStructuredText], built using +[Sphinx] via a [Nox] session, and hosted by [Read the Docs]. + +The "stable" documentation corresponds to PlasmaPy's most recent +release, and can be found at https://docs.plasmapy.org/en/stable. The +"latest" documentation corresponds to the current state of the `main` +branch on PlasmaPy's GitHub repository, and is available at +https://docs.plasmapy.org/en/latest. + +The [`docs`] directory contains the source files for PlasmaPy's +narrative documentation. The configuration file is +[`docs/source/conf.py`]. + +## Building documentation + +> [!TIP] +> When making a pull request, the documentation can be previewed by +> clicking on *Details* next to **docs/readthedocs.org** in the +> list of checks. + +Prior to building documentation locally, please install [Nox] and its +dependencies with: + +```shell +python -m pip install nox uv +``` + +> [!NOTE] +> It may also be necessary to [install graphviz] and [install pandoc]. + +The documentation can be built by going to the top-level directory of +your clone of PlasmaPy and running: + +```shell +nox -s docs +``` + +The documentation preview will be built in `docs/build/html` in your +local clone of this repository. From 571820ac5186ab1ff8757c205f9e8a8c45e36a9d Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 11:51:47 -0400 Subject: [PATCH 05/26] Update tests/README.md --- tests/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/README.md b/tests/README.md index e69de29..71b1ead 100644 --- a/tests/README.md +++ b/tests/README.md @@ -0,0 +1,44 @@ +# Tests + +[**testing guide**]: https://docs.plasmapy.org/en/latest/contributing/testing_guide.html +[`src/hack/formulary/frequencies.py`]: ../src/hack/formulary/frequencies.py +[`tests/formulary/test_frequencies.py`]: formulary/test_frequencies.py +[`tests`]: . +[`src/hack`]: ../src/hack +[Nox]: https://nox.thea.codes +[pytest]: https://docs.pytest.org +[running tests]: https://docs.plasmapy.org/en/latest/contributing/testing_guide.html#running-tests + +> [!TIP] +> To learn more about software testing, please check out PlasmaPy's +> [**testing guide**]. + +The tests in this directory are written using the [pytest] framework, +with [Nox] as the test runner. + +## Locating tests + +The [`tests`] directory contains the tests for the `hack` Python package +in this repository. The directory structure and organization of +[`tests`] largely mirrors that of [`src/hack`], which contains the +source code. For example, the source code of `hack.formulary.frequencies` +is located at [`src/hack/formulary/frequencies.py`], while its tests are +located at [`tests/formulary/test_frequencies.py`] + +## Running tests + +To run tests locally, first install [Nox] and its dependencies with: + +```shell +python -m pip install nox uv +``` + +To run all but the slowest tests, enter the top-level directory of your +clone of this repository and run: + +```shell +nox +``` + +For more information, please see the section in the testing guide on +[running tests]. From fea654fb572a7201936aee07e88dec999536e60b Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 15:04:17 -0400 Subject: [PATCH 06/26] Update docs/source/conf.py --- docs/source/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1935b4f..771a224 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ project = "hack" copyright = "2024, PlasmaPy Community" author = "PlasmaPy Community" -release = "0.1.0" +release = "2024.7.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -21,6 +21,7 @@ "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "sphinx_automodapi.automodapi", + "nbsphinx", ] exclude_patterns = [] From f5411f4bc40069e2f6e3b81b275928a25214b4b6 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 15:04:30 -0400 Subject: [PATCH 07/26] Add example notebook page --- docs/source/examples.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/source/examples.rst diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 0000000..aabc840 --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,18 @@ +.. _examples: + +Examples +======== + +This page provides a gallery of example notebooks. + +.. contents:: + :local: + + +Tutorials +--------- + +.. nbgallery:: + + ../notebooks/astropy-units-completed + ../notebooks/particles-formulary-completed From d901833dd450c4446712fe676e5c792997a443ea Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:32:55 -0400 Subject: [PATCH 08/26] Add notebook gallery --- docs/source/examples.rst | 4 ++-- docs/source/index.rst | 1 + .../source/notebooks}/astropy-units-completed.ipynb | 0 .../source}/particles-formulary-completed.ipynb | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename {notebooks => docs/source/notebooks}/astropy-units-completed.ipynb (100%) rename {notebooks => docs/source}/particles-formulary-completed.ipynb (100%) diff --git a/docs/source/examples.rst b/docs/source/examples.rst index aabc840..6f029e6 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -13,6 +13,6 @@ Tutorials --------- .. nbgallery:: + :glob: - ../notebooks/astropy-units-completed - ../notebooks/particles-formulary-completed + notebooks/* diff --git a/docs/source/index.rst b/docs/source/index.rst index 58c6e67..a82db7f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,6 +5,7 @@ PlasmaPy Summer School 2024 Documentation :maxdepth: 2 :caption: Contents: + examples densities frequencies lengths diff --git a/notebooks/astropy-units-completed.ipynb b/docs/source/notebooks/astropy-units-completed.ipynb similarity index 100% rename from notebooks/astropy-units-completed.ipynb rename to docs/source/notebooks/astropy-units-completed.ipynb diff --git a/notebooks/particles-formulary-completed.ipynb b/docs/source/particles-formulary-completed.ipynb similarity index 100% rename from notebooks/particles-formulary-completed.ipynb rename to docs/source/particles-formulary-completed.ipynb From 8e9b66303095ee73d8d4b5a92c9e34e12c9ad905 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:36:16 -0400 Subject: [PATCH 09/26] Move notebook to correct location --- docs/source/{ => notebooks}/particles-formulary-completed.ipynb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/source/{ => notebooks}/particles-formulary-completed.ipynb (100%) diff --git a/docs/source/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb similarity index 100% rename from docs/source/particles-formulary-completed.ipynb rename to docs/source/notebooks/particles-formulary-completed.ipynb From bd8cc30c50fcfdbd1dc869bd5f50a68c3e78699f Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:36:40 -0400 Subject: [PATCH 10/26] Update top-level toctree --- docs/source/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index a82db7f..bf37bc6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,15 +2,14 @@ PlasmaPy Summer School 2024 Documentation ========================================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Contents: - examples densities frequencies lengths speeds - + examples Indices and tables ================== From 4fb5d322fe002614a7f13ee91132f4e0bc3d5cd9 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:37:00 -0400 Subject: [PATCH 11/26] Include notebook names in nbgallery directive --- docs/source/examples.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 6f029e6..2418fca 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -15,4 +15,5 @@ Tutorials .. nbgallery:: :glob: - notebooks/* + notebooks/astropy-units-completed + notebooks/plasmapy-particles-completed From 0b6ddff97a31ded49662f4996720e42cc11914da Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:37:59 -0400 Subject: [PATCH 12/26] Fix filename --- docs/source/examples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 2418fca..017a920 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -16,4 +16,4 @@ Tutorials :glob: notebooks/astropy-units-completed - notebooks/plasmapy-particles-completed + notebooks/particles-formulary-completed From 6af63602779f58accbea1ea5b6178a746963371e Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 16:59:25 -0400 Subject: [PATCH 13/26] Stop using backticks in Markdown cells of rendered docs Having [`astropy.units`](...) works fine in normal Markdown and on Google Colab, but the formatting gets messy when rendered by nbsphinx. --- .../notebooks/astropy-units-completed.ipynb | 675 ++++-------------- 1 file changed, 128 insertions(+), 547 deletions(-) diff --git a/docs/source/notebooks/astropy-units-completed.ipynb b/docs/source/notebooks/astropy-units-completed.ipynb index 9816da8..164a31f 100644 --- a/docs/source/notebooks/astropy-units-completed.ipynb +++ b/docs/source/notebooks/astropy-units-completed.ipynb @@ -15,9 +15,9 @@ "nbsphinx": "hidden" }, "source": [ - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", "\n", - "This tutorial introduces us to [`astropy.units`], which is heavily used in PlasmaPy." + "This tutorial introduces us to [astropy.units], which is heavily used in PlasmaPy." ] }, { @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "33fe81d8-c1e6-4fe2-97a4-d62ce59b94b3", "metadata": {}, "outputs": [], @@ -70,18 +70,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "ee456b33", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "25.0\n" - ] - } - ], + "outputs": [], "source": [ "distance_in_miles = 50\n", "time_in_hours = 2\n", @@ -94,13 +86,13 @@ "id": "93658d4f", "metadata": {}, "source": [ - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", - "[`plasmapy.particles`]: ../../particles/index.rst\n", - "[`plasmapy.formulary`]: ../../formulary/index.rst\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/latest/particles/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/latest/formulary/index.html\n", "\n", "Representing a physical quantity as a number has risks. We might unknowingly perform operations with different units, like `time_in_seconds + time_in_hours`. We might even accidentally perform operations with physically incompatible units, like `length + time`, without catching our mistake. Unit conversion errors can be costly mistakes, such as the loss of spacecraft like the [Mars Climate Orbiter](https://science.nasa.gov/mission/mars-climate-orbiter).\n", "\n", - "We can avoid these problems by using a units package. This notebook introduces [`astropy.units`] with an emphasis on the functionality needed to work with [`plasmapy.particles`] and [`plasmapy.formulary`]. We typically import [`astropy.units`] subpackage as `u`." + "We can avoid these problems by using a units package. This notebook introduces [astropy.units] with an emphasis on the functionality needed to work with [plasmapy.particles] and [plasmapy.formulary]. We typically import [astropy.units] subpackage as `u`." ] }, { @@ -121,18 +113,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "8437650a", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "60.0 km\n" - ] - } - ], + "outputs": [], "source": [ "distance = 60 * u.km\n", "print(distance)" @@ -143,28 +127,17 @@ "id": "ccc6659f", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "This operation creates a [`Quantity`] object: a number, sequence, or array that has been assigned a physical unit." + "This operation creates a [Quantity] object: a number, sequence, or array that has been assigned a physical unit." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "a6354b93", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "astropy.units.quantity.Quantity" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "type(distance)" ] @@ -174,14 +147,14 @@ "id": "744a9b01", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "We can create an object by using the [`Quantity`] class itself." + "We can create an object by using the [Quantity] class itself." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "8d2bb681", "metadata": {}, "outputs": [], @@ -194,31 +167,17 @@ "id": "1bb28951", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "We can create [`Quantity`] objects with compound units." + "We can create [Quantity] objects with compound units." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "4932159b", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$88 \\; \\mathrm{\\frac{mi}{h}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "88 * u.imperial.mile / u.hour" ] @@ -228,31 +187,17 @@ "id": "9cc9adba", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "We can even create [`Quantity`] objects that are explicitly dimensionless. " + "We can even create [Quantity] objects that are explicitly dimensionless. " ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "09e9752c", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$3 \\; \\mathrm{}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "3 * u.dimensionless_unscaled" ] @@ -262,55 +207,27 @@ "id": "4cffc8a0", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "We can also create a [`Quantity`] based off of a NumPy array or a list." + "We can also create a [Quantity] based off of a NumPy array or a list." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "e3235d80", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$[2.5,~3.2,~1.1] \\; \\mathrm{kg}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "np.array([2.5, 3.2, 1.1]) * u.kg" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "a686fd93", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$[2,~3,~4] \\; \\mathrm{\\frac{m}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "[2, 3, 4] * u.m / u.s" ] @@ -322,31 +239,17 @@ "source": [ "## Unit operations\n", "\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "Operations between [`Quantity`] objects handle unit conversions automatically. We can add [`Quantity`] objects together as long as their units have the same physical type." + "Operations between [Quantity] objects handle unit conversions automatically. We can add [Quantity] objects together as long as their units have the same physical type." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "65e08284", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$1.25 \\; \\mathrm{m}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "1 * u.m + 25 * u.cm" ] @@ -361,18 +264,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "c36788db", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.5 km / min\n" - ] - } - ], + "outputs": [], "source": [ "velocity = distance / time\n", "print(velocity)" @@ -380,18 +275,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "04b48a57", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3600.0 km2\n" - ] - } - ], + "outputs": [], "source": [ "area = distance**2\n", "print(area)" @@ -407,38 +294,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "0877feb6", "metadata": { "tags": [ "raises-exception" ] }, - "outputs": [ - { - "ename": "UnitConversionError", - "evalue": "Can only apply 'add' function to quantities with compatible dimensions", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:77\u001b[0m, in \u001b[0;36mget_converters_and_unit\u001b[0;34m(f, unit1, unit2)\u001b[0m\n\u001b[1;32m 76\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 77\u001b[0m converters[changeable] \u001b[38;5;241m=\u001b[39m \u001b[43mget_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43munit2\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit1\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError:\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:42\u001b[0m, in \u001b[0;36mget_converter\u001b[0;34m(from_unit, to_unit)\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Like Unit._get_converter, except returns None if no scaling is needed,\u001b[39;00m\n\u001b[1;32m 40\u001b[0m \u001b[38;5;124;03mi.e., if the inferred scale is unity.\u001b[39;00m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m---> 42\u001b[0m converter \u001b[38;5;241m=\u001b[39m \u001b[43mfrom_unit\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mto_unit\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 43\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01mif\u001b[39;00m converter \u001b[38;5;129;01mis\u001b[39;00m unit_scale_converter \u001b[38;5;28;01melse\u001b[39;00m converter\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1125\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1123\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mlambda\u001b[39;00m v: b(converter(v))\n\u001b[0;32m-> 1125\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1108\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1107\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1108\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_apply_equivalencies\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mother\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_normalize_equivalencies\u001b[49m\u001b[43m(\u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1112\u001b[0m \u001b[38;5;66;03m# Last hope: maybe other knows how to do it?\u001b[39;00m\n\u001b[1;32m 1113\u001b[0m \u001b[38;5;66;03m# We assume the equivalencies have the unit itself as first item.\u001b[39;00m\n\u001b[1;32m 1114\u001b[0m \u001b[38;5;66;03m# TODO: maybe better for other to have a `_back_converter` method?\u001b[39;00m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1086\u001b[0m, in \u001b[0;36mUnitBase._apply_equivalencies\u001b[0;34m(self, unit, other, equivalencies)\u001b[0m\n\u001b[1;32m 1084\u001b[0m other_str \u001b[38;5;241m=\u001b[39m get_err_str(other)\n\u001b[0;32m-> 1086\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00munit_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mother_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m are not convertible\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mUnitConversionError\u001b[0m: 's' (time) and 'm' (length) are not convertible", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;241;43m3\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mm\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43ms\u001b[49m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity.py:696\u001b[0m, in \u001b[0;36mQuantity.__array_ufunc__\u001b[0;34m(self, function, method, *inputs, **kwargs)\u001b[0m\n\u001b[1;32m 694\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mNotImplemented\u001b[39m\n\u001b[1;32m 695\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 696\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity.py:641\u001b[0m, in \u001b[0;36mQuantity.__array_ufunc__\u001b[0;34m(self, function, method, *inputs, **kwargs)\u001b[0m\n\u001b[1;32m 636\u001b[0m \u001b[38;5;66;03m# Determine required conversion functions -- to bring the unit of the\u001b[39;00m\n\u001b[1;32m 637\u001b[0m \u001b[38;5;66;03m# input to that expected (e.g., radian for np.sin), or to get\u001b[39;00m\n\u001b[1;32m 638\u001b[0m \u001b[38;5;66;03m# consistent units between two inputs (e.g., in np.add) --\u001b[39;00m\n\u001b[1;32m 639\u001b[0m \u001b[38;5;66;03m# and the unit of the result (or tuple of units for nout > 1).\u001b[39;00m\n\u001b[1;32m 640\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 641\u001b[0m converters, unit \u001b[38;5;241m=\u001b[39m \u001b[43mconverters_and_unit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfunction\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43minputs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 643\u001b[0m out \u001b[38;5;241m=\u001b[39m kwargs\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mout\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 644\u001b[0m \u001b[38;5;66;03m# Avoid loop back by turning any Quantity output into array views.\u001b[39;00m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/converters.py:181\u001b[0m, in \u001b[0;36mconverters_and_unit\u001b[0;34m(function, method, *args)\u001b[0m\n\u001b[1;32m 178\u001b[0m units \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28mgetattr\u001b[39m(arg, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124munit\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;28;01mfor\u001b[39;00m arg \u001b[38;5;129;01min\u001b[39;00m args]\n\u001b[1;32m 180\u001b[0m \u001b[38;5;66;03m# Determine possible conversion functions, and the result unit.\u001b[39;00m\n\u001b[0;32m--> 181\u001b[0m converters, result_unit \u001b[38;5;241m=\u001b[39m \u001b[43mufunc_helper\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfunction\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43munits\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 183\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28many\u001b[39m(converter \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m \u001b[38;5;28;01mfor\u001b[39;00m converter \u001b[38;5;129;01min\u001b[39;00m converters):\n\u001b[1;32m 184\u001b[0m \u001b[38;5;66;03m# for multi-argument ufuncs with a quantity and a non-quantity,\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \u001b[38;5;66;03m# the quantity normally needs to be dimensionless, *except*\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 188\u001b[0m \u001b[38;5;66;03m# can just have the unit of the quantity\u001b[39;00m\n\u001b[1;32m 189\u001b[0m \u001b[38;5;66;03m# (this allows, e.g., `q > 0.` independent of unit)\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 191\u001b[0m \u001b[38;5;66;03m# Don't fold this loop in the test above: this rare case\u001b[39;00m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;66;03m# should not make the common case slower.\u001b[39;00m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:79\u001b[0m, in \u001b[0;36mget_converters_and_unit\u001b[0;34m(f, unit1, unit2)\u001b[0m\n\u001b[1;32m 77\u001b[0m converters[changeable] \u001b[38;5;241m=\u001b[39m get_converter(unit2, unit1)\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError:\n\u001b[0;32m---> 79\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\n\u001b[1;32m 80\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCan only apply \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mf\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m function to quantities \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 81\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwith compatible dimensions\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 82\u001b[0m )\n\u001b[1;32m 84\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m converters, unit1\n", - "\u001b[0;31mUnitConversionError\u001b[0m: Can only apply 'add' function to quantities with compatible dimensions" - ] - } - ], + "outputs": [], "source": [ "3 * u.m + 3 * u.s" ] @@ -448,26 +311,18 @@ "id": "4f0c461e", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", - "[`numpy.ndarray`]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[numpy.ndarray]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html\n", "\n", - "[`Quantity`] arrays behave very similarly to NumPy arrays. In fact, [`Quantity`] is a subclass of [`numpy.ndarray`]." + "[Quantity] arrays behave very similarly to NumPy arrays. In fact, [Quantity] is a subclass of [numpy.ndarray]." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "59389429", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "656.0 nm\n" - ] - } - ], + "outputs": [], "source": [ "balmer_series = [656, 486, 434, 410] * u.nm\n", "Hα = balmer_series[0]\n", @@ -476,24 +331,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "a6545132", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$656 \\; \\mathrm{nm}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "np.max(balmer_series)" ] @@ -506,10 +347,10 @@ "[NumPy]: https://numpy.org/\n", "[SciPy]: https://scipy.org/\n", "\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "[lose their units]: https://docs.astropy.org/en/stable/known_issues.html#quantities-lose-their-units-with-some-operations\n", "\n", - "⚠️ Most frequently encountered [NumPy] and [SciPy] functions can be used with [`Quantity`] objects. However, there are some functions that cause [`Quantity`] objects to [lose their units]!" + "⚠️ Most frequently encountered [NumPy] and [SciPy] functions can be used with [Quantity] objects. However, there are some functions that cause [Quantity] objects to [lose their units]!" ] }, { @@ -519,56 +360,28 @@ "source": [ "## Unit conversions\n", "\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", - "[`to`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[.to]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", "\n", - "The [`to`] method allows us to convert a [`Quantity`] to different units of the same physical type. This method accepts strings that represent a unit (including compound units) or a unit object." + "The [.to] method allows us to convert a [Quantity] to different units of the same physical type. This method accepts strings that represent a unit (including compound units) or a unit object." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "80f4f133", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "velocity.to(\"m/s\")" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "21c46ef1", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "velocity.to(u.m / u.s)" ] @@ -578,57 +391,29 @@ "id": "c1e742ca", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", - "[`si`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.si\n", - "[`cgs`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.cgs\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[si]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.si\n", + "[cgs]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.cgs\n", "\n", - "The [`si`] and [`cgs`] attributes convert the [`Quantity`] to SI or CGS units, respectively. " + "The [si] and [cgs] attributes convert the [Quantity] to SI or CGS units, respectively. " ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "02bda4d2", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "velocity.si" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "560a0cfe", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$833.33333 \\; \\mathrm{\\frac{cm}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "velocity.cgs" ] @@ -648,21 +433,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "24e8403c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "120.0" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "time.value" ] @@ -672,32 +446,18 @@ "id": "4c6c027b", "metadata": {}, "source": [ - "[`unit`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.unit\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[unit]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.unit\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "The [`unit`] attribute of a [`Quantity`] provides the unit without the value." + "The [unit] attribute of a [Quantity] provides the unit without the value." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "2c41a2fa", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\mathrm{min}$" - ], - "text/plain": [ - "Unit(\"min\")" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "time.unit" ] @@ -723,30 +483,14 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "6b6fafd6", "metadata": { "tags": [ "raises-exception" ] }, - "outputs": [ - { - "ename": "UnitConversionError", - "evalue": "'eV' (energy/torque/work) and 'K' (temperature) are not convertible", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[22], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meV\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mto\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mK\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1196\u001b[0m, in \u001b[0;36mUnitBase.to\u001b[0;34m(self, other, value, equivalencies)\u001b[0m\n\u001b[1;32m 1194\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m UNITY\n\u001b[1;32m 1195\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1196\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mUnit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mother\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m(value)\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1125\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1122\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1123\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mlambda\u001b[39;00m v: b(converter(v))\n\u001b[0;32m-> 1125\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1108\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1106\u001b[0m \u001b[38;5;66;03m# if that doesn't work, maybe we can do it with equivalencies?\u001b[39;00m\n\u001b[1;32m 1107\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1108\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_apply_equivalencies\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mother\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_normalize_equivalencies\u001b[49m\u001b[43m(\u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1112\u001b[0m \u001b[38;5;66;03m# Last hope: maybe other knows how to do it?\u001b[39;00m\n\u001b[1;32m 1113\u001b[0m \u001b[38;5;66;03m# We assume the equivalencies have the unit itself as first item.\u001b[39;00m\n\u001b[1;32m 1114\u001b[0m \u001b[38;5;66;03m# TODO: maybe better for other to have a `_back_converter` method?\u001b[39;00m\n\u001b[1;32m 1115\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(other, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mequivalencies\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1086\u001b[0m, in \u001b[0;36mUnitBase._apply_equivalencies\u001b[0;34m(self, unit, other, equivalencies)\u001b[0m\n\u001b[1;32m 1083\u001b[0m unit_str \u001b[38;5;241m=\u001b[39m get_err_str(unit)\n\u001b[1;32m 1084\u001b[0m other_str \u001b[38;5;241m=\u001b[39m get_err_str(other)\n\u001b[0;32m-> 1086\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00munit_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mother_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m are not convertible\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mUnitConversionError\u001b[0m: 'eV' (energy/torque/work) and 'K' (temperature) are not convertible" - ] - } - ], + "outputs": [], "source": [ "u.eV.to(\"K\")" ] @@ -756,33 +500,19 @@ "id": "9299c8a1", "metadata": {}, "source": [ - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", "[equivalencies]: https://docs.astropy.org/en/stable/units/equivalencies.html\n", - "[`temperature_energy()`]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", + "[temperature_energy()]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", "\n", - "To handle non-standard unit conversions, [`astropy.units`] allows the use of [equivalencies]. The conversion from eV to K can be done by using the [`temperature_energy()`] equivalency." + "To handle non-standard unit conversions, [astropy.units] allows the use of [equivalencies]. The conversion from eV to K can be done by using the [temperature_energy()] equivalency." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "afac5b4a", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$11604.518 \\; \\mathrm{K}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "(1 * u.eV).to(\"K\", equivalencies=u.temperature_energy())" ] @@ -792,34 +522,19 @@ "id": "b4c492cc", "metadata": {}, "source": [ - "[`dimensionless_angles()`]: https://docs.astropy.org/en/stable/api/astropy.units.equivalencies.dimensionless_angles.html#dimensionless-angles\n", - "\n", + "[dimensionless_angles()]: https://docs.astropy.org/en/stable/api/astropy.units.equivalencies.dimensionless_angles.html#dimensionless-angles\n", "[frequency]: https://en.wikipedia.org/wiki/Frequency\n", "[angular frequency]: https://en.wikipedia.org/wiki/Angular_frequency\n", "\n", - "Radians are treated dimensionlessly when the [`dimensionless_angles()`] equivalency is in effect. Note that this equivalency does not account for the multiplicative factor of $2π$ that is used when converting between [frequency] and [angular frequency]." + "Radians are treated dimensionlessly when the [dimensionless_angles()] equivalency is in effect. Note that this equivalency does not account for the multiplicative factor of $2π$ that is used when converting between [frequency] and [angular frequency]." ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "9735710b", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$3.2 \\; \\mathrm{\\frac{1}{s}}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "(3.2 * u.rad / u.s).to(\"1 / s\", equivalencies=u.dimensionless_angles())" ] @@ -837,29 +552,17 @@ "id": "b714de6a", "metadata": {}, "source": [ - "[`astropy.constants`]: https://docs.astropy.org/en/stable/constants/index.html\n", + "[astropy.constants]: https://docs.astropy.org/en/stable/constants/index.html\n", "\n", - "few can use [`astropy.constants`] to access the most commonly needed physical constants." + "few can use [astropy.constants] to access the most commonly needed physical constants." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "746a79a7", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Name = Speed of light in vacuum\n", - " Value = 299792458.0\n", - " Uncertainty = 0.0\n", - " Unit = m / s\n", - " Reference = CODATA 2018\n" - ] - } - ], + "outputs": [], "source": [ "print(constants.c)" ] @@ -869,27 +572,19 @@ "id": "c3ee9feb", "metadata": {}, "source": [ - "[`Constant`]: https://docs.astropy.org/en/stable/api/astropy.constants.Constant.html#astropy.constants.Constant\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", - "[`u.temperature_energy()`]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", + "[Constant]: https://docs.astropy.org/en/stable/api/astropy.constants.Constant.html#astropy.constants.Constant\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[u.temperature_energy()]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", "\n", - "A [`Constant`] behaves very similarly to a [`Quantity`]. For example, we can use the Boltzmann constant to mimic the behavior of [`u.temperature_energy()`]." + "A [Constant] behaves very similarly to a [Quantity]. For example, we can use the Boltzmann constant to mimic the behavior of [u.temperature_energy()]." ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "id": "d2d59d08", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "6.962710872930049 MK\n" - ] - } - ], + "outputs": [], "source": [ "thermal_energy_per_particle = 0.6 * u.keV\n", "temperature = thermal_energy_per_particle / constants.k_B\n", @@ -906,51 +601,24 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "015de7fc", "metadata": { "tags": [ "raises-exception" ] }, - "outputs": [ - { - "ename": "TypeError", - "evalue": "Constant 'e' does not have physically compatible units across all systems of units and cannot be combined with other values without specifying a system (eg. e.emu)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;241;43m2\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mconstants\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43me\u001b[49m\n", - "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/constants/constant.py:49\u001b[0m, in \u001b[0;36mConstantMeta.__new__..wrap..wrapper\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 47\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msystem \u001b[38;5;129;01mand\u001b[39;00m name_lower \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_has_incompatible_units:\n\u001b[1;32m 48\u001b[0m systems \u001b[38;5;241m=\u001b[39m \u001b[38;5;28msorted\u001b[39m(x \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m instances \u001b[38;5;28;01mif\u001b[39;00m x)\n\u001b[0;32m---> 49\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 50\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mConstant \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mabbrev\u001b[38;5;132;01m!r}\u001b[39;00m\u001b[38;5;124m does not have physically compatible \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 51\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124munits across all systems of units and cannot be \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcombined with other values without specifying a \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 53\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msystem (eg. \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mabbrev\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00msystems[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m)\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 54\u001b[0m )\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m meth(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n", - "\u001b[0;31mTypeError\u001b[0m: Constant 'e' does not have physically compatible units across all systems of units and cannot be combined with other values without specifying a system (eg. e.emu)" - ] - } - ], + "outputs": [], "source": [ "2 * constants.e" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "1a90c979", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$3.2043533 \\times 10^{-19} \\; \\mathrm{C}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "2 * constants.e.si" ] @@ -977,14 +645,14 @@ "id": "90ced3d3-aa0d-47b7-b526-359e466e5cb5", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "Astropy has built-in support for plotting [`Quantity`] objects. Let's plot the number density of electrons in the solar wind using an empirical formula given by [Kruparova et al. (2023)](https://iopscience.iop.org/article/10.3847/1538-4357/acf572), which has a range of validity from 13 to 50 solar radii." + "Astropy has built-in support for plotting [Quantity] objects. Let's plot the number density of electrons in the solar wind using an empirical formula given by [Kruparova et al. (2023)](https://iopscience.iop.org/article/10.3847/1538-4357/acf572), which has a range of validity from 13 to 50 solar radii." ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "id": "2fce5587-16cf-40ea-baa5-dbd9023281c3", "metadata": {}, "outputs": [], @@ -1004,7 +672,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "id": "7a734940-a970-4991-9ada-f0844ae80567", "metadata": {}, "outputs": [], @@ -1012,41 +680,20 @@ "n_e = 343_466 * u.cm**-3 * (radii / constants.R_sun) ** -1.87" ] }, - { - "cell_type": "markdown", - "id": "1ed451a9-909f-40ae-bec7-c67d8767545c", - "metadata": {}, - "source": [ - "We can use the [`astropy.visualization.quantity_support`] to help with plotting `Quantity` objects against each other.\n", - "\n", - "Let's do some imports." - ] - }, { "cell_type": "markdown", "id": "07547260-da42-40b7-98de-693b60dbb788", "metadata": {}, "source": [ - "Will make use make use of [`astropy.visualization.quantity_support`](https://docs.astropy.org/en/stable/api/astropy.visualization.quantity_support.html). This is a [_context manager_](https://realpython.com/python-with-statement/), which means that we use the `with` statement." + "Will make use make use of [astropy.visualization.quantity_support](https://docs.astropy.org/en/stable/api/astropy.visualization.quantity_support.html). This is a [_context manager_](https://realpython.com/python-with-statement/), which means that we use the `with` statement." ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "id": "a4563556-64d3-4b7e-80f6-7df22ccf4488", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "with quantity_support():\n", " plt.figure()\n", @@ -1062,29 +709,20 @@ "## Optimizing unit operations (if time)\n", "\n", "[performance tips]: https://docs.astropy.org/en/stable/units/index.html#performance-tips\n", - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", - "[`%timeit`]: https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", + "[%timeit]: https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit\n", "\n", - "Astropy's documentation includes [performance tips] for using [`astropy.units`] in computationally intensive situations. We can test it with [`%timeit`], which runs a command repeatedly to see how long it takes.\n", + "Astropy's documentation includes [performance tips] for using [astropy.units] in computationally intensive situations. We can test it with [%timeit], which runs a command repeatedly to see how long it takes.\n", "\n", "Putting compound units in parentheses speeds things up by reducing the number of copies made by the operation." ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "e3bc2348", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "11.2 µs ± 836 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n", - "6.71 µs ± 231 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit 1.6 * u.barn * u.Mpc\n", "%timeit 1.6 * (u.barn * u.Mpc)" @@ -1095,25 +733,17 @@ "id": "4ff6e798-91dd-460d-bf29-172a95bf34b7", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "We can assign a unit to a value using the [`Quantity`] class directly." + "We can assign a unit to a value using the [Quantity] class directly." ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "868b008e-1560-4edd-96ee-7ec1c00aca16", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5.45 µs ± 88.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit u.Quantity(1.6, u.barn * u.Mpc)" ] @@ -1128,18 +758,10 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "id": "3ecb16da-9dc3-4a62-a03a-e3afb312ae84", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.85 µs ± 19.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "volume_unit = u.barn * u.Mpc # pre-define the unit\n", "%timeit u.Quantity(1.6, volume_unit)" @@ -1159,50 +781,28 @@ "metadata": {}, "source": [ "[physical type]: https://docs.astropy.org/en/stable/units/physical_types.html\n", - "[`physical_type`]: https://docs.astropy.org/en/stable/api/astropy.units.UnitBase.html#astropy.units.UnitBase.physical_type\n", - "[`get_physical_type()`]: https://docs.astropy.org/en/stable/api/astropy.units.get_physical_type.html#astropy.units.get_physical_type\n", + "[physical_type]: https://docs.astropy.org/en/stable/api/astropy.units.UnitBase.html#astropy.units.UnitBase.physical_type\n", + "[get_physical_type()]: https://docs.astropy.org/en/stable/api/astropy.units.get_physical_type.html#astropy.units.get_physical_type\n", "\n", - "A [physical type] corresponds to physical quantities with dimensionally compatible units. Astropy has functionality that represents different physical types. These physical type objects can be accessed using either the [`physical_type`] attribute of a unit or [`get_physical_type()`]." + "A [physical type] corresponds to physical quantities with dimensionally compatible units. Astropy has functionality that represents different physical types. These physical type objects can be accessed using either the [physical_type] attribute of a unit or [get_physical_type()]." ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "id": "da6c9c7d", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PhysicalType({'diffusivity', 'kinematic viscosity'})" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "(u.m**2 / u.s).physical_type" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "f0d49b03", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PhysicalType('number density')" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "u.get_physical_type(\"number density\")" ] @@ -1217,18 +817,10 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "d03b8eb0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "energy flux/irradiance\n" - ] - } - ], + "outputs": [], "source": [ "energy_density = (u.J * u.m**-3).physical_type\n", "velocity = u.get_physical_type(\"velocity\")\n", @@ -1245,21 +837,10 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "id": "2371647f-f29d-41cb-b89d-226d4743ce26", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PhysicalType('absement')" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "u.get_physical_type(u.m * u.s)" ] @@ -1282,7 +863,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.12.4" } }, "nbformat": 4, From bf5785f01bec0aaff247822a292ca676ba0fdbdd Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 17:13:12 -0400 Subject: [PATCH 14/26] Update pip installation in .readthedocs.yaml I'm trying to resolve a problem where RTD isn't finding the `hack` package. --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 665ba33..725c604 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -15,4 +15,6 @@ sphinx: python: install: + - method: pip + path: . - requirements: docs/requirements.txt From a23077da6c71dce2519c8aca407664ef342d31c8 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 17:16:09 -0400 Subject: [PATCH 15/26] Change a test Formerly it was expected to fail, but now it passes. --- tests/test_example.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index 41b0cad..0157aaa 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -1,3 +1,6 @@ -def test_that_intentionally_fails(): - """An example that is designed to fail.""" - assert 6 * 9 == 42 +"""Example tests.""" + + +def test_multiplication(): + """An sample test.""" + assert 4 * 5 == 20 From 58a59ea148f1e417e891a49bbd28458c352b7d92 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 17:19:00 -0400 Subject: [PATCH 16/26] Update docs/README.md I removed some PlasmaPy-specific stuff. --- docs/README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/README.md b/docs/README.md index e65dc75..2c4a3fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,18 +14,11 @@ > To learn more about writing documentation, please check out the > [**documentation guide**]. -PlasmaPy's documentation is written in [reStructuredText], built using -[Sphinx] via a [Nox] session, and hosted by [Read the Docs]. - -The "stable" documentation corresponds to PlasmaPy's most recent -release, and can be found at https://docs.plasmapy.org/en/stable. The -"latest" documentation corresponds to the current state of the `main` -branch on PlasmaPy's GitHub repository, and is available at -https://docs.plasmapy.org/en/latest. +The [`docs`] directory contains the source files for the narrative +documentation. The configuration file is [`docs/source/conf.py`]. -The [`docs`] directory contains the source files for PlasmaPy's -narrative documentation. The configuration file is -[`docs/source/conf.py`]. +The documentation is written in [reStructuredText], built using +[Sphinx] via a [Nox] session, and hosted by [Read the Docs]. ## Building documentation @@ -42,7 +35,8 @@ python -m pip install nox uv ``` > [!NOTE] -> It may also be necessary to [install graphviz] and [install pandoc]. +> It may also be necessary to [install pandoc] (and possibly [install graphviz]) +> if you want to build the documentation locally. The documentation can be built by going to the top-level directory of your clone of PlasmaPy and running: From 918b5c71e2977adaa0142f8cd28308dba7313a2e Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:32:11 -0400 Subject: [PATCH 17/26] Shorten CI names --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfbc5fa..c08c1e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,17 +31,17 @@ jobs: matrix: include: - - name: Tests, Python 3.12, Ubuntu + - name: Tests os: ubuntu-latest python: '3.12' nox_session: tests - - name: Documentation, Python 3.12, Ubuntu + - name: Documentation os: ubuntu-latest python: '3.12' nox_session: docs - - name: Packaging, Python 3.12, Ubuntu + - name: Packaging os: ubuntu-latest python: '3.12' nox_session: build From 84ca39b6c2a165c1bc9f790eaf9c2543cf58cd93 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:39:57 -0400 Subject: [PATCH 18/26] Minor reStructuredText fix --- docs/source/notebooks/astropy-units-completed.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/notebooks/astropy-units-completed.ipynb b/docs/source/notebooks/astropy-units-completed.ipynb index 164a31f..672ce8e 100644 --- a/docs/source/notebooks/astropy-units-completed.ipynb +++ b/docs/source/notebooks/astropy-units-completed.ipynb @@ -425,10 +425,10 @@ "source": [ "## Detaching units and values\n", "\n", - "[`value`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.value \n", - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[value]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.value \n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "The [`value`] attribute of a [`Quantity`] provides the number or array *without* the unit." + "The [value] attribute of a [Quantity] provides the number or array *without* the unit." ] }, { From d174924cc791416037765433ba528a984e1a70e3 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:41:33 -0400 Subject: [PATCH 19/26] Minor fixes --- .../particles-formulary-completed.ipynb | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index bca09e8..3babc16 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -13,13 +13,13 @@ "id": "c8c364e5", "metadata": {}, "source": [ - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", - "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "[decorators]: https://www.geeksforgeeks.org/decorators-in-python/\n", "[Type hint annotations]: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html\n", "\n", - "We'll start this notebook with a quick refresher on [`astropy.units`], before going through examples from [`plasmapy.particles`] ⚛️ and [`plasmapy.formulary`] 🧮. After an interlude about [type hint annotations] and [decorators], we'll \n", + "We'll start this notebook with a quick refresher on [astropy.units], before going through examples from [plasmapy.particles] ⚛️ and [plasmapy.formulary] 🧮. After an interlude about [type hint annotations] and [decorators], we'll \n", "\n", "Let's start with some preliminary imports & settings. To execute a cell in a Jupyter notebook, press `Shift + Enter`. If you have to restart the notebook, please re-execute the following cell.\n", "\n", @@ -60,9 +60,9 @@ "id": "93658d4f", "metadata": {}, "source": [ - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", "\n", - "We'll be using [`astropy.units`] throughout the rest of this notebook, so we'll begin with a brief reminder o fhow to use it. We typically import this subpackage as `u`." + "We'll be using [astropy.units] throughout the rest of this notebook, so we'll begin with a brief reminder o fhow to use it. We typically import this subpackage as `u`." ] }, { @@ -88,9 +88,9 @@ "id": "ccc6659f", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "\n", - "This operation creates a [`Quantity`] object: a number, sequence, or array that has been assigned a physical unit. We can create [`Quantity`] objects with compound units." + "This operation creates a [Quantity] object: a number, sequence, or array that has been assigned a physical unit. We can create [Quantity] objects with compound units." ] }, { @@ -109,10 +109,10 @@ "id": "f9dd4743-ffab-40d7-9407-62840ea1c7ba", "metadata": {}, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "[`Quantity.to()`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", "\n", - "We can use [`Quantity.to()`] to convert a [`Quantity`] to different units." + "We can use [`Quantity.to()`] to convert a [Quantity] to different units." ] }, { @@ -138,9 +138,9 @@ "id": "0c7eeced", "metadata": {}, "source": [ - "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", "\n", - "The [`plasmapy.particles`] subpackage contains functions to access basic particle data and classes to represent particles." + "The [plasmapy.particles] subpackage contains functions to access basic particle data and classes to represent particles." ] }, { @@ -235,11 +235,11 @@ "metadata": {}, "source": [ "[mass number]: https://en.wikipedia.org/wiki/Mass_number\n", - "[`Quantity`]: https://docs.astropy.org/en/stable/units/quantity.html#quantity\n", - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[Quantity]: https://docs.astropy.org/en/stable/units/quantity.html#quantity\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", "[`half_life`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.atomic.half_life.html#half-life\n", "\n", - "We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. Let's use [`half_life`] to return the half-life of a radioactive particle in seconds as a [`Quantity`]." + "We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. Let's use [`half_life`] to return the half-life of a radioactive particle in seconds as a [Quantity]." ] }, { @@ -276,9 +276,9 @@ "metadata": {}, "source": [ "[particle-like]: https://docs.plasmapy.org/en/latest/glossary.html#term-particle-like\n", - "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", "\n", - "Functions in [`plasmapy.particles`] are quite flexible in terms of string inputs representing particles. An input is [particle-like] if it can be used to represent a physical particle. " + "Functions in [plasmapy.particles] are quite flexible in terms of string inputs representing particles. An input is [particle-like] if it can be used to represent a physical particle. " ] }, { @@ -582,9 +582,9 @@ "metadata": {}, "source": [ "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "[`CustomParticle`] objects can provided to most of the commonly used functions in [`plasmapy.formulary`], and we're planning to improve interoperability in future releases of PlasmaPy." + "[`CustomParticle`] objects can provided to most of the commonly used functions in [plasmapy.formulary], and we're planning to improve interoperability in future releases of PlasmaPy." ] }, { @@ -703,9 +703,9 @@ "metadata": {}, "source": [ "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "[`ParticleList`] objects can also be provided to the most commonly used functions in [`plasmapy.formulary`], with more complete interoperability expected in the future." + "[`ParticleList`] objects can also be provided to the most commonly used functions in [plasmapy.formulary], with more complete interoperability expected in the future." ] }, { @@ -858,9 +858,9 @@ "id": "58fb006b", "metadata": {}, "source": [ - "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", "\n", - "We can use [`plasmapy.particles`] to calculate the energy of a nuclear reaction using the `>` operator. " + "We can use [plasmapy.particles] to calculate the energy of a nuclear reaction using the `>` operator. " ] }, { @@ -925,9 +925,9 @@ "source": [ "## PlasmaPy formulary\n", "\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "The [`plasmapy.formulary`] subpackage contains a broad variety of formulas needed by plasma scientists across disciplines, in particular to calculate plasma parameters." + "The [plasmapy.formulary] subpackage contains a broad variety of formulas needed by plasma scientists across disciplines, in particular to calculate plasma parameters." ] }, { @@ -957,7 +957,7 @@ "\n", "[Plasma beta] ($β$) is one of the most fundamental plasma parameters. $β$ is the ratio of the plasma (gas) pressure to the magnetic pressure. How a plasma behaves depends strongly on $β$. When $β ≫ 1$, the magnetic field is not strong enough to exert much of a force on the plasma, so its motions start to resemble a gas. When $β ≪ 1$, magnetic tension and pressure are the dominant macroscopic forces. \n", "\n", - "Let's use [`plasmapy.formulary`](https://docs.plasmapy.org/en/stable/formulary/index.html) to calculate plasma β in different regions of the solar atmosphere and see what we can learn." + "Let's use [plasmapy.formulary](https://docs.plasmapy.org/en/stable/formulary/index.html) to calculate plasma β in different regions of the solar atmosphere and see what we can learn." ] }, { @@ -1066,9 +1066,9 @@ "metadata": {}, "source": [ "[magnetic reconnection]: https://en.wikipedia.org/wiki/Magnetic_reconnection\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "The [*Magnetospheric Multiscale Mission*](https://www.nasa.gov/mission_pages/mms/overview/index.html) (*MMS*) is a constellation of four identical spacecraft. The goal of *MMS* is to investigate the small-scale physics of [magnetic reconnection] in Earth's magnetosphere. In order to do this, the spacecraft need to orbit in a tight configuration. But how tight does the tetrahedron have to be? Let's use [`plasmapy.formulary`] to find out." + "The [*Magnetospheric Multiscale Mission*](https://www.nasa.gov/mission_pages/mms/overview/index.html) (*MMS*) is a constellation of four identical spacecraft. The goal of *MMS* is to investigate the small-scale physics of [magnetic reconnection] in Earth's magnetosphere. In order to do this, the spacecraft need to orbit in a tight configuration. But how tight does the tetrahedron have to be? Let's use [plasmapy.formulary] to find out." ] }, { @@ -1245,9 +1245,9 @@ "metadata": {}, "source": [ "[`lower_hybrid_frequency`]: https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.frequencies.lower_hybrid_frequency.html#lower-hybrid-frequency\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "Most of the functions in [`plasmapy.formulary`] have descriptions of the plasma parameters that include the formula and physical interpretation. If we ever forget what the [`lower_hybrid_frequency`] represents, we can check out its documentation page! " + "Most of the functions in [plasmapy.formulary] have descriptions of the plasma parameters that include the formula and physical interpretation. If we ever forget what the [`lower_hybrid_frequency`] represents, we can check out its documentation page! " ] }, { @@ -1263,9 +1263,9 @@ "id": "aac532e4-5249-458b-bf4b-aad05f36fc74", "metadata": {}, "source": [ - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "Now that we've gone through how to use [`plasmapy.particles`] and [`plasmapy.formulary`], we'll move on to the process for adding a function to [`plasmapy.formulary`]. Before that, we need to introduce two capabilities for the Python language:\n", + "Now that we've gone through how to use [plasmapy.particles] and [plasmapy.formulary], we'll move on to the process for adding a function to [plasmapy.formulary]. Before that, we need to introduce two capabilities for the Python language:\n", "\n", " - Type hint annotations\n", " - Decorators" @@ -1736,18 +1736,18 @@ "id": "227f37ab-5f1b-4a98-a34f-88ca38afce7c", "metadata": {}, "source": [ - "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", - "[`@validate_quantities`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", - "[`@check_relativistic`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", - "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[@validate_quantities]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[@check_relativistic]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", "PlasmaPy has three decorators that help us with writing formulary functions.\n", "\n", - " - [`@check_relativistic`]\n", - " - [`@validate_quantities`]\n", - " - [`@particle_input`]\n", + " - [@check_relativistic]\n", + " - [@validate_quantities]\n", + " - [@particle_input]\n", "\n", "Let's get some imports out of the way." ] @@ -1778,10 +1778,10 @@ "id": "9fab64f6-a591-456c-b482-7753c31e35da", "metadata": {}, "source": [ - "[`@check_relativistic`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[@check_relativistic]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "Many functions that return velocities in [`plasmapy.formulary`] assume that relativistic velocities are unimportant. To check for this, we can use the [`@check_relativistic`] decorator. " + "Many functions that return velocities in [plasmapy.formulary] assume that relativistic velocities are unimportant. To check for this, we can use the [@check_relativistic] decorator. " ] }, { @@ -1865,11 +1865,11 @@ "tags": [] }, "source": [ - "[`Quantity`]: https://docs.astropy.org/en/stable/units/quantity.html\n", - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/\n", - "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[Quantity]: https://docs.astropy.org/en/stable/units/quantity.html\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/\n", + "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "Most [`plasmapy.formulary`] functions accept and return [`Quantity`] objects from [`astropy.units`]. For example, we might write a function to calculate the magnetic pressure:\n", + "Most [plasmapy.formulary] functions accept and return [Quantity] objects from [astropy.units]. For example, we might write a function to calculate the magnetic pressure:\n", "\n", "$$ p_B ≡ \\frac{B^2}{2μ_o} $$" ] @@ -1918,9 +1918,9 @@ "id": "4c1fa3a1-128d-4c09-bde5-341ba0a3add6", "metadata": {}, "source": [ - "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[@validate_quantities]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", "\n", - "The [`@validate_quantities`] decorator makes sure that " + "The [@validate_quantities] decorator makes sure that " ] }, { @@ -1985,12 +1985,12 @@ "id": "1ea028ab-730e-4a83-bd49-a9e8d5799af3", "metadata": {}, "source": [ - "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[@validate_quantities]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", "[equivalency]: https://docs.astropy.org/en/stable/units/equivalencies.html\n", - "[`astropy.units`]: https://docs.astropy.org/en/stable/units/\n", + "[astropy.units]: https://docs.astropy.org/en/stable/units/\n", "\n", "\n", - "We can also provide arguments directly to [`@validate_quantities`], for example if we want to do a validation on the return value whilst using an [equivalency] from [`astropy.units`]." + "We can also provide arguments directly to [@validate_quantities], for example if we want to do a validation on the return value whilst using an [equivalency] from [astropy.units]." ] }, { @@ -2022,7 +2022,7 @@ "id": "bd2fe7dc-6304-4e4d-a259-9fd730be272c", "metadata": {}, "source": [ - "The docstring for [`@validate_quantities`](https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities) contains examples for several more validations that it can do, such as:\n", + "The docstring for [@validate_quantities](https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities) contains examples for several more validations that it can do, such as:\n", "\n", " - Allowing only non-negative values\n", " - Restricting/allowing `complex` values\n", @@ -2161,10 +2161,10 @@ "metadata": {}, "source": [ "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", - "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "\n", "\n", - "The argument got converted into a [`Particle`] object by [`@particle_input`].\n", + "The argument got converted into a [`Particle`] object by [@particle_input].\n", "\n", "If we add `Z` and `mass_numb` as parameters, they'll get incorporated into the particle." ] @@ -2196,9 +2196,9 @@ "id": "081c1b64-ebc4-4fe0-b76f-a0d36c5faa33", "metadata": {}, "source": [ - "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "\n", - "If we name the parameter `element`, `ion`, or `isotope`, then [`@particle_input`] will make sure that the particle matches that identity. " + "If we name the parameter `element`, `ion`, or `isotope`, then [@particle_input] will make sure that the particle matches that identity. " ] }, { @@ -2254,10 +2254,10 @@ "id": "9994fef5-6c29-4639-a3be-f186e271302d", "metadata": {}, "source": [ - "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", - "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[@validate_quantities]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "\n", - "Let's use both [`@particle_input`], [`@validate_quantities`], and [`check_relativistic`] to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " + "Let's use both [@particle_input], [@validate_quantities], and [`check_relativistic`] to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " ] }, { From ead67f8daf878fffc348346ac00a6ef74767912a Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:42:51 -0400 Subject: [PATCH 20/26] Revert notebooks --- notebooks/astropy-units-completed.ipynb | 1290 +++++++++ notebooks/particles-formulary-completed.ipynb | 2328 +++++++++++++++++ 2 files changed, 3618 insertions(+) create mode 100644 notebooks/astropy-units-completed.ipynb create mode 100644 notebooks/particles-formulary-completed.ipynb diff --git a/notebooks/astropy-units-completed.ipynb b/notebooks/astropy-units-completed.ipynb new file mode 100644 index 0000000..9816da8 --- /dev/null +++ b/notebooks/astropy-units-completed.ipynb @@ -0,0 +1,1290 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6e8f0d92", + "metadata": {}, + "source": [ + "# Using Astropy Units" + ] + }, + { + "cell_type": "markdown", + "id": "fbdc1fd8-6542-45a6-9375-c8016a15ac67", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "\n", + "This tutorial introduces us to [`astropy.units`], which is heavily used in PlasmaPy." + ] + }, + { + "cell_type": "markdown", + "id": "6d4214ce-e9a3-4e4a-a51c-6dcf3f00357b", + "metadata": {}, + "source": [ + "Let's start with some preliminary imports. To execute a cell in a Jupyter notebook, press **Shift + Enter**. If you need to restart the notebook, please execute this cell again.\n", + "\n", + "If using Google Colab, click **Run anyway** when prompted. (If prompted again, select **Restart runtime** when the installation finishes.)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "33fe81d8-c1e6-4fe2-97a4-d62ce59b94b3", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import sys\n", + "\n", + "if 'google.colab' in str(get_ipython()):\n", + " if 'plasmapy' not in sys.modules:\n", + " !pip install astropy matplotlib numpy\n", + "\n", + "import astropy.units as u\n", + "from astropy.visualization import quantity_support\n", + "from astropy import constants\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "7c7ff8c4-5846-4cfd-9634-8b2aa8d871fa", + "metadata": {}, + "source": [ + "## Motivation" + ] + }, + { + "cell_type": "markdown", + "id": "c566fe1c", + "metadata": {}, + "source": [ + "In scientific computing, we often represent physical quantities as numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ee456b33", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25.0\n" + ] + } + ], + "source": [ + "distance_in_miles = 50\n", + "time_in_hours = 2\n", + "velocity_in_mph = distance_in_miles / time_in_hours\n", + "print(velocity_in_mph)" + ] + }, + { + "cell_type": "markdown", + "id": "93658d4f", + "metadata": {}, + "source": [ + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[`plasmapy.particles`]: ../../particles/index.rst\n", + "[`plasmapy.formulary`]: ../../formulary/index.rst\n", + "\n", + "Representing a physical quantity as a number has risks. We might unknowingly perform operations with different units, like `time_in_seconds + time_in_hours`. We might even accidentally perform operations with physically incompatible units, like `length + time`, without catching our mistake. Unit conversion errors can be costly mistakes, such as the loss of spacecraft like the [Mars Climate Orbiter](https://science.nasa.gov/mission/mars-climate-orbiter).\n", + "\n", + "We can avoid these problems by using a units package. This notebook introduces [`astropy.units`] with an emphasis on the functionality needed to work with [`plasmapy.particles`] and [`plasmapy.formulary`]. We typically import [`astropy.units`] subpackage as `u`." + ] + }, + { + "cell_type": "markdown", + "id": "a9fad673", + "metadata": {}, + "source": [ + "## Unit essentials" + ] + }, + { + "cell_type": "markdown", + "id": "e9ccbb06", + "metadata": {}, + "source": [ + "We can create a _physical quantity_ by multiplying or dividing a number or array with a unit." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8437650a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "60.0 km\n" + ] + } + ], + "source": [ + "distance = 60 * u.km\n", + "print(distance)" + ] + }, + { + "cell_type": "markdown", + "id": "ccc6659f", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "This operation creates a [`Quantity`] object: a number, sequence, or array that has been assigned a physical unit." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a6354b93", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "astropy.units.quantity.Quantity" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(distance)" + ] + }, + { + "cell_type": "markdown", + "id": "744a9b01", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "We can create an object by using the [`Quantity`] class itself." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8d2bb681", + "metadata": {}, + "outputs": [], + "source": [ + "time = u.Quantity(120, u.min)" + ] + }, + { + "cell_type": "markdown", + "id": "1bb28951", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "We can create [`Quantity`] objects with compound units." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4932159b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$88 \\; \\mathrm{\\frac{mi}{h}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "88 * u.imperial.mile / u.hour" + ] + }, + { + "cell_type": "markdown", + "id": "9cc9adba", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "We can even create [`Quantity`] objects that are explicitly dimensionless. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "09e9752c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$3 \\; \\mathrm{}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "3 * u.dimensionless_unscaled" + ] + }, + { + "cell_type": "markdown", + "id": "4cffc8a0", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "We can also create a [`Quantity`] based off of a NumPy array or a list." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e3235d80", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$[2.5,~3.2,~1.1] \\; \\mathrm{kg}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array([2.5, 3.2, 1.1]) * u.kg" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a686fd93", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$[2,~3,~4] \\; \\mathrm{\\frac{m}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[2, 3, 4] * u.m / u.s" + ] + }, + { + "cell_type": "markdown", + "id": "a60e9ea9", + "metadata": {}, + "source": [ + "## Unit operations\n", + "\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "Operations between [`Quantity`] objects handle unit conversions automatically. We can add [`Quantity`] objects together as long as their units have the same physical type." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "65e08284", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$1.25 \\; \\mathrm{m}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 * u.m + 25 * u.cm" + ] + }, + { + "cell_type": "markdown", + "id": "edb43067", + "metadata": {}, + "source": [ + "Units get handled automatically during operations like multiplication, division, and exponentiation." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c36788db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.5 km / min\n" + ] + } + ], + "source": [ + "velocity = distance / time\n", + "print(velocity)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "04b48a57", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3600.0 km2\n" + ] + } + ], + "source": [ + "area = distance**2\n", + "print(area)" + ] + }, + { + "cell_type": "markdown", + "id": "98331629", + "metadata": {}, + "source": [ + "Attempting an operation between physically incompatible units gives us an error, which we can use to find bugs in our code." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0877feb6", + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "UnitConversionError", + "evalue": "Can only apply 'add' function to quantities with compatible dimensions", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:77\u001b[0m, in \u001b[0;36mget_converters_and_unit\u001b[0;34m(f, unit1, unit2)\u001b[0m\n\u001b[1;32m 76\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 77\u001b[0m converters[changeable] \u001b[38;5;241m=\u001b[39m \u001b[43mget_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43munit2\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit1\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError:\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:42\u001b[0m, in \u001b[0;36mget_converter\u001b[0;34m(from_unit, to_unit)\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Like Unit._get_converter, except returns None if no scaling is needed,\u001b[39;00m\n\u001b[1;32m 40\u001b[0m \u001b[38;5;124;03mi.e., if the inferred scale is unity.\u001b[39;00m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m---> 42\u001b[0m converter \u001b[38;5;241m=\u001b[39m \u001b[43mfrom_unit\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mto_unit\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 43\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01mif\u001b[39;00m converter \u001b[38;5;129;01mis\u001b[39;00m unit_scale_converter \u001b[38;5;28;01melse\u001b[39;00m converter\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1125\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1123\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mlambda\u001b[39;00m v: b(converter(v))\n\u001b[0;32m-> 1125\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1108\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1107\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1108\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_apply_equivalencies\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mother\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_normalize_equivalencies\u001b[49m\u001b[43m(\u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1112\u001b[0m \u001b[38;5;66;03m# Last hope: maybe other knows how to do it?\u001b[39;00m\n\u001b[1;32m 1113\u001b[0m \u001b[38;5;66;03m# We assume the equivalencies have the unit itself as first item.\u001b[39;00m\n\u001b[1;32m 1114\u001b[0m \u001b[38;5;66;03m# TODO: maybe better for other to have a `_back_converter` method?\u001b[39;00m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1086\u001b[0m, in \u001b[0;36mUnitBase._apply_equivalencies\u001b[0;34m(self, unit, other, equivalencies)\u001b[0m\n\u001b[1;32m 1084\u001b[0m other_str \u001b[38;5;241m=\u001b[39m get_err_str(other)\n\u001b[0;32m-> 1086\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00munit_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mother_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m are not convertible\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mUnitConversionError\u001b[0m: 's' (time) and 'm' (length) are not convertible", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;241;43m3\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mm\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43ms\u001b[49m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity.py:696\u001b[0m, in \u001b[0;36mQuantity.__array_ufunc__\u001b[0;34m(self, function, method, *inputs, **kwargs)\u001b[0m\n\u001b[1;32m 694\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mNotImplemented\u001b[39m\n\u001b[1;32m 695\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 696\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity.py:641\u001b[0m, in \u001b[0;36mQuantity.__array_ufunc__\u001b[0;34m(self, function, method, *inputs, **kwargs)\u001b[0m\n\u001b[1;32m 636\u001b[0m \u001b[38;5;66;03m# Determine required conversion functions -- to bring the unit of the\u001b[39;00m\n\u001b[1;32m 637\u001b[0m \u001b[38;5;66;03m# input to that expected (e.g., radian for np.sin), or to get\u001b[39;00m\n\u001b[1;32m 638\u001b[0m \u001b[38;5;66;03m# consistent units between two inputs (e.g., in np.add) --\u001b[39;00m\n\u001b[1;32m 639\u001b[0m \u001b[38;5;66;03m# and the unit of the result (or tuple of units for nout > 1).\u001b[39;00m\n\u001b[1;32m 640\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 641\u001b[0m converters, unit \u001b[38;5;241m=\u001b[39m \u001b[43mconverters_and_unit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfunction\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43minputs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 643\u001b[0m out \u001b[38;5;241m=\u001b[39m kwargs\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mout\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 644\u001b[0m \u001b[38;5;66;03m# Avoid loop back by turning any Quantity output into array views.\u001b[39;00m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/converters.py:181\u001b[0m, in \u001b[0;36mconverters_and_unit\u001b[0;34m(function, method, *args)\u001b[0m\n\u001b[1;32m 178\u001b[0m units \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28mgetattr\u001b[39m(arg, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124munit\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;28;01mfor\u001b[39;00m arg \u001b[38;5;129;01min\u001b[39;00m args]\n\u001b[1;32m 180\u001b[0m \u001b[38;5;66;03m# Determine possible conversion functions, and the result unit.\u001b[39;00m\n\u001b[0;32m--> 181\u001b[0m converters, result_unit \u001b[38;5;241m=\u001b[39m \u001b[43mufunc_helper\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfunction\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43munits\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 183\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28many\u001b[39m(converter \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m \u001b[38;5;28;01mfor\u001b[39;00m converter \u001b[38;5;129;01min\u001b[39;00m converters):\n\u001b[1;32m 184\u001b[0m \u001b[38;5;66;03m# for multi-argument ufuncs with a quantity and a non-quantity,\u001b[39;00m\n\u001b[1;32m 185\u001b[0m \u001b[38;5;66;03m# the quantity normally needs to be dimensionless, *except*\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 188\u001b[0m \u001b[38;5;66;03m# can just have the unit of the quantity\u001b[39;00m\n\u001b[1;32m 189\u001b[0m \u001b[38;5;66;03m# (this allows, e.g., `q > 0.` independent of unit)\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 191\u001b[0m \u001b[38;5;66;03m# Don't fold this loop in the test above: this rare case\u001b[39;00m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;66;03m# should not make the common case slower.\u001b[39;00m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/quantity_helper/helpers.py:79\u001b[0m, in \u001b[0;36mget_converters_and_unit\u001b[0;34m(f, unit1, unit2)\u001b[0m\n\u001b[1;32m 77\u001b[0m converters[changeable] \u001b[38;5;241m=\u001b[39m get_converter(unit2, unit1)\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError:\n\u001b[0;32m---> 79\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\n\u001b[1;32m 80\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCan only apply \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mf\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m function to quantities \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 81\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwith compatible dimensions\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 82\u001b[0m )\n\u001b[1;32m 84\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m converters, unit1\n", + "\u001b[0;31mUnitConversionError\u001b[0m: Can only apply 'add' function to quantities with compatible dimensions" + ] + } + ], + "source": [ + "3 * u.m + 3 * u.s" + ] + }, + { + "cell_type": "markdown", + "id": "4f0c461e", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[`numpy.ndarray`]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html\n", + "\n", + "[`Quantity`] arrays behave very similarly to NumPy arrays. In fact, [`Quantity`] is a subclass of [`numpy.ndarray`]." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "59389429", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "656.0 nm\n" + ] + } + ], + "source": [ + "balmer_series = [656, 486, 434, 410] * u.nm\n", + "Hα = balmer_series[0]\n", + "print(Hα)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "a6545132", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$656 \\; \\mathrm{nm}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.max(balmer_series)" + ] + }, + { + "cell_type": "markdown", + "id": "e1bb9434", + "metadata": {}, + "source": [ + "[NumPy]: https://numpy.org/\n", + "[SciPy]: https://scipy.org/\n", + "\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[lose their units]: https://docs.astropy.org/en/stable/known_issues.html#quantities-lose-their-units-with-some-operations\n", + "\n", + "⚠️ Most frequently encountered [NumPy] and [SciPy] functions can be used with [`Quantity`] objects. However, there are some functions that cause [`Quantity`] objects to [lose their units]!" + ] + }, + { + "cell_type": "markdown", + "id": "b910a84b", + "metadata": {}, + "source": [ + "## Unit conversions\n", + "\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[`to`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", + "\n", + "The [`to`] method allows us to convert a [`Quantity`] to different units of the same physical type. This method accepts strings that represent a unit (including compound units) or a unit object." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "80f4f133", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "velocity.to(\"m/s\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "21c46ef1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "velocity.to(u.m / u.s)" + ] + }, + { + "cell_type": "markdown", + "id": "c1e742ca", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[`si`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.si\n", + "[`cgs`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.cgs\n", + "\n", + "The [`si`] and [`cgs`] attributes convert the [`Quantity`] to SI or CGS units, respectively. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "02bda4d2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$8.3333333 \\; \\mathrm{\\frac{m}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "velocity.si" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "560a0cfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$833.33333 \\; \\mathrm{\\frac{cm}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "velocity.cgs" + ] + }, + { + "cell_type": "markdown", + "id": "1429d5a2", + "metadata": {}, + "source": [ + "## Detaching units and values\n", + "\n", + "[`value`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.value \n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "The [`value`] attribute of a [`Quantity`] provides the number or array *without* the unit." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "24e8403c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "120.0" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "time.value" + ] + }, + { + "cell_type": "markdown", + "id": "4c6c027b", + "metadata": {}, + "source": [ + "[`unit`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.unit\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "The [`unit`] attribute of a [`Quantity`] provides the unit without the value." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2c41a2fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\mathrm{min}$" + ], + "text/plain": [ + "Unit(\"min\")" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "time.unit" + ] + }, + { + "cell_type": "markdown", + "id": "be0c5349", + "metadata": {}, + "source": [ + "## Equivalencies" + ] + }, + { + "cell_type": "markdown", + "id": "07c3b9c1", + "metadata": {}, + "source": [ + "[electron-volt]: https://en.wikipedia.org/wiki/Electronvolt\n", + "[Boltzmann constant]: https://en.wikipedia.org/wiki/Boltzmann_constant\n", + "\n", + "Plasma scientists often use the [electron-volt] (eV) as a unit of temperature. This is a shortcut for describing the thermal energy per particle, or more accurately the temperature multiplied by the [Boltzmann constant], $k_B$. Because an electron-volt is a unit of energy rather than temperature, we cannot directly convert electron-volts to kelvin." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "6b6fafd6", + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "UnitConversionError", + "evalue": "'eV' (energy/torque/work) and 'K' (temperature) are not convertible", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[22], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meV\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mto\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mK\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1196\u001b[0m, in \u001b[0;36mUnitBase.to\u001b[0;34m(self, other, value, equivalencies)\u001b[0m\n\u001b[1;32m 1194\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m UNITY\n\u001b[1;32m 1195\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1196\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_converter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mUnit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mother\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m(value)\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1125\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1122\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1123\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mlambda\u001b[39;00m v: b(converter(v))\n\u001b[0;32m-> 1125\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1108\u001b[0m, in \u001b[0;36mUnitBase._get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 1106\u001b[0m \u001b[38;5;66;03m# if that doesn't work, maybe we can do it with equivalencies?\u001b[39;00m\n\u001b[1;32m 1107\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1108\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_apply_equivalencies\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mother\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_normalize_equivalencies\u001b[49m\u001b[43m(\u001b[49m\u001b[43mequivalencies\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m UnitsError \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1112\u001b[0m \u001b[38;5;66;03m# Last hope: maybe other knows how to do it?\u001b[39;00m\n\u001b[1;32m 1113\u001b[0m \u001b[38;5;66;03m# We assume the equivalencies have the unit itself as first item.\u001b[39;00m\n\u001b[1;32m 1114\u001b[0m \u001b[38;5;66;03m# TODO: maybe better for other to have a `_back_converter` method?\u001b[39;00m\n\u001b[1;32m 1115\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(other, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mequivalencies\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/units/core.py:1086\u001b[0m, in \u001b[0;36mUnitBase._apply_equivalencies\u001b[0;34m(self, unit, other, equivalencies)\u001b[0m\n\u001b[1;32m 1083\u001b[0m unit_str \u001b[38;5;241m=\u001b[39m get_err_str(unit)\n\u001b[1;32m 1084\u001b[0m other_str \u001b[38;5;241m=\u001b[39m get_err_str(other)\n\u001b[0;32m-> 1086\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m UnitConversionError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00munit_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m and \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mother_str\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m are not convertible\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mUnitConversionError\u001b[0m: 'eV' (energy/torque/work) and 'K' (temperature) are not convertible" + ] + } + ], + "source": [ + "u.eV.to(\"K\")" + ] + }, + { + "cell_type": "markdown", + "id": "9299c8a1", + "metadata": {}, + "source": [ + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[equivalencies]: https://docs.astropy.org/en/stable/units/equivalencies.html\n", + "[`temperature_energy()`]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", + "\n", + "To handle non-standard unit conversions, [`astropy.units`] allows the use of [equivalencies]. The conversion from eV to K can be done by using the [`temperature_energy()`] equivalency." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "afac5b4a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$11604.518 \\; \\mathrm{K}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(1 * u.eV).to(\"K\", equivalencies=u.temperature_energy())" + ] + }, + { + "cell_type": "markdown", + "id": "b4c492cc", + "metadata": {}, + "source": [ + "[`dimensionless_angles()`]: https://docs.astropy.org/en/stable/api/astropy.units.equivalencies.dimensionless_angles.html#dimensionless-angles\n", + "\n", + "[frequency]: https://en.wikipedia.org/wiki/Frequency\n", + "[angular frequency]: https://en.wikipedia.org/wiki/Angular_frequency\n", + "\n", + "Radians are treated dimensionlessly when the [`dimensionless_angles()`] equivalency is in effect. Note that this equivalency does not account for the multiplicative factor of $2π$ that is used when converting between [frequency] and [angular frequency]." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9735710b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$3.2 \\; \\mathrm{\\frac{1}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(3.2 * u.rad / u.s).to(\"1 / s\", equivalencies=u.dimensionless_angles())" + ] + }, + { + "cell_type": "markdown", + "id": "7ec6d857", + "metadata": {}, + "source": [ + "## Physical constants" + ] + }, + { + "cell_type": "markdown", + "id": "b714de6a", + "metadata": {}, + "source": [ + "[`astropy.constants`]: https://docs.astropy.org/en/stable/constants/index.html\n", + "\n", + "few can use [`astropy.constants`] to access the most commonly needed physical constants." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "746a79a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Name = Speed of light in vacuum\n", + " Value = 299792458.0\n", + " Uncertainty = 0.0\n", + " Unit = m / s\n", + " Reference = CODATA 2018\n" + ] + } + ], + "source": [ + "print(constants.c)" + ] + }, + { + "cell_type": "markdown", + "id": "c3ee9feb", + "metadata": {}, + "source": [ + "[`Constant`]: https://docs.astropy.org/en/stable/api/astropy.constants.Constant.html#astropy.constants.Constant\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[`u.temperature_energy()`]: https://docs.astropy.org/en/stable/units/equivalencies.html#temperature-energy-equivalency\n", + "\n", + "A [`Constant`] behaves very similarly to a [`Quantity`]. For example, we can use the Boltzmann constant to mimic the behavior of [`u.temperature_energy()`]." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d2d59d08", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.962710872930049 MK\n" + ] + } + ], + "source": [ + "thermal_energy_per_particle = 0.6 * u.keV\n", + "temperature = thermal_energy_per_particle / constants.k_B\n", + "print(temperature.to(\"MK\"))" + ] + }, + { + "cell_type": "markdown", + "id": "7c145497", + "metadata": {}, + "source": [ + "Electromagnetic constants often need the unit system to be specified, or will result in an exception." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "015de7fc", + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Constant 'e' does not have physically compatible units across all systems of units and cannot be combined with other values without specifying a system (eg. e.emu)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;241;43m2\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mconstants\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43me\u001b[49m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Applications/miniconda3/envs/pldev/lib/python3.12/site-packages/astropy/constants/constant.py:49\u001b[0m, in \u001b[0;36mConstantMeta.__new__..wrap..wrapper\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 47\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msystem \u001b[38;5;129;01mand\u001b[39;00m name_lower \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_has_incompatible_units:\n\u001b[1;32m 48\u001b[0m systems \u001b[38;5;241m=\u001b[39m \u001b[38;5;28msorted\u001b[39m(x \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m instances \u001b[38;5;28;01mif\u001b[39;00m x)\n\u001b[0;32m---> 49\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 50\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mConstant \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mabbrev\u001b[38;5;132;01m!r}\u001b[39;00m\u001b[38;5;124m does not have physically compatible \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 51\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124munits across all systems of units and cannot be \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 52\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcombined with other values without specifying a \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 53\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msystem (eg. \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mabbrev\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00msystems[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m)\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 54\u001b[0m )\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m meth(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n", + "\u001b[0;31mTypeError\u001b[0m: Constant 'e' does not have physically compatible units across all systems of units and cannot be combined with other values without specifying a system (eg. e.emu)" + ] + } + ], + "source": [ + "2 * constants.e" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "1a90c979", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$3.2043533 \\times 10^{-19} \\; \\mathrm{C}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 * constants.e.si" + ] + }, + { + "cell_type": "markdown", + "id": "41d93ce4-d0d5-4146-8e2e-5461c6ba12ce", + "metadata": {}, + "source": [ + "Code within PlasmaPy generally uses SI units." + ] + }, + { + "cell_type": "markdown", + "id": "cd539f42-ce70-4ecf-9bdc-e1f56a86dd07", + "metadata": {}, + "source": [ + "\n", + "## Plotting quantities" + ] + }, + { + "cell_type": "markdown", + "id": "90ced3d3-aa0d-47b7-b526-359e466e5cb5", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "Astropy has built-in support for plotting [`Quantity`] objects. Let's plot the number density of electrons in the solar wind using an empirical formula given by [Kruparova et al. (2023)](https://iopscience.iop.org/article/10.3847/1538-4357/acf572), which has a range of validity from 13 to 50 solar radii." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "2fce5587-16cf-40ea-baa5-dbd9023281c3", + "metadata": {}, + "outputs": [], + "source": [ + "radii = np.linspace(13, 50, num=50) * constants.R_sun" + ] + }, + { + "cell_type": "markdown", + "id": "d73d6343-d563-4f3d-ae41-a1eefaed4618", + "metadata": {}, + "source": [ + "Next we can apply the formula to get the electron density:\n", + "\n", + "$$ n_e(R) = \\left( 343466\\ \\mbox{cm}^{-3} \\right) × \\left( \\frac{R}{R_☉} \\right)^{-1.87} $$" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "7a734940-a970-4991-9ada-f0844ae80567", + "metadata": {}, + "outputs": [], + "source": [ + "n_e = 343_466 * u.cm**-3 * (radii / constants.R_sun) ** -1.87" + ] + }, + { + "cell_type": "markdown", + "id": "1ed451a9-909f-40ae-bec7-c67d8767545c", + "metadata": {}, + "source": [ + "We can use the [`astropy.visualization.quantity_support`] to help with plotting `Quantity` objects against each other.\n", + "\n", + "Let's do some imports." + ] + }, + { + "cell_type": "markdown", + "id": "07547260-da42-40b7-98de-693b60dbb788", + "metadata": {}, + "source": [ + "Will make use make use of [`astropy.visualization.quantity_support`](https://docs.astropy.org/en/stable/api/astropy.visualization.quantity_support.html). This is a [_context manager_](https://realpython.com/python-with-statement/), which means that we use the `with` statement." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "a4563556-64d3-4b7e-80f6-7df22ccf4488", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "with quantity_support():\n", + " plt.figure()\n", + " plt.plot(radii.to(u.R_sun), n_e)\n", + " plt.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "f4da2ab0", + "metadata": {}, + "source": [ + "## Optimizing unit operations (if time)\n", + "\n", + "[performance tips]: https://docs.astropy.org/en/stable/units/index.html#performance-tips\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[`%timeit`]: https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit\n", + "\n", + "Astropy's documentation includes [performance tips] for using [`astropy.units`] in computationally intensive situations. We can test it with [`%timeit`], which runs a command repeatedly to see how long it takes.\n", + "\n", + "Putting compound units in parentheses speeds things up by reducing the number of copies made by the operation." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "e3bc2348", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "11.2 µs ± 836 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n", + "6.71 µs ± 231 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit 1.6 * u.barn * u.Mpc\n", + "%timeit 1.6 * (u.barn * u.Mpc)" + ] + }, + { + "cell_type": "markdown", + "id": "4ff6e798-91dd-460d-bf29-172a95bf34b7", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "We can assign a unit to a value using the [`Quantity`] class directly." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "868b008e-1560-4edd-96ee-7ec1c00aca16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.45 µs ± 88.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit u.Quantity(1.6, u.barn * u.Mpc)" + ] + }, + { + "cell_type": "markdown", + "id": "0f6bcf83-70d4-42e3-9392-9eaa73d3f3f9", + "metadata": {}, + "source": [ + "What else can we do to save time from the above operation?" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "3ecb16da-9dc3-4a62-a03a-e3afb312ae84", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.85 µs ± 19.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)\n" + ] + } + ], + "source": [ + "volume_unit = u.barn * u.Mpc # pre-define the unit\n", + "%timeit u.Quantity(1.6, volume_unit)" + ] + }, + { + "cell_type": "markdown", + "id": "55b8dcd1", + "metadata": {}, + "source": [ + "## Physical types (if time)" + ] + }, + { + "cell_type": "markdown", + "id": "0421ee56", + "metadata": {}, + "source": [ + "[physical type]: https://docs.astropy.org/en/stable/units/physical_types.html\n", + "[`physical_type`]: https://docs.astropy.org/en/stable/api/astropy.units.UnitBase.html#astropy.units.UnitBase.physical_type\n", + "[`get_physical_type()`]: https://docs.astropy.org/en/stable/api/astropy.units.get_physical_type.html#astropy.units.get_physical_type\n", + "\n", + "A [physical type] corresponds to physical quantities with dimensionally compatible units. Astropy has functionality that represents different physical types. These physical type objects can be accessed using either the [`physical_type`] attribute of a unit or [`get_physical_type()`]." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "da6c9c7d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PhysicalType({'diffusivity', 'kinematic viscosity'})" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(u.m**2 / u.s).physical_type" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "f0d49b03", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PhysicalType('number density')" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "u.get_physical_type(\"number density\")" + ] + }, + { + "cell_type": "markdown", + "id": "8cd7e09a", + "metadata": {}, + "source": [ + "These physical type objects can be used for dimensional analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d03b8eb0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "energy flux/irradiance\n" + ] + } + ], + "source": [ + "energy_density = (u.J * u.m**-3).physical_type\n", + "velocity = u.get_physical_type(\"velocity\")\n", + "print(energy_density * velocity)" + ] + }, + { + "cell_type": "markdown", + "id": "ff6c5c8d-2a21-4314-8b03-5b1096e94232", + "metadata": {}, + "source": [ + "There are some pretty obscure physical types too!" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "2371647f-f29d-41cb-b89d-226d4743ce26", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PhysicalType('absement')" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "u.get_physical_type(u.m * u.s)" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "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.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/particles-formulary-completed.ipynb b/notebooks/particles-formulary-completed.ipynb new file mode 100644 index 0000000..bca09e8 --- /dev/null +++ b/notebooks/particles-formulary-completed.ipynb @@ -0,0 +1,2328 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5b8f9968", + "metadata": {}, + "source": [ + "# Particles and Formulary" + ] + }, + { + "cell_type": "markdown", + "id": "c8c364e5", + "metadata": {}, + "source": [ + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[decorators]: https://www.geeksforgeeks.org/decorators-in-python/\n", + "[Type hint annotations]: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html\n", + "\n", + "We'll start this notebook with a quick refresher on [`astropy.units`], before going through examples from [`plasmapy.particles`] ⚛️ and [`plasmapy.formulary`] 🧮. After an interlude about [type hint annotations] and [decorators], we'll \n", + "\n", + "Let's start with some preliminary imports & settings. To execute a cell in a Jupyter notebook, press `Shift + Enter`. If you have to restart the notebook, please re-execute the following cell.\n", + "\n", + "If using Google Colab, click **Run anyway** when prompted. (If prompted again, select **Restart runtime** when the installation finishes.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa326226", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "if 'google.colab' in str(get_ipython()):\n", + " if 'plasmapy' not in sys.modules:\n", + " !pip install plasmapy==2024.7.0\n", + "\n", + "import astropy.units as u\n", + "import numpy as np\n", + "from plasmapy.particles import *\n", + "from plasmapy.particles.particle_class import valid_categories\n", + "from plasmapy.formulary import *\n", + "from plasmapy.utils.decorators import validate_quantities" + ] + }, + { + "cell_type": "markdown", + "id": "6e8f0d92", + "metadata": {}, + "source": [ + "## Quick refresher on `astropy.units`" + ] + }, + { + "cell_type": "markdown", + "id": "93658d4f", + "metadata": {}, + "source": [ + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "\n", + "We'll be using [`astropy.units`] throughout the rest of this notebook, so we'll begin with a brief reminder o fhow to use it. We typically import this subpackage as `u`." + ] + }, + { + "cell_type": "markdown", + "id": "e9ccbb06", + "metadata": {}, + "source": [ + "We can create a physical quantity by multiplying or dividing a number or array with a unit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8437650a", + "metadata": {}, + "outputs": [], + "source": [ + "60 * u.km" + ] + }, + { + "cell_type": "markdown", + "id": "ccc6659f", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "\n", + "This operation creates a [`Quantity`] object: a number, sequence, or array that has been assigned a physical unit. We can create [`Quantity`] objects with compound units." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4932159b", + "metadata": {}, + "outputs": [], + "source": [ + "V = 88 * u.imperial.mile / u.hour\n", + "print(V)" + ] + }, + { + "cell_type": "markdown", + "id": "f9dd4743-ffab-40d7-9407-62840ea1c7ba", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", + "[`Quantity.to()`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", + "\n", + "We can use [`Quantity.to()`] to convert a [`Quantity`] to different units." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8b5a277-de41-4039-8442-2a392d1a553c", + "metadata": {}, + "outputs": [], + "source": [ + "V.to(\"m/s\")" + ] + }, + { + "cell_type": "markdown", + "id": "97a8d49a", + "metadata": {}, + "source": [ + "## Particles" + ] + }, + { + "cell_type": "markdown", + "id": "0c7eeced", + "metadata": {}, + "source": [ + "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "\n", + "The [`plasmapy.particles`] subpackage contains functions to access basic particle data and classes to represent particles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24125310", + "metadata": {}, + "outputs": [], + "source": [ + "from plasmapy.particles import *" + ] + }, + { + "cell_type": "markdown", + "id": "e224284a", + "metadata": {}, + "source": [ + "### Particle properties" + ] + }, + { + "cell_type": "markdown", + "id": "7696deba", + "metadata": {}, + "source": [ + "[representation of a particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.ParticleLike.html#particlelike\n", + "\n", + "There are several functions that provide information about different particles that might be present in a plasma. The input of these functions is a [representation of a particle], such as a string for the atomic symbol or the element name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "006b8d3e", + "metadata": {}, + "outputs": [], + "source": [ + "atomic_number(\"Fe\")" + ] + }, + { + "cell_type": "markdown", + "id": "2aa5af34", + "metadata": {}, + "source": [ + "[atomic number]: https://en.wikipedia.org/wiki/Atomic_number\n", + "\n", + "We can provide a number that represents the [atomic number]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69958326", + "metadata": {}, + "outputs": [], + "source": [ + "element_name(26)" + ] + }, + { + "cell_type": "markdown", + "id": "cca89cef", + "metadata": {}, + "source": [ + "We can provide standard symbols or the names of particles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16ca5b6f", + "metadata": {}, + "outputs": [], + "source": [ + "is_stable(\"e-\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35d8e49e", + "metadata": {}, + "outputs": [], + "source": [ + "charge_number(\"proton\")" + ] + }, + { + "cell_type": "markdown", + "id": "8f2e5b30", + "metadata": {}, + "source": [ + "[mass number]: https://en.wikipedia.org/wiki/Mass_number\n", + "[`Quantity`]: https://docs.astropy.org/en/stable/units/quantity.html#quantity\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[`half_life`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.atomic.half_life.html#half-life\n", + "\n", + "We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. Let's use [`half_life`] to return the half-life of a radioactive particle in seconds as a [`Quantity`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94fcf452", + "metadata": {}, + "outputs": [], + "source": [ + "half_life(\"C-14\")" + ] + }, + { + "cell_type": "markdown", + "id": "58ab7782", + "metadata": {}, + "source": [ + "We typically represent an ion in a string by putting together the atomic symbol or isotope symbol, a space, the charge number, and the sign of the charge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a65e15da", + "metadata": {}, + "outputs": [], + "source": [ + "charge_number(\"Fe-56 13+\")" + ] + }, + { + "cell_type": "markdown", + "id": "ce4886cf", + "metadata": {}, + "source": [ + "[particle-like]: https://docs.plasmapy.org/en/latest/glossary.html#term-particle-like\n", + "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "\n", + "Functions in [`plasmapy.particles`] are quite flexible in terms of string inputs representing particles. An input is [particle-like] if it can be used to represent a physical particle. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05e03a8b", + "metadata": {}, + "outputs": [], + "source": [ + "particle_mass(\"iron-56 +13\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dd9541b", + "metadata": {}, + "outputs": [], + "source": [ + "particle_mass(\"iron-56+++++++++++++\")" + ] + }, + { + "cell_type": "markdown", + "id": "d3adf3a6", + "metadata": {}, + "source": [ + "Most of these functions take additional arguments, with `Z` representing the charge number of an ion and `mass_numb` representing the mass number of an isotope. These arguments are often [keyword-only](https://docs.plasmapy.org/en/latest/glossary.html#term-keyword-only) to avoid ambiguity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61e0f960", + "metadata": {}, + "outputs": [], + "source": [ + "particle_mass(\"Fe\", Z=13, mass_numb=56)" + ] + }, + { + "cell_type": "markdown", + "id": "53a1d993", + "metadata": {}, + "source": [ + "### Particle objects" + ] + }, + { + "cell_type": "markdown", + "id": "3c2c3835", + "metadata": {}, + "source": [ + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "\n", + "Up until now, we have been using functions that accept representations of particles and then return particle properties. With the [`Particle`] class, we can create objects that represent physical particles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe9f824a", + "metadata": {}, + "outputs": [], + "source": [ + "proton = Particle(\"p+\")\n", + "electron = Particle(\"electron\")\n", + "iron56_nucleus = Particle(\"Fe\", Z=26, mass_numb=56)" + ] + }, + { + "cell_type": "markdown", + "id": "160df55b", + "metadata": {}, + "source": [ + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "\n", + "Particle properties can be accessed via attributes of the [`Particle`] class." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bebcc477", + "metadata": {}, + "outputs": [], + "source": [ + "proton.mass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df0f2eed", + "metadata": {}, + "outputs": [], + "source": [ + "electron.charge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "116cd6ad", + "metadata": {}, + "outputs": [], + "source": [ + "electron.charge_number" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99d22bd3", + "metadata": {}, + "outputs": [], + "source": [ + "iron56_nucleus.nuclear_binding_energy" + ] + }, + { + "cell_type": "markdown", + "id": "8986cef8", + "metadata": {}, + "source": [ + "#### Antiparticles\n", + "\n", + "[`antiparticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.antiparticle\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "\n", + "We can get antiparticles of fundamental particles by using the [`antiparticle`] attribute of a [`Particle`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6663952", + "metadata": {}, + "outputs": [], + "source": [ + "electron.antiparticle" + ] + }, + { + "cell_type": "markdown", + "id": "bf40e592", + "metadata": {}, + "source": [ + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "\n", + "\n", + "We can also use the tilde (`~`) operator on a [`Particle`] to get its antiparticle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "180a1edf", + "metadata": {}, + "outputs": [], + "source": [ + "~proton" + ] + }, + { + "cell_type": "markdown", + "id": "8500bc48", + "metadata": {}, + "source": [ + "#### Ionization and recombination\n", + "\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[`recombine()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.recombine\n", + "[`ionize()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.ionize\n", + "\n", + "The [`recombine()`] and [`ionize()`] methods of a [`Particle`] representing an ion or neutral atom will return a different [`Particle`] with fewer or more electrons." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "186b1dfa", + "metadata": {}, + "outputs": [], + "source": [ + "deuterium = Particle(\"D 0+\")\n", + "deuterium.ionize()" + ] + }, + { + "cell_type": "markdown", + "id": "4ee95241", + "metadata": {}, + "source": [ + "When provided with a number, these methods tell how many bound electrons to add or remove." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "242b53bd", + "metadata": {}, + "outputs": [], + "source": [ + "alpha = Particle(\"alpha\")\n", + "alpha.recombine(2)" + ] + }, + { + "cell_type": "markdown", + "id": "e5302bef", + "metadata": {}, + "source": [ + "### Custom particles" + ] + }, + { + "cell_type": "markdown", + "id": "4c8c2a63", + "metadata": {}, + "source": [ + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "\n", + "Sometimes we want to use a particle with custom properties. For example, we might want to represent an average ion in a multi-species plasma or a dust particle. For that we can use [`CustomParticle`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35b94505", + "metadata": {}, + "outputs": [], + "source": [ + "cp = CustomParticle(9e-26 * u.kg, 2.18e-18 * u.C, symbol=\"Fe 13.6+\")" + ] + }, + { + "cell_type": "markdown", + "id": "dfc19404", + "metadata": {}, + "source": [ + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "\n", + "Many of the attributes of [`CustomParticle`] are the same as in [`Particle`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a4b412b", + "metadata": {}, + "outputs": [], + "source": [ + "cp.mass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2da2b3b", + "metadata": {}, + "outputs": [], + "source": [ + "cp.charge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecb450f6", + "metadata": {}, + "outputs": [], + "source": [ + "cp.symbol" + ] + }, + { + "cell_type": "markdown", + "id": "21a992a9", + "metadata": {}, + "source": [ + "[`numpy.nan`]: https://numpy.org/doc/stable/reference/constants.html#numpy.nan\n", + "\n", + "If we do not include one of the physical quantities, it gets set to [`numpy.nan`] (not a number) in the appropriate units." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a31c421a", + "metadata": {}, + "outputs": [], + "source": [ + "CustomParticle(9.27e-26 * u.kg).charge" + ] + }, + { + "cell_type": "markdown", + "id": "6292a1f3", + "metadata": {}, + "source": [ + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "[`CustomParticle`] objects can provided to most of the commonly used functions in [`plasmapy.formulary`], and we're planning to improve interoperability in future releases of PlasmaPy." + ] + }, + { + "cell_type": "markdown", + "id": "0f2b0634", + "metadata": {}, + "source": [ + "### Particle lists" + ] + }, + { + "cell_type": "markdown", + "id": "7939cfad", + "metadata": {}, + "source": [ + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "\n", + "The [`ParticleList`] class is a container for [`Particle`] and [`CustomParticle`] objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30cc7d36", + "metadata": {}, + "outputs": [], + "source": [ + "iron_ions = ParticleList([\"Fe 12+\", \"Fe 13+\", \"Fe 14+\"])" + ] + }, + { + "cell_type": "markdown", + "id": "d56ddb98", + "metadata": {}, + "source": [ + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "\n", + "By using a [`ParticleList`], we can access the properties of multiple particles at once." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2434f71b", + "metadata": {}, + "outputs": [], + "source": [ + "iron_ions.mass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14a431dd", + "metadata": {}, + "outputs": [], + "source": [ + "iron_ions.charge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48cd1c7a", + "metadata": {}, + "outputs": [], + "source": [ + "iron_ions.symbols" + ] + }, + { + "cell_type": "markdown", + "id": "cccc6c60", + "metadata": {}, + "source": [ + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "\n", + "We can also create a [`ParticleList`] by adding [`Particle`] and/or [`CustomParticle`] objects together." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b953065", + "metadata": {}, + "outputs": [], + "source": [ + "proton + electron" + ] + }, + { + "cell_type": "markdown", + "id": "b78ee7d2", + "metadata": {}, + "source": [ + "We can also get an average particle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf01f619", + "metadata": {}, + "outputs": [], + "source": [ + "iron_ions.average_particle()" + ] + }, + { + "cell_type": "markdown", + "id": "e9038618", + "metadata": {}, + "source": [ + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "[`ParticleList`] objects can also be provided to the most commonly used functions in [`plasmapy.formulary`], with more complete interoperability expected in the future." + ] + }, + { + "cell_type": "markdown", + "id": "8167cf2d-a13e-4847-ac6b-ca169c1ab96d", + "metadata": {}, + "source": [ + "### Particle Categorization" + ] + }, + { + "cell_type": "markdown", + "id": "1fa46bf5-200d-4297-aa13-fcdbd9ca286f", + "metadata": {}, + "source": [ + "[`categories`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.categories\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "[`set`]: https://docs.python.org/3/library/stdtypes.html#set\n", + "\n", + "The [`categories`] attribute of a [`Particle`] provides a set of the categories that the particle belongs to as a [`set`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1833eff2-2fe1-4923-bb2f-94277504dff8", + "metadata": {}, + "outputs": [], + "source": [ + "muon = Particle(\"muon\")\n", + "muon.categories" + ] + }, + { + "cell_type": "markdown", + "id": "38d96c41-2e03-48fb-86ab-effae5c2bd87", + "metadata": {}, + "source": [ + "[`is_category()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.is_category\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "\n", + "The [`is_category()`] method of a [`Particle`] lets us determine if the particle belongs to one or more categories." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d73f130-fa56-472a-a04c-23e364d8fdeb", + "metadata": {}, + "outputs": [], + "source": [ + "muon.is_category(\"lepton\")" + ] + }, + { + "cell_type": "markdown", + "id": "1f427bc9-83cf-4b88-916d-aa3d3d17dbb5", + "metadata": {}, + "source": [ + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "\n", + "If we need to be more specific with `is_category()`, we can use its keyword arguments: \n", + "\n", + " - `require`: for categories that a [`Particle`] _must_ belong to\n", + " - `exclude`: for categories that the [`Particle`] _cannot_ belong to\n", + " - `any_of`: for categories of which a [`Particle`] must belong to _at least one of_" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c06149e-7ac3-4531-b5ab-7aa4eab21f67", + "metadata": {}, + "outputs": [], + "source": [ + "electron.is_category(require=\"lepton\", exclude=\"baryon\", any_of={\"boson\", \"fermion\"})" + ] + }, + { + "cell_type": "markdown", + "id": "77874c36-3b01-43f2-86f0-a4d28978fe12", + "metadata": {}, + "source": [ + "[`plasmapy.particles.particle_class.valid_categories`]: https://docs.plasmapy.org/en/latest/api/plasmapy.particles.particle_class.valid_categories.html\n", + "\n", + "All valid particle categories are included in [`plasmapy.particles.particle_class.valid_categories`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0818ec0-2593-47ca-b58a-28b40a774f25", + "metadata": {}, + "outputs": [], + "source": [ + "print(valid_categories)" + ] + }, + { + "cell_type": "markdown", + "id": "aceafe85-aa39-43fd-89aa-637f017d2574", + "metadata": {}, + "source": [ + "[`is_category()`]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.is_category\n", + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html#plasmapy.particles.particle_collections.ParticleList\n", + "[`list`]: https://docs.python.org/3/library/stdtypes.html#list\n", + "\n", + "The [`is_category()`] method of [`ParticleList`] returns `True` if all particles match the criteria, and `False` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eafd9671-41d3-4062-9ec8-75e822ef950c", + "metadata": {}, + "outputs": [], + "source": [ + "particles = ParticleList([\"e-\", \"p+\", \"n\"])\n", + "particles.is_category(require=\"lepton\")" + ] + }, + { + "cell_type": "markdown", + "id": "368173b7-6af5-4be0-8f3f-0c683345e9d9", + "metadata": {}, + "source": [ + "If we want to do this operation for all of the particles, we can set the `particlewise` argument to `True`. We will then get a `list` of booleans." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea9a8d0a-ad09-4558-8b1f-383fbdce47f4", + "metadata": {}, + "outputs": [], + "source": [ + "particles.is_category(require=\"lepton\", particlewise=True)" + ] + }, + { + "cell_type": "markdown", + "id": "efe199c8", + "metadata": {}, + "source": [ + "### Nuclear reactions" + ] + }, + { + "cell_type": "markdown", + "id": "58fb006b", + "metadata": {}, + "source": [ + "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "\n", + "We can use [`plasmapy.particles`] to calculate the energy of a nuclear reaction using the `>` operator. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8a9ab9e", + "metadata": {}, + "outputs": [], + "source": [ + "deuteron = Particle(\"D+\")\n", + "triton = Particle(\"T+\")\n", + "alpha = Particle(\"α\")\n", + "neutron = Particle(\"n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb95c644", + "metadata": {}, + "outputs": [], + "source": [ + "energy = deuteron + triton > alpha + neutron" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7a4c236", + "metadata": {}, + "outputs": [], + "source": [ + "energy.to(\"MeV\")" + ] + }, + { + "cell_type": "markdown", + "id": "8bf7e515", + "metadata": {}, + "source": [ + "If the nuclear reaction is invalid, then an exception is raised that states the reason why." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67b32a3a", + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "deuteron + triton > alpha + 56 * neutron" + ] + }, + { + "cell_type": "markdown", + "id": "f065a288", + "metadata": {}, + "source": [ + "## PlasmaPy formulary\n", + "\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "The [`plasmapy.formulary`] subpackage contains a broad variety of formulas needed by plasma scientists across disciplines, in particular to calculate plasma parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20c3b9b1", + "metadata": {}, + "outputs": [], + "source": [ + "from plasmapy.formulary import *" + ] + }, + { + "cell_type": "markdown", + "id": "955ce3dd", + "metadata": {}, + "source": [ + "### Plasma beta in the solar corona" + ] + }, + { + "cell_type": "markdown", + "id": "1bb15c25", + "metadata": {}, + "source": [ + "[Plasma beta]: https://en.wikipedia.org/wiki/Beta_(plasma_physics)\n", + "\n", + "[Plasma beta] ($β$) is one of the most fundamental plasma parameters. $β$ is the ratio of the plasma (gas) pressure to the magnetic pressure. How a plasma behaves depends strongly on $β$. When $β ≫ 1$, the magnetic field is not strong enough to exert much of a force on the plasma, so its motions start to resemble a gas. When $β ≪ 1$, magnetic tension and pressure are the dominant macroscopic forces. \n", + "\n", + "Let's use [`plasmapy.formulary`](https://docs.plasmapy.org/en/stable/formulary/index.html) to calculate plasma β in different regions of the solar atmosphere and see what we can learn." + ] + }, + { + "cell_type": "markdown", + "id": "24162ca9", + "metadata": {}, + "source": [ + "#### Solar corona" + ] + }, + { + "cell_type": "markdown", + "id": "60aa77b5", + "metadata": {}, + "source": [ + "Let's start by defining some plasma parameters for an active region in the solar corona." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4b98f58", + "metadata": {}, + "outputs": [], + "source": [ + "B_corona = 50 * u.G\n", + "n_corona = 1e9 * u.cm ** -3\n", + "T_corona = 1 * u.MK" + ] + }, + { + "cell_type": "markdown", + "id": "cdc0c334", + "metadata": {}, + "source": [ + "When we use these parameters in [`beta`](https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.dimensionless.beta.html#plasmapy.formulary.dimensionless.beta), we find that $β$ is quite small so that the corona is magnetically dominated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98b23229", + "metadata": {}, + "outputs": [], + "source": [ + "beta(T=T_corona, n=n_corona, B=B_corona)" + ] + }, + { + "cell_type": "markdown", + "id": "deb41bfa-bad5-4bdc-a0f2-75b4c0a16054", + "metadata": {}, + "source": [ + "#### Solar photosphere" + ] + }, + { + "cell_type": "markdown", + "id": "2442b569-a98d-4528-ab1a-374867be06f3", + "metadata": {}, + "source": [ + "Let's specify some characteristic plasma parameters for the solar photosphere, away from any sunspots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1654acbb-056b-46e5-9fcf-900ab0fbd2b7", + "metadata": {}, + "outputs": [], + "source": [ + "T_photosphere = 5800 * u.K\n", + "B_photosphere = 400 * u.G\n", + "n_photosphere = 1e17 * u.cm ** -3" + ] + }, + { + "cell_type": "markdown", + "id": "be434e24-080a-449e-a3f2-6f18149ff362", + "metadata": {}, + "source": [ + "When we calculate for the photosphere, we find that it is an order of magnitude larger than 1, so plasma pressure forces are more important than magnetic tension and pressure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cf5b298-f68b-4bcb-bbf4-2fb93d8cdc6a", + "metadata": {}, + "outputs": [], + "source": [ + "beta(T_photosphere, n_photosphere, B_photosphere)" + ] + }, + { + "cell_type": "markdown", + "id": "dee08605", + "metadata": {}, + "source": [ + "### Plasma parameters in Earth's magnetosphere" + ] + }, + { + "cell_type": "markdown", + "id": "9587734f", + "metadata": {}, + "source": [ + "[magnetic reconnection]: https://en.wikipedia.org/wiki/Magnetic_reconnection\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "The [*Magnetospheric Multiscale Mission*](https://www.nasa.gov/mission_pages/mms/overview/index.html) (*MMS*) is a constellation of four identical spacecraft. The goal of *MMS* is to investigate the small-scale physics of [magnetic reconnection] in Earth's magnetosphere. In order to do this, the spacecraft need to orbit in a tight configuration. But how tight does the tetrahedron have to be? Let's use [`plasmapy.formulary`] to find out." + ] + }, + { + "cell_type": "markdown", + "id": "706818eb", + "metadata": {}, + "source": [ + "#### Physics background" + ] + }, + { + "cell_type": "markdown", + "id": "e7f1ddf9", + "metadata": {}, + "source": [ + "[Magnetic reconnection]: https://en.wikipedia.org/wiki/Magnetic_reconnection\n", + "\n", + "[Magnetic reconnection] is the fundamental plasma process that converts stored magnetic energy into kinetic energy, thermal energy, and particle acceleration. Reconnection powers solar flares and is a key component of geomagnetic storms in Earth's magnetosphere. Reconnection can also degrade confinement in fusion devices such as tokamaks.\n", + "\n", + "The **inertial length** is the characteristic length scale for a particle to get accelerated or decelerated by electromagnetic forces in a plasma.\n", + "\n", + "When the reconnection layer thickness is shorter than the **ion inertial length**, $d_i ≡ c/ω_{pi}$, collisionless effects and the Hall effect enable reconnection to be fast (Zweibel & Yamada 2009). The inner electron diffusion region has a thickness of about the **electron inertial length**, $d_e ≡ c/ω_{pi}$. (Here, $ω_{pi}$ and $ω_{pe}$ are the ion and electron plasma frequencies.)\n", + "\n", + "**Our goal: calculate $d_i$ and $d_e$ to get an idea of how far the MMS spacecraft should be separated from each other to investigate reconnection.**" + ] + }, + { + "cell_type": "markdown", + "id": "749fba51", + "metadata": {}, + "source": [ + "### Length scales" + ] + }, + { + "cell_type": "markdown", + "id": "9db83228", + "metadata": {}, + "source": [ + "Let's choose some characteristic plasma parameters for the magnetosphere." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f317f40", + "metadata": {}, + "outputs": [], + "source": [ + "n = 1 * u.cm ** -3\n", + "B = 5 * u.nT\n", + "T = 30000 * u.K" + ] + }, + { + "cell_type": "markdown", + "id": "591bef20", + "metadata": {}, + "source": [ + "Let's calculate the ion inertial length, $d_i$. On length scales shorter than $d_i$, the Hall effect becomes important as the ions and electrons decouple from each other." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a717c75f", + "metadata": {}, + "outputs": [], + "source": [ + "inertial_length(n=n, particle=\"p+\").to(\"km\")" + ] + }, + { + "cell_type": "markdown", + "id": "5c56977f", + "metadata": {}, + "source": [ + "The ion diffusion regions should therefore be a few hundred kilometers thick. Let's calculate the electron inertial length next." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e5a4dd9", + "metadata": {}, + "outputs": [], + "source": [ + "inertial_length(n=n, particle=\"e-\").to(\"km\")" + ] + }, + { + "cell_type": "markdown", + "id": "bb4b8d8d", + "metadata": {}, + "source": [ + "The electron diffusion region should therefore have a characteristic length scale of a few kilometers, which is significantly smaller than the ion diffusion region." + ] + }, + { + "cell_type": "markdown", + "id": "2b2875fc", + "metadata": {}, + "source": [ + "We can also calculate the gyroradii for different particles " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96fee1ef", + "metadata": {}, + "outputs": [], + "source": [ + "gyroradius(B=B, particle=[\"p+\", \"e-\"], T=T).to(\"km\")" + ] + }, + { + "cell_type": "markdown", + "id": "1b2b32b3", + "metadata": {}, + "source": [ + "The four *MMS* spacecraft have separations of ten to hundreds of kilometers, and thus are well-positioned to investigate Hall physics during reconnection in the magnetosphere." + ] + }, + { + "cell_type": "markdown", + "id": "38e1aff3", + "metadata": {}, + "source": [ + "#### Frequencies" + ] + }, + { + "cell_type": "markdown", + "id": "fc1a183f", + "metadata": {}, + "source": [ + "We can calculate some of the fundamental frequencies associated with magnetospheric plasma. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b9f9403", + "metadata": {}, + "outputs": [], + "source": [ + "plasma_frequency(n=n, particle=[\"p+\", \"e-\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5cc9c0ca", + "metadata": {}, + "outputs": [], + "source": [ + "gyrofrequency(B=B, particle=[\"p+\", \"e-\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88534868", + "metadata": {}, + "outputs": [], + "source": [ + "lower_hybrid_frequency(B=B, n_i=n, ion=\"p+\")" + ] + }, + { + "cell_type": "markdown", + "id": "8242486e-dcb4-4815-aa6f-04e6d6406546", + "metadata": {}, + "source": [ + "[`lower_hybrid_frequency`]: https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.frequencies.lower_hybrid_frequency.html#lower-hybrid-frequency\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "Most of the functions in [`plasmapy.formulary`] have descriptions of the plasma parameters that include the formula and physical interpretation. If we ever forget what the [`lower_hybrid_frequency`] represents, we can check out its documentation page! " + ] + }, + { + "cell_type": "markdown", + "id": "04bf2ba1-c580-4ca5-b410-530ad3b4e31a", + "metadata": {}, + "source": [ + "## Python interlude" + ] + }, + { + "cell_type": "markdown", + "id": "aac532e4-5249-458b-bf4b-aad05f36fc74", + "metadata": {}, + "source": [ + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "Now that we've gone through how to use [`plasmapy.particles`] and [`plasmapy.formulary`], we'll move on to the process for adding a function to [`plasmapy.formulary`]. Before that, we need to introduce two capabilities for the Python language:\n", + "\n", + " - Type hint annotations\n", + " - Decorators" + ] + }, + { + "cell_type": "markdown", + "id": "44e501d8-3065-4dea-a673-7b953725902d", + "metadata": {}, + "source": [ + "### Type hint annotations" + ] + }, + { + "cell_type": "markdown", + "id": "41d0e999-a6d5-474c-a5b2-cbff44e00f44", + "metadata": {}, + "source": [ + "[_dynamically typed language_]: https://en.wikipedia.org/wiki/Dynamic_programming_language\n", + "[Type hint annotations]: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html\n", + "\n", + "In a _statically typed language_, variable types are explicitly declared. If `s` is declared to be a string, then `s` will always be a string. Type errors are found when code is _compiled_.\n", + "\n", + "Python is a [_dynamically typed language_]. Variable types are determined at runtime rather than at compile time, and it's possible for variables to even change types! \n", + "\n", + "There are tradeoffs! ⚖️ Dynamically are _more flexible_, but at the cost of _reduced type safety_.\n", + "\n", + "[Type hint annotations] provide a middle ground between statically vs. dynamically typed languages. \n", + "\n", + "🏷 Let's define a variable called `name` and provide it with a type hint annotation that says the variable should be a a `str`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd171e0c-7233-479d-9e3d-18d5b71e7c3e", + "metadata": {}, + "outputs": [], + "source": [ + "name: str = \"Darth Fortran\"" + ] + }, + { + "cell_type": "markdown", + "id": "681d8f87-abdf-4a78-b4a8-619f0ed95d9b", + "metadata": {}, + "source": [ + "The type hint of `str` in the above cell didn't actually do anything when we ran it. But when we read the code, it does tell us what type to expect `name` to be.\n", + "\n", + "How about a `list` that starts empty but will eventually contain names? " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8733b1c7-1d85-463a-9d63-718379032fb0", + "metadata": {}, + "outputs": [], + "source": [ + "names: list[str] = []" + ] + }, + { + "cell_type": "markdown", + "id": "d8597c81-8e6e-49b3-9b6e-bbfe86f88c8a", + "metadata": {}, + "source": [ + "This type hint still doesn't do anything at runtime, but it helps us read the code, and lets us use code quality tools that can help us find errors.\n", + "\n", + "We can specify that a variable should be one of multiple types with `|`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24b7a377-0370-48ba-a3f6-a64181508a11", + "metadata": {}, + "outputs": [], + "source": [ + "identifier: str | int" + ] + }, + { + "cell_type": "markdown", + "id": "d5c8ac47-fcd1-4968-a14a-df7459a5538f", + "metadata": {}, + "source": [ + "Type hints are particularly helpful when defining functions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c74d5de-3a75-4ea8-bac9-ac5dcb94f9e6", + "metadata": {}, + "outputs": [], + "source": [ + "def stringify(number: int) -> str:\n", + " return str(num)" + ] + }, + { + "cell_type": "markdown", + "id": "830261bc-0cb7-405f-91fa-9125f8cc7bec", + "metadata": {}, + "source": [ + "[static type checking]: https://mypy.readthedocs.io/en/stable/getting_started.html#dynamic-vs-static-typing\n", + "[mypy]: https://mypy.readthedocs.io/en/stable/\n", + "\n", + "Many Python packages, including PlasmaPy, make use of [static type checking] tools like [mypy]. These tools can help us find type errors such as in the following function before we even run the code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9190e68f-88d0-4746-93e0-5527f7ea014f", + "metadata": {}, + "outputs": [], + "source": [ + "def return_argument(x: int) -> str:\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "id": "ac74f48b-a3cd-4f1d-9909-9c71faf5804a", + "metadata": {}, + "source": [ + "What if we want to specify that a function accepts a length and returns a volume?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3607d300-7ede-4a6c-a4bd-464877c2ea6a", + "metadata": {}, + "outputs": [], + "source": [ + "def volume(d: u.Quantity[u.m]) -> u.Quantity[u.m**3]:\n", + " return d**3" + ] + }, + { + "cell_type": "markdown", + "id": "28fa6bed-52ad-4a2c-b51c-32e2ba7e5c49", + "metadata": {}, + "source": [ + "### Decorators" + ] + }, + { + "cell_type": "markdown", + "id": "ce91e852-6f12-434d-943b-5ca64225b550", + "metadata": {}, + "source": [ + "In Python, functions are objects. This means that we can write a function that returns another function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8d7e33f-9dd8-4351-a498-cd4ced4ce68c", + "metadata": {}, + "outputs": [], + "source": [ + "def return_function():\n", + "\n", + " print(\"Defining inner_function...\")\n", + " \n", + " def inner_function(): \n", + " print(\"Calling inner_function!\")\n", + " \n", + " return inner_function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef09dad1-3d99-4d36-ae81-795433d25711", + "metadata": {}, + "outputs": [], + "source": [ + "function = return_function()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac56c46b-06d5-4296-b9c6-40697f25be11", + "metadata": {}, + "outputs": [], + "source": [ + "function()" + ] + }, + { + "cell_type": "markdown", + "id": "03d3bae8-e510-4bcf-a01d-4d998e0b24a5", + "metadata": {}, + "source": [ + "Or we can pass a function as an argument to another function! We can use `typing.Callable` as the corresponding type hint annotation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27d99306-584e-4d1d-a913-0ead6c9b982f", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Callable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db188ae4-5ff7-48ff-b335-6d0bbfbe8b2f", + "metadata": {}, + "outputs": [], + "source": [ + "def apply_function(function: Callable, array):\n", + " return function(array)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cc6dc5c-37f8-45a4-813c-5576b2e326f7", + "metadata": {}, + "outputs": [], + "source": [ + "array = [1, 2, 3, 4, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2137ef7e-f0ac-4c7a-8a8b-596c98e2402d", + "metadata": {}, + "outputs": [], + "source": [ + "apply_function(max, array)" + ] + }, + { + "cell_type": "markdown", + "id": "bf4b7d59-1f13-42e6-87df-b3a31d635b4f", + "metadata": {}, + "source": [ + "[**decorator**]: https://www.geeksforgeeks.org/decorators-in-python/\n", + "\n", + "A function that _modifies another function_ is a [**decorator**]. \n", + "\n", + "Decorators in Python are a way to modify or enhance functions without changing their actual code. They wrap another function, potentially adding extra functionality before and after the original function runs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5095d1d9-a6de-401d-994c-83ffd16271c1", + "metadata": {}, + "outputs": [], + "source": [ + "def decorator(function: Callable):\n", + " \n", + " def decorated_function() -> None:\n", + "\n", + " print(\"Before calling the function.\")\n", + " result = function()\n", + " print(\"After calling the function.\")\n", + "\n", + " return result\n", + "\n", + " return decorated_function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "537d6027-fefc-4117-8c37-44555d38c49c", + "metadata": {}, + "outputs": [], + "source": [ + "def example_function():\n", + " print(\"Inside original example_function!\")" + ] + }, + { + "cell_type": "markdown", + "id": "d2aa4361-997e-49e7-bddc-87705ba858b6", + "metadata": {}, + "source": [ + "Let's try it out!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5c89a55-260b-46de-bb30-6023e3f90c2c", + "metadata": {}, + "outputs": [], + "source": [ + "modified_function = decorator(example_function)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d15b8474-7cc1-4e0f-83cc-2f56d2c387ec", + "metadata": {}, + "outputs": [], + "source": [ + "example_function()" + ] + }, + { + "cell_type": "markdown", + "id": "41320c46-19d7-4cbc-9ad7-dda5933208de", + "metadata": {}, + "source": [ + "In Python, we have _syntactic sugar_ for decorators, using `@`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f08b76f-d26b-4124-b453-a04d210366ba", + "metadata": {}, + "outputs": [], + "source": [ + "@decorator\n", + "def function2():\n", + " print(\"Inside function2!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8f5eec3-136c-4ca8-876f-2a02362a32d1", + "metadata": {}, + "outputs": [], + "source": [ + "function2()" + ] + }, + { + "cell_type": "markdown", + "id": "4508ad5d-88a5-41e9-aba1-51d2bbe2907d", + "metadata": {}, + "source": [ + "#### Fibonacci sequence" + ] + }, + { + "cell_type": "markdown", + "id": "4bc96fbc-4b0c-4190-a1ca-9313bb149342", + "metadata": {}, + "source": [ + "Let's look at a _recursive_ function that computes the $n$th Fibonacci number. If we define $F_0 ≡ 0$ and $F_1 ≡ 1$, then $ F_n = F_{n-1} + F_{n-2}$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27b42156-f06e-484d-ae8c-767d8267fb4d", + "metadata": {}, + "outputs": [], + "source": [ + "def fibonacci(n: int) -> int:\n", + " print(\"Calculating Fibonacci number for n =\", n)\n", + " return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7ddee57-66fb-4c28-9cd9-30fddd7d0b0b", + "metadata": {}, + "outputs": [], + "source": [ + "fibonacci(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fca36cc4-9349-4433-b13c-e2e2a1e66c23", + "metadata": {}, + "outputs": [], + "source": [ + "fibonacci(4)" + ] + }, + { + "cell_type": "markdown", + "id": "bb81be84-72a8-428d-8324-c9690f350719", + "metadata": {}, + "source": [ + "[`@functools.cache`]: https://docs.python.org/3/library/functools.html#functools.cache\n", + "\n", + "We'll use [`@functools.cache`] to store the output of a function for a particular argument." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb8147dc-165d-48e7-a5ae-a325410b2d22", + "metadata": {}, + "outputs": [], + "source": [ + "from functools import cache\n", + "\n", + "\n", + "@cache\n", + "def fibonacci_cached(n: int) -> int:\n", + " print(\"Calculating Fibonacci number for n =\", n)\n", + " return n if n < 2 else fibonacci_cached(n - 1) + fibonacci_cached(n - 2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c861945a-c8e9-43d9-9dfe-00bde320129c", + "metadata": {}, + "outputs": [], + "source": [ + "fibonacci_cached(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01f5996f-5218-45c8-9fa1-c97755600a3e", + "metadata": {}, + "outputs": [], + "source": [ + "fibonacci_cached(5)" + ] + }, + { + "cell_type": "markdown", + "id": "8fb186d4-0a5c-462c-ba67-117906be5771", + "metadata": {}, + "source": [ + "Let's try calling it again!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fee0a698-e73e-4f69-a690-68daa39a043c", + "metadata": {}, + "outputs": [], + "source": [ + "fibonacci_cached(5)" + ] + }, + { + "cell_type": "markdown", + "id": "f7377f28-0f01-40f9-9125-e9f5ae78132f", + "metadata": {}, + "source": [ + "## Writing a formulary function" + ] + }, + { + "cell_type": "markdown", + "id": "227f37ab-5f1b-4a98-a34f-88ca38afce7c", + "metadata": {}, + "source": [ + "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[`@validate_quantities`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[`@check_relativistic`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/index.html\n", + "[`plasmapy.particles`]: https://docs.plasmapy.org/en/stable/particles/index.html\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "PlasmaPy has three decorators that help us with writing formulary functions.\n", + "\n", + " - [`@check_relativistic`]\n", + " - [`@validate_quantities`]\n", + " - [`@particle_input`]\n", + "\n", + "Let's get some imports out of the way." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2080cc3-2567-4ee1-87c4-a93aea165c47", + "metadata": {}, + "outputs": [], + "source": [ + "import astropy.units as u\n", + "from astropy import constants\n", + "from plasmapy.utils.decorators import check_relativistic\n", + "from plasmapy.particles import particle_input" + ] + }, + { + "cell_type": "markdown", + "id": "5ce64829-a508-488d-a571-133c3b5b57af", + "metadata": {}, + "source": [ + "### Checking relativistic velocities" + ] + }, + { + "cell_type": "markdown", + "id": "9fab64f6-a591-456c-b482-7753c31e35da", + "metadata": {}, + "source": [ + "[`@check_relativistic`]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "Many functions that return velocities in [`plasmapy.formulary`] assume that relativistic velocities are unimportant. To check for this, we can use the [`@check_relativistic`] decorator. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09dcca0c-4f88-4788-a901-146238c556c4", + "metadata": {}, + "outputs": [], + "source": [ + "@check_relativistic\n", + "def speed(distance, time):\n", + " return distance / time" + ] + }, + { + "cell_type": "markdown", + "id": "d380c763-f2f8-4944-845b-4256a191e3ae", + "metadata": {}, + "source": [ + "Let's try this with a velocity _approaching_ the speed of light." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed16449c-39e3-4422-9a76-7bbceae478ce", + "metadata": {}, + "outputs": [], + "source": [ + "speed(299792457 * u.m, 1 * u.s)" + ] + }, + { + "cell_type": "markdown", + "id": "ac094c66-2990-48f8-958f-eb30baf46e08", + "metadata": {}, + "source": [ + "Now let's try this with a velocity _exceeding_ the speed of light." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a24084ea-f588-44c9-b945-401717acd2ec", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "speed(299792459 * u.m, 1 * u.s)" + ] + }, + { + "cell_type": "markdown", + "id": "03f106da-02aa-4ad0-a35a-28d9d9ea3d4e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "### Validating quantities" + ] + }, + { + "cell_type": "markdown", + "id": "36d89f38-c4cf-4def-83c0-fe041082f26f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/units/quantity.html\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/\n", + "[`plasmapy.formulary`]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "\n", + "Most [`plasmapy.formulary`] functions accept and return [`Quantity`] objects from [`astropy.units`]. For example, we might write a function to calculate the magnetic pressure:\n", + "\n", + "$$ p_B ≡ \\frac{B^2}{2μ_o} $$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87b09a20-3ea4-4581-a095-f32b67437bb9", + "metadata": {}, + "outputs": [], + "source": [ + "def magnetic_pressure(B):\n", + " return B**2 / (2 * constants.mu0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa8103a8-a0ac-475c-bb75-ad4b0a7c3066", + "metadata": {}, + "outputs": [], + "source": [ + "magnetic_pressure(1 * u.T)" + ] + }, + { + "cell_type": "markdown", + "id": "659b63ad-9575-4676-bcbe-f9593c485c46", + "metadata": {}, + "source": [ + "But the above function doesn't prevent us from making mistakes simple mistakes..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb4fd6db-71d6-4427-9c21-0cef9706d699", + "metadata": {}, + "outputs": [], + "source": [ + "magnetic_pressure(1 * u.B)" + ] + }, + { + "cell_type": "markdown", + "id": "4c1fa3a1-128d-4c09-bde5-341ba0a3add6", + "metadata": {}, + "source": [ + "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "\n", + "The [`@validate_quantities`] decorator makes sure that " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc96b8eb-20e6-4812-921d-cfe4f3935a7a", + "metadata": {}, + "outputs": [], + "source": [ + "@validate_quantities\n", + "def magnetic_pressure(B: u.Quantity[u.T]) -> u.Quantity[u.Pa]:\n", + " return B**2 / (2 * constants.mu0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "450c05cc-41bd-4d41-bb54-ddc67ac8e421", + "metadata": {}, + "outputs": [], + "source": [ + "magnetic_pressure(1 * u.T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aace6447-013b-4030-90bd-ec725a8ba9a4", + "metadata": { + "editable": true, + "scrolled": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "magnetic_pressure(1 * u.B)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f64a511-5882-4f6d-8ac6-22dadf422071", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "magnetic_pressure(1)" + ] + }, + { + "cell_type": "markdown", + "id": "1ea028ab-730e-4a83-bd49-a9e8d5799af3", + "metadata": {}, + "source": [ + "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[equivalency]: https://docs.astropy.org/en/stable/units/equivalencies.html\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/\n", + "\n", + "\n", + "We can also provide arguments directly to [`@validate_quantities`], for example if we want to do a validation on the return value whilst using an [equivalency] from [`astropy.units`]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b47f2360-1037-4ded-a71e-ea94cd54f757", + "metadata": {}, + "outputs": [], + "source": [ + "@validate_quantities(\n", + " validations_on_return={\"units\": u.K, \"equivalencies\": u.temperature_energy()}\n", + ")\n", + "def get_temperature(T):\n", + " return T" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3f27c37-01b1-4a36-847a-124d752e1342", + "metadata": {}, + "outputs": [], + "source": [ + "get_temperature(1 * u.eV)" + ] + }, + { + "cell_type": "markdown", + "id": "bd2fe7dc-6304-4e4d-a259-9fd730be272c", + "metadata": {}, + "source": [ + "The docstring for [`@validate_quantities`](https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities) contains examples for several more validations that it can do, such as:\n", + "\n", + " - Allowing only non-negative values\n", + " - Restricting/allowing `complex` values\n", + " - Restricting/allowing `numpy.nan` values \n", + " - Allowing `None` instead of a `Quantity`" + ] + }, + { + "cell_type": "markdown", + "id": "c64c1d1f-31d0-4b85-ae18-ebc29add503c", + "metadata": {}, + "source": [ + "## Particle inputs" + ] + }, + { + "cell_type": "markdown", + "id": "dd6998ba-38ac-4d91-bea8-c7a9e22ed241", + "metadata": {}, + "source": [ + "[particle-like]: https://docs.plasmapy.org/en/latest/glossary.html#term-particle-like\n", + "\n", + "Plasma parameters often depend on particle properties, especially the particle's charge and mass. \n", + "\n", + "Early on, we wrote formulary functions like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b002c4f9-bbdf-4a26-8cae-0d2690d25749", + "metadata": {}, + "outputs": [], + "source": [ + "def get_particle_mass(particle_string):\n", + " particle_object = Particle(particle_string)\n", + " return particle_object.mass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10b511e8-9691-4a11-a6d7-c0bf44dc2d1e", + "metadata": {}, + "outputs": [], + "source": [ + "get_particle_mass(\"p+\")" + ] + }, + { + "cell_type": "markdown", + "id": "123e5696-8cf5-44a5-a8e1-abe340b6856d", + "metadata": {}, + "source": [ + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "\n", + "The above example isn't particularly bad. But if we wanted to make a function compatible with [`ParticleList`] objects too, then it started to become a mess." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dead2c8f-d7af-450e-acea-f7eaf2462da1", + "metadata": {}, + "outputs": [], + "source": [ + "from collections.abc import Iterable\n", + "\n", + "\n", + "def get_particle_mass(particle):\n", + " if isinstance(particle, str):\n", + " particle_object = Particle(particle)\n", + " elif isinstance(particle, Iterable):\n", + " particle_object = ParticleList(particle)\n", + " return particle_object.mass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bced4c71-7a29-470b-a54e-d34f6097102a", + "metadata": {}, + "outputs": [], + "source": [ + "get_particle_mass([\"p+\", \"e-\"])" + ] + }, + { + "cell_type": "markdown", + "id": "6db27d70-3f74-4cf3-976b-06294295eede", + "metadata": {}, + "source": [ + "[**`@particle_input`**]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[`ParticleLike`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.ParticleLike.html#particlelike\n", + "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "\n", + "\n", + "The [**`@particle_input`**] decorator transforms arguments annotated with [`ParticleLike`] into a [`Particle`], [`CustomParticle`], or [`ParticleList`]. This transformation occurs _before_ the argument gets passed into the decorated function, so we end up with cleaner code!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1955b73-9714-4280-8aeb-2e6cb3b855b3", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "def make_particle(particle: ParticleLike):\n", + " return particle" + ] + }, + { + "cell_type": "markdown", + "id": "a285a095-544b-42cb-aed8-942a04c339aa", + "metadata": {}, + "source": [ + "Let's try passing it a string representing a particle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fa4d5f6-0670-4c38-9f84-2af55f1f5a01", + "metadata": {}, + "outputs": [], + "source": [ + "make_particle(\"p+\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb07e7af-52db-4630-afdf-55dd38fa3f67", + "metadata": {}, + "source": [ + "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "\n", + "\n", + "The argument got converted into a [`Particle`] object by [`@particle_input`].\n", + "\n", + "If we add `Z` and `mass_numb` as parameters, they'll get incorporated into the particle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8dc7186-2f24-40f1-af6f-0aea0f6266d5", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "def make_particle(particle: ParticleLike, Z=None, mass_numb=None):\n", + " return particle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79d4951b-bd64-465f-ab8d-2204a1dba965", + "metadata": {}, + "outputs": [], + "source": [ + "make_particle(\"He\", Z=2, mass_numb=4)" + ] + }, + { + "cell_type": "markdown", + "id": "081c1b64-ebc4-4fe0-b76f-a0d36c5faa33", + "metadata": {}, + "source": [ + "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "\n", + "If we name the parameter `element`, `ion`, or `isotope`, then [`@particle_input`] will make sure that the particle matches that identity. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bba411ee-8825-4056-8fcb-4860171c1cb2", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "def mass_number(isotope: ParticleLike):\n", + " return isotope.mass_number" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4438edbb-ae2e-4733-a7c9-45e59aa4c3e9", + "metadata": {}, + "outputs": [], + "source": [ + "mass_number(\"Fe-56\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b7cb398-74d8-487b-900a-d9cbe8b3dcf6", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "raises-exception" + ] + }, + "outputs": [], + "source": [ + "mass_number(\"e-\")" + ] + }, + { + "cell_type": "markdown", + "id": "b00a6e00-3baf-4e1c-aa8e-271470a3fb5e", + "metadata": {}, + "source": [ + "### Putting it all together" + ] + }, + { + "cell_type": "markdown", + "id": "9994fef5-6c29-4639-a3be-f186e271302d", + "metadata": {}, + "source": [ + "[`@validate_quantities`]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", + "[`@particle_input`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "\n", + "Let's use both [`@particle_input`], [`@validate_quantities`], and [`check_relativistic`] to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "827f4dff-cb23-4c06-b45f-0771ddd511a4", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "@check_relativistic\n", + "@validate_quantities\n", + "def alfven_speed(\n", + " B: u.Quantity[u.T],\n", + " n: u.Quantity[u.m**-3], \n", + " ion: ParticleLike,\n", + ") -> u.Quantity[u.m / u.s]:\n", + " \"\"\"Return the Alfvén speed.\"\"\"\n", + " return B / np.sqrt(constants.mu0 * n * ion.mass)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73efd087-fd5d-482f-b131-e420e8fd2dfb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "alfven_speed(B = 0.02 * u.T, n = 1e15 * u.m**-3, ion=\"p+\")" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "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.12.1" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3c46a3237dbbdbfeea42fdc484ffe3c6e7de92f0 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:44:54 -0400 Subject: [PATCH 21/26] reStructuredText edits --- docs/source/notebooks/particles-formulary-completed.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index 3babc16..bbcb52c 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -19,7 +19,7 @@ "[decorators]: https://www.geeksforgeeks.org/decorators-in-python/\n", "[Type hint annotations]: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html\n", "\n", - "We'll start this notebook with a quick refresher on [astropy.units], before going through examples from [plasmapy.particles] ⚛️ and [plasmapy.formulary] 🧮. After an interlude about [type hint annotations] and [decorators], we'll \n", + "We'll start this notebook with a quick refresher on [astropy.units], before going through examples from [plasmapy.particles] ⚛️ and [plasmapy.formulary] 🧮. After an interlude about [type hint annotations] and [decorators], we'll discuss how to create a formulary function.\n", "\n", "Let's start with some preliminary imports & settings. To execute a cell in a Jupyter notebook, press `Shift + Enter`. If you have to restart the notebook, please re-execute the following cell.\n", "\n", @@ -237,9 +237,9 @@ "[mass number]: https://en.wikipedia.org/wiki/Mass_number\n", "[Quantity]: https://docs.astropy.org/en/stable/units/quantity.html#quantity\n", "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", - "[`half_life`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.atomic.half_life.html#half-life\n", + "[half_life]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.atomic.half_life.html#half-life\n", "\n", - "We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. Let's use [`half_life`] to return the half-life of a radioactive particle in seconds as a [Quantity]." + "We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. Let's use [half_life] to return the half-life of a radioactive particle in seconds as a [Quantity]." ] }, { @@ -2313,7 +2313,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.12.4" }, "widgets": { "application/vnd.jupyter.widget-state+json": { From 883593f131de4e85535fedde1add798f34b9426b Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 18:53:44 -0400 Subject: [PATCH 22/26] Update notebook --- .../particles-formulary-completed.ipynb | 137 +++++++++--------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index bbcb52c..3464bee 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -110,9 +110,9 @@ "metadata": {}, "source": [ "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", - "[`Quantity.to()`]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", + "[Quantity.to()]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity.to\n", "\n", - "We can use [`Quantity.to()`] to convert a [Quantity] to different units." + "We can use [Quantity.to()] to convert a [Quantity] to different units." ] }, { @@ -332,9 +332,9 @@ "id": "3c2c3835", "metadata": {}, "source": [ - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", "\n", - "Up until now, we have been using functions that accept representations of particles and then return particle properties. With the [`Particle`] class, we can create objects that represent physical particles." + "Up until now, we have been using functions that accept representations of particles and then return particle properties. With the [Particle] class, we can create objects that represent physical particles." ] }, { @@ -354,9 +354,9 @@ "id": "160df55b", "metadata": {}, "source": [ - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", "\n", - "Particle properties can be accessed via attributes of the [`Particle`] class." + "Particle properties can be accessed via attributes of the [Particle] class." ] }, { @@ -406,10 +406,10 @@ "source": [ "#### Antiparticles\n", "\n", - "[`antiparticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.antiparticle\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[antiparticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.antiparticle\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", "\n", - "We can get antiparticles of fundamental particles by using the [`antiparticle`] attribute of a [`Particle`]." + "We can get antiparticles of fundamental particles by using the [antiparticle] attribute of a [Particle]." ] }, { @@ -427,10 +427,10 @@ "id": "bf40e592", "metadata": {}, "source": [ - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", "\n", "\n", - "We can also use the tilde (`~`) operator on a [`Particle`] to get its antiparticle." + "We can also use the tilde (`~`) operator on a [Particle] to get its antiparticle." ] }, { @@ -450,11 +450,11 @@ "source": [ "#### Ionization and recombination\n", "\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", - "[`recombine()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.recombine\n", - "[`ionize()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.ionize\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle\n", + "[recombine()]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.recombine\n", + "[ionize()]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.ionize\n", "\n", - "The [`recombine()`] and [`ionize()`] methods of a [`Particle`] representing an ion or neutral atom will return a different [`Particle`] with fewer or more electrons." + "The [recombine()] and [ionize()] methods of a [Particle] representing an ion or neutral atom will return a different [Particle] with fewer or more electrons." ] }, { @@ -500,9 +500,9 @@ "id": "4c8c2a63", "metadata": {}, "source": [ - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", "\n", - "Sometimes we want to use a particle with custom properties. For example, we might want to represent an average ion in a multi-species plasma or a dust particle. For that we can use [`CustomParticle`]." + "Sometimes we want to use a particle with custom properties. For example, we might want to represent an average ion in a multi-species plasma or a dust particle. For that we can use [CustomParticle]." ] }, { @@ -520,10 +520,10 @@ "id": "dfc19404", "metadata": {}, "source": [ - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", "\n", - "Many of the attributes of [`CustomParticle`] are the same as in [`Particle`]." + "Many of the attributes of [CustomParticle] are the same as in [Particle]." ] }, { @@ -561,9 +561,9 @@ "id": "21a992a9", "metadata": {}, "source": [ - "[`numpy.nan`]: https://numpy.org/doc/stable/reference/constants.html#numpy.nan\n", + "[numpy.nan]: https://numpy.org/doc/stable/reference/constants.html#numpy.nan\n", "\n", - "If we do not include one of the physical quantities, it gets set to [`numpy.nan`] (not a number) in the appropriate units." + "If we do not include one of the physical quantities, it gets set to [numpy.nan] (not a number) in the appropriate units." ] }, { @@ -581,10 +581,10 @@ "id": "6292a1f3", "metadata": {}, "source": [ - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "[`CustomParticle`] objects can provided to most of the commonly used functions in [plasmapy.formulary], and we're planning to improve interoperability in future releases of PlasmaPy." + "[CustomParticle] objects can provided to most of the commonly used functions in [plasmapy.formulary], and we're planning to improve interoperability in future releases of PlasmaPy." ] }, { @@ -600,11 +600,11 @@ "id": "7939cfad", "metadata": {}, "source": [ - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", "\n", - "The [`ParticleList`] class is a container for [`Particle`] and [`CustomParticle`] objects." + "The [ParticleList] class is a container for [Particle] and [CustomParticle] objects." ] }, { @@ -622,9 +622,9 @@ "id": "d56ddb98", "metadata": {}, "source": [ - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", "\n", - "By using a [`ParticleList`], we can access the properties of multiple particles at once." + "By using a [ParticleList], we can access the properties of multiple particles at once." ] }, { @@ -662,11 +662,11 @@ "id": "cccc6c60", "metadata": {}, "source": [ - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", "\n", - "We can also create a [`ParticleList`] by adding [`Particle`] and/or [`CustomParticle`] objects together." + "We can also create a [ParticleList] by adding [Particle] and/or [CustomParticle] objects together." ] }, { @@ -702,10 +702,10 @@ "id": "e9038618", "metadata": {}, "source": [ - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "[`ParticleList`] objects can also be provided to the most commonly used functions in [plasmapy.formulary], with more complete interoperability expected in the future." + "[ParticleList] objects can also be provided to the most commonly used functions in [plasmapy.formulary], with more complete interoperability expected in the future." ] }, { @@ -721,11 +721,11 @@ "id": "1fa46bf5-200d-4297-aa13-fcdbd9ca286f", "metadata": {}, "source": [ - "[`categories`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.categories\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", - "[`set`]: https://docs.python.org/3/library/stdtypes.html#set\n", + "[categories]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.categories\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "[set]: https://docs.python.org/3/library/stdtypes.html#set\n", "\n", - "The [`categories`] attribute of a [`Particle`] provides a set of the categories that the particle belongs to as a [`set`]." + "The [categories] attribute of a [Particle] provides a set of the categories that the particle belongs to as a [set]." ] }, { @@ -744,10 +744,10 @@ "id": "38d96c41-2e03-48fb-86ab-effae5c2bd87", "metadata": {}, "source": [ - "[`is_category()`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.is_category\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "[is_category()]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#plasmapy.particles.particle_class.Particle.is_category\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", "\n", - "The [`is_category()`] method of a [`Particle`] lets us determine if the particle belongs to one or more categories." + "The [is_category()] method of a [Particle] lets us determine if the particle belongs to one or more categories." ] }, { @@ -765,13 +765,13 @@ "id": "1f427bc9-83cf-4b88-916d-aa3d3d17dbb5", "metadata": {}, "source": [ - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html#particle\n", "\n", "If we need to be more specific with `is_category()`, we can use its keyword arguments: \n", "\n", - " - `require`: for categories that a [`Particle`] _must_ belong to\n", - " - `exclude`: for categories that the [`Particle`] _cannot_ belong to\n", - " - `any_of`: for categories of which a [`Particle`] must belong to _at least one of_" + " - `require`: for categories that a [Particle] _must_ belong to\n", + " - `exclude`: for categories that the [Particle] _cannot_ belong to\n", + " - `any_of`: for categories of which a [Particle] must belong to _at least one of_" ] }, { @@ -789,9 +789,9 @@ "id": "77874c36-3b01-43f2-86f0-a4d28978fe12", "metadata": {}, "source": [ - "[`plasmapy.particles.particle_class.valid_categories`]: https://docs.plasmapy.org/en/latest/api/plasmapy.particles.particle_class.valid_categories.html\n", + "[plasmapy.particles.particle_class.valid_categories]: https://docs.plasmapy.org/en/latest/api/plasmapy.particles.particle_class.valid_categories.html\n", "\n", - "All valid particle categories are included in [`plasmapy.particles.particle_class.valid_categories`]." + "All valid particle categories are included in [plasmapy.particles.particle_class.valid_categories]." ] }, { @@ -809,11 +809,10 @@ "id": "aceafe85-aa39-43fd-89aa-637f017d2574", "metadata": {}, "source": [ - "[`is_category()`]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.is_category\n", - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html#plasmapy.particles.particle_collections.ParticleList\n", - "[`list`]: https://docs.python.org/3/library/stdtypes.html#list\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html#plasmapy.particles.particle_collections.ParticleList\n", + "[list]: https://docs.python.org/3/library/stdtypes.html#list\n", "\n", - "The [`is_category()`] method of [`ParticleList`] returns `True` if all particles match the criteria, and `False` otherwise." + "The `is_category()` method of [ParticleList] returns `True` if all particles match the criteria, and `False` otherwise." ] }, { @@ -993,7 +992,7 @@ "id": "cdc0c334", "metadata": {}, "source": [ - "When we use these parameters in [`beta`](https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.dimensionless.beta.html#plasmapy.formulary.dimensionless.beta), we find that $β$ is quite small so that the corona is magnetically dominated." + "When we use these parameters in [beta](https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.dimensionless.beta.html#plasmapy.formulary.dimensionless.beta), we find that $β$ is quite small so that the corona is magnetically dominated." ] }, { @@ -1244,10 +1243,10 @@ "id": "8242486e-dcb4-4815-aa6f-04e6d6406546", "metadata": {}, "source": [ - "[`lower_hybrid_frequency`]: https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.frequencies.lower_hybrid_frequency.html#lower-hybrid-frequency\n", + "[lower_hybrid_frequency]: https://docs.plasmapy.org/en/stable/api/plasmapy.formulary.frequencies.lower_hybrid_frequency.html#lower-hybrid-frequency\n", "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", "\n", - "Most of the functions in [plasmapy.formulary] have descriptions of the plasma parameters that include the formula and physical interpretation. If we ever forget what the [`lower_hybrid_frequency`] represents, we can check out its documentation page! " + "Most of the functions in [plasmapy.formulary] have descriptions of the plasma parameters that include the formula and physical interpretation. If we ever forget what the [lower_hybrid_frequency] represents, we can check out its documentation page! " ] }, { @@ -1664,9 +1663,9 @@ "id": "bb81be84-72a8-428d-8324-c9690f350719", "metadata": {}, "source": [ - "[`@functools.cache`]: https://docs.python.org/3/library/functools.html#functools.cache\n", + "[@functools.cache]: https://docs.python.org/3/library/functools.html#functools.cache\n", "\n", - "We'll use [`@functools.cache`] to store the output of a function for a particular argument." + "We'll use [@functools.cache] to store the output of a function for a particular argument." ] }, { @@ -2077,9 +2076,9 @@ "id": "123e5696-8cf5-44a5-a8e1-abe340b6856d", "metadata": {}, "source": [ - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", "\n", - "The above example isn't particularly bad. But if we wanted to make a function compatible with [`ParticleList`] objects too, then it started to become a mess." + "The above example isn't particularly bad. But if we wanted to make a function compatible with [ParticleList] objects too, then it started to become a mess." ] }, { @@ -2115,14 +2114,14 @@ "id": "6db27d70-3f74-4cf3-976b-06294295eede", "metadata": {}, "source": [ - "[**`@particle_input`**]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", - "[`ParticleLike`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.ParticleLike.html#particlelike\n", - "[`ParticleList`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", - "[`CustomParticle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[**@particle_input**]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[ParticleLike]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.ParticleLike.html#particlelike\n", + "[ParticleList]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_collections.ParticleList.html\n", + "[CustomParticle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.CustomParticle.html\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", "\n", "\n", - "The [**`@particle_input`**] decorator transforms arguments annotated with [`ParticleLike`] into a [`Particle`], [`CustomParticle`], or [`ParticleList`]. This transformation occurs _before_ the argument gets passed into the decorated function, so we end up with cleaner code!" + "The [**@particle_input**] decorator transforms arguments annotated with [ParticleLike] into a [Particle], [CustomParticle], or [ParticleList]. This transformation occurs _before_ the argument gets passed into the decorated function, so we end up with cleaner code!" ] }, { @@ -2160,11 +2159,11 @@ "id": "eb07e7af-52db-4630-afdf-55dd38fa3f67", "metadata": {}, "source": [ - "[`Particle`]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", + "[Particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.particle_class.Particle.html\n", "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "\n", "\n", - "The argument got converted into a [`Particle`] object by [@particle_input].\n", + "The argument got converted into a [Particle] object by [@particle_input].\n", "\n", "If we add `Z` and `mass_numb` as parameters, they'll get incorporated into the particle." ] @@ -2257,7 +2256,7 @@ "[@validate_quantities]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "\n", - "Let's use both [@particle_input], [@validate_quantities], and [`check_relativistic`] to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " + "Let's use both [@particle_input], [@validate_quantities], and `@check_relativistic` to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " ] }, { From f4674ec1be5f01d9252c8ea38f83498f6695e221 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 21:53:31 -0400 Subject: [PATCH 23/26] Minor notebook updates --- docs/source/notebooks/particles-formulary-completed.ipynb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index 3464bee..841b617 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -1263,6 +1263,7 @@ "metadata": {}, "source": [ "[plasmapy.formulary]: https://docs.plasmapy.org/en/stable/formulary/index.html\n", + "[plasmapy.particles]: https://docs.plasmapy.org/en/stable/particles/index.html\n", "\n", "Now that we've gone through how to use [plasmapy.particles] and [plasmapy.formulary], we'll move on to the process for adding a function to [plasmapy.formulary]. Before that, we need to introduce two capabilities for the Python language:\n", "\n", @@ -2255,6 +2256,7 @@ "source": [ "[@validate_quantities]: https://docs.plasmapy.org/en/latest/api/plasmapy.utils.decorators.validators.validate_quantities.html#validate-quantities\n", "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", + "[@check_relativistic]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", "\n", "Let's use both [@particle_input], [@validate_quantities], and `@check_relativistic` to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " ] From 4b1c30343973b356435ce8ae76aacd4b1289a5f7 Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 21:58:17 -0400 Subject: [PATCH 24/26] Remove some less important pre-commit hooks --- .pre-commit-config.yaml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d770454..caf5493 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,16 +8,8 @@ repos: hooks: - id: check-ast name: validate Python code - - id: check-merge-conflict - name: check for git merge conflicts exclude: .*\.rst - id: check-case-conflict - name: check for filename case conflicts - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-json - - id: check-toml - - id: check-yaml - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.28.6 @@ -35,16 +27,6 @@ repos: hooks: - id: rst-directive-colons - id: rst-inline-touching-normal - - id: text-unicode-replacement-char - -- repo: https://github.com/codespell-project/codespell - rev: v2.3.0 - hooks: - - id: codespell - name: codespell (add false positives to pyproject.toml) - args: [--write-changes] - additional_dependencies: - - tomli - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.5.0 From 75c97f9b57d507ccf7cab9507ce30ea3d88a6f7e Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 21:58:42 -0400 Subject: [PATCH 25/26] Minor notebook update --- docs/source/notebooks/particles-formulary-completed.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index 841b617..3b6ece2 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -2258,7 +2258,7 @@ "[@particle_input]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.decorators.particle_input.html\n", "[@check_relativistic]: https://docs.plasmapy.org/en/stable/api/plasmapy.utils.decorators.checks.check_relativistic.html#check-relativistic\n", "\n", - "Let's use both [@particle_input], [@validate_quantities], and `@check_relativistic` to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " + "Let's use both [@particle_input], [@validate_quantities], and [@check_relativistic] to write an Alfvén speed function. The formula is $$ V_A ≡ \\frac{B}{\\sqrt{μ_0 ρ}} $$ where we approximate the mass density as $ρ ≈ m_i n_i$. " ] }, { From fda855deb0dc3e3c1c6118d5e0ad6dafced3857b Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Sun, 28 Jul 2024 22:01:43 -0400 Subject: [PATCH 26/26] Minor notebook update --- docs/source/notebooks/particles-formulary-completed.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/notebooks/particles-formulary-completed.ipynb b/docs/source/notebooks/particles-formulary-completed.ipynb index 3b6ece2..c2f5f3a 100644 --- a/docs/source/notebooks/particles-formulary-completed.ipynb +++ b/docs/source/notebooks/particles-formulary-completed.ipynb @@ -1091,7 +1091,7 @@ "\n", "When the reconnection layer thickness is shorter than the **ion inertial length**, $d_i ≡ c/ω_{pi}$, collisionless effects and the Hall effect enable reconnection to be fast (Zweibel & Yamada 2009). The inner electron diffusion region has a thickness of about the **electron inertial length**, $d_e ≡ c/ω_{pi}$. (Here, $ω_{pi}$ and $ω_{pe}$ are the ion and electron plasma frequencies.)\n", "\n", - "**Our goal: calculate $d_i$ and $d_e$ to get an idea of how far the MMS spacecraft should be separated from each other to investigate reconnection.**" + "Our goal: calculate $d_i$ and $d_e$ to get an idea of how far the MMS spacecraft should be separated from each other to investigate reconnection." ] }, {