I am writing this post as a reminder to myself of how to create units in AstroPy.
The first enchantment to use units in AstroPy is to type (remeber that >>>
represents the Python prompt, and must not be typed):
>>> from astropy import units as u
This allows you to just use u.
as a prefix to your units, and to things like
>>> (u.km/u.h).to(u.m/u.s)
to convert between between kilometres per hour and metres per second, or the more astronomically oriented:
>>> (u.lightyear).to(u.m)
9460730472580800.0
But many times you want to do calculations that involve currencies mixed with other units, and there is —understandably— no support for those in AstroPy.
It does not matter: you can create your own units in AstroPy!
The problem with AstroPy documentation
Unfortunately, it is not very easy to find the documentation for how to create units in AstroPy: if you type
>>> help(u.Unit)
you get something like this:
Help on class Unit in module astropy.units.core:
class Unit(NamedUnit)
| Unit(s, represents=None, format=None, namespace=None, doc=None, parse_strict='raise')
|
| The main unit class.
|
| There are a number of different ways to construct a Unit, but
| always returns a `UnitBase` instance. If the arguments refer to
| an already-existing unit, that existing unit instance is returned,
| rather than a new one.
|
| - From a string::
|
| Unit(s, format=None, parse_strict='silent')
|
| Construct from a string representing a (possibly compound) unit.
|
| The optional `format` keyword argument specifies the format the
| string is in, by default ``"generic"``. For a description of
| the available formats, see `astropy.units.format`.
|
| The optional ``parse_strict`` keyword controls what happens when an
| unrecognized unit string is passed in. It may be one of the following:
|
| - ``'raise'``: (default) raise a ValueError exception.
|
| - ``'warn'``: emit a Warning, and return an
| `UnrecognizedUnit` instance.
|
| - ``'silent'``: return an `UnrecognizedUnit` instance.
|
| - From a number::
|
| Unit(number)
|
| Creates a dimensionless unit.
|
| - From a `UnitBase` instance::
|
| Unit(unit)
|
| Returns the given unit unchanged.
|
| - From `None`::
|
| Unit()
|
| Returns the null unit.
|
| - The last form, which creates a new `Unit` is described in detail
| below.
The last phrase is tantalising: it promises you that you can create a new unit based on an empty definition!
However, if you type:
>>> u.Unit()
you will be greeted by an error: TypeError: __call__() missing 1 required positional argument: 's'
You can try instead:
>>> u.Unit("")
which successfuly returns a dimensionless unit… that has no name, and to which you cannot attach any conversions!
The solution: use u.def_unit()
If you search through AstroPy documentation —i.e., by searching site:astropy.org define new unit
—, you will find the AstroPy documentation page for creating new units, aptly named Combining and Defining Units.
With that required reading finished, I can now define a unit for the Euro as:
>>> EUR=u.def_unit(["EUR", "€", "euro", "euros", "eur"])
I can later define a unit for the US Dolar, with an equivalency to the Euro (as of March 19, 2019):
>>> USD=u.def_unit(["USD", "$", "dolar", "dolars", "US dolar"], 1000./882.20*EUR)
In this way, you can convert USD to EUR and viceversa:
>>> (500*USD).to(EUR)
<Quantity 566.76490592 EUR>
>>> (500*EUR).to(USD)
<Quantity 441.1 USD>
Using other units with your own: storage price calculations
And why am I working with currencies? I’m looking at storage prices across different cloud providers, and being able to attach a price per gigabyte per month, and then calculate the price for a full petabyte for a year is quite useful.
For instance, assuming a price of 0.05 USD per gigabyte per month, the price of storing a petabyte for a year can be easily calculated now as:
>>> storage_cost = 0.05*USD/u.gigabyte/(30*u.day)
>>> storage_cost*u.petabyte*u.year
<Quantity 0.00166667 Pbyte USD yr / (d Gbyte)>
So, if you want to normalise it, you need to calculate it as:
>>> (storage_cost*u.petabyte*u.year).to(USD)
<Quantity 608750. USD>
or
>>> (storage_cost*u.petabyte*u.year).decompose()
<Quantity 608750. USD>
You could, instead, just get the factor in USD per petabyte per year:
>>> storage_cost.to(USD/u.petabyte/u.year)
<Quantity 608750. USD / (Pbyte yr)>
I hope that helps! It will sure help me remember how to do this the next time ;-)