# Symbolic units: yt.units¶

Notebook

### Dimensional analysis¶

The fastest way to get into the unit system is to explore the quantities that live in the yt.units namespace:

In [1]:
from yt.units import meter, gram, kilogram, second, joule
print (kilogram*meter**2/second**2 == joule)
print (kilogram*meter**2/second**2)

True
1.0 kg*m**2/s**2

In [2]:
from yt.units import m, kg, s, W
kg*m**2/s**3 == W

Out[2]:
array(True, dtype=bool)
In [3]:
from yt.units import kilometer
three_kilometers = 3*kilometer
print (three_kilometers)

3.0 km

In [4]:
from yt.units import gram, kilogram
print (gram+kilogram)

print (kilogram+gram)

print (kilogram/gram)

1001.0 g
1.001 kg
1000.0 dimensionless


These unit symbols are all instances of a new class we've added to yt 3.0, YTQuantity. YTQuantity is useful for storing a single data point.

In [5]:
type(kilogram)

Out[5]:
yt.units.yt_array.YTQuantity

We also provide YTArray, which can store arrays of quantities:

In [6]:
arr = [3,4,5]*kilogram

print (arr)

print (type(arr))

[ 3.  4.  5.] kg
<class 'yt.units.yt_array.YTArray'>


### Creating arrays and quantities¶

Most people will interact with the new unit system using YTArray and YTQuantity. These are both subclasses of numpy's fast array type, ndarray, and can be used interchangeably with other NumPy arrays. These new classes make use of the unit system to append unit metadata to the underlying ndarray. YTArray is intended to store array data, while YTQuantitity is intended to store scalars in a particular unit system.

There are two ways to create arrays and quantities. The first is to explicitly create it by calling the class constructor and supplying a unit string:

In [7]:
from yt.units.yt_array import YTArray

sample_array = YTArray([1,2,3], 'g/cm**3')

print (sample_array)

[ 1.  2.  3.] g/cm**3


The unit string can be an arbitrary combination of metric unit names. Just a few examples:

In [8]:
from yt.units.yt_array import YTQuantity
from yt.units import kboltz
from numpy.random import random
import numpy as np

print ("Length:")
print (YTQuantity(random(), 'm'))
print (YTQuantity(random(), 'cm'))
print (YTQuantity(random(), 'Mpc'))
print (YTQuantity(random(), 'AU'))
print ('')

print ("Time:")
print (YTQuantity(random(), 's'))
print (YTQuantity(random(), 'min'))
print (YTQuantity(random(), 'hr'))
print (YTQuantity(random(), 'day'))
print (YTQuantity(random(), 'yr'))
print ('')

print ("Mass:")
print (YTQuantity(random(), 'g'))
print (YTQuantity(random(), 'kg'))
print (YTQuantity(random(), 'Msun'))
print ('')

print ("Energy:")
print (YTQuantity(random(), 'erg'))
print (YTQuantity(random(), 'g*cm**2/s**2'))
print (YTQuantity(random(), 'eV'))
print (YTQuantity(random(), 'J'))
print ('')

print ("Temperature:")
print (YTQuantity(random(), 'K'))
print ((YTQuantity(random(), 'eV')/kboltz).in_cgs())

Length:
0.03607912399324564 m
0.21760693984700008 cm
0.8414640426907352 Mpc
0.033933568608201514 AU

Time:
0.8911439461304195 s
0.1802695899044401 min
0.21231105841218778 hr
0.2589958335269581 day
0.6040860219445164 yr

Mass:
0.5847604723410841 g
0.502488619749348 kg
0.3830781638461719 Msun

Energy:
0.9544105471609697 erg
0.9081225077640931 cm**2*g/s**2
0.3286024564865013 eV
0.03241642781342313 J

Temperature:
0.9776255356340509 K
11550.537455988087 K


Dimensional arrays and quantities can also be created by multiplication with another array or quantity:

In [9]:
from yt.units import kilometer
print (kilometer)

1.0 km

In [10]:
three_kilometers = 3*kilometer
print (three_kilometers)

3.0 km


When working with a YTArray with complicated units, you can use unit_array and unit_quantity to conveniently apply units to data:

In [11]:
test_array = YTArray(np.random.random(20), 'erg/s')

print (test_array)

[ 0.99551163  0.70118876  0.19092779  0.39102264  0.11718479  0.4061332
0.28649543  0.73938664  0.62566367  0.67767322  0.55276525  0.77856706
0.43532683  0.91990622  0.58723459  0.76428882  0.04188202  0.35209993
0.47348182  0.1234411 ] erg/s


unit_quantity returns a YTQuantity with a value of 1.0 and the same units as the array it is a attached to.

In [12]:
print (test_array.unit_quantity)

1.0 erg/s


unit_array returns a YTArray with the same units and shape as the array it is a attached to and with all values set to 1.0.

