forked from LSSTDESC/descqa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GalacticusGalaxyCatalog.py
423 lines (372 loc) · 18.7 KB
/
GalacticusGalaxyCatalog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# Argonne galaxy catalog class.
import os
import re
import numpy as np
import h5py
import astropy.cosmology
import astropy.units as u
from .GalaxyCatalogInterface import GalaxyCatalog
class GalacticusGalaxyCatalog(GalaxyCatalog):
"""
Argonne galaxy catalog class. Uses generic quantity and filter mechanisms
defined by GalaxyCatalog base class. In addition, implements the use of
'stored' vs. 'derived' quantity getter methods. Additional data structures:
catalog A dictionary whose keys are halo names and whose values are
themselves dictionaries. The halo dictionaries have as keys
the names of the various stored properties, and as values
arrays containing the values of these quantities for each of
the galaxies in the halos.
derived A dictionary whose keys are the names of derived quantities
and whose values are tuples containing the string name of a
corresponding stored quantity (actually present in the file)
and a pointer to the function used to compute the derived
quantity from the stored one. Some catalogs may support
having the stored quantity be a tuple of stored quantity
names.
"""
Output='Output'
Outputs='Outputs'
z='z'
def __init__(self, **kwargs):
self.kwargs = kwargs
self.type_ext = 'galacticus'
self.filters = { 'zlo': True,
'zhi': True
}
self.quantities = { 'redshift': self._get_stored_property,
'ra': self._get_stored_property,
'dec': self._get_stored_property,
'v_pec': self._get_stored_property,
'mass': self._get_derived_property,
'age': self._get_stored_property,
'stellar_mass': self._get_derived_property,
'log_stellarmass': self._get_stored_property,
'log_halomass': self._get_stored_property,
'gas_mass': self._get_stored_property,
'metallicity': self._get_stored_property,
'sfr': self._get_stored_property,
'ellipticity': self._get_stored_property,
#'positionX': self._get_derived_property, #units are in physical Mpc
#'positionY': self._get_derived_property,
#'positionZ': self._get_derived_property,
'positionX': self._get_stored_property, #units are now in comoving Mpc
'positionY': self._get_stored_property,
'positionZ': self._get_stored_property,
'velocityX': self._get_stored_property,
'velocityY': self._get_stored_property,
'velocityZ': self._get_stored_property,
'parent_halo_id': self._get_stored_property,
'disk_ra': self._get_stored_property,
'disk_dec': self._get_stored_property,
'disk_sigma0': self._get_stored_property,
'disk_re': self._get_stored_property,
'disk_index': self._get_stored_property,
'disk_a': self._get_stored_property,
'disk_b': self._get_stored_property,
'disk_theta_los': self._get_stored_property,
'disk_phi': self._get_stored_property,
'disk_stellarmass': self._get_derived_property,
'log_disk_stellarmass': self._get_stored_property,
'disk_metallicity': self._get_stored_property,
'disk_age': self._get_stored_property,
'disk_sfr': self._get_stored_property,
'disk_ellipticity': self._get_stored_property,
'bulge_ra': self._get_stored_property,
'bulge_dec': self._get_stored_property,
'bulge_sigma0': self._get_stored_property,
'bulge_re': self._get_stored_property,
'bulge_index': self._get_stored_property,
'bulge_a': self._get_stored_property,
'bulge_b': self._get_stored_property,
'bulge_theta_los': self._get_stored_property,
'bulge_phi': self._get_stored_property,
'bulge_stellarmass': self._get_derived_property,
'log_bulge_stellarmass': self._get_stored_property,
'bulge_age': self._get_stored_property,
'bulge_sfr': self._get_stored_property,
'bulge_metallicity': self._get_stored_property,
'bulge_ellipticity': self._get_stored_property,
'agn_ra': self._get_stored_property,
'agn_dec': self._get_stored_property,
'agn_mass': self._get_stored_property,
'agn_accretnrate': self._get_stored_property,
'SDSS_u:rest:': self._get_stored_property,
'SDSS_g:rest:': self._get_stored_property,
'SDSS_r:rest:': self._get_stored_property,
'SDSS_i:rest:': self._get_stored_property,
'SDSS_z:rest:': self._get_stored_property,
'SDSS_u:observed:': self._get_stored_property,
'SDSS_g:observed:': self._get_stored_property,
'SDSS_r:observed:': self._get_stored_property,
'SDSS_i:observed:': self._get_stored_property,
'SDSS_z:observed:': self._get_stored_property,
'DES_g:rest:': None,
'DES_r:rest:': None,
'DES_i:rest:': None,
'DES_z:rest:': None,
'DES_Y:rest:': None,
'DES_g:observed:': None,
'DES_r:observed:': None,
'DES_i:observed:': None,
'DES_z:observed:': None,
'DES_Y:observed:': None,
'LSST_u:rest:': None,
'LSST_g:rest:': None,
'LSST_r:rest:': None,
'LSST_i:rest:': None,
'LSST_z:rest:': None,
'LSST_y4:rest:': None,
'LSST_u:observed:': None,
'LSST_g:observed:': None,
'LSST_r:observed:': None,
'LSST_i:observed:': None,
'LSST_z:observed:': None,
'LSST_y4:observed:': None,
'B': None,
'U': None,
'V': None,
'CFHTL_g:rest:': None,
'CFHTL_r:rest:': None,
'CFHTL_i:rest:': None,
'CFHTL_z:rest:': None,
'CFHTL_g:observed:': None,
'CFHTL_r:observed:': None,
'CFHTL_i:observed:': None,
'CFHTL_z:observed:': None,
}
self.derived = {'stellar_mass': ('log_stellarmass', self._unlog10),
'mass': ('log_halomass', self._unlog10),
'disk_stellarmass': ('log_disk_stellarmass', self._unlog10),
'bulge_stellarmass': ('log_bulge_stellarmass', self._unlog10)}
self.catalog = {}
self.sky_area = 4.*np.pi*u.sr # all sky by default
self.cosmology = None
self.lightcone = False
self.box_size = None
self.outkeys = []
self.zvalues = []
self.load_init() #get catalog output keys only
def load_init(self):
"""
Given a catalog path, attempt to read the catalog output groups and cosmological parameters
"""
fn = os.path.join(self.kwargs['base_catalog_dir'],self.kwargs['filename'])
hdfFile = h5py.File(fn, 'r')
hdfKeys, self.hdf5groups = self._gethdf5group(hdfFile)
self.outkeys=sorted([key for key in hdfKeys if key.find(self.Output)!=-1],key=self.stringSplitByIntegers)
self.zvalues=self.getzvalues(self.outkeys)
allowed_parkeys = ['parameters','Parameters']
for key in allowed_parkeys:
if (key in hdfKeys):
mydict = self._gethdf5attributes(hdfFile, key)
self.cosmology = astropy.cosmology.LambdaCDM(H0 = mydict['H_0'],
Om0 = mydict['Omega_Matter'],
Ode0 = mydict['Omega_DE'])
self.box_size=mydict['boxSize'] #already in Mpc
self.sigma_8=mydict['sigma_8']
self.n_s=mydict['N_s']
self.redshift = [] #empty until values requested by test
self.catalog = {} #init empty catalog
#kluge for color test on snapshot
self.SDSS_kcorrection_z=.05
# TODO: how to get sky area?
hdfFile.close()
def load(self,hdfKeys=[]):
"""
Given a catalog path, attempt to read the catalog and set up its
internal data structures.
"""
if len(self.outkeys)==0:
self.load_init()
#check for requested keys and use all keys as default
if(len(hdfKeys)==0):
hdfKeys=self.outkeys
fn = os.path.join(self.kwargs['base_catalog_dir'],self.kwargs['filename'])
hdfFile = h5py.File(fn, 'r')
#print "load using keys: ", hdfKeys
#loop over requested keys and fetch those not already loaded
for key in hdfKeys:
if 'Output' in key and not(key in self.catalog.keys()): #check key, check if already loaded
outgroup = hdfFile[key]
dataKeys, dataAttrs = self._gethdf5group(outgroup)
self.catalog[key] = self._gethdf5arrays(outgroup)
hdfFile.close()
return
# Functions for applying filters
#check this function to see if really necessary
def _check_halo(self, halo, filters):
"""
Apply the requested filters to a given halo and return True if it
passes them all, False if not.
"""
status = True
if type(filters) is not dict:
raise TypeError("check_halo: filters must be given as dict")
for filter_name in filters.keys():
if filter_name == 'zlo':
try:
zmax = max(halo['redshift'])
status = status and (zmax >= filters[filter_name])
except KeyError:
status = False
elif filter_name == 'zhi':
try:
zmin = min(halo['redshift'])
status = status and (zmin <= filters[filter_name])
except KeyError:
status = False
return status
# Functions for returning quantities from the catalog
def _getfiltered_outkeys(self,filters):
outkeys=[]
zvalues=[]
for z,outkey in zip(self.zvalues, self.outkeys):
if z > filters.get('zlo',-0.01) and z < filters.get('zhi',9999.):
outkeys.append(outkey)
zvalues.append(z)
return outkeys, zvalues
def _get_stored_property(self, quantity, filters):
"""
Return the requested property of galaxies in the catalog as a NumPy
array. This is for properties that are explicitly stored in the
catalog.
"""
props = []
outkeys, zvalues = self._getfiltered_outkeys(filters)
#get any data that hasn't been loaded
self.load(hdfKeys=outkeys)
if (len(outkeys)>0):
self.redshift=zvalues
for outkey in outkeys:
outdict = self.catalog[outkey]
if self._check_halo(outdict, filters):
if quantity in outdict.keys():
props.extend(outdict[quantity])
else:
raise ValueError('No catalog outputs available for redshifts requested')
return np.asarray(props)
def _get_derived_property(self, quantity, filters):
"""
Return a derived halo property. These properties aren't stored
in the catalog but can be computed from properties that are via
a simple function call.
"""
#print "in get_derived_property: ", quantity, filters
props = []
#if 'position' in quantity:
# return np.asarray()
stored_qty_rec = self.derived[quantity]
stored_qty_name = stored_qty_rec[0]
stored_qty_fctn = stored_qty_rec[1]
#print 'stored_qty:', stored_qty_name, stored_qty_fctn
outkeys, zvalues = self._getfiltered_outkeys(filters)
#get any data that hasn't been loaded
self.load(hdfKeys=outkeys)
if (len(outkeys)>0):
self.redshift=zvalues
for outkey in outkeys:
outdict = self.catalog[outkey]
if self._check_halo(outdict, filters):
#if type(stored_qty_name) is tuple and stored_qty_name[0] in outdict.keys():
# values = outdict[stored_qty_name[0]]
# props.extend(stored_qty_fctn(values, stored_qty_name[1:]))
#else:
if stored_qty_name in outdict.keys():
props.extend(stored_qty_fctn( outdict[stored_qty_name] ))
else:
print ('No catalog outputs available for redshifts requested')
return np.asarray(props)
# Functions for computing derived values
def _unlog10(self, propList):
"""
Take a list of numbers and return 10.**(the numbers).
"""
result = []
for value in propList:
result.append(10.**value)
return result
# HDF5 utility routines
def _gethdf5keys(self,id,*args):
keys=id.keys()
keylist=[str(x) for x in keys]
return keylist
def _gethdf5attributes(self,id,key,*args):
#Return dictionary with group attributes and values
group=id[key]
mydict={}
for item in group.attrs.items():
attribute=str(item[0])
mydict[attribute]=item[1]
#endfor
return mydict
def _gethdf5group(self,group,*args):
#return dictionary of (sub)group dictionaries
groupkeys=self._gethdf5keys(group)
groupdict={}
for key in groupkeys:
mydict=self._gethdf5attributes(group,key)
groupdict[str(key)]=mydict
#endfor
return groupkeys,groupdict
def _gethdf5arrays(self,group,*args):
groupkeys=self._gethdf5keys(group)
arraydict={}
oldlen=-1
for key in groupkeys:
array=np.array(group[key])
arraylen=len(array)
if(oldlen>-1): #check that array length is unchanged
if(oldlen!=arraylen):
print "Warning: hdf5 array length changed for key",key
#endif
else:
oldlen=arraylen #set to ist array length
#endif
arraydict[str(key)]=array
#endfor
return arraydict
def _multiply(self, propList, factor_tuple):
"""
Multiplication routine -- derived quantity is equal to a stored
quantity times some factor. Additional args for the derived quantity
routines are passed in as a tuple, so extract the factor first.
"""
#print "in _multiply: ", propList, " ; factor_tuple: ", factor_tuple
factor = factor_tuple[0]
return propList * factor
def _add(self, propList):
"""
Routine that returns element-wise addition of two arrays.
"""
x = sum(propList)
return x
def stringSplitByIntegers(self,x):
r = re.compile('(\d+)')
l = r.split(x)
return [int(y) if y.isdigit() else y for y in l]
def getzvalues(self,outkeys,hdf5groups=None):
myname=self.getzvalues.__name__
zvalues=[]
if(type(outkeys)==str): #create list if necessary
outkeys=[outkeys]
#endif
if(hdf5groups is None):
hdf5groups=self.hdf5groups
#endif
for outkey in outkeys:
if(hdf5groups.has_key(outkey)):
if(outkey.find(self.Output)!=-1):
if (hdf5groups[outkey].has_key(self.z)):
outputz=hdf5groups[outkey][self.z]
elif(hdf5groups[outkey].has_key("z")):
outputz=hdf5groups[outkey]["z"]
else:
print("Missing attribute",self.z)
#elif (hdf5groups.has_key(self.Outputs)):
# outputz=hdf5groups[self.Outputs][outkey][self.outputRedshift]
else:
print("Unknown catalog key",outkey)
#endif
zvalues.append(outputz)
#endfor
return np.asarray(zvalues)