forked from mfouesneau/ezmist
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ezmist.py
381 lines (294 loc) · 8.7 KB
/
ezmist.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
"""
EZMIST -- A python package that allows you to download MESA isochrones directly from the MIST
directly website
based on EZPADOVA
:version: 1.0
:author: MF
"""
from __future__ import print_function, unicode_literals, division
import sys
import os
import inspect
import time
if sys.version_info[0] > 2:
py3k = True
from urllib.parse import urlencode
from urllib import request
from urllib.request import urlopen
else:
py3k = False
from urllib import urlencode
from urllib2 import urlopen
from io import StringIO, BytesIO
import zlib
import re
import json
from .simpletable import SimpleTable as Table
localpath = '/'.join(os.path.abspath(inspect.getfile(inspect.currentframe())).split('/')[:-1])
with open(localpath + '/mist.json') as f:
_cfg = json.load(f)
# Help messages
# -------------
def file_type(filename, stream=False):
""" Detect potential compressed file
Returns the gz, bz2 or zip if a compression is detected, else None.
"""
magic_dict = {"\x1f\x8b\x08": "gz",
"\x42\x5a\x68": "bz2",
"\x50\x4b\x03\x04": "zip",
b"\x50\x4b\x03\x04": "zip",
"PK\x03\x04": "zip",
b"PK\x03\x04": "zip",
}
max_len = max(len(x) for x in magic_dict)
if not stream:
with open(filename) as f:
file_start = f.read(max_len)
for magic, filetype in magic_dict.items():
if file_start.startswith(magic):
return filetype
else:
for magic, filetype in magic_dict.items():
if filename[:len(magic)] == magic:
return filetype
return None
# Build up URL request
# --------------------
def _get_url_args(**opts):
""" Generates the query arguments given the selected options
Parameters
----------
opts: dict
any field value
Returns
-------
q: str
string of arguments (joined by `&` char)
"""
_opts = _cfg['defaults']
_opts.update(**opts)
# check None = ""
q = []
keys = _cfg["query_options"]
for k in keys:
val = _opts.get(k, "")
if val is None:
val = ""
q.append("{key}={val}".format(key=k, val=val))
return '&'.join(q)
def _extract_zip(zip_bytes):
""" Extract the content of a zip file
Parameters
----------
zip_bytes: bytes
string that contains the binary code
Returns
-------
content:str
ascii string contained in the zip code.
"""
import io
import zipfile
fp = zipfile.ZipFile(io.BytesIO(zip_bytes))
data = {name: fp.read(name) for name in fp.namelist()}
if len(data) > 1:
return data
else:
return data[list(data.keys())[0]]
def _query_website(q):
""" Run the query on the website
Parameters
----------
q: str
string of arguments (joined by `&` char)
Returns
-------
r: str or bytes
unzipped content of the query
"""
url = _cfg["request_url"]
print('Interrogating {0}...'.format(url))
print('Request...', end='')
if py3k:
req = request.Request(url, q.encode('utf8'))
print('done.')
print("Reading content...", end='')
c = urlopen(req).read().decode('utf8')
else:
c = urlopen(url, q).read()
print('done.')
try:
fname = re.compile('<a href=".*">').findall(c)[0][9:-2]
except Exception as e:
print(e)
raise RuntimeError("Something went wrong")
furl = _cfg['download_url'] + fname
print('Downloading data...{0}...'.format(furl), end='')
if py3k:
req = request.Request(furl)
bf = urlopen(req)
else:
bf = urlopen(furl)
r = bf.read()
print("done.")
typ = file_type(r, stream=True)
# force format
if (typ is None) & ('zip' in fname):
typ = 'zip'
if typ is not None:
print(r[:100], type(r), bytes(r[:10]))
print("decompressing archive (type={0})...".format(typ), end='')
if 'zip' in typ:
r = _extract_zip(bytes(r))
else:
r = zlib.decompress(bytes(r), 15 + 32)
print("done.")
return r
def _read_mist_iso_filecontent(data):
"""
Reads in the isochrone file.
Parameters
----------
data: str or bytes
content from the unzipped website query
Returns
-------
t: Table
table of the isochrones
"""
import numpy as np
try:
f = data.decode('utf8').split('\n')
except:
f = data.split('\n')
content = [line.split() for line in f]
hdr = {'MIST': content[0][-1], 'MESA': content[1][-1]}
abun = {content[3][i]:float(content[4][i]) for i in range(1,5)}
hdr.update(**abun)
hdr['ROT'] = float(content[4][-1])
num_ages = int(content[6][-1])
hdr['num_ages'] = num_ages
#read one block for each isochrone
iso_set = []
counter = 0
data = content[8:]
# isochrone format
for i_age in range(num_ages):
#grab info for each isochrone
_d = data[counter]
num_eeps = int(_d[-2])
num_cols = int(_d[-1])
hdr_list = data[counter + 2][1:]
formats = tuple([np.int32] + [np.float64 for i in range(num_cols - 1)])
iso = np.zeros((num_eeps), {'names':tuple(hdr_list),'formats':tuple(formats)})
#read through EEPs for each isochrone
for eep in range(num_eeps):
iso_chunk = data[3+counter+eep]
iso[eep] = tuple(iso_chunk)
iso_set.append(iso)
counter += 3 + num_eeps + 2
_data = np.lib.recfunctions.stack_arrays(iso_set, usemask=False)
t = Table(_data, header=hdr)
# make some aliases
aliases = (('logL', 'log_L'),
('logT', 'log_Teff'),
('mass', 'star_mass'),
('logg', 'log_g'))
if 'log10_isochrone_age_yr' in t:
aliases += (('logA', 'log10_isochrone_age_yr'),)
else:
aliases += (('age', 'isochrone_age_yr'),)
for a, b in aliases:
t.set_alias(a, b)
t.header['NAME'] = 'MIST/MESA isochrones'
return t
# Convenient Functions
# --------------------
def simple_options(**kwargs):
opts = _cfg['defaults']
opts.update(kwargs)
return opts
def get_standard_isochrone(ret_table=True, **kwargs):
""" get the default isochrone set at a given time and [Fe/H]
MIST standard age grid (107 ages for 5 < logAge < 10.3 in 0.05 dex steps)
Parameters
----------
ret_table: bool
if set, return a eztable.Table object of the data
**kwargs: other options
Returns
-------
r: Table or str
if ret_table is set, return a eztable.Table object of the data
else return the string content of the data
"""
opts = simple_options(
age_type='standard',
**kwargs)
d = _get_url_args(**opts)
r = _query_website(d)
if ret_table is True:
return _read_mist_iso_filecontent(r)
else:
return r
def get_one_isochrone(age, FeH, age_scale='linear', ret_table=True, **kwargs):
""" get one isochrone at a given time and [Fe/H]
Parameters
----------
age: float
age of the isochrone (in yr)
metal: float
metalicity of the isochrone
age_scale: str
linear or log10 for units of age
ret_table: bool
if set, return a eztable.Table object of the data
**kwargs: other options
Returns
-------
r: Table or str
if ret_table is set, return a eztable.Table object of the data
else return the string content of the data
"""
opts = simple_options(
age_type='single',
age_value=age,
age_scale=age_scale,
**kwargs)
d = _get_url_args(**opts)
r = _query_website(d)
if ret_table is True:
return _read_mist_iso_filecontent(r)
else:
return r
def get_t_isochrones(logt0, logt1, dlogt, age_scale='log10', ret_table=True, **kwargs):
""" get a sequence of isochrones at constant Z
Parameters
----------
logt0: float
minimal value of log(t/yr)
logt1: float
maximal value of log(t/yr)
dlogt: float
step in log(t/yr) for the sequence
ret_table: bool
if set, return a eztable.Table object of the data
Returns
-------
r: Table or str
if ret_table is set, return a eztable.Table object of the data
else return the string content of the data
"""
opts = simple_options(
age_type='range',
age_range_low=logt0,
age_range_high=logt1,
age_range_delta=dlogt,
age_scale=age_scale,
**kwargs)
d = _get_url_args(**opts)
r = _query_website(d)
if ret_table is True:
return _read_mist_iso_filecontent(r)
else:
return r