{
 "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",
    "[xrtpy]: https://xrtpy.readthedocs.io/en/latest\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 [xrtpy]. 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. [Optimizing unit operations](#Optimizing-unit-operations)\n",
    "8. [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",
    "Occasionally 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, eps0, hbar, k_B, mu0\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": "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",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
