User Guide¶
This example goes through the main usages of gizio. To follow along, make sure gizio is installed, and the sample data is downloaded.
[1]:
import gizio
Snapshot¶
First load the snapshot
[2]:
snap = gizio.load('data/FIRE_M12i_ref11/snapshot_600.hdf5')
Inspection¶
Once a snapshot is loaded, several of its general peoperties are extracted as attributes, and could be inspected like the following:
[3]:
snap.header
[3]:
{'time': unyt_quantity(13.79874688, 'Gyr'),
'n_file': 1,
'mass_tab': unyt_array([0., 0., 0., 0., 0., 0.], 'code_mass'),
'f_sfr': 1,
'f_cool': 1,
'f_fb': 1,
'f_age': 1,
'f_met': 11,
'n_part': array([ 753678, 1104128, 2567905, 0, 361239, 0], dtype=uint32),
'n_part_pf': [array([ 753678, 1104128, 2567905, 0, 361239, 0], dtype=int32)],
'box_size': unyt_quantity(60000., 'code_length'),
'Om0': 0.272,
'OmL': 0.728,
'h': 0.702,
'z': 0.0,
'cosmological': True}
[4]:
snap.shape
[4]:
OrderedDict([('PartType0', 753678),
('PartType1', 1104128),
('PartType2', 2567905),
('PartType3', 0),
('PartType4', 361239),
('PartType5', 0)])
[5]:
snap.cosmology
[5]:
LambdaCDM(H0=70.2 km / (Mpc s), Om0=0.272, Ode0=0.728, Tcmb0=0 K, Neff=3.04, m_nu=None, Ob0=None)
Field Access¶
Fields could be accessed through a dictionary interface. The available keys could be queried by
[6]:
snap.keys()
[6]:
[('PartType0', 'Coordinates'),
('PartType0', 'Density'),
('PartType0', 'ElectronAbundance'),
('PartType0', 'InternalEnergy'),
('PartType0', 'Masses'),
('PartType0', 'Metallicity'),
('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'),
('PartType4', 'ParticleIDs'),
('PartType4', 'Potential'),
('PartType4', 'StellarFormationTime'),
('PartType4', 'Velocities')]
A field could be accessed through
[7]:
snap['PartType0', 'Coordinates']
[7]:
unyt_array([[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')
Note that the field is loaded as a unyt array.
Particle Selector¶
Particle selector is a more flexible interface to access fields. Default particle selectors according to particle types are created automatically when loading the snapshot, and could be accessed from
[8]:
snap.pt
[8]:
{'gas': <gizio.core.ParticleSelector at 0x7f96d7855f28>,
'hdm': <gizio.core.ParticleSelector at 0x7f9700eddef0>,
'ldm': <gizio.core.ParticleSelector at 0x7f9700ed5eb8>,
'star': <gizio.core.ParticleSelector at 0x7f9700ed5d68>,
'all': <gizio.core.ParticleSelector at 0x7f9700ed5da0>}
Let’s have a look at the gas particle selector:
[9]:
gas = snap.pt['gas']
gas.keys()
[9]:
dict_keys(['z', 'Potential', 'p', 'ne', 'v', 'sfr', 'u', 'nh', 'h', 'id', 'rho', 'm', 't'])
Different from the snapshot interface, shorthand keys are used in the particle selector interface, except for unrecognized ones like 'Potential'
. So we have the following equality:
[10]:
(gas['p'] == snap['PartType0', 'Coordinates']).all()
[10]:
True
The number of selected particles can be gotten from
[11]:
len(gas)
[11]:
753678
Some other particle selector properties could be queried like the following:
[12]:
gas.pmask
[12]:
OrderedDict([('PartType0',
array([ True, True, True, ..., True, True, True])),
('PartType1', None),
('PartType2', None),
('PartType3', None),
('PartType4', None),
('PartType5', None)])
[13]:
gas.shape
[13]:
OrderedDict([('PartType0', 753678),
('PartType1', 0),
('PartType2', 0),
('PartType3', 0),
('PartType4', 0),
('PartType5', 0)])
[14]:
gas.direct_fields()
[14]:
{'z': 'Metallicity',
'Potential': 'Potential',
'p': 'Coordinates',
'ne': 'ElectronAbundance',
'v': 'Velocities',
'sfr': 'StarFormationRate',
'u': 'InternalEnergy',
'nh': 'NeutralHydrogenAbundance',
'h': 'SmoothingLength',
'id': 'ParticleIDs',
'rho': 'Density',
'm': 'Masses'}
Derived Fields¶
Among the listed keys, one is special: 't'
for temperature. There isn’t a ('PartType0', 'Temperature')
field in the snapshot. Actually, temperature is computed from internal energy. So we call it a derived field:
[15]:
gas['t']
[15]:
unyt_array([ 59962.26953125, 84842.5 , 82207.421875, ...,
121806.1171875, 157144.0625, 134721.953125], dtype=float32, units='K')
We could check the code that does the computation (be careful this is not part of the public interface yet):
[16]:
import inspect
print(inspect.getsource(gas._field_registry['t']))
@staticmethod
def compute_temperature(ps):
"""Compute temperature from internal energy.
Parameters
----------
ps : ParticleSelector
A particle selector.
Returns
-------
unyt.unyt_array
The temperature array.
"""
# See the note following InternalEnergy on this page:
# http://www.tapir.caltech.edu/~phopkins/Site/GIZMO_files/gizmo_documentation.html#snaps-reading
ne = ps["ne"]
u = ps["u"]
z_he = ps["z"][:, 1]
from unyt.physical_constants import kb, mp
gamma = 5 / 3
y = z_he / (4 * (1 - z_he))
mu = (1 + 4 * y) / (1 + y + ne)
temperature = mu * mp * (gamma - 1) * u / kb
return temperature.to("K")
It’s also pretty easy to register a custom derived field:
[17]:
def volume(field_system):
return (field_system['m'] / field_system['rho']).to('kpc**3')
gas.register_field('vol', volume)
gas['vol']
[17]:
unyt_array([2.0862592e+06, 2.6118410e+06, 1.1494234e+06, ...,
1.3297848e+03, 1.0290620e+03, 1.3337478e+03], dtype=float32, units='kpc**3')
Boolean Masking¶
The particle selection could be further refined by boolean masking. For example, to select hot gas, we could do:
[18]:
hot_gas = gas[gas['t'].to_value('K') > 1e5]
hot_gas['t'].min()
[18]:
unyt_quantity(100000.79, dtype=float32, units='K')
Set-like Composing¶
Another way to construct a new particle selector is to compose existing ones. A set-like interface is implemented for particle selectors to compose. For example, to define baryon as gas or star:
[19]:
star = snap.pt['star']
baryon = gas | star
Only keys from both gas and star are kept in the result:
[20]:
set(baryon.keys()) == set(gas.keys()) & set(star.keys())
[20]:
True
Here’s another example to define dark matter as all but baryon:
[21]:
dm = snap.pt['all'] - baryon
Snapshot Format Spec¶
Snapshot format are specified by a class. A GIZMO spec is built into gizio. It could be accessed as an attribute:
[22]:
snap.spec
[22]:
<gizio.spec.GIZMOSpec at 0x7f96d7855e48>
To customize a spec, subclass SpecBase
or GIZMOSpec
and modify its class attributes. For example, here we subclass GIZMOSpec
:
[23]:
from gizio.spec import GIZMOSpec
class MySpec(GIZMOSpec):
pass
MySpec.FIELD_SPEC
[23]:
[('Coordinates', 'p', 'code_length'),
('Velocities', 'v', 'code_velocity'),
('ParticleIDs', 'id', ''),
('Masses', 'm', 'code_mass'),
('InternalEnergy', 'u', 'code_specific_energy'),
('Density', 'rho', 'code_mass / code_length**3'),
('SmoothingLength', 'h', 'code_length'),
('ElectronAbundance', 'ne', ''),
('NeutralHydrogenAbundance', 'nh', ''),
('StarFormationRate', 'sfr', 'Msun / yr'),
('Metallicity', 'z', 'code_metallicity'),
('ArtificialViscosity', 'alpha', ''),
('MagneticField', 'b', 'code_magnetic_field'),
('DivergenceOfMagneticField', 'divb', 'code_magnetic_field / code_length'),
('StellarFormationTime', 'sft', ''),
('BH_Mass', 'mbh', 'code_mass'),
('BH_Mdot', 'mdot', 'code_mass / code_time'),
('BH_Mass_AlphaDisk', 'mad', 'code_mass')]
And then modify the field spec so that 'ParticleIDs'
is aliased to 'pid'
instead of 'id'
:
[24]:
MySpec.FIELD_SPEC[2] = ('ParticleIDs', 'pid', '')
MySpec.FIELD_SPEC
[24]:
[('Coordinates', 'p', 'code_length'),
('Velocities', 'v', 'code_velocity'),
('ParticleIDs', 'pid', ''),
('Masses', 'm', 'code_mass'),
('InternalEnergy', 'u', 'code_specific_energy'),
('Density', 'rho', 'code_mass / code_length**3'),
('SmoothingLength', 'h', 'code_length'),
('ElectronAbundance', 'ne', ''),
('NeutralHydrogenAbundance', 'nh', ''),
('StarFormationRate', 'sfr', 'Msun / yr'),
('Metallicity', 'z', 'code_metallicity'),
('ArtificialViscosity', 'alpha', ''),
('MagneticField', 'b', 'code_magnetic_field'),
('DivergenceOfMagneticField', 'divb', 'code_magnetic_field / code_length'),
('StellarFormationTime', 'sft', ''),
('BH_Mass', 'mbh', 'code_mass'),
('BH_Mdot', 'mdot', 'code_mass / code_time'),
('BH_Mass_AlphaDisk', 'mad', 'code_mass')]
Load the snapshot with this new spec and we see 'pid'
in the keys.
[25]:
snap = gizio.load('data/FIRE_M12i_ref11/snapshot_600.hdf5', spec=MySpec())
snap.pt['gas'].keys()
[25]:
dict_keys(['z', 'Potential', 'p', 'ne', 'v', 'sfr', 'u', 'nh', 'h', 'pid', 'rho', 'm', 't'])