Great Lakes Cosmology Workshop

yt tutorial

Nathan Goldbaum

University of Illinois at Urbana Champaign

National Center of Supercomputing Applications

This presentaion is available at the following URL as slides and a notebook at the following URLs:

http://yt-project.org/McMaster_yt_demo/yt_tutorial.slides.html

http://yt-project.org/McMaster_yt_demo/yt_tutorial.ipynb

The data I use in this presentation are available at the following URL:

http://yt-project.org/McMaster_yt_demo/data

yt is a framework for working with the ouputs of simulation codes

visualization

  • slices
  • projections
  • volume rendering
  • phase diagrams

analysis

  • data selection and derived quantities
  • low-level data inspection
  • halo finding and cataloging
  • profiling (how does a quantity vary with radius? density?)

yt supports many production astrophysical simulation codes

  • SPH
  • Patch AMR
  • Octree AMR
  • Unstructured Mesh

Developed by a global team of astrophysics researchers to solve their real-world problems.

Tom Abel Andrew Cunningham Cameron Hummels Michael Kuhlen Desika Narayanan Hsi-Yu Schive Ting-Wai To
Gabriel Altay Bili Dong Suoqing Ji Meagan Lang Kaylea Nelson Anthony Scopatz Joseph Tomlinson
Kenza Arraki Nicholas Earl Allyson Julian Eve Lee Brian O'Shea Noel Scudder Stephanie Tonnesen
Kirk Barrow Hilary Egan Anni Järvenpää Doris Lee J.S. Oishi Sam Skillman Matthew Turk
Ricarda Beckmann Rasmi Elasmar Christian Karch Sam Leitner JC Passy Stephen Skory Casey W. Stark
Elliott Biondo Daniel Fenn Max Katz Stuart Levy John Regan Aaron Smith Miguel de Val-Borro
Alex Bogert John Forbes BW Keller Yuan Li Mark Richardson Britton Smith Rick Wagner
Robert Bradshaw Sam Geen Ji-hoon Kim Joshua Moloney Sherwood Richers Geoffrey So Mike Warren
Yi-Hao Chen Adam Ginsburg Steffen Klemer Christopher Moody Thomas Robitaille Antoine Strugarek Andrew Wetzel
Pengfei Chen Nathan Goldbaum Fabian Koller Stuart Mumford Anna Rosen Elizabeth Tasker John Wise
David Collins Eric Hallman Kacper Kowalik Andrew Myers Chuck Rozhon Benjamin Thompson Michael Zingale
Brian Crosby David Hannasch Mark Krumholz Jill Naiman Douglas Rudd Robert Thompson John ZuHone
In [1]:
from IPython.display import IFrame

IFrame('http://yt-project.org/docs/dev/reference/code_support.html', width=960, height=600)
Out[1]:

Sample datasets loadable by yt

In [2]:
IFrame('http://yt-project.org/data', width=700, height=500)
Out[2]:

What is yt?

(slides adapted from Britton Smith's PyAstro 16 talk)

Data on disk has no inherent physical meaning

yt lets you think about the data using a physically motivated interface

yt lets you think about the data using a physically motivated interface

In [3]:
import yt

ds = yt.load('GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175')

Allowing you to forget about what your data looks like as a file format

And select only the data you want to select

And only the data you want to select

Physical objects to select data are called "data objects"

In [4]:
import yt.units as u

sp = ds.sphere(ds.domain_center, 2*u.Mpc)

yt natively deals with multiresolution data

Data from each selected cell is returned as a flat, 1D array, with unit metadata attached

In [5]:
print(sp['density'])
[  2.23563528e-29   2.24107182e-29   2.25198071e-29 ...,   3.23967718e-29
   3.21395428e-29   3.23893930e-29] g/cm**3

Data objects can be queried for many fields

In [6]:
print(sp['temperature'])
[ 25392568.  25404696.  25423080. ...,  23711580.  23668796.  23679796.] K

Data objects can be queried for many fields

In [7]:
from pprint import pprint

# fields that are defined in the on-disk data file
pprint(ds.field_list)
[('flash', 'clr1'),
 ('flash', 'clr2'),
 ('flash', 'dens'),
 ('flash', 'gpot'),
 ('flash', 'pden'),
 ('flash', 'temp'),
 ('flash', 'velx'),
 ('flash', 'vely'),
 ('flash', 'velz')]

Data objects can be queried for many fields

In [8]:
# fields that yt can calculate given the fields in ds.field_list
# (only showing gas fields to fit on one screen)
pprint([f for f in ds.derived_field_list if f[0]=='gas'])
[('gas', 'H_nuclei_density'),
 ('gas', 'He_nuclei_density'),
 ('gas', 'angular_momentum_magnitude'),
 ('gas', 'angular_momentum_x'),
 ('gas', 'angular_momentum_y'),
 ('gas', 'angular_momentum_z'),
 ('gas', 'averaged_density'),
 ('gas', 'cell_mass'),
 ('gas', 'cell_volume'),
 ('gas', 'cutting_plane_velocity_x'),
 ('gas', 'cutting_plane_velocity_y'),
 ('gas', 'cutting_plane_velocity_z'),
 ('gas', 'cylindrical_radial_velocity'),
 ('gas', 'cylindrical_radial_velocity_absolute'),
 ('gas', 'cylindrical_tangential_velocity'),
 ('gas', 'cylindrical_tangential_velocity_absolute'),
 ('gas', 'density'),
 ('gas', 'density_gradient_magnitude'),
 ('gas', 'density_gradient_x'),
 ('gas', 'density_gradient_y'),
 ('gas', 'density_gradient_z'),
 ('gas', 'dx'),
 ('gas', 'dy'),
 ('gas', 'dynamical_time'),
 ('gas', 'dz'),
 ('gas', 'emission_measure'),
 ('gas', 'entropy'),
 ('gas', 'gravitational_potential'),
 ('gas', 'kT'),
 ('gas', 'kinetic_energy'),
 ('gas', 'mazzotta_weighting'),
 ('gas', 'path_element_x'),
 ('gas', 'path_element_y'),
 ('gas', 'path_element_z'),
 ('gas', 'radial_velocity'),
 ('gas', 'radial_velocity_absolute'),
 ('gas', 'shear'),
 ('gas', 'specific_angular_momentum_magnitude'),
 ('gas', 'specific_angular_momentum_x'),
 ('gas', 'specific_angular_momentum_y'),
 ('gas', 'specific_angular_momentum_z'),
 ('gas', 'sz_kinetic'),
 ('gas', 'szy'),
 ('gas', 'tangential_over_velocity_magnitude'),
 ('gas', 'tangential_velocity'),
 ('gas', 'temperature'),
 ('gas', 'velocity_cylindrical_radius'),
 ('gas', 'velocity_cylindrical_theta'),
 ('gas', 'velocity_cylindrical_z'),
 ('gas', 'velocity_divergence'),
 ('gas', 'velocity_divergence_absolute'),
 ('gas', 'velocity_magnitude'),
 ('gas', 'velocity_spherical_phi'),
 ('gas', 'velocity_spherical_radius'),
 ('gas', 'velocity_spherical_theta'),
 ('gas', 'velocity_x'),
 ('gas', 'velocity_y'),
 ('gas', 'velocity_z'),
 ('gas', 'vertex_x'),
 ('gas', 'vertex_y'),
 ('gas', 'vertex_z'),
 ('gas', 'vorticity_magnitude'),
 ('gas', 'vorticity_squared'),
 ('gas', 'vorticity_stretching_magnitude'),
 ('gas', 'vorticity_stretching_x'),
 ('gas', 'vorticity_stretching_y'),
 ('gas', 'vorticity_stretching_z'),
 ('gas', 'vorticity_x'),
 ('gas', 'vorticity_y'),
 ('gas', 'vorticity_z'),
 ('gas', 'x'),
 ('gas', 'xray_emissivity'),
 ('gas', 'y'),
 ('gas', 'z')]

Spatial information is not lost

In [9]:
sp['x']
Out[9]:
YTArray([ -1.78320313e+24,  -1.74023438e+24,  -1.74023438e+24, ...,
         1.74023438e+24,   1.74023438e+24,   1.78320313e+24]) code_length
In [10]:
sp['x'].to('kpc')
Out[10]:
YTArray([-577.89677574, -563.97155222, -563.97155222, ...,  563.97155222,
        563.97155222,  577.89677574]) kpc

Spatial information is not lost

In [11]:
import numpy as np

np.unique(sp['dx']).to('kpc')
Out[11]:
YTArray([  6.96261176,  13.92522351,  27.85044702]) kpc

Data objects return fields as unit-aware numpy arrays

In [12]:
from yt.units import kboltz, mh

pressure = sp['density']*sp['temperature']*kboltz/(0.6*mh)

pressure.in_units('Pa')
Out[12]:
YTArray([  7.80463850e-15,   7.82735426e-15,   7.87114735e-15, ...,
         1.05610584e-14,   1.04582995e-14,   1.05444997e-14]) Pa
In [13]:
pressure.in_units('dyne/cm**2')
Out[13]:
YTArray([  7.80463850e-14,   7.82735426e-14,   7.87114735e-14, ...,
         1.05610584e-13,   1.04582995e-13,   1.05444997e-13]) dyne/cm**2

yt has very flexible handling for units and unit conversions

In [14]:
rho = sp['density']

print (rho.to('g/cm**3'))
print ()
print (rho.to('Msun/kpc**3'))
print ()
print (rho.to('code_mass/code_length**3'))
[  2.23563528e-29   2.24107182e-29   2.25198071e-29 ...,   3.23967718e-29
   3.21395428e-29   3.23893930e-29] g/cm**3

[ 330.3279872   331.13126716  332.74311826 ...,  478.68095986  474.88025312
  478.57193504] Msun/kpc**3

[  2.23563528e-29   2.24107182e-29   2.25198071e-29 ...,   3.23967718e-29
   3.21395428e-29   3.23893930e-29] code_mass/code_length**3
In [15]:
rho + 200*u.Msun/u.kpc**3
Out[15]:
YTArray([  3.58922042e-29,   3.59465696e-29,   3.60556585e-29, ...,
         4.59326232e-29,   4.56753942e-29,   4.59252445e-29]) g/cm**3

Derived quantities turn fields into single values

In [16]:
sp['cell_mass']
Out[16]:
YTArray([  1.77361402e+39,   1.77792703e+39,   1.78658147e+39, ...,
         2.57015843e+39,   2.54975148e+39,   2.56957305e+39]) g

Derived quantities turn fields into single values

\begin{equation} M = \sum_i m_i \end{equation}
In [17]:
sp.quantities.total_quantity('cell_mass')
Out[17]:
1.6032220502823186e+47 g
In [18]:
sp.quantities.angular_momentum_vector()
Out[18]:
YTArray([  4.82814331e+28,   5.82024775e+26,  -4.98907925e+31]) cm**2/s
In [19]:
IFrame("http://yt-project.org/docs/dev/analyzing/objects.html#available-derived-quantities", width=700, height=600)
Out[19]:

Create new fields by defining a python function

In [20]:
def my_entropy(field, data):
    return (kboltz * data['temperature'] / data['number_density']**(2/3))

ds.add_field('entropy', function=my_entropy, units="keV*cm**2")

In [21]:
sp['entropy']
Out[21]:
YTArray([ 3886.89973241,  3882.46458155,  3872.71677529, ...,  2834.37093432,
        2844.33265169,  2831.00150156]) cm**2*keV

Data Containers

In [22]:
ad = ds.all_data()

box = ds.box(ds.domain_left_edge + 500*u.kpc, ds.domain_right_edge - 500*u.kpc)

sp = ds.sphere(ds.domain_center, 500*u.kpc)

disk = ds.disk(ds.domain_center, [0, 0, 1], 500*u.kpc, 100*u.kpc)

ray = ds.ray(ds.domain_left_edge, ds.domain_right_edge)
In [23]:
IFrame("http://yt-project.org/doc/analyzing/objects.html#available-objects", width=700, height=600)
Out[23]:

Particle fields

In [24]:
ds = yt.load('FIRE_M12i_ref11/snapshot_600.hdf5')


print(type(ds))
<class 'yt.frontends.gizmo.data_structures.GizmoDataset'>
In [25]:
ds.field_list
Out[25]:
[('PartType0', 'Coordinates'),
 ('PartType0', 'Density'),
 ('PartType0', 'ElectronAbundance'),
 ('PartType0', 'InternalEnergy'),
 ('PartType0', 'Masses'),
 ('PartType0', 'Metallicity_00'),
 ('PartType0', 'Metallicity_01'),
 ('PartType0', 'Metallicity_02'),
 ('PartType0', 'Metallicity_03'),
 ('PartType0', 'Metallicity_04'),
 ('PartType0', 'Metallicity_05'),
 ('PartType0', 'Metallicity_06'),
 ('PartType0', 'Metallicity_07'),
 ('PartType0', 'Metallicity_08'),
 ('PartType0', 'Metallicity_09'),
 ('PartType0', 'Metallicity_10'),
 ('PartType0', 'NeutralHydrogenAbundance'),
 ('PartType0', 'ParticleIDs'),
 ('PartType0', 'Potential'),
 ('PartType0', 'SmoothingLength'),
 ('PartType0', 'StarFormationRate'),
 ('PartType0', 'Velocities'),
 ('PartType1', 'Coordinates'),
 ('PartType1', 'Masses'),
 ('PartType1', 'ParticleIDs'),
 ('PartType1', 'Potential'),
 ('PartType1', 'Velocities'),
 ('PartType2', 'Coordinates'),
 ('PartType2', 'Masses'),
 ('PartType2', 'ParticleIDs'),
 ('PartType2', 'Potential'),
 ('PartType2', 'Velocities'),
 ('PartType4', 'Coordinates'),
 ('PartType4', 'Masses'),
 ('PartType4', 'Metallicity_00'),
 ('PartType4', 'Metallicity_01'),
 ('PartType4', 'Metallicity_02'),
 ('PartType4', 'Metallicity_03'),
 ('PartType4', 'Metallicity_04'),
 ('PartType4', 'Metallicity_05'),
 ('PartType4', 'Metallicity_06'),
 ('PartType4', 'Metallicity_07'),
 ('PartType4', 'Metallicity_08'),
 ('PartType4', 'Metallicity_09'),
 ('PartType4', 'Metallicity_10'),
 ('PartType4', 'ParticleIDs'),
 ('PartType4', 'Potential'),
 ('PartType4', 'StellarFormationTime'),
 ('PartType4', 'Velocities'),
 ('all', 'Coordinates'),
 ('all', 'Masses'),
 ('all', 'ParticleIDs'),
 ('all', 'Potential'),
 ('all', 'Velocities')]
In [26]:
ad = ds.all_data()

gas_ppos = ad['PartType0', 'particle_position']
gas_mass = ad['PartType0', 'particle_mass']

dm_ppos = ad['PartType1', 'particle_position']
dm_mass = ad['PartType1', 'particle_mass']

print(gas_ppos, '\n')
print(gas_mass)
[[ 30711.56320229  33303.32765069  33743.24254345]
 [ 30801.09893721  33396.72993339  33673.13785209]
 [ 30721.03179246  33313.72662534  33657.53180413]
 ..., 
 [ 28702.32012144  32308.36857189  32512.44014079]
 [ 28695.79401442  32304.20735574  32514.23300421]
 [ 28700.69483794  32308.30239816  32518.00387993]] code_length 

[  3.17518607e-05   3.17518607e-05   3.17518607e-05 ...,   3.17518607e-05
   3.17518607e-05   3.17518607e-05] code_mass
In [27]:
print(gas_ppos.shape, gas_mass.shape, '\n')
print(dm_ppos.shape, dm_ppos.shape, '\n')
pprint(ds.particle_type_counts)
(753678, 3) (753678,) 

(1104128, 3) (1104128, 3) 

{'PartType0': 753678,
 'PartType1': 1104128,
 'PartType2': 2567905,
 'PartType3': 0,
 'PartType4': 361239,
 'PartType5': 0}

Particle unions

In [28]:
print(ds.particle_unions)

union = ds.particle_unions['all']

print(union.sub_types)
{'all': <yt.data_objects.particle_unions.ParticleUnion object at 0x1169ea208>}
['PartType0', 'PartType4', 'PartType2', 'PartType1']
In [29]:
all_ppos = ad['all', 'particle_position']

print(all_ppos.shape)
(4786950, 3)
In [30]:
from yt.data_objects.particle_unions import \
    ParticleUnion

u = ParticleUnion("star", ["PartType2", "PartType4"])
ds.add_particle_union(u)

ad['star', 'particle_mass']
Out[30]:
YTArray([  3.44093735e+41,   3.44093735e+41,   3.44093735e+41, ...,
         6.39615344e+38,   6.53085902e+38,   7.76605092e+38]) g

Particle filters

In [31]:
from yt.data_objects.particle_filters import add_particle_filter

def young_star(pfilter, data):
    filter = data.ds.current_time - data[pfilter.filtered_type, "creation_time"]
    filter.convert_to_units('Myr')
    return filter < 10

add_particle_filter("young_stars", function=young_star, filtered_type='io',
                    requires=["creation_time"])
In [32]:
ds = yt.load('DD0600/DD0600')

ds.add_particle_filter('young_stars')

ad = ds.all_data()

print(ad['io', 'particle_mass'].shape)
print(ad['young_stars', 'particle_mass'].shape)
(26111208,)
(85493,)
This uses ParticlePlot, a new feature in yt 3.3
In [33]:
from yt import ParticlePlot

p = ParticlePlot(ds, 
                 ('young_stars', 'particle_position_x'), 
                 ('young_stars', 'particle_position_y'), 
                 ('young_stars', 'particle_mass'), 
                 width=(10, 'kpc'))

p.set_unit(('young_stars', 'particle_mass'), 'Msun')
Out[33]:

What can we do using this language of fields and data objects?

Visualization and analysis

  • SlicePlot, ProjectionPlot, ProfilePlot, PhasePlot
  • ParticlePlot, ParticlePhasePlot
  • Slices, projections, profiles, covering grids, and fixed resolution buffers

Halo analysis

  • Support for FOF, HOP, and Rockstar halo finders
  • Halo catalogs

Volume rendering

  • Pretty pictures!
  • Software volume renderer and OpenGL interactive volume rendering

SlicePlot

In [34]:
from yt import SlicePlot

ds = yt.load('DD0600/DD0600')

SlicePlot(ds, 2, ('gas', 'density'))
Out[34]:

In [35]:
slc = SlicePlot(ds, 2, 'density', width=(15, 'kpc'), center='m')

slc.show()

In [36]:
slc.set_origin('native')
Out[36]:

In [37]:
slc.set_cmap('density', 'magma')
Out[37]:

Plot callbacks

In [38]:
slc.annotate_grids()
Out[38]:

In [39]:
slc.save('my_amazing_plot.eps')
Out[39]:
['my_amazing_plot.eps']
In [40]:
slc.save('my_amazing_plot.png')
Out[40]:
['my_amazing_plot.png']

Push-button visualization

ProjectionPlot

In [41]:
from yt import ProjectionPlot

prj = ProjectionPlot(ds, 2, 'density')
In [42]:
prj
Out[42]:

In [43]:
prj.zoom(20)
Out[43]:

In [44]:
prj.zoom(3)
Out[44]:

In [45]:
dd = ds.sphere(ds.domain_center, (30, 'kpc'))

prj = ProjectionPlot(ds, 2, 'density', data_source=dd, width=(80, 'kpc'))

prj.set_zlim('density', 1e-4, 1e0)
Out[45]:

In [46]:
ProjectionPlot(ds, 2, 'density', max_level=5, width=(80, 'kpc'))
Out[46]:

In [47]:
ProjectionPlot(ds, 2, 'temperature', weight_field='density', width=(15, 'kpc'))
Out[47]:

In [48]:
ProjectionPlot(ds, 2, 'density', width=(15, 'kpc'), method='mip')
Out[48]:

In [49]:
import numexpr as ne
from yt.units import G
from numpy import pi

def _total_dynamical_time(field, data):
    dens = data['density']
    dmd = data['dark_matter_density']
    dens.convert_to_cgs()
    dmd.convert_to_cgs()
    tdyn = ne.evaluate("sqrt(3*pi/(32*G*(dens + dmd)))")
    tdyn = tdyn*yt.units.s
    return tdyn

def _sfr(field, data):
    dens = data['density']
    tdyn = data['total_dynamical_time']
    Mu = np.float64(data.ds.parameters['Mu'])
    dens.convert_to_cgs()
    tdyn.convert_to_cgs()
    sfr = ne.evaluate("where(dens*Mu/mh > 50, 0.01*dens/tdyn, 0)")
    sfr = sfr*(yt.units.g/yt.units.cm**3/yt.units.s)
    return sfr

ds.add_field('total_dynamical_time', _total_dynamical_time, units='s')
ds.add_field('sfr_density', _sfr, units='g/cm**3/s')
In [50]:
prj = yt.ProjectionPlot(ds, 2, 'sfr_density', width=(10, 'kpc'))

prj.set_unit('sfr_density', 'Msun/kpc**2/yr')

prj.set_zlim('sfr_density', 1e-1, 3e1)
Out[50]:

PhasePlot and ProfilePlot

In [51]:
from yt import PhasePlot

PhasePlot(ds.all_data(), 'density', 'temperature', 'cell_mass', 
          weight_field=None, fractional=True)
Out[51]:

Analysis

  • Profiles
  • covering grids
  • Many more that I don't have time to talk about in detail

Profiles

Profiles can create a 1D, 2D, or 3D histogram of a field versus a set of other fields.

We already saw a 2D histogram above when we made a PhasePlot of density, temperature, and cell mass

We can use profiles to calculate the average value or total of a field vs another field.

In [52]:
from yt import create_profile

sph = ds.sphere(ds.domain_center, (15, 'kpc'))

profile = create_profile(sph, ('radius'), ('sfr_density'), logs={'radius': False})
In [53]:
%matplotlib inline
from matplotlib import pyplot as plt

plt.plot(profile.x.to('kpc'), profile['sfr_density'].to('Msun/kpc**3/yr'))
plt.xlabel('Radius (kpc)')
plt.ylabel(r'$\Sigma_{\rm{SFR}}\ (M_\odot kpc^{-3} yr^{-1}))$')
Out[53]:
<matplotlib.text.Text at 0x135868c18>

Covering grids

Covering grids are a uniform resolution 3D representation of a yt dataset. Very useful for analysis tools that expect a uniform resolution grid, or for simplifying analysis to avoid thinking about AMR.

In [54]:
ds = yt.load('output_00080/info_00080.txt')

SlicePlot(ds, 2, 'density', center=[0.5, 0.5, 0.75])
Out[54]:

In [55]:
# create covering grid of density at AMR level 0
cgrid = ds.covering_grid(0, left_edge=[0, 0, 0], dims=[64, 64, 64])
density_field = cgrid["density"]

print (density_field.shape)

plt.imshow(np.log10(density_field[:,:,48].v), interpolation='none')
plt.show()
(64, 64, 64)

Analysis Modules

In [57]:
IFrame("http://yt-project.org/docs/dev/analyzing/analysis_modules/index.html", width=700, height=600)
Out[57]:

Spinoffs

In [59]:
IFrame("http://caesar.readthedocs.io/en/latest/", width=700, height=600)
Out[59]:
In [60]:
IFrame("http://trident-project.org/", width=700, height=600)
Out[60]:

Coming soon

  • Improved Scalability for Particle Codes
  • First-class support for unstructured meshes
  • Improved volume rendering interface
  • Support for more kinds of synthetic observations

Community

  • 20,304 changesets since 2007 by 108 unique contributors
  • 350 subscribers to users mailing list, 120 subscribers to developer mailing list
  • Funding for aspects of yt development comes from the NSF, NASA and the Gordon and Betty Moore Foundation
In [61]:
IFrame("https://www.openhub.net/p/yt_amr", width=700, height=600)
Out[61]:

Getting involved and asking for help

Documentation: http://yt-project.org/doc

Users mailing list: http://lists.spacepope.org/listinfo.cgi/yt-users-spacepope.org

Developer mailing list: http://lists.spacepope.org/listinfo.cgi/yt-dev-spacepope.org

Developer guide: http://yt-project.org/docs/dev/developing/index.html

Slack Channel: https://yt-project.slack.com

IRC Channel: http://yt-project.org/irc.html (or #yt on freenode with your favorite IRC client)