In [13]:
print (test_array.unit_array)

[ 1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.
1.  1.] erg/s


These are useful when doing arithmetic:

In [14]:
print (test_array + 1.0*test_array.unit_quantity)

[ 1.99551163  1.70118876  1.19092779  1.39102264  1.11718479  1.4061332
1.28649543  1.73938664  1.62566367  1.67767322  1.55276525  1.77856706
1.43532683  1.91990622  1.58723459  1.76428882  1.04188202  1.35209993
1.47348182  1.1234411 ] erg/s

In [15]:
print (test_array + np.arange(20)*test_array.unit_array)

[  0.99551163   1.70118876   2.19092779   3.39102264   4.11718479
5.4061332    6.28649543   7.73938664   8.62566367   9.67767322
10.55276525  11.77856706  12.43532683  13.91990622  14.58723459
15.76428882  16.04188202  17.35209993  18.47348182  19.1234411 ] erg/s


For convenience, unit_quantity is also available via uq and unit_array is available via ua. You can use these arrays to create dummy arrays with the same units as another array - this is sometimes easier than manually creating a new array or quantity.

In [16]:
print (test_array.uq)

print (test_array.unit_quantity == test_array.uq)

1.0 erg/s
True

In [17]:
from numpy import array_equal

print (test_array.ua)

print (array_equal(test_array.ua, test_array.unit_array))

[ 1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.  1.
1.  1.] erg/s
True


Unit metadata is encoded in the units attribute that hangs off of YTArray or YTQuantity instances:

In [18]:
from yt.units import kilometer, erg

print ("kilometer's units:", kilometer.units)
print ("kilometer's dimensions:", kilometer.units.dimensions)

print ('')

print ("erg's units:", erg.units)
print ("erg's dimensions: ", erg.units.dimensions)

kilometer's units: km
kilometer's dimensions: (length)

erg's units: erg
erg's dimensions:  (length)**2*(mass)/(time)**2


### Arithmetic with YTQuantity and YTArray¶

Of course it wouldn't be very useful if all we could do is create data with units. The real power of the new unit system is that we can add, subtract, mutliply, and divide using quantities and dimensional arrays:

In [19]:
a = YTQuantity(3, 'cm')
b = YTQuantity(3, 'm')

print (a+b)
print (b+a)
print ('')

print ((a+b).in_units('ft'))

303.0 cm
3.03 m

9.940944881889763 ft

In [20]:
a = YTQuantity(42, 'mm')
b = YTQuantity(1, 's')

print (a/b)
print ((a/b).in_cgs())
print ((a/b).in_mks())
print ((a/b).in_units('km/s'))
print ('')

print (a*b)
print ((a*b).in_cgs())
print ((a*b).in_mks())

42.0 mm/s
4.2 cm/s
0.042 m/s
4.2e-05 km/s

42.0 mm*s
4.2 cm*s
0.042 m*s

In [21]:
m = YTQuantity(35, 'g')
a = YTQuantity(9.8, 'm/s**2')

print (m*a)
print ((m*a).in_units('dyne'))

343.0 g*m/s**2
34300.0 dyne

In [22]:
from yt.units import G, kboltz

print ("Newton's constant: ", G)
print ("Newton's constant in MKS: ", G.in_mks(), "\n")

print ("Boltzmann constant: ", kboltz)
print ("Boltzmann constant in MKS: ", kboltz.in_mks())

Newton's constant:  6.67384e-08 cm**3/(g*s**2)
Newton's constant in MKS:  6.67384e-11 m**3/(kg*s**2)

Boltzmann constant:  1.3806488e-16 erg/K
Boltzmann constant in MKS:  1.3806488e-23 kg*m**2/(K*s**2)

In [23]:
rho = YTQuantity(1, 'g/cm**3')
t_ff = (G*rho)**(-0.5)

print (t_ff)

3870.901361178502 s


An exception is raised if we try to do a unit operation that doesn't make any sense:

In [24]:
from yt.utilities.exceptions import YTUnitOperationError

a = YTQuantity(3, 'm')
b = YTQuantity(5, 'erg')

try:
print (a+b)
except YTUnitOperationError as e:
print (e)

The addition operator for YTArrays with units (m) and (erg) is not well defined.


A plain ndarray or a YTArray created with empty units is treated as a dimensionless quantity and can be used in situations where unit consistency allows it to be used:

In [25]:
a = YTArray([1.,2.,3.], 'm')
b = np.array([2.,2.,2.])

print ("a:   ", a)
print ("b:   ", b)
print ("a*b: ", a*b)

a:    [ 1.  2.  3.] m
b:    [ 2.  2.  2.]
a*b:  [ 2.  4.  6.] m

In [26]:
c = YTArray([2,2,2])

print ("c:    ", c)
print ("a*c:  ", a*c)

c:     [ 2.  2.  2.] dimensionless
a*c:   [ 2.  4.  6.] m


### Saving and Loading YTArrays to/from disk¶

YTArrays can be written to disk, to be loaded again to be used in yt or in a different context later. There are two formats that can be written to/read from: HDF5 and ASCII.

#### HDF5¶

To write to HDF5, use write_hdf5:

In [27]:
my_dens = YTArray(np.random.random(10), 'Msun/kpc**3')
my_temp = YTArray(np.random.random(10), 'K')
my_dens.write_hdf5("my_data.h5", dataset_name="density")
my_temp.write_hdf5("my_data.h5", dataset_name="temperature")


Where we used the dataset_name keyword argument to create a separate dataset for each array in the same file.

We can use the from_hdf5 classmethod to read the data back in:

In [28]:
read_dens = YTArray.from_hdf5("my_data.h5", dataset_name="density")
print (my_dens)

[ 0.5470994   0.28257745  0.40467517  0.12600176  0.87565368  0.33516399
0.86380573  0.50254724  0.78379075  0.77948378] Msun/kpc**3
[ 0.5470994   0.28257745  0.40467517  0.12600176  0.87565368  0.33516399
0.86380573  0.50254724  0.78379075  0.77948378] Msun/kpc**3


We can use the info keyword argument to write_hdf5 to write some additional data to the file, which will be stored as attributes of the dataset:

In [29]:
my_vels = YTArray(np.random.normal(10), 'km/s')
info = {"source":"galaxy cluster","user":"jzuhone"}
my_vels.write_hdf5("my_data.h5", dataset_name="velocity", info=info)


If you want to read/write a dataset from/to a specific group within the HDF5 file, use the group_name keyword:

In [30]:
my_vels.write_hdf5("data_in_group.h5", dataset_name="velocity", info=info, group_name="/data/fields")


where we have used the standard HDF5 slash notation for writing a group hierarchy (e.g., group within a group):

#### ASCII¶

To write one or more YTArrays to an ASCII text file, use yt.savetxt, which works a lot like NumPy's savetxt, except with units:

In [31]:
import yt
a = YTArray(np.random.random(size=10), "cm")
b = YTArray(np.random.random(size=10), "g")
c = YTArray(np.random.random(size=10), "s")
yt.savetxt("my_data.dat", [a,b,c], header='My cool data', footer='Data is over', delimiter="\t")


The file we wrote can then be easily used in other contexts, such as plotting in Gnuplot, or loading into a spreadsheet, or just for causal examination. We can quickly check it here:

In [32]:
%%bash
more my_data.dat

::::::::::::::
my_data.dat
::::::::::::::
#My cool data
# Units
# cm	g	s
4.057935625392183887e-02	8.711667532507825218e-01	8.127802307480926913e-01
5.746666671493825262e-01	4.172771245265916029e-02	2.940916176147608807e-01
4.624875357857489711e-01	5.892938438211555718e-01	9.400196687499809967e-01
3.111423424735044740e-01	5.403545624369741063e-01	2.806483348634729591e-01
6.815646850532465217e-01	8.518602853035727174e-01	7.506965357785084780e-01
5.509288542743016048e-01	6.380320637064880174e-01	4.790294395366836433e-01
3.064762072658372416e-01	1.029605283105493596e-01	6.201683090590193181e-02
6.869316238081700776e-01	9.315523311051505662e-01	2.825448368502097019e-01
1.828736252172742383e-01	6.882007363078359585e-01	2.636159493114935692e-01
7.333345799629624739e-01	6.643050764288385146e-01	3.060951008789341010e-01
#Data is over


You can see that the header comes first, and then right before the data we have a subheader marking the units of each column. The footer comes after the data.

yt.loadtxt can be used to read the same data with units back in, or read data that has been generated from some other source. Just make sure it's in the format above. loadtxt can also selectively read from particular columns in the file with the usecols keyword argument:

In [33]:
bb, cc = yt.loadtxt("my_data.dat", usecols=(1,2), delimiter="\t")
print (bb)
print (b)
print ('')
print (cc)
print (c)

[ 0.87116675  0.04172771  0.58929384  0.54035456  0.85186029  0.63803206
0.10296053  0.93155233  0.68820074  0.66430508] g
[ 0.87116675  0.04172771  0.58929384  0.54035456  0.85186029  0.63803206
0.10296053  0.93155233  0.68820074  0.66430508] g

[ 0.81278023  0.29409162  0.94001967  0.28064833  0.75069654  0.47902944
0.06201683  0.28254484  0.26361595  0.3060951 ] s
[ 0.81278023  0.29409162  0.94001967  0.28064833  0.75069654  0.47902944
0.06201683  0.28254484  0.26361595  0.3060951 ] s