{ "cells": [ { "cell_type": "markdown", "id": "6e8f0d92", "metadata": {}, "source": [ "# Using Astropy Units" ] }, { "cell_type": "code", "execution_count": null, "id": "e2751e56", "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "%xmode minimal" ] }, { "cell_type": "markdown", "id": "c566fe1c", "metadata": {}, "source": [ "In scientific computing, we often represent physical quantities as numbers." ] }, { "cell_type": "code", "execution_count": null, "id": "ee456b33", "metadata": {}, "outputs": [], "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. We can avoid these problems by using a units package.\n", "\n", "This notebook introduces [astropy.units] with an emphasis on the functionality needed to work with [plasmapy.particles] and [plasmapy.formulary]. We typically import this subpackage as `u`." ] }, { "cell_type": "code", "execution_count": null, "id": "8c20320a", "metadata": {}, "outputs": [], "source": [ "import astropy.units as u" ] }, { "cell_type": "markdown", "id": "ad28288e", "metadata": {}, "source": [ "## Contents\n", "\n", "1. [Unit basics](#Unit-basics)\n", "2. [Unit operations](#Unit-operations) \n", "3. [Unit conversations](#Unit-conversions)\n", "4. [Detaching units and values](#Detaching-units-and-values)\n", "5. [Equivalencies](#Equivalencies)\n", "6. [Physical constants](#Physical-constants)\n", "7. [Units in PlasmaPy](#Units-in-PlasmaPy)\n", "8. [Optimizing unit operations](#Optimizing-unit-operations)\n", "9. [Physical Types](#Physical-types)" ] }, { "cell_type": "markdown", "id": "a9fad673", "metadata": {}, "source": [ "## Unit basics\n", "\n" ] }, { "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": [ "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]: a number, sequence, or array that has been assigned a physical unit." ] }, { "cell_type": "code", "execution_count": null, "id": "a6354b93", "metadata": {}, "outputs": [], "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 also create an object by using the [Quantity] class itself." ] }, { "cell_type": "code", "execution_count": null, "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": null, "id": "4932159b", "metadata": {}, "outputs": [], "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": null, "id": "09e9752c", "metadata": {}, "outputs": [], "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": null, "id": "e3235d80", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "np.array([2.5, 3.2, 1.1]) * u.kg" ] }, { "cell_type": "code", "execution_count": null, "id": "a686fd93", "metadata": {}, "outputs": [], "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": null, "id": "65e08284", "metadata": {}, "outputs": [], "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": null, "id": "c36788db", "metadata": {}, "outputs": [], "source": [ "velocity = distance / time\n", "print(velocity)" ] }, { "cell_type": "code", "execution_count": null, "id": "04b48a57", "metadata": {}, "outputs": [], "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": null, "id": "0877feb6", "metadata": { "tags": [ "raises-exception" ] }, "outputs": [], "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] objects behave very similarly to NumPy arrays because [Quantity] is a subclass of [numpy.ndarray]." ] }, { "cell_type": "code", "execution_count": null, "id": "59389429", "metadata": {}, "outputs": [], "source": [ "balmer_series = [656.279, 486.135, 434.0472, 410.1734] * u.nm\n", "Hα = balmer_series[0]\n", "print(Hα)" ] }, { "cell_type": "code", "execution_count": null, "id": "a6545132", "metadata": {}, "outputs": [], "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", "[Quantity objects lose their units with some operations]: 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, [Quantity objects lose their units with some operations]. " ] }, { "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": null, "id": "80f4f133", "metadata": {}, "outputs": [], "source": [ "velocity.to(\"m/s\")" ] }, { "cell_type": "code", "execution_count": null, "id": "21c46ef1", "metadata": {}, "outputs": [], "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": null, "id": "02bda4d2", "metadata": {}, "outputs": [], "source": [ "velocity.si" ] }, { "cell_type": "code", "execution_count": null, "id": "560a0cfe", "metadata": {}, "outputs": [], "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 (as a NumPy scalar) or NumPy array without the unit." ] }, { "cell_type": "code", "execution_count": null, "id": "24e8403c", "metadata": {}, "outputs": [], "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": null, "id": "2c41a2fa", "metadata": {}, "outputs": [], "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": null, "id": "6b6fafd6", "metadata": { "tags": [ "raises-exception" ] }, "outputs": [], "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": null, "id": "afac5b4a", "metadata": {}, "outputs": [], "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": null, "id": "9735710b", "metadata": {}, "outputs": [], "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", "We can use [astropy.constants] to access the most commonly needed physical constants." ] }, { "cell_type": "code", "execution_count": null, "id": "746a79a7", "metadata": {}, "outputs": [], "source": [ "from astropy.constants import c, e, k_B\n", "\n", "print(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": null, "id": "d2d59d08", "metadata": {}, "outputs": [], "source": [ "thermal_energy_per_particle = 0.6 * u.keV\n", "temperature = thermal_energy_per_particle / k_B\n", "print(temperature.to(\"MK\"))" ] }, { "cell_type": "markdown", "id": "7c145497", "metadata": {}, "source": [ "Electromagnetic constants often need the unit system to be specified. Code within PlasmaPy uses SI units." ] }, { "cell_type": "code", "execution_count": null, "id": "015de7fc", "metadata": { "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "2 * e" ] }, { "cell_type": "code", "execution_count": null, "id": "1a90c979", "metadata": {}, "outputs": [], "source": [ "2 * e.si" ] }, { "cell_type": "markdown", "id": "7cbd96f1", "metadata": {}, "source": [ "## Units in PlasmaPy" ] }, { "cell_type": "markdown", "id": "ba4bc791", "metadata": {}, "source": [ "[astropy.units]: https://docs.astropy.org/en/stable/units/index.html\n", "[plasmapy.particles]: ../../particles/index.rst\n", "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "[Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst\n", "[ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst\n", "\n", "Now we can show some uses of [astropy.units] in PlasmaPy, starting with [plasmapy.particles]. Many of the attributes of [Particle] and [ParticleList] provide [Quantity] objects." ] }, { "cell_type": "code", "execution_count": null, "id": "2f4b2c9a", "metadata": {}, "outputs": [], "source": [ "from plasmapy.particles import Particle, ParticleList\n", "\n", "alpha = Particle(\"He-4 2+\")" ] }, { "cell_type": "code", "execution_count": null, "id": "db32e787", "metadata": {}, "outputs": [], "source": [ "alpha.charge" ] }, { "cell_type": "code", "execution_count": null, "id": "c1c310e9", "metadata": {}, "outputs": [], "source": [ "ions = ParticleList([\"O 1+\", \"O 2+\", \"O 3+\"])\n", "ions.mass" ] }, { "cell_type": "markdown", "id": "5fa976cb", "metadata": {}, "source": [ "[Quantity]: https://docs.astropy.org/en/stable/api/astropy.units.Quantity.html#astropy.units.Quantity\n", "[plasmapy.formulary]: ../../formulary/index.rst\n", "\n", "Similarly, [Quantity] objects are the expected inputs and outputs of most functions in [plasmapy.formulary]. We can use them to calculate some plasma parameters for a typical region of the solar corona." ] }, { "cell_type": "code", "execution_count": null, "id": "b8344c57", "metadata": {}, "outputs": [], "source": [ "from plasmapy.formulary import Alfven_speed, Debye_length, gyrofrequency" ] }, { "cell_type": "code", "execution_count": null, "id": "aedf8b48", "metadata": {}, "outputs": [], "source": [ "B = 0.01 * u.T\n", "n = 1e15 * u.m**-3\n", "proton = Particle(\"p+\")" ] }, { "cell_type": "code", "execution_count": null, "id": "390eeb2e", "metadata": {}, "outputs": [], "source": [ "Alfven_speed(B=B, density=n, ion=proton).to(\"km /s\")" ] }, { "cell_type": "code", "execution_count": null, "id": "45c00b7e", "metadata": {}, "outputs": [], "source": [ "gyrofrequency(B=B, particle=\"e-\")" ] }, { "cell_type": "markdown", "id": "6b60ae97", "metadata": {}, "source": [ "The `to_hz` keyword provides the frequency in hertz rather than radians per second, and accounts for the factor of $2π$." ] }, { "cell_type": "code", "execution_count": null, "id": "65a40a6b", "metadata": {}, "outputs": [], "source": [ "gyrofrequency(B=B, particle=\"e-\", to_hz=True)" ] }, { "cell_type": "markdown", "id": "eada431a", "metadata": {}, "source": [ "Formulary functions perform calculations based on SI units, but accept input arguments in other units. Temperature can be given in units of temperature (e.g., kelvin) or energy (e.g., electron-volts)." ] }, { "cell_type": "code", "execution_count": null, "id": "dc05fc3a", "metadata": {}, "outputs": [], "source": [ "Debye_length(T_e=1e6 * u.K, n_e=1e9 * u.m**-3)" ] }, { "cell_type": "code", "execution_count": null, "id": "cfaed93b", "metadata": {}, "outputs": [], "source": [ "Debye_length(T_e=86.17 * u.eV, n_e=1e3 * u.cm**-3)" ] }, { "cell_type": "markdown", "id": "f4da2ab0", "metadata": {}, "source": [ "## Optimizing unit operations\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", "\n", "Astropy's documentation includes [performance tips] for using [astropy.units] in computationally intensive situations. For example, putting compound units in parentheses reduces the need to make multiple copies of the data." ] }, { "cell_type": "code", "execution_count": null, "id": "e3bc2348", "metadata": {}, "outputs": [], "source": [ "volume = 0.62 * (u.barn * u.Mpc)" ] }, { "cell_type": "markdown", "id": "55b8dcd1", "metadata": {}, "source": [ "## Physical types" ] }, { "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": null, "id": "da6c9c7d", "metadata": {}, "outputs": [], "source": [ "(u.m**2 / u.s).physical_type" ] }, { "cell_type": "code", "execution_count": null, "id": "f0d49b03", "metadata": {}, "outputs": [], "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": null, "id": "d03b8eb0", "metadata": {}, "outputs": [], "source": [ "energy_density = (u.J * u.m**-3).physical_type\n", "velocity = u.get_physical_type(\"velocity\")\n", "print(energy_density * velocity)" ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 5 }