From 1689699aa29109bacf8adf50be644b50947cc75d Mon Sep 17 00:00:00 2001 From: Nick Murphy Date: Wed, 24 Jul 2024 21:49:12 -0400 Subject: [PATCH] Update sections on writing formulary functions --- ...asmapy-particles-formulary-completed.ipynb | 586 +++++++++++++++--- 1 file changed, 507 insertions(+), 79 deletions(-) diff --git a/notebooks/plasmapy-particles-formulary-completed.ipynb b/notebooks/plasmapy-particles-formulary-completed.ipynb index 4b8401e..ff7b690 100644 --- a/notebooks/plasmapy-particles-formulary-completed.ipynb +++ b/notebooks/plasmapy-particles-formulary-completed.ipynb @@ -342,7 +342,7 @@ "source": [ "proton = Particle(\"p+\")\n", "electron = Particle(\"electron\")\n", - "iron56_nuclide = Particle(\"Fe\", Z=26, mass_numb=56)" + "iron56_nucleus = Particle(\"Fe\", Z=26, mass_numb=56)" ] }, { @@ -392,7 +392,7 @@ "metadata": {}, "outputs": [], "source": [ - "iron56_nuclide.binding_energy" + "iron56_nucleus.binding_energy" ] }, { @@ -1252,7 +1252,7 @@ "id": "04bf2ba1-c580-4ca5-b410-530ad3b4e31a", "metadata": {}, "source": [ - "## Writing a formulary function" + "## Python interlude" ] }, { @@ -1260,7 +1260,10 @@ "id": "aac532e4-5249-458b-bf4b-aad05f36fc74", "metadata": {}, "source": [ - "Next we'll move on to the process for adding a function to `plasmapy.formulary`. But before that, we'll introduce two parts of the Python language that are " + "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" ] }, { @@ -1268,7 +1271,7 @@ "id": "44e501d8-3065-4dea-a673-7b953725902d", "metadata": {}, "source": [ - "### Prelude: type hint annotations" + "### Type hint annotations" ] }, { @@ -1279,26 +1282,20 @@ "[_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` defined to be a string, it will always be a string. Type errors are checked at compile time.\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. Variables can even change types.\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", - "As always, there are tradeoffs! ⚖️ Dynamically typed languages are much more flexible, but it is harder to find & prevent type errors.\n", + "⚖️ As always, there are tradeoffs. Dynamically typed languages offer _flexibility_, but at the cost of _reduced type safety_.\n", "\n", - "[Type hint annotations] help provide a middle ground between statically vs. dynamically typed languages. " - ] - }, - { - "cell_type": "markdown", - "id": "3631201f-a316-4caa-9127-6eb41e050f6a", - "metadata": {}, - "source": [ - "Let's define a variable called `name` and provide it with a type hint that says the variable should be a a `str`." + "[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": 179, + "execution_count": 193, "id": "cd171e0c-7233-479d-9e3d-18d5b71e7c3e", "metadata": {}, "outputs": [], @@ -1313,7 +1310,7 @@ "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", - "What about if we wanted to create a list of `names` but start with it empty? Then we can do:" + "How about a `list` that starts empty but will eventually contain names? " ] }, { @@ -1406,12 +1403,6 @@ " return d**3" ] }, - { - "cell_type": "markdown", - "id": "3dd76396-9b2d-4389-bd57-2263bbc17b13", - "metadata": {}, - "source": [] - }, { "cell_type": "markdown", "id": "28fa6bed-52ad-4a2c-b51c-32e2ba7e5c49", @@ -1430,16 +1421,14 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 194, "id": "e8d7e33f-9dd8-4351-a498-cd4ced4ce68c", "metadata": {}, "outputs": [], "source": [ - "from typing import Callable\n", - "\n", - "def return_function() -> Callable:\n", + "def return_function():\n", "\n", - " print(\"Defining inner_function!\")\n", + " print(\"Defining inner_function...\")\n", " \n", " def inner_function(): \n", " print(\"Calling inner_function!\")\n", @@ -1449,7 +1438,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 195, "id": "ef09dad1-3d99-4d36-ae81-795433d25711", "metadata": {}, "outputs": [ @@ -1457,7 +1446,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Defining inner_function!\n" + "Defining inner_function...\n" ] } ], @@ -1467,7 +1456,7 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 196, "id": "ac56c46b-06d5-4296-b9c6-40697f25be11", "metadata": {}, "outputs": [ @@ -1493,22 +1482,18 @@ }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 203, "id": "db188ae4-5ff7-48ff-b335-6d0bbfbe8b2f", "metadata": {}, "outputs": [], "source": [ - "def apply_function_to_array(\n", - " function: Callable, \n", - " array: list[int],\n", - ") -> int:\n", - " \n", + "def apply_function(function, array):\n", " return function(array)" ] }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 204, "id": "3cc6dc5c-37f8-45a4-813c-5576b2e326f7", "metadata": {}, "outputs": [], @@ -1518,7 +1503,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 205, "id": "2137ef7e-f0ac-4c7a-8a8b-596c98e2402d", "metadata": {}, "outputs": [ @@ -1528,13 +1513,13 @@ "5" ] }, - "execution_count": 130, + "execution_count": 205, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "apply_function_to_array(max, array)" + "apply_function(max, array)" ] }, { @@ -1544,14 +1529,14 @@ "source": [ "[**decorator**]: https://www.geeksforgeeks.org/decorators-in-python/\n", "\n", - "In Python, a function that _modifies another function_ is called a [**decorator**]. Let's try one out that \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": 131, + "execution_count": 212, "id": "5095d1d9-a6de-401d-994c-83ffd16271c1", "metadata": {}, "outputs": [], @@ -1559,40 +1544,48 @@ "def decorator(function: Callable):\n", " \n", " def decorated_function() -> None:\n", - " \n", + "\n", " print(\"Before calling the function.\")\n", " result = function()\n", " print(\"After calling the function.\")\n", - " \n", + "\n", " return result\n", - " \n", + "\n", " return decorated_function" ] }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 222, "id": "537d6027-fefc-4117-8c37-44555d38c49c", "metadata": {}, "outputs": [], "source": [ - "def example_function() -> None:\n", + "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": 139, + "execution_count": 223, "id": "a5c89a55-260b-46de-bb30-6023e3f90c2c", "metadata": {}, "outputs": [], "source": [ - "example_function = decorator(example_function)" + "modified_function = decorator(example_function)" ] }, { "cell_type": "code", - "execution_count": 141, + "execution_count": 224, "id": "d15b8474-7cc1-4e0f-83cc-2f56d2c387ec", "metadata": {}, "outputs": [ @@ -1600,9 +1593,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Before calling the function.\n", - "Inside original example_function!\n", - "After calling the function.\n" + "Inside original example_function!\n" ] } ], @@ -1615,24 +1606,24 @@ "id": "41320c46-19d7-4cbc-9ad7-dda5933208de", "metadata": {}, "source": [ - "In Python, we have special syntax for decorators" + "In Python, we have _syntactic sugar_ for decorators, using `@`:" ] }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 225, "id": "2f08b76f-d26b-4124-b453-a04d210366ba", "metadata": {}, "outputs": [], "source": [ "@decorator\n", - "def another_function() -> None:\n", + "def another_function():\n", " print(\"Inside function2!\")" ] }, { "cell_type": "code", - "execution_count": 143, + "execution_count": 226, "id": "b8f5eec3-136c-4ca8-876f-2a02362a32d1", "metadata": {}, "outputs": [ @@ -1655,7 +1646,7 @@ "id": "4508ad5d-88a5-41e9-aba1-51d2bbe2907d", "metadata": {}, "source": [ - "#### Example: Fibonacci sequence with caching" + "#### Fibonacci sequence" ] }, { @@ -1663,14 +1654,12 @@ "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}$.\n", - "\n", - "Here's an example recursive function:" + "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": 147, + "execution_count": 227, "id": "27b42156-f06e-484d-ae8c-767d8267fb4d", "metadata": {}, "outputs": [], @@ -1682,7 +1671,7 @@ }, { "cell_type": "code", - "execution_count": 148, + "execution_count": 228, "id": "e7ddee57-66fb-4c28-9cd9-30fddd7d0b0b", "metadata": {}, "outputs": [ @@ -1701,7 +1690,7 @@ "1" ] }, - "execution_count": 148, + "execution_count": 228, "metadata": {}, "output_type": "execute_result" } @@ -1712,7 +1701,7 @@ }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 229, "id": "fca36cc4-9349-4433-b13c-e2e2a1e66c23", "metadata": {}, "outputs": [ @@ -1737,7 +1726,7 @@ "3" ] }, - "execution_count": 175, + "execution_count": 229, "metadata": {}, "output_type": "execute_result" } @@ -1751,12 +1740,14 @@ "id": "bb81be84-72a8-428d-8324-c9690f350719", "metadata": {}, "source": [ - "We'll use `@functools.cache`, which _caches_ (stores) the result from a function so that it doesn't need to be recalculated every time. " + "[`@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": 176, + "execution_count": 230, "id": "bb8147dc-165d-48e7-a5ae-a325410b2d22", "metadata": {}, "outputs": [], @@ -1771,7 +1762,7 @@ }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 231, "id": "c861945a-c8e9-43d9-9dfe-00bde320129c", "metadata": {}, "outputs": [ @@ -1781,7 +1772,7 @@ "1" ] }, - "execution_count": 177, + "execution_count": 231, "metadata": {}, "output_type": "execute_result" } @@ -1792,7 +1783,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 232, "id": "01f5996f-5218-45c8-9fa1-c97755600a3e", "metadata": {}, "outputs": [ @@ -1802,7 +1793,36 @@ "5" ] }, - "execution_count": 178, + "execution_count": 232, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci_decorated(5)" + ] + }, + { + "cell_type": "markdown", + "id": "8fb186d4-0a5c-462c-ba67-117906be5771", + "metadata": {}, + "source": [ + "Let's try calling it again!" + ] + }, + { + "cell_type": "code", + "execution_count": 191, + "id": "fee0a698-e73e-4f69-a690-68daa39a043c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 191, "metadata": {}, "output_type": "execute_result" } @@ -1811,21 +1831,429 @@ "fibonacci_decorated(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", + "\n", + "PlasmaPy has two decorators that help us with writing formulary functions.\n", + "\n", + " - [`@validate_quantities`]\n", + " - [`@particle_input`]\n", + "\n", + "Let's get some imports out of the way." + ] + }, + { + "cell_type": "code", + "execution_count": 236, + "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 validate_quantities\n", + "from plasmapy.particles import particle_input" + ] + }, + { + "cell_type": "markdown", + "id": "03f106da-02aa-4ad0-a35a-28d9d9ea3d4e", + "metadata": {}, + "source": [ + "### Validating quantities" + ] + }, + { + "cell_type": "markdown", + "id": "36d89f38-c4cf-4def-83c0-fe041082f26f", + "metadata": {}, + "source": [ + "[`Quantity`]: https://docs.astropy.org/en/stable/units/quantity.html\n", + "[`astropy.units`]: https://docs.astropy.org/en/stable/units/\n", + "[`plasmapy.formulary`]: \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": 286, + "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": 287, + "id": "fa8103a8-a0ac-475c-bb75-ad4b0a7c3066", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$397887.36 \\; \\mathrm{\\frac{A^{2}\\,T^{2}}{N}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 287, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 289, + "id": "eb4fd6db-71d6-4427-9c21-0cef9706d699", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$397887.36 \\; \\mathrm{\\frac{A^{2}\\,byte^{2}}{N}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 289, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": 290, + "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": 291, + "id": "450c05cc-41bd-4d41-bb54-ddc67ac8e421", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$397887.36 \\; \\mathrm{Pa}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 291, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "magnetic_pressure(1 * u.T)" + ] + }, + { + "cell_type": "code", + "execution_count": 292, + "id": "aace6447-013b-4030-90bd-ec725a8ba9a4", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "ename": "UnitTypeError", + "evalue": "The argument 'B' to function magnetic_pressure() should be an astropy Quantity with the following unit: T", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mUnitTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[292], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mmagnetic_pressure\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\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[43mB\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Projects/PlasmaPy/src/plasmapy/utils/decorators/validators.py:199\u001b[0m, in \u001b[0;36mValidateQuantities.__call__..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;66;03m# validate argument & update for conversion\u001b[39;00m\n\u001b[0;32m--> 199\u001b[0m arg \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_validate_quantity\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 200\u001b[0m \u001b[43m \u001b[49m\u001b[43mbound_args\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43marguments\u001b[49m\u001b[43m[\u001b[49m\u001b[43marg_name\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43marg_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvalidations\u001b[49m\u001b[43m[\u001b[49m\u001b[43marg_name\u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 201\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 202\u001b[0m bound_args\u001b[38;5;241m.\u001b[39marguments[arg_name] \u001b[38;5;241m=\u001b[39m arg\n\u001b[1;32m 204\u001b[0m \u001b[38;5;66;03m# call function\u001b[39;00m\n", + "File \u001b[0;32m/run/media/namurphy/d423d80e-c227-4a33-b50f-545b44160ce3/namurphy/Projects/PlasmaPy/src/plasmapy/utils/decorators/validators.py:376\u001b[0m, in \u001b[0;36mValidateQuantities._validate_quantity\u001b[0;34m(self, arg, arg_name, arg_validations)\u001b[0m\n\u001b[1;32m 374\u001b[0m arg \u001b[38;5;241m=\u001b[39m arg\u001b[38;5;241m.\u001b[39mto(unit, equivalencies\u001b[38;5;241m=\u001b[39mequiv)\n\u001b[1;32m 375\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m err \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 376\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m err\n\u001b[1;32m 378\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_value(arg, arg_name, arg_validations)\n\u001b[1;32m 380\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m arg\n", + "\u001b[0;31mUnitTypeError\u001b[0m: The argument 'B' to function magnetic_pressure() should be an astropy Quantity with the following unit: T" + ] + } + ], + "source": [ + "magnetic_pressure(1 * u.B)" + ] + }, + { + "cell_type": "code", + "execution_count": 250, + "id": "1f64a511-5882-4f6d-8ac6-22dadf422071", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$397887.36 \\; \\mathrm{Pa}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 250, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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]: \n", + "[`astropy.units`]: [`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": 275, + "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": 276, + "id": "e3f27c37-01b1-4a36-847a-124d752e1342", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$11604.518 \\; \\mathrm{K}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 276, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_temperature(1 * u.eV)" + ] + }, + { + "cell_type": "markdown", + "id": "c64c1d1f-31d0-4b85-ae18-ebc29add503c", + "metadata": {}, + "source": [ + "## Particle inputs" + ] + }, + { + "cell_type": "markdown", + "id": "6db27d70-3f74-4cf3-976b-06294295eede", + "metadata": {}, + "source": [ + "The `@particle_input` decorator transforms arguments annotated with `ParticleLike` into a particle object. " + ] + }, + { + "cell_type": "code", + "execution_count": 305, + "id": "f1955b73-9714-4280-8aeb-2e6cb3b855b3", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "def get_particle(particle: ParticleLike) -> Particle | CustomParticle | ParticleList:\n", + " \n", + " return particle" + ] + }, + { + "cell_type": "code", + "execution_count": 320, + "id": "1fa4d5f6-0670-4c38-9f84-2af55f1f5a01", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Particle(\"p+\")" + ] + }, + "execution_count": 320, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_particle(\"p+\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d8579c9-6cdb-423e-bfff-255f8bd2ed37", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e1f25b9-6d52-4462-8fae-e1403d7b669a", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, - "id": "81673a7b-3cd1-4868-be01-f5fbbb9e0bbd", + "id": "a9ecd2f5-a317-4f1b-87a2-f8b696204af9", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "b8387a7f-a9b2-44c0-b1d9-d0b45d7ea44d", + "id": "b00a6e00-3baf-4e1c-aa8e-271470a3fb5e", "metadata": {}, "source": [ - "fibonacci_decorated(5)" + "### Writing an Alfvén speed function" ] + }, + { + "cell_type": "markdown", + "id": "9994fef5-6c29-4639-a3be-f186e271302d", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 316, + "id": "827f4dff-cb23-4c06-b45f-0771ddd511a4", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input\n", + "@validate_quantities\n", + "def alfven_speed(B: u.Quantity[u.T], n: u.Quantity[u.m**-3], ion: ParticleLike) -> u.Quantity[u.m / u.s]:\n", + " return B / np.sqrt(constants.mu0 * n * ion.mass)" + ] + }, + { + "cell_type": "code", + "execution_count": 317, + "id": "73efd087-fd5d-482f-b131-e420e8fd2dfb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$13795142 \\; \\mathrm{\\frac{m}{s}}$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 317, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alfven_speed(B = 0.02 * u.T, n = 1e15 * u.m**-3, ion=\"p+\")" + ] + }, + { + "cell_type": "code", + "execution_count": 319, + "id": "53b52567-a439-4250-aa97-5ee4881dcb9c", + "metadata": {}, + "outputs": [], + "source": [ + "@particle_input(\n", + " require=\"ion\",\n", + " allow_particle_lists=False,\n", + ")\n", + "@validate_quantities(\n", + " n = {\"can_be_negative\": False},\n", + ")\n", + "def alfven_speed(B: u.Quantity[u.T], n: u.Quantity[u.m**-3], ion: ParticleLike) -> u.Quantity[u.m / u.s]:\n", + " return B / np.sqrt(constants.mu0 * n * ion.mass)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cc51035-293b-45a8-badf-9234b6e962cc", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {