{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial\n", "\n", "Este tutorial busca servir de una guia para la creación de clases utilizando *uttrs*.\n", "\n", " DRAFT!\n", "\n", "## Versión interactiva\n", "\n", "Puede ejecutarse este mismo tutorial de manera interactiba en Binder.\n", "\n", "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/quatrope/uttrs/HEAD?filepath=%2Fdocs%2Fsource%2Ftutorial.ipynb)\n", "\n", "## Imports\n", "\n", "En primer lugar es necesario importar todas las librerias que vamos a utilizar. En general, son solo tres:\n", "\n", "- `attr` (*attrs*) que es la libreria en la cual se basa uttrs para crear clases con menos boiler plate.\n", "- `astropy.units` La cual contiene todo el marco de utilidades para tratar con unidades físicas/astronómicas.\n", "- `uttr` (*uttrs*) La librería que corresponde este tutorial.\n", "\n", "\n", "> Nota: Este tutorial asume un conocimiento sobre estas librerías, una serie de enlaces de referencias pueden encontrarse al final de la página." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import attr\n", "import uttr\n", "\n", "import astropy.units as u" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The galaxy class\n", "\n", "La clase que vamos a crear, consiste en una simplicación de la clase Galaxia del proyecot [Galaxy-Chop](https://github.com/vcristiani/galaxy-chop). \n", "\n", "Solo tiene 8 atributos y solo los primeros 7 tienen unidades y por lo tanto seran implementadas con la funcion `uttr.ib` de la librería. Estos son`x`, `y`, `z` son las posiciones de las particulas/estrellas en KiloParsecs (`kpc`) ; `vx`, `vy`, `vz` las velocidades correspondientes a las particulas ($Km/s$); `m` su masa en masas solares ($M_\\odot$). Todos\n", "\n", "Finalmente `notes` texto libre sobre las galaxias y pueden ser implementadas con la librería *attrs* estandar" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "@attr.s\n", "class Galaxy:\n", " x = uttr.ib(unit=u.kpc)\n", " y = uttr.ib(unit=u.kpc)\n", " z = uttr.ib(unit=u.kpc)\n", "\n", " vx = uttr.ib(unit=u.km / u.s)\n", " vy = uttr.ib(unit=u.km / u.s)\n", " vz = uttr.ib(unit=u.km / u.s)\n", "\n", " m = uttr.ib(unit=u.M_sun)\n", "\n", " notes = attr.ib(validator=attr.validators.instance_of(str))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Galaxy with default units\n", "\n", "Finalmente con la clase ya disponible podemos proceder a crear un objeto del tipo *Galaxy*,\n", "\n", "Por cuestiones de simplicidadad, vamos a asumir solo 4 particulas con numeros totalmente arbitrarios en cada atributo.\n", "\n", "Parte de la utilidad de usar uttrs, es la capacidad que tiene la libreria para agregar unidades por defecto automáticamente, o validar que la unidad ingresada sea equivalente.\n", "\n", "Empecemos con un objeto en el cual todas las unidades se asignan autoḿaticamente" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "gal = Galaxy(\n", " x=[1, 1, 3, 4],\n", " y=[10, 2, 3, 100],\n", " z=[1, 1, 1, 1],\n", " vx=[1000, 1023, 2346, 1334],\n", " vy=[9956, 833, 954, 1024],\n", " vz=[1253, 956, 1054, 3568],\n", " m=[200, 100, 20, 5],\n", " notes=\"a random galaxy made with random numbers\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Si nos fijamos en cualquier atributo de la clase, vamos a ver que todas las unidades se agregaron de manera correcta" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[1,~1,~3,~4] \\; \\mathrm{kpc}$" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.x" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[10,~2,~3,~100] \\; \\mathrm{kpc}$" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.y" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[1000,~1023,~2346,~1334] \\; \\mathrm{\\frac{km}{s}}$" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.vx" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[200,~100,~20,~5] \\; \\mathrm{M_{\\odot}}$" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.m" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'a random galaxy made with random numbers'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.notes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Galaxy with explicit units\n", "\n", "Otra alternativa consiste en proveer unidades compatibles con las establecidas, hay que tener en cuenta que estas unidades tienen que ser equivalentes a las propuestas en la creacion de la clase. \n", "\n", "Por ejemplo podemos sugerir que la dimension `Z` este dada en parsecs, `vy` en $Km/h$ y las masas en $Kg$" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "gal = Galaxy(\n", " x=[1, 1, 3, 4],\n", " y=[10, 2, 3, 100],\n", " z=[1000, 1000, 1000, 1000] * u.parsec,\n", " vx=[1000, 1023, 2346, 1334],\n", " vy=[9956, 833, 954, 1024] * (u.km / u.h),\n", " vz=[1253, 956, 1054, 3568],\n", " m=[200, 100, 20, 5] * u.kg,\n", " notes=\"a random galaxy made with random numbers\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como se nota en el ejemplo, esto funciona perfectamente, y ningun error aparece. Es mas podemos acceder a culquiera de los atributos presentes y todos mantienen las unidades sugeridas o explicitas" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[1000,~1000,~1000,~1000] \\; \\mathrm{pc}$" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.z # parsecs" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[200,~100,~20,~5] \\; \\mathrm{kg}$" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.m # kg" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[1000,~1023,~2346,~1334] \\; \\mathrm{\\frac{km}{s}}$" ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.vx # default km/s" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$[9956,~833,~954,~1024] \\; \\mathrm{\\frac{km}{h}}$" ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.vy # km/h" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por otro lado si por error cargamos un valor que tiene una unidad que no es equivalente a la sugerida, se lanza un error de valor (`ValueError`)\n", "\n", "Para demostrar esto vamos a extender el ejemplo, tratando de asignar a `x` un valor expresado en gramos (`g`)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Unit of attribute 'x' must be equivalent to 'kpc'. Found 'g'.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mUnitConversionError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m~/proyectos/uttrs/src/uttr.py\u001b[0m in \u001b[0;36mvalidate_is_equivalent_unit\u001b[0;34m(self, instance, attribute, value)\u001b[0m\n\u001b[1;32m 135\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 136\u001b[0;31m \u001b[0munity\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 137\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mUnitConversionError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/quantity.py\u001b[0m in \u001b[0;36mto\u001b[0;34m(self, unit, equivalencies)\u001b[0m\n\u001b[1;32m 688\u001b[0m \u001b[0munit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mUnit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 689\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_new_view\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_to_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mequivalencies\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 690\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/quantity.py\u001b[0m in \u001b[0;36m_to_value\u001b[0;34m(self, unit, equivalencies)\u001b[0m\n\u001b[1;32m 659\u001b[0m \u001b[0mequivalencies\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_equivalencies\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 660\u001b[0;31m return self.unit.to(unit, self.view(np.ndarray),\n\u001b[0m\u001b[1;32m 661\u001b[0m equivalencies=equivalencies)\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/core.py\u001b[0m in \u001b[0;36mto\u001b[0;34m(self, other, value, equivalencies)\u001b[0m\n\u001b[1;32m 986\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 987\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_converter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mequivalencies\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mequivalencies\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 988\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/core.py\u001b[0m in \u001b[0;36m_get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 917\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 918\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 919\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/core.py\u001b[0m in \u001b[0;36m_get_converter\u001b[0;34m(self, other, equivalencies)\u001b[0m\n\u001b[1;32m 902\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 903\u001b[0;31m return self._apply_equivalencies(\n\u001b[0m\u001b[1;32m 904\u001b[0m self, other, self._normalize_equivalencies(equivalencies))\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/astropy/units/core.py\u001b[0m in \u001b[0;36m_apply_equivalencies\u001b[0;34m(self, unit, other, equivalencies)\u001b[0m\n\u001b[1;32m 885\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 886\u001b[0;31m raise UnitConversionError(\n\u001b[0m\u001b[1;32m 887\u001b[0m \"{} and {} are not convertible\".format(\n", "\u001b[0;31mUnitConversionError\u001b[0m: 'g' (mass) and 'kpc' (length) are not convertible", "\nDuring handling of the above exception, another exception occurred:\n", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m gal = Galaxy(\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m100\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mz\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1000\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparsec\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mvx\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1023\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2346\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1334\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, x, y, z, vx, vy, vz, m, notes)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnotes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnotes\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m_config\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_run_validators\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0m__attr_validator_x\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__attr_x\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 12\u001b[0m \u001b[0m__attr_validator_y\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__attr_y\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0m__attr_validator_z\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__attr_z\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mz\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/lib/python3.8/site-packages/attr/_make.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, inst, attr, value)\u001b[0m\n\u001b[1;32m 2721\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__call__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2722\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_validators\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2723\u001b[0;31m \u001b[0mv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2724\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2725\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/proyectos/uttrs/src/uttr.py\u001b[0m in \u001b[0;36mvalidate_is_equivalent_unit\u001b[0;34m(self, instance, attribute, value)\u001b[0m\n\u001b[1;32m 137\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mUnitConversionError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 138\u001b[0m \u001b[0munit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maname\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mufound\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattribute\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 139\u001b[0;31m raise ValueError(\n\u001b[0m\u001b[1;32m 140\u001b[0m \u001b[0;34mf\"Unit of attribute '{aname}' must be equivalent to '{unit}'.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 141\u001b[0m \u001b[0;34mf\" Found '{ufound}'.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Unit of attribute 'x' must be equivalent to 'kpc'. Found 'g'." ] } ], "source": [ "gal = Galaxy(\n", " x=[1, 1, 3, 4] * u.g,\n", " y=[10, 2, 3, 100],\n", " z=[1000, 1000, 1000, 1000] * u.parsec,\n", " vx=[1000, 1023, 2346, 1334],\n", " vy=[9956, 833, 954, 1024] * (u.km / u.h),\n", " vz=[1253, 956, 1054, 3568],\n", " m=[200, 100, 20, 5] * u.kg,\n", " notes=\"a random galaxy made with random numbers\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Automatic cohersion of units: Array Accessor\n", "\n", "El mayor poder de *uttrs* es la capacidad de transformar de manera sencilla todas las unidades a `numpy.ndarray` planos, utilizando las unidades por defecto.\n", "\n", "Para esto se provee de una funcion `uttr.array_accessor()` la cual permite acceder a los atributos definidos por *uttrs* de manera uniforme en una estructura de datos mas veloz que las que possen unidad.\n", "\n", "Para agregar esta característica, se debe agregar un atributo extra a la clase que se iguale a `uttr.array_accessor()`. Se propone utilizar el nombre `arr_`\n", "\n", "Extendiendo el ejemplo anterior" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "@attr.s\n", "class Galaxy:\n", " x = uttr.ib(unit=u.kpc)\n", " y = uttr.ib(unit=u.kpc)\n", " z = uttr.ib(unit=u.kpc)\n", "\n", " vx = uttr.ib(unit=u.km / u.s)\n", " vy = uttr.ib(unit=u.km / u.s)\n", " vz = uttr.ib(unit=u.km / u.s)\n", "\n", " m = uttr.ib(unit=u.M_sun)\n", "\n", " notes = attr.ib(validator=attr.validators.instance_of(str))\n", "\n", " arr_ = uttr.array_accessor() # el accessor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahora volvemos a instanciar la clase con algunos parámetros con unidades custom" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "gal = Galaxy(\n", " x=[1, 1, 3, 4],\n", " y=[10, 2, 3, 100],\n", " z=[1000, 1000, 1000, 1000] * u.parsec,\n", " vx=[1000, 1023, 2346, 1334],\n", " vy=[9956, 833, 954, 1024] * (u.km / u.h),\n", " vz=[1253, 956, 1054, 3568],\n", " m=[200, 100, 20, 5] * u.kg,\n", " notes=\"a random galaxy made with random numbers\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ahora si accedemos a `z` a travez de `arr_`, uttrs se encargara de convertir los parsecs en kiloparsecs y luego convertirlo en un numpy array" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1., 1., 1., 1.])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.arr_.z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mientras que `z` mantiene sus unidades originales" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1., 1., 1., 1.])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.arr_.z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lo mismo si accedemos a `vy` y `m`" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1.00582884e-28, 5.02914422e-29, 1.00582884e-29, 2.51457211e-30])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.arr_.m" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2.76555556, 0.23138889, 0.265 , 0.28444444])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.arr_.vy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tratar de acceder a un atributo privado o que no sea un `uttr.ib`, lanza un error del tipo `AttributeError`" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "No uttr.Attribute 'notes'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgal\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marr_\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnotes\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/proyectos/uttrs/src/uttr.py\u001b[0m in \u001b[0;36m__getattr__\u001b[0;34m(self, a)\u001b[0m\n\u001b[1;32m 278\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0marr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 279\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 280\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"No uttr.Attribute '{a}'\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 281\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 282\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mAttributeError\u001b[0m: No uttr.Attribute 'notes'" ] } ], "source": [ "gal.arr_.notes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## El uso de `array_accessor`\n", "\n", "Es conocido que las unidades de Astropy son lentas cuando los calculos son complejos. \n", "\n", "Para evitar esto, los desarrolladores optan por unificar las unidades y luego convertir los valores a arrays de numpy para operarlos mas rapidamente; y al final se vuelve a asignas las unidades.\n", "\n", "Para evitar esto, `array_accesor` realiza toda esta trasnformacion transparentemente para el usuario, evitando la necesidad de replicar informacion sobre las unidades.\n", "\n", "Por ejemplo, si quisieramos programar un codigo que genere un nuevo objeto galaxia con una sola particula, promedio de las demas,\n", "el código seria el siguiente:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "@attr.s\n", "class Galaxy:\n", " x = uttr.ib(unit=u.kpc)\n", " y = uttr.ib(unit=u.kpc)\n", " z = uttr.ib(unit=u.kpc)\n", "\n", " vx = uttr.ib(unit=u.km / u.s)\n", " vy = uttr.ib(unit=u.km / u.s)\n", " vz = uttr.ib(unit=u.km / u.s)\n", "\n", " m = uttr.ib(unit=u.M_sun)\n", "\n", " notes = attr.ib(validator=attr.validators.instance_of(str))\n", "\n", " arr_ = uttr.array_accessor() # el accessor\n", "\n", " def mean(self):\n", " x = np.mean(self.arr_.x)\n", " y = np.mean(self.arr_.y)\n", " z = np.mean(self.arr_.z)\n", "\n", " vx = np.mean(self.arr_.vx)\n", " vy = np.mean(self.arr_.vy)\n", " vz = np.mean(self.arr_.vz)\n", "\n", " m = np.mean(self.arr_.m)\n", "\n", " return Galaxy(\n", " x=x, y=y, z=z, vx=vx, vy=vy, vz=vz, m=m, notes=self.notes\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahora podemos crear una galaxia de 1 millon de elementos aleatorios y calcular la galaxia \"media\"" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "# fijamos la semilla random\n", "random = np.random.default_rng(seed=42)\n", "\n", "size = 1_000_000\n", "\n", "gal = Galaxy(\n", " x=random.random(size=size),\n", " y=random.random(size=size),\n", " z=random.random(size=size) * u.parsec,\n", " vx=random.random(size=size),\n", " vy=random.random(size=size),\n", " vz=random.random(size=size) * (u.km / u.h),\n", " m=random.random(size=size) * u.kg,\n", " notes=\"a random galaxy made with random numbers\",\n", ")" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Galaxy(x=, y=, z=, vx=, vy=, vz=, m=, notes='a random galaxy made with random numbers')" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gal.mean()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por completitud se ejemplifica a continuacion como seria el miemos codigo de `mean` pero sin usar `array_accessor`" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "@attr.s\n", "class Galaxy:\n", " x = uttr.ib(unit=u.kpc)\n", " y = uttr.ib(unit=u.kpc)\n", " z = uttr.ib(unit=u.kpc)\n", "\n", " vx = uttr.ib(unit=u.km / u.s)\n", " vy = uttr.ib(unit=u.km / u.s)\n", " vz = uttr.ib(unit=u.km / u.s)\n", "\n", " m = uttr.ib(unit=u.M_sun)\n", "\n", " notes = attr.ib(validator=attr.validators.instance_of(str))\n", "\n", " arr_ = uttr.array_accessor() # el accessor\n", "\n", " def mean(self):\n", " x = np.mean(self.x.to_value(u.kpc))\n", " y = np.mean(self.y.to_value(u.kpc))\n", " z = np.mean(self.z.to_value(u.kpc))\n", "\n", " vx = np.mean(self.vx.to_value(u.km / u.s))\n", " vy = np.mean(self.vy.to_value(u.km / u.s))\n", " vz = np.mean(self.vz.to_value(u.km / u.s))\n", "\n", " m = np.mean(self.m.to_value(u.M_sun))\n", "\n", " return Galaxy(\n", " x=x, y=y, z=z, vx=vx, vy=vy, vz=vz, m=m, notes=self.notes\n", " )" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "datetime.datetime(2020, 11, 21, 20, 52, 24, 347474)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import datetime as dt\n", "dt.datetime.now()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }