"""tipsy
=====
Implements classes and functions for handling tipsy files. You rarely
need to access this module directly as it will be invoked
automatically via pynbody.load.
**Input**:
*filename*: file name string
**Optional Keywords**:
*paramfile*: string specifying the parameter file to load. If not
specified, the loader will look for a file `*.param` in the current and
parent directories.
"""
import copy
import glob
import logging
import math
import os
import struct
import sys
import warnings
import numpy as np
from .. import array, chunk, config, config_parser, family, units, util
from . import SimSnap, namemapper, nchilada
_name_map, _rev_name_map = namemapper.setup_name_maps('tipsy-name-mapping')
_translate_array_name = namemapper.name_map_function(_name_map, _rev_name_map)
logger = logging.getLogger('pynbody.snapshot.tipsy')
[docs]
class TipsySnap(SimSnap):
_basic_loadable_keys = {family.dm: {'phi', 'pos', 'eps', 'mass', 'vel'},
family.gas: {'phi', 'temp', 'pos', 'metals', 'eps',
'mass', 'rho', 'vel'},
family.star: {'phi', 'tform', 'pos', 'metals',
'eps', 'mass', 'vel'},
None: {'phi', 'pos', 'eps', 'mass', 'vel'}}
[docs]
def __init__(self, filename, **kwargs):
global config
super().__init__()
only_header = kwargs.get('only_header', False)
if only_header:
warnings.warn(
"only_header kwarg is deprecated: all loading in TipsySnap is now lazy by default", RuntimeWarning)
must_have_paramfile = kwargs.get('must_have_paramfile', False)
take = kwargs.get('take', None)
self.partial_load = take is not None
self._filename = str(util.cutgz(filename))
if not only_header:
logger.info("Loading %s", filename)
with util.open_(filename, 'rb') as f:
t, n, ndim, ng, nd, ns,pad = struct.unpack("diiiiii", f.read(32))
if (ndim > 3 or ndim < 1):
self._byteswap = True
f.seek(0)
t, n, ndim, ng, nd, ns,pad = struct.unpack(">diiiiii", f.read(32))
else:
self._byteswap = False
assert ndim == 3
if (n == 0 or n != ng+nd+ns):
n += ((pad & 0x000000ff) << 32)
ng += ((pad & 0x0000ff00) << 24)
nd += ((pad & 0x00ff0000) << 16)
ns += ((pad & 0xff000000) << 8)
assert n == ng+nd+ns
self._header_t = t
disk_family_slice = dict({family.gas: slice(0, ng),
family.dm: slice(ng, nd + ng),
family.star: slice(nd + ng, ng + nd + ns)})
self._load_control = chunk.LoadControl(disk_family_slice, 10240, take)
self._family_slice = self._load_control.mem_family_slice
self._num_particles = self._load_control.mem_num_particles
self._paramfilename = kwargs.get('paramfile', None)
self._decorate()
# describe the file structure as list of (num_parts, [list_of_properties])
# by default all fields are floats -- we look at the param file to determine
# whether we should expect some doubles
if self._paramfile.get('bDoublePos', 0):
ptype = 'd'
else:
ptype = 'f'
if self._paramfile.get('bDoubleVel', 0):
vtype = 'd'
else:
vtype = 'f'
self._g_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "rho", "temp", "eps", "metals", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f', 'f', 'f', 'f')})
self._d_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "eps", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f')})
self._s_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "metals", "tform", "eps", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f', 'f', 'f')})
if 'dKpcUnit' not in self._paramfile:
if must_have_paramfile:
raise RuntimeError("Could not find .param file for this run. Place it in the run's directory or parent directory.")
else:
warnings.warn(
"No readable param file in the run directory or parent directory: using defaults.", RuntimeWarning)
self._file_units_system = [
units.Unit(x) for x in ('G', '1 kpc', '1e10 Msol')]
time_unit = None
try:
time_unit = self.infer_original_units('yr')
except units.UnitsException:
pass
if time_unit is not None:
self.properties['time'] *= time_unit
def _load_main_file(self):
logger.info("Loading data from main file %s", self._filename)
f = util.open_(self._filename, 'rb')
f.seek(32)
write = []
for w, ndim in ("pos", 3), ("vel", 3), ("mass", 1), ("eps", 1), ("phi", 1):
if w not in list(self.keys()):
self._create_array(w, ndim, zeros=False)
write.append(w)
for w in "rho", "temp":
if w not in list(self.gas.keys()):
self.gas._create_array(w, zeros=False)
write.append(w)
if ("metals" not in list(self.gas.keys())) and ("metals" not in list(self.star.keys())):
self.gas._create_array("metals", zeros=False)
self.star._create_array("metals", zeros=False)
write.append("metals")
if "tform" not in list(self.star.keys()):
self.star._create_array("tform", zeros=False)
write.append("tform")
if "temp" in write:
self.gas["temp"].units = "K"
for k in "pos", "vel", "mass", "eps", "phi":
if k in write:
self[k].set_default_units(quiet=True)
# only do this for cosmo runs
if "phi" in write and 'h' in self.properties:
self['phi'].units = self['phi'].units * units.a ** -3 # messy :-(
for k in "rho", "temp", "metals":
if k in write:
self.gas[k].set_default_units(quiet=True)
for k in "metals", "tform":
if k in write:
self.star[k].set_default_units(quiet=True)
if "pos" in write:
write += ['x', 'y', 'z']
if "vel" in write:
write += ['vx', 'vy', 'vz']
max_item_size = max(
q.itemsize for q in (self._g_dtype, self._d_dtype, self._s_dtype))
tbuf = bytearray(max_item_size * 10240)
for fam, dtype in ((family.gas, self._g_dtype), (family.dm, self._d_dtype), (family.star, self._s_dtype)):
self_fam = self[fam]
st_len = dtype.itemsize
for readlen, buf_index, mem_index in self._load_control.iterate([fam], [fam], multiskip=True):
# Read in the block
if mem_index is None:
f.seek(st_len * readlen, 1)
continue
buf = np.frombuffer(f.read(st_len * readlen), dtype=dtype)
if self._byteswap:
buf = buf.byteswap()
if mem_index is not None:
# Copy into the correct arrays
for name in dtype.names:
if name in write:
self_fam[name][mem_index] = buf[name][buf_index]
f.close()
def _update_loadable_keys(self):
def is_readable_array(x):
try:
with util.open_(x, 'rt') as f:
return int(f.readline()) == len(self)
except ValueError:
# could be a binary file
with util.open_(x, 'rb') as f:
header = f.read(4)
if len(header) != 4:
return False
if self._byteswap:
buflen = struct.unpack(">i", header)[0]
else:
buflen = struct.unpack("i", header)[0]
ourlen_1 = (self._load_control.disk_num_particles)& 0xffffffff
ourlen_3 = (self._load_control.disk_num_particles*3)& 0xffffffff
if buflen == ourlen_1: # it's a vector
return True
elif buflen == ourlen_3: # it's an array
return True
else:
return False
except OSError:
return False
fs = list(map(util.cutgz, glob.glob(self._filename + ".*")))
res = [q[len(self._filename) + 1:] for q in list(filter(is_readable_array, fs))]
# Create an empty dictionary of sets to store the loadable
# arrays for each family
rdict = {x: set() for x in self.families()}
rdict.update({a: copy.copy(b)
for a, b in self._basic_loadable_keys.items() if a is not None})
# Now work out which families can load which arrays
# according to the stored metadata
for r in res:
fams = self._get_loadable_array_metadata(r)[1]
for x in fams or list(rdict.keys()):
rdict[x].add(r)
if r != _translate_array_name(r, reverse=True):
rdict[x].add(_translate_array_name(r, reverse=True))
if r != _translate_array_name(r):
rdict[x].add(_translate_array_name(r))
self._loadable_keys_registry = rdict
[docs]
def loadable_keys(self, fam=None):
"""Produce and return a list of loadable arrays for this TIPSY file."""
if len(self._loadable_keys_registry) == 0:
self._update_loadable_keys()
if fam is not None:
# Return what is loadable for this family
return list(self._loadable_keys_registry[fam])
else:
# Return what is loadable to all families
return list(set.intersection(*list(self._loadable_keys_registry.values())))
def _update_snapshot(self, arrays, filename=None, fam_out=[family.gas, family.dm, family.star]):
"""
Write a TIPSY file, but only updating the requested information and leaving the
rest intact on disk.
"""
if self.partial_load:
raise RuntimeError("Writing back to partially loaded files not yet supported")
global config
# make arrays be a list
if isinstance(arrays, str):
arrays = [arrays]
# check if arrays includes a 3D array
if 'pos' in arrays:
arrays.remove('pos')
for arr in ['x', 'y', 'z']:
arrays.append(arr)
if 'vel' in arrays:
arrays.remove('vel')
for arr in ['vx', 'vy', 'vz']:
arrays.append(arr)
with (
self.lazy_off,
util.open_(self.filename, "rb") as fin,
util.open_(self.filename + ".tmp", "wb") as fout
):
if self._byteswap:
t, n, ndim, ng, nd, ns = struct.unpack(">diiiii", fin.read(28))
fout.write(struct.pack(">diiiiii", t, n, ndim, ng, nd, ns, 0))
else:
t, n, ndim, ng, nd, ns = struct.unpack("diiiii", fin.read(28))
fout.write(struct.pack("diiiiii", t, n, ndim, ng, nd, ns, 0))
fin.read(4)
if family.gas in fam_out:
assert(ng == len(self[family.gas]))
if family.dm in fam_out:
assert(nd == len(self[family.dm]))
if family.star in fam_out:
assert(ns == len(self[family.star]))
max_block_size = 1024 ** 2 # particles
# describe the file structure as list of (num_parts,
# [list_of_properties])
file_structure = ((ng, family.gas, ["mass", "x", "y", "z", "vx", "vy", "vz", "rho", "temp", "eps", "metals", "phi"]),
(nd, family.dm, [
"mass", "x", "y", "z", "vx", "vy", "vz", "eps", "phi"]),
(ns, family.star, ["mass", "x", "y", "z", "vx", "vy", "vz", "metals", "tform", "eps", "phi"]))
# do the read/write -- at each block, replace the relevant array
for n_left, fam, st in file_structure:
n_done = 0
self_fam = self[fam]
while n_left > 0:
n_block = min(n_left, max_block_size)
# Read in the block
if(self._byteswap):
g = np.frombuffer(
fin.read(len(st) * n_block * 4), 'f').byteswap().reshape((n_block, len(st)))
else:
g = np.frombuffer(
fin.read(len(st) * n_block * 4), 'f').reshape((n_block, len(st)))
if fam in fam_out:
self_sub = self[fam][n_done:n_done + n_block]
# write over the relevant data
with self_sub.immediate_mode:
for i, name in enumerate(st):
if name in arrays:
ar = self_sub[name]
try:
if ar.units != 1 and ar.units != units.NoUnit():
g[:, i] = ar.in_original_units().view(
np.ndarray)
else:
g[:, i] = ar.view(np.ndarray)
except KeyError:
pass
# Write out the block
if self._byteswap:
g.byteswap().tofile(fout)
else:
g.tofile(fout)
# Increment total ptcls read in, decrement ptcls left of
# this type
n_left -= n_block
n_done += n_block
fin.close()
fout.close()
os.system("mv " + self.filename + ".tmp " + self.filename)
@staticmethod
def _infer_cosmological_from_properties(snapshot):
return 'a' in snapshot.properties
@staticmethod
def _write(snapshot, filename=None, double_pos=None, double_vel=None, binary_aux_arrays=None, cosmological=None):
"""
Write a TIPSY (standard) formatted file.
Additionally, you can specify whether you want position and/or
velocity arrays written out in double precision. If you are
writing out a snapshot that was originally in tipsy format and
the bDoublePos/bDoubleVel flags are set in the parameter file,
then the write routine will follow those choices. If you are
writing a snapshot other than a tipsy snapshot, then you have
to specify these by hand.
**Optional Keywords**
*filename* (None): name of the file to be written out. If
None, the original file is overwritten.
*double_pos* (False): set to 'True' if you want to write out positions as doubles
*double_vel* (False): set to 'True' if you want to write out velocities as doubles
*binary_aux_arrays* (None): set to 'True' to write auxiliary
arrays in binary format; if left 'None', the preference is
taken from the param file
*cosmological* (None): if True, the header will store the self.properties['a'];
if False, it will store self.properties['time'];
if None (default), an attempt will be made to infer which is more appropriate
"""
global config
if filename is None:
filename = snapshot._filename
logger.info("Writing main file %s", filename)
output_file = util.open_(filename, 'wb')
t = 0.0
try:
if cosmological is None:
cosmological = TipsySnap._infer_cosmological_from_properties(snapshot)
if cosmological:
t = snapshot.properties['a']
else:
t = snapshot.properties['time']
except KeyError:
warnings.warn(
"Time is unknown: writing zero in header", RuntimeWarning)
n = len(snapshot)
ndim = 3
ng = len(snapshot.gas)
nd = len(snapshot.dark)
ns = len(snapshot.star)
if n != ng + nd + ns:
warnings.warn("Snapshot contains extra particles than DM, stars and gas (extra families?). "
"Tipsy writing is not set up to handle those. Reverting Tipsy header ntot to ng + nd + ns")
n = ng + nd + ns
byteswap = getattr(snapshot, "_byteswap", sys.byteorder == "little")
if byteswap:
output_file.write(struct.pack(">diiiiii", t, n, ndim, ng, nd, ns, 0))
else:
output_file.write(struct.pack("diiiiii", t, n, ndim, ng, nd, ns, 0))
# needs to be done in blocks like reading
# describe the file structure as list of (num_parts,
# [list_of_properties])
if type(snapshot) is not TipsySnap:
if double_pos is None:
double_pos = False
if double_vel is None:
double_vel = False
ptype = 'd' if double_pos else 'f'
vtype = 'd' if double_vel else 'f'
else:
dpos_param = snapshot._paramfile.get('bDoublePos', False)
dvel_param = snapshot._paramfile.get('bDoubleVel', False)
if double_pos:
ptype = 'd'
elif not double_pos:
ptype = 'f'
else:
ptype = 'd' if dpos_param else 'f'
if double_vel:
vtype = 'd'
elif not double_vel:
vtype = 'f'
else:
vtype = 'd' if dvel_param else 'f'
g_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "rho", "temp", "eps", "metals", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f', 'f', 'f', 'f')})
d_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "eps", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f')})
s_dtype = np.dtype({'names': ("mass", "x", "y", "z", "vx", "vy", "vz", "metals", "tform", "eps", "phi"),
'formats': ('f', ptype, ptype, ptype, vtype, vtype, vtype, 'f', 'f', 'f', 'f')})
file_structure = ((ng, family.gas, g_dtype),
(nd, family.dm, d_dtype),
(ns, family.star, s_dtype))
max_block_size = 1024 ** 2 # particles
with snapshot.lazy_derive_off:
for n_left, fam, dtype in file_structure:
n_done = 0
self_type = snapshot[fam]
while n_left > 0:
n_block = min(n_left, max_block_size)
#g = np.zeros((n_block,len(st)),dtype=np.float32)
g = np.empty(n_block, dtype=dtype)
self_type_block = self_type[n_done:n_done + n_block]
with self_type_block.immediate_mode:
# Copy from the correct arrays
for i, name in enumerate(dtype.names):
try:
g[name] = self_type_block[name]
except KeyError:
pass
# Write out the block
if byteswap:
g.byteswap().tofile(output_file)
else:
g.tofile(output_file)
# Increment total ptcls written, decrement ptcls left of
# this type
n_left -= n_block
n_done += n_block
output_file.close()
logger.info("Writing auxiliary arrays")
with snapshot.lazy_off: # prevent any lazy reading or evaluation
for x in set(snapshot.keys()).union(snapshot.family_keys()):
if not snapshot.is_derived_array(x) and x not in ["mass", "pos", "x", "y", "z", "vel", "vx", "vy", "vz", "rho", "temp",
"eps", "metals", "phi", "tform"]:
TipsySnap._write_array(
snapshot, x, filename=filename + "." + x, binary=binary_aux_arrays)
@staticmethod
def __get_write_dtype(stored_dtype):
if issubclass(stored_dtype.type, np.integer):
return np.int32
else:
return np.float32
@staticmethod
def __write_block(f, ar, binary, byteswap):
ar = np.asarray(ar, dtype=TipsySnap.__get_write_dtype(ar.dtype))
if binary:
if byteswap:
ar.byteswap().tofile(f)
else:
ar.tofile(f)
else:
if issubclass(ar.dtype.type, np.integer):
fmt = "%d"
else:
fmt = "%e"
np.savetxt(f, ar, fmt=fmt)
def _get_loadable_array_metadata(self, array_name):
"""Given an array name, returns the metadata consisting of
the tuple units, families.
Returns:
*units*: the units of this data on disk
*families*: a list of family objects for which data is on disk
for this array, or None if this cannot be determined"""
try:
with open(self.filename + "." + array_name + ".pynbody-meta") as f:
lines = f.readlines()
except OSError:
return self._default_units_for(array_name), None, None
res = {}
for l in lines:
X = l.split(":")
if len(X) == 2:
res[X[0].strip()] = X[1].strip()
try:
u = units.Unit(res['units'])
except Exception:
u = None
try:
fams = [family.get_family(x.strip())
for x in res['families'].split(" ")]
except Exception:
fams = None
try:
dtype = np.dtype(res['dtype'])
except Exception:
dtype = None
return u, fams, dtype
@staticmethod
def _write_array_metafile(self, filename, units, families, dtype):
with open(filename + ".pynbody-meta", "w") as f:
print("# This file automatically created by pynbody", file=f)
if not hasattr(units, "_no_unit"):
print("units:", units, file=f)
print("families:", end=' ', file=f)
for x in families:
print(x.name, end=' ', file=f)
print(file=f)
if dtype is not None:
print("dtype:", TipsySnap.__get_write_dtype(dtype), file=f)
if isinstance(self, TipsySnap):
# update the loadable keys if this operation is likely to have
# changed them
self._update_loadable_keys()
@staticmethod
def _families_in_main_file(array_name, fam=None):
fam_for_default = [fX for fX, ars in TipsySnap._basic_loadable_keys.items(
) if array_name in ars and fX in fam]
return fam_for_default
def _update_array(self, array_name, fam=None,
filename=None, binary=None, byteswap=None):
assert fam is not None
if self.partial_load:
raise RuntimeError("Writing back to partially loaded files not yet supported")
fam_in_main = self._families_in_main_file(array_name, fam)
if len(fam_in_main) > 0:
self._update_snapshot(array_name, fam_out=fam_in_main)
fam = list(set(fam).difference(fam_in_main))
if len(fam) == 0:
return
# If we have disk units for this array, check we can convert into them
aux_u, aux_f, aux_type = self._get_loadable_array_metadata(array_name)
if aux_f is None:
aux_f = self.families()
if aux_u is not None:
for f in fam:
if array_name in self[f]:
# check convertible
try:
self[f][array_name].units.in_units(aux_u)
except units.UnitsException:
raise OSError(
"Units must match the existing auxiliary array on disk.")
try:
data = self.__read_array_from_disk(array_name, filename=filename)
except OSError:
# doesn't really exist, probably because the other data on disk was
# in the main snapshot
self._write_array(self, array_name, fam, filename=filename, binary=binary,
byteswap=byteswap)
return
for f in fam:
if aux_u is not None:
data[self._get_family_slice(f)] = self[f][
array_name].in_units(aux_u)
else:
data[self._get_family_slice(f)] = self[f][array_name]
fam = list(set(aux_f).union(fam))
self._write_array(self, array_name, fam=fam, contents=data, binary=binary,
byteswap=byteswap, filename=filename)
@staticmethod
def _write_array(self, array_name, fam=None, contents=None,
filename=None, binary=None, byteswap=None):
"""Write the array to file."""
fam_in_main = TipsySnap._families_in_main_file(array_name, fam)
if len(fam_in_main) > 0:
if isinstance(self, TipsySnap):
self._update_snapshot(array_name, fam_out=fam_in_main)
fam = list(set(fam).difference(fam_in_main))
if len(fam) == 0:
return
else:
raise RuntimeError("Cannot call static _write_array to write into main tipsy file.")
units_out = None
dtype = None
if binary is None:
binary = getattr(self.ancestor, "_tipsy_arrays_binary", False)
if binary and (byteswap is None):
byteswap = getattr(self.ancestor, "_byteswap", False)
with self.lazy_off: # prevent any lazy reading or evaluation
if filename is None:
if self._filename[-3:] == '.gz':
filename = self._filename[:-3] + "." + array_name + ".gz"
else:
filename = self._filename + "." + array_name
if binary:
fhand = util.open_(filename, 'wb')
if byteswap:
fhand.write(struct.pack(">i", len(self)))
else:
fhand.write(struct.pack("i", len(self)))
else:
fhand = util.open_(filename, 'wb')
fhand.write((str(len(self)) + '\n').encode('utf-8'))
if contents is None:
if array_name in self.family_keys():
for f in [family.gas, family.dm, family.star]:
try:
dtype = self[f][array_name].dtype
ar = self[f][array_name]
units_out = ar.units
except KeyError:
ar = np.zeros(len(self[f]), dtype=int)
TipsySnap.__write_block(fhand, ar, binary, byteswap)
else:
ar = self[array_name]
dtype = self[array_name].dtype
units_out = ar.units
TipsySnap.__write_block(fhand, ar, binary, byteswap)
else:
TipsySnap.__write_block(fhand, contents, binary, byteswap)
units_out = contents.units
fhand.close()
if fam is None:
fam = [family.gas, family.dm, family.star]
TipsySnap._write_array_metafile(self, filename, units_out, fam, dtype)
def _load_array(self, array_name, fam=None, filename=None,
packed_vector=None):
if array_name in self._basic_loadable_keys[fam]:
self._load_main_file()
return
fams = self._get_loadable_array_metadata(
array_name)[1] or self.families()
if (fam is None and fams is not None and len(fams) != len(self.families())) or \
(fam is not None and fam not in fams):
# Top line says 'you requested all families but at least one isn't available'
# Bottom line says 'you requested one family, but that one's not
# available'
raise OSError("This array is marked as available only for families %s" % fams)
data = self.__read_array_from_disk(array_name, fam=fam,
filename=filename,
packed_vector=packed_vector)
if fam is None:
self[array_name] = data
else:
self[fam][array_name] = data
def __read_array_from_disk(self, array_name, fam=None, filename=None,
packed_vector=None):
"""Read a TIPSY-ASCII or TIPSY-BINARY auxiliary file with the
specified name. If fam is not None, read only the particles of
the specified family."""
starlog_keys = ['rhoform', 'tempform', 'phiform', 'nsmooth',
'xform', 'yform', 'zform', 'vxform', 'vyform', 'vzform',
'posform', 'velform','h2form']
# if massform available as auxiliary array, faster to load it instead
if array_name == 'massform' and 'massform' not in self.loadable_keys():
starlog_keys += ['massform']
if filename is None and array_name in starlog_keys:
try:
self.read_starlog()
with self.lazy_off:
if fam is not None:
return self[fam][array_name]
else:
return self[array_name]
except OSError:
pass
# N.B. this code is a bit inefficient for loading
# family-specific arrays, because it reads in the whole array
# then slices it. But of course the memory is only wasted
# while still inside this routine, so it's only really the
# wasted time that's a problem.
# determine whether the array exists in a file
if filename is None:
if self._filename[-3:] == '.gz':
filename = self._filename[:-3] + "." + array_name
else:
filename = self._filename + "." + array_name
reverse = (_translate_array_name(array_name) == array_name)
if not os.path.isfile(filename):
if self._filename[-3:] == '.gz':
filename = self._filename[:-3] + "." + _translate_array_name(array_name, reverse=reverse)
else:
filename = self._filename + "." + _translate_array_name(array_name, reverse=reverse)
logger.info("Attempting to load auxiliary array %s", filename)
# if we get here, we've got the file - try loading it
units, _, dtype = self._get_loadable_array_metadata(array_name)
if dtype is None:
dtype = self._get_preferred_dtype(array_name)
try:
f = util.open_(filename, 'r')
l = int(f.readline())
binary = False
if l != self._load_control.disk_num_particles:
raise OSError("Incorrect file format")
if not dtype:
# Inspect the first line to see whether it's float or int
l = "0\n"
while l == "0\n":
l = f.readline()
if "." in l or "e" in l or l[:-1] == "inf":
dtype = float
else:
dtype = int
# Restart at head of file
f.seek(0)
f.readline()
loadblock = lambda count: np.fromfile(
f, dtype=dtype, sep="\n", count=count)
# data = np.fromfile(f, dtype=tp, sep="\n")
except ValueError:
f.close()
# this is probably a binary file
binary = True
f = util.open_(filename, 'rb')
# Read header and check endianness
if self._byteswap:
l = struct.unpack(">i", f.read(4))[0]
else:
l = struct.unpack("i", f.read(4))[0]
if l != self._load_control.disk_num_particles:
raise OSError("Incorrect file format")
if dtype is None:
# Set data format to be read (float or int) based on config
int_arrays = list(map(
str.strip, config_parser.get('tipsy', 'binary-int-arrays').split(",")))
if array_name in int_arrays:
dtype = 'i'
else:
dtype = 'f'
# Read longest data array possible.
# Assume byteswap since most will be.
if self._byteswap:
loadblock = lambda count: np.frombuffer(
f.read(count * 4), dtype=dtype, count=count).byteswap()
# data = np.fromstring(f.read(3*len(self)*4),dtype).byteswap()
else:
loadblock = lambda count: np.frombuffer(
f.read(count * 4), dtype=dtype, count=count)
# data = np.fromstring(f.read(3*len(self)*4),dtype)
self.ancestor._tipsy_arrays_binary = binary
all_fam = [family.dm, family.gas, family.star]
if fam is None:
fam = all_fam
r = np.empty(len(self), dtype=dtype).view(array.SimArray)
else:
r = np.empty(len(self[fam]), dtype=dtype).view(array.SimArray)
try:
for readlen, buf_index, mem_index in self._load_control.iterate(all_fam, fam):
buf = loadblock(readlen)
if mem_index is not None:
r[mem_index] = buf[buf_index]
finally:
f.close()
if units is not None:
r.units = units
return r
[docs]
def read_starlog(self, fam=None):
"""Read a TIPSY-starlog file."""
import pynbody.bridge
x = os.path.abspath(self._filename)
done = False
filename = None
x = os.path.dirname(x)
# Attempt the loading of information
l = glob.glob(os.path.join(x, "*.starlog"))
if (len(l)):
for filename in l:
starlog = StarLog(filename)
else:
l = glob.glob(os.path.join(x, "../*.starlog"))
if (len(l) == 0):
raise OSError("Couldn't find starlog file")
for filename in l:
starlog = StarLog(filename)
logger.info("Bridging starlog into SimSnap")
b = pynbody.bridge.OrderBridge(self, starlog)
source = b(b(starlog))
dest = b(source)
dest.star['iorderGas'] = source.star['iorderGas']
dest.star['massform'] = source.star['massform']
dest.star['rhoform'] = source.star['rhoform']
dest.star['tempform'] = source.star['tempform']
dest['posform'] = source['pos']
dest['velform'] = source['vel']
if 'h2form' in list(starlog.star.keys()):
dest.star['h2form'] = source.star['h2form']
else:
logger.info("No H2 data found in StarLog file")
for i, x in enumerate(['x', 'y', 'z']):
self._arrays[x + 'form'] = self['posform'][:, i]
for i, x in enumerate(['vx', 'vy', 'vz']):
self._arrays[x + 'form'] = self['velform'][:, i]
@classmethod
def _can_load(cls, f):
try:
with warnings.catch_warnings():
warnings.simplefilter("ignore", RuntimeWarning)
check = TipsySnap(f)
del check
except Exception as e:
return False
return True
# caculate the number fraction YH, YHe as a function of metalicity. Cosmic
# production rate of helium relative to metals (in mass)
# delta Y/delta Z = 2.1 and primordial He Yp = 0.236 (Jimenez et al. 2003,
# Science 299, 5612.
# piecewise linear
# Y = Yp + dY/dZ*ZMetal up to ZMetal = 0.1, then linear decrease to 0 at Z=1)
# SUM_Metal = sum(ni/nH *mi),it is a fixed number for cloudy abundance.
# Massfraction fmetal = Z*SUM_metal/(1 + 4*nHe/nH + Z*SUM_metal) (1)
# 4*nHe/nH = mHe/mH = fHe/fH
# also fH + fHe + fMetal = 1 (2)
# if fHe specified, combining the 2 eq above will solve for
# fH and fMetal
def _abundance_estimator(metal):
Y_He = ((0.236 + 2.1 * metal) / 4.0) * (metal <= 0.1)
Y_He += ((-0.446 * (metal - 0.1) / 0.9 + 0.446) / 4.0) * (metal > 0.1)
Y_H = 1.0 - Y_He * 4. - metal
return Y_H, Y_He
[docs]
@TipsySnap.derived_array
def HII(sim):
"""Number of HII ions per proton mass"""
Y_H, Y_He = _abundance_estimator(sim["metals"])
try:
return Y_H - sim["HI"] - 2 * sim["H2"]
except KeyError:
return Y_H - sim["HI"]
[docs]
@TipsySnap.derived_array
def HeIII(sim):
"""Number of HeIII ions per proton mass"""
Y_H, Y_He = _abundance_estimator(sim["metals"])
return Y_He - sim["HeII"] - sim["HeI"]
[docs]
@TipsySnap.derived_array
def ne(sim):
"""Number of electrons per proton mass"""
return sim["HII"] + sim["HeII"] + 2 * sim["HeIII"]
[docs]
@TipsySnap.derived_array
def hetot(self):
"""Helium mass fraction including correction based on metallicity"""
return 0.236 + (2.1 * self['metals'])
[docs]
@TipsySnap.derived_array
def hydrogen(self):
"""Hydrogen mass fraction including correction based on metallicity"""
return 1.0 - self['metals'] - self['hetot']
#from .tipsy import TipsySnap
# Asplund et al (2009) ARA&A solar abundances (Table 1)
# m_frac = 10.0^([X/H] - 12)*M_X/M_H*0.74
# OR
# http://en.wikipedia.org/wiki/Abundance_of_the_chemical_elements
# puts stuff more straighforwardly cites Arnett (1996)
# A+G from http://www.t4.lanl.gov/opacity/grevand1.html
# Anders + Grev (1989) Asplund
XSOLFe = 0.125E-2 # 1.31e-3
# Looks very wrong ([O/Fe] ~ 0.2-0.3 higher than solar),
# probably because SN ejecta are calculated with
# Woosley + Weaver (1995) based on Anders + Grevesse (1989)
# XSOLO=0.59E-2 # 5.8e-2
XSOLO = 0.84E-2
XSOLH = 0.706 # 0.74
XSOLC = 3.03e-3 # 2.39e-3
XSOLN = 9.2e-4 # 7e-4
XSOLNe = 1.66e-3 # 1.26e-3
XSOLMg = 6.44e-4 # 7e-4
XSOLSi = 7e-4 # 6.7e-4
[docs]
@TipsySnap.derived_array
def feh(self):
"""Iron abundance [Fe/H] derived from tipsy array FeMassFrac, with solar
values from Asplund et al 09"""
minfe = np.amin(self['FeMassFrac'][np.where(self['FeMassFrac'] > 0)])
self['FeMassFrac'][np.where(self['FeMassFrac'] == 0)] = minfe
return np.log10(self['FeMassFrac'] / self['hydrogen']) - np.log10(XSOLFe / XSOLH)
[docs]
@TipsySnap.derived_array
def oxh(self):
"""Oxygen abundance [O/H] derived from tipsy array FeMassFrac, with solar
values from Asplund et al 09"""
minox = np.amin(self['OxMassFrac'][np.where(self['OxMassFrac'] > 0)])
self['OxMassFrac'][np.where(self['OxMassFrac'] == 0)] = minox
return np.log10(self['OxMassFrac'] / self['hydrogen']) - np.log10(XSOLO / XSOLH)
[docs]
@TipsySnap.derived_array
def ofe(self):
"""Oxygen-to-iron ratio [O/Fe] derived from tipsy arrays OxMassFrac and FeMassFrac
with solar values from Asplund et al 09"""
minox = np.amin(self['OxMassFrac'][np.where(self['OxMassFrac'] > 0)])
self['OxMassFrac'][np.where(self['OxMassFrac'] == 0)] = minox
minfe = np.amin(self['FeMassFrac'][np.where(self['FeMassFrac'] > 0)])
self['FeMassFrac'][np.where(self['FeMassFrac'] == 0)] = minfe
return np.log10(self['OxMassFrac'] / self['FeMassFrac']) - np.log10(XSOLO / XSOLFe)
[docs]
@TipsySnap.derived_array
def mgfe(sim):
"""Magnesium-to-iron ratio [Mg/Fe] derived from tipsy arrays MgMassFrac and FeMassFrac
with solar values from Asplund et al 09"""
minmg = np.amin(sim['MgMassFrac'][np.where(sim['MgMassFrac'] > 0)])
sim['MgMassFrac'][np.where(sim['MgMassFrac'] == 0)] = minmg
minfe = np.amin(sim['FeMassFrac'][np.where(sim['FeMassFrac'] > 0)])
sim['FeMassFrac'][np.where(sim['FeMassFrac'] == 0)] = minfe
return np.log10(sim['MgMassFrac'] / sim['FeMassFrac']) - np.log10(XSOLMg / XSOLFe)
[docs]
@TipsySnap.derived_array
def nefe(sim):
"""Neon-to-iron ratio [Ne/Fe] derived from tipsy arrays MgMassFrac and FeMassFrac
with solar values from Asplund et al 09"""
minne = np.amin(sim['NeMassFrac'][np.where(sim['NeMassFrac'] > 0)])
sim['NeMassFrac'][np.where(sim['NeMassFrac'] == 0)] = minne
minfe = np.amin(sim['FeMassFrac'][np.where(sim['FeMassFrac'] > 0)])
sim['FeMassFrac'][np.where(sim['FeMassFrac'] == 0)] = minfe
return np.log10(sim['NeMassFrac'] / sim['FeMassFrac']) - np.log10(XSOLNe / XSOLFe)
[docs]
@TipsySnap.derived_array
def sife(sim):
"""Silicon-to-iron ratio [Si/Fe] derived from tipsy arrays MgMassFrac and FeMassFrac
with solar values from Asplund et al 09"""
minsi = np.amin(sim['SiMassFrac'][np.where(sim['SiMassFrac'] > 0)])
sim['SiMassFrac'][np.where(sim['SiMassFrac'] == 0)] = minsi
minfe = np.amin(sim['FeMassFrac'][np.where(sim['FeMassFrac'] > 0)])
sim['FeMassFrac'][np.where(sim['FeMassFrac'] == 0)] = minfe
return np.log10(sim['SiMassFrac'] / sim['FeMassFrac']) - np.log10(XSOLSi / XSOLFe)
[docs]
@TipsySnap.derived_array
def c_s(self):
"""Ideal gas sound speed based on pressure and density"""
#x = np.sqrt(5./3.*units.k*self['temp']*self['mu'])
x = np.sqrt(5. / 3. * self['p'] / self['rho'])
x.convert_units('km s^-1')
return x
[docs]
@TipsySnap.derived_array
def c_s_turb(self):
"""Turbulent sound speed (from Mac Low & Klessen 2004)"""
x = np.sqrt(self['c_s'] ** 2 + 1. / 3 * self['v_disp'] ** 2)
x.convert_units('km s^-1')
return x
[docs]
@TipsySnap.derived_array
def mjeans(self):
"""Classical Jeans mass"""
x = np.pi ** (5. / 2.) * self['c_s'] ** 3 / \
(6. * units.G ** (3, 2) * self['rho'] ** (1, 2))
x.convert_units('Msol')
return x
[docs]
@TipsySnap.derived_array
def mjeans_turb(self):
"""Turbulent Jeans mass"""
x = np.pi ** (5. / 2.) * self['c_s_turb'] ** 3 / \
(6. * units.G ** (3, 2) * self['rho'] ** (1, 2))
x.convert_units('Msol')
return x
[docs]
@TipsySnap.derived_array
def ljeans(self):
"""Jeans length"""
x = self['c_s'] * np.sqrt(np.pi / (units.G * self['rho']))
x.convert_units('kpc')
return x
[docs]
@TipsySnap.derived_array
def ljeans_turb(self):
"""Turbulent Jeans length"""
x = self['c_s_turb'] * np.sqrt(np.pi / (units.G * self['rho']))
x.convert_units('kpc')
return x
[docs]
class StarLog(SimSnap):
[docs]
def __init__(self, filename, sort=True, paramfile=None, use_log=True):
import os
super().__init__()
self._filename = filename
self._paramfilename = paramfile
with util.open_(filename, "rb") as f:
self.properties = {}
size = struct.unpack("i", f.read(4))
if (size[0] > 1000 or size[0] < 10):
self._byteswap = True
f.seek(0)
size = struct.unpack(">i", f.read(4))
iSize = size[0]
bigstarlog = False
molecH = False
bigIOrds = False
# replace only the .starlog suffix with .log
self._logfile = '.'.join([x for x in filename.split('.')[:-1]]) + '.log'
if use_log:
# assumes log file would be in the same location as starlog file
logger.info('Attempting to load starlog metadata from log file')
try:
with open(self._logfile) as g:
read_metadata = False
structure_names = []
structure_formats = []
for line in g:
if line.startswith('# end starlog data'):
break
if read_metadata:
meta_name, meta_type = line.strip().strip('#').split()
meta_name = self._infer_name_from_tipsy_log(meta_name)
structure_names.append(meta_name)
structure_formats.append(meta_type)
if line.startswith('# starlog data:'):
read_metadata = True
file_structure = np.dtype({'names': structure_names,
'formats': structure_formats})
if file_structure.itemsize != iSize:
raise ValueError('Starlog metadata size does not match with starlog file size')
if file_structure['iord'] == 'f8':
bigIOrds = True
if 'h2form' in file_structure.names:
molecH = True
except FileNotFoundError:
warnings.warn('No log file found, so the precise structure of the starlog is not defined; reverting to guess-and-check')
use_log = False
except ValueError:
warnings.warn('log file found, but there was a problem with the starlog metadata. '
'Reverting to guess-and-check')
use_log = False
if use_log is False:
file_structure = np.dtype({'names': ("iord", "iorderGas", "tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform"),
'formats': ('i4', 'i4', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8')})
if (iSize > file_structure.itemsize):
file_structure = np.dtype({'names': ("iord", "iorderGas", "tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform","h2form"),
'formats': ('i4', 'i4', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8','f8')})
molecH = True
# Unfortunately molecularH with small iOrders has the same as
# no moleculuarH with big iOrders. Attempt to distinguish here
if(iSize == file_structure.itemsize):
if(self._byteswap):
testread = np.frombuffer(
f.read(iSize), dtype=file_structure).byteswap()
else:
testread = np.frombuffer(f.read(iSize), dtype=file_structure)
# All star iorders are greater than any gas iorder
# so this indicates a bad format. (N.B. there is the
# possibility of a false negative)
if(testread['iord'][0] < testread['iorderGas'][0]):
file_structure = np.dtype({'names': ("iord", "iorderGas",
"tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform"),
'formats': ('i8', 'i8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8')})
f.seek(4)
logger.info("Using 64 bit iOrders")
molecH = False
bigIOrds = True
if (iSize != file_structure.itemsize):
file_structure = np.dtype({'names': ("iord", "iorderGas", "tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform",
"phiform", "nsmooth"),
'formats': ('i4', 'i4', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'i4')})
molecH = False
if (iSize != file_structure.itemsize and iSize != 104):
raise OSError("Unknown starlog structure iSize:" + \
str(iSize) + ", file_structure itemsize:" + \
str(file_structure.itemsize))
else:
bigstarlog = True
if molecH is True: print("h2 information found in StarLog!")
datasize = os.path.getsize(filename) - f.tell()
# check whether datasize is a multiple of iSize. If it is not,
# the starlog is likely corrupted, but try to read it anyway
if (datasize % iSize > 0) and (iSize != 104):
warnings.warn(
"The size of the starlog file does not make sense -- it is likely corrupted. Pynbody will read it anyway, but use with caution.")
datasize -= datasize % iSize
logger.info("Reading starlog file %s", filename)
if(self._byteswap):
g = np.frombuffer(
f.read(datasize), dtype=file_structure).byteswap()
else:
g = np.frombuffer(f.read(datasize), dtype=file_structure)
# hoping to provide backward compatibility for np.unique by
# copying relavent part of current numpy source:
# numpy/lib/arraysetops.py:192 (on 22nd March 2011)
if sort:
tmp = g['iord'].flatten()
# mergesort for stability
perm = tmp.argsort(kind='mergesort')
aux = tmp[perm]
flag = np.concatenate((aux[1:] != aux[:-1], [True]))
iord = aux[flag]
indices = perm[flag]
self._num_particles = len(indices)
else:
self._num_particles = len(g)
self._family_slice[family.star] = slice(0, self._num_particles)
self._create_arrays(["pos", "vel"], 3)
if(bigIOrds):
self._create_arrays(["iord"], dtype='int64')
else:
self._create_arrays(["iord"], dtype='int32')
self._create_arrays(["iord"], dtype='int32')
self._create_arrays(
["iorderGas", "massform", "rhoform", "tempform", "metals", "tform"])
if molecH:
self._create_arrays(["h2form"])
if bigstarlog:
self._create_arrays(["phiform", "nsmooth"])
self._decorate()
if sort:
for name in list(file_structure.fields.keys()):
self.star[name][:] = g[name][indices]
else:
for name in list(file_structure.fields.keys()):
self.star[name] = g[name]
@staticmethod
def _write(self, filename=None):
"""Write the starlog file. """
global config
with self.lazy_off:
if filename is None:
filename = self._filename
logger.info("Writing starlog file as %s", filename)
f = util.open_(filename, 'wb')
if 'phiform' in list(self.keys()): # long starlog format
file_structure = np.dtype({'names': ("iord", "iorderGas", "tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform",
"phiform", "nsmooth"),
'formats': ('i4', 'i4', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'i4')})
else: # short (old) starlog format
file_structure = np.dtype({'names': ("iord", "iorderGas", "tform",
"x", "y", "z",
"vx", "vy", "vz",
"massform", "rhoform", "tempform"),
'formats': ('i4', 'i4', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8',
'f8', 'f8', 'f8')})
if self._byteswap:
f.write(struct.pack(">i", file_structure.itemsize))
else:
f.write(struct.pack("i", file_structure.itemsize))
max_block_size = 1024 # particles
n_left = len(self)
n_done = 0
while n_left > 0:
n_block = min(n_left, max_block_size)
g = np.zeros(n_block, dtype=file_structure)
for arr in file_structure.names:
g[arr] = self[arr][n_done:n_done + n_block]
if self._byteswap:
g.byteswap().tofile(f)
else:
g.tofile(f)
n_left -= n_block
n_done += n_block
f.close()
def _infer_name_from_tipsy_log(self, meta_name):
"""Convert starlog metadata name to pynbody array name
In the future, it may be necessary to expand this or make
it more flexible. For now, a dictionary should do.
"""
conversion_dict = {'iOrdStar': 'iord', 'iOrdGas': 'iorderGas',
'timeForm': 'tform',
'rForm[0]': 'x', 'rForm[1]': 'y', 'rForm[2]': 'z',
'vForm[0]': 'vx', 'vForm[1]': 'vy', 'vForm[2]': 'vz',
'massForm': 'massform', 'rhoForm': 'rhoform',
'TForm': 'tempform', 'tCoolForm': 'tcoolform',
'H2FracForm': 'h2form'}
if meta_name in conversion_dict.keys():
return conversion_dict[meta_name]
else: # this is where cleverness will be needed in the future
raise ValueError('Unknown starlog entry: ' + meta_name)
[docs]
@TipsySnap.decorator
@StarLog.decorator
@nchilada.NchiladaSnap.decorator
def load_paramfile(sim):
x = os.path.dirname(os.path.abspath(sim._filename))
sim._paramfile = {}
ok = False
if sim._paramfilename is None:
candidates = [
*glob.glob(os.path.join(x, "*.param")),
*glob.glob(os.path.join(x, "../*.param")),
]
else:
candidates = [sim._paramfilename]
for filename in candidates:
try:
with open(filename):
pass
ok = True
break
except OSError:
pass
if not ok:
if sim._paramfile:
raise OSError("The parameter filename you supplied is invalid")
return
with open(filename) as f:
lines = f.readlines()
for line in lines:
if line.startswith("#"):
continue
# Remove comments
line = line.split("#")[0]
# Lines are "key = val"
try:
key, val = (_.strip() for _ in line.split("="))
except ValueError:
continue
sim._paramfile[key] = val
if len(sim._paramfile) > 1:
sim._paramfile["filename"] = filename
[docs]
@TipsySnap.decorator
@StarLog.decorator
@nchilada.NchiladaSnap.decorator
def param2units(sim):
with sim.lazy_off:
munit = dunit = hub = None
logger.debug("paramfile: %s", sim._paramfile)
try:
hub = float(sim._paramfile["dHubble0"])
sim.properties['omegaM0'] = float(sim._paramfile["dOmega0"])
sim.properties['omegaL0'] = float(sim._paramfile["dLambda"])
except KeyError:
pass
try:
munit_st = sim._paramfile["dMsolUnit"] + " Msol"
munit = float(sim._paramfile["dMsolUnit"])
dunit_st = sim._paramfile["dKpcUnit"] + " kpc"
dunit = float(sim._paramfile["dKpcUnit"])
logger.debug("munit: %s, dunit: %s", munit_st, dunit_st)
except KeyError:
pass
if munit is None or dunit is None:
if hub is not None:
sim.properties['h'] = hub
return
denunit = munit / dunit ** 3
denunit_st = str(denunit) + " Msol kpc^-3"
#
# the obvious way:
#
#denunit_cgs = denunit * 6.7696e-32
#kpc_in_km = 3.0857e16
#secunit = 1./math.sqrt(denunit_cgs*6.67e-8)
#velunit = dunit*kpc_in_km/secunit
# the sensible way:
# avoid numerical accuracy problems by concatinating factors:
velunit = 8.0285 * math.sqrt(6.6743e-8 * denunit) * dunit
velunit_st = ("%.5g" % velunit) + " km s^-1"
# You have: kpc s / km
# You want: Gyr
#* 0.97781311
timeunit = dunit / velunit * 0.97781311
timeunit_st = ("%.5g" % timeunit) + " Gyr"
# Assuming G=1 in code units, phi is actually vel^2/a^3.
# See also gasoline's master.c:5511.
# Or should we be calculating phi as GM/R units (which
# is the same for G=1 runs)?
potunit_st = "%.5g km^2 s^-2" % (velunit ** 2)
if 'bComove' in sim._paramfile and int(sim._paramfile['bComove']) != 0:
hubunit = 10. * velunit / dunit
hubunit_st = ("%.3f" % (hubunit * hub))
sim.properties['h'] = hub * hubunit
if isinstance(sim, StarLog):
a = "aform"
else:
a = "a"
# append dependence on 'a' for cosmological runs
dunit_st += " " + a
denunit_st += " " + a + "^-3"
velunit_st += " " + a
potunit_st += " " + a + "^-1"
# Assume the box size is equal to the length unit
sim.properties['boxsize'] = units.Unit(dunit_st)
try:
sim["vel"].units = velunit_st
except KeyError:
pass
try:
sim["phi"].units = potunit_st
sim["eps"].units = dunit_st
except KeyError:
pass
try:
sim["pos"].units = dunit_st
except KeyError:
pass
try:
sim.gas["rho"].units = denunit_st
except KeyError:
pass
try:
sim["mass"].units = munit_st
except KeyError:
pass
try:
sim.star["tform"].units = timeunit_st
except KeyError:
pass
try:
sim.gas["metals"].units = ""
except KeyError:
pass
try:
sim.star["metals"].units = ""
except KeyError:
pass
try:
sim._file_units_system = [
sim["vel"].units, sim.star["mass"].units, sim["pos"].units, units.K]
logger.debug("file_units_system set from arrays %s", sim._file_units_system)
except KeyError:
try:
sim._file_units_system = [
sim["vel"].units, sim.star["massform"].units, sim["pos"].units, units.K]
logger.debug("file_units_system set from arrays inc star massform %s", sim._file_units_system)
except KeyError:
try:
sim._file_units_system = [units.Unit(velunit_st),
units.Unit(munit_st),
units.Unit(dunit_st), units.K]
logger.debug("file_units_system set from paramfile %s", sim._file_units_system)
except Exception:
pass
sim._override_units_system()
[docs]
@TipsySnap.decorator
def settime(sim):
if 'bComove' in sim._paramfile and int(sim._paramfile['bComove']) != 0:
t = sim._header_t
sim.properties['a'] = t
try:
sim.properties['z'] = 1.0 / t - 1.0
except ZeroDivisionError:
# no sensible redshift
pass
if (sim.properties['z'] is not None and
'dMsolUnit' in sim._paramfile and
'dKpcUnit' in sim._paramfile):
from ..analysis import cosmology
sim.properties['time'] = cosmology.age(
sim, unit=sim.infer_original_units('yr'))
else:
# something has gone wrong with the cosmological side of
# things
warnings.warn(
"Paramfile suggests time is cosmological, but header values are not sensible in this context.", RuntimeWarning)
sim.properties['time'] = t
sim.properties['a'] = t
else:
# Assume a non-cosmological run
sim.properties['time'] = sim._header_t
[docs]
@nchilada.NchiladaSnap.decorator
def settimeN(sim):
if 'bComove' in sim._paramfile and int(sim._paramfile['bComove']) != 0:
#t = sim._header_t
#sim.properties['a'] = t
try:
sim.properties['z'] = 1.0 / sim.properties['a'] - 1.0
except ZeroDivisionError:
# no sensible redshift
pass
if (sim.properties['z'] is not None and
'dMsolUnit' in sim._paramfile and
'dKpcUnit' in sim._paramfile):
from ..analysis import cosmology
sim.properties['time'] = cosmology.age(
sim, unit=sim.infer_original_units('yr'))
else:
# something has gone wrong with the cosmological side of
# things
warnings.warn(
"Paramfile suggests time is cosmological, but header values are not sensible in this context.", RuntimeWarning)
sim.properties['time'] = sim.properties['a']
#sim.properties['a'] = t
else:
# Assume a non-cosmological run
sim.properties['time'] = sim.properties['a']
time_unit = None
try:
time_unit = sim.infer_original_units('yr')
except units.UnitsException:
pass
if time_unit is not None:
sim.properties['time'] *= time_unit
[docs]
@StarLog.decorator
def slparam2units(sim):
with sim.lazy_off:
munit = dunit = hub = None
try:
munit_st = sim._paramfile["dMsolUnit"] + " Msol"
munit = float(sim._paramfile["dMsolUnit"])
dunit_st = sim._paramfile["dKpcUnit"] + " kpc"
dunit = float(sim._paramfile["dKpcUnit"])
hub = float(sim._paramfile["dHubble0"])
except KeyError:
pass
if munit is None or dunit is None:
return
denunit = munit / dunit ** 3
denunit_st = str(denunit) + " Msol kpc^-3"
velunit = 8.0285 * math.sqrt(6.6743e-8 * denunit) * dunit
velunit_st = ("%.5g" % velunit) + " km s^-1"
# You have: kpc s / km
# You want: Gyr
#* 0.97781311
timeunit = dunit / velunit * 0.97781311
timeunit_st = ("%.5g" % timeunit) + " Gyr"
if hub is not None:
# append dependence on 'a' for cosmological runs
dunit_st += " aform"
# denunit_st += " a^-3"
# N.B. density comoving -> physical conversion is done by Gasoline
# itself
sim.star["rhoform"].units = denunit_st
sim.star["massform"].units = munit_st