-
Notifications
You must be signed in to change notification settings - Fork 3
/
data.py
934 lines (749 loc) · 28.4 KB
/
data.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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
"""
Module that loads the data components
"""
# getting future division
from __future__ import division
# numpy
import numpy as np
from numpy import pi, sqrt, log, log10, power, exp
from sklearn.linear_model import LinearRegression as LR
from scipy.optimize import curve_fit
from astroquery.simbad import Simbad
from astropy.coordinates import SkyCoord
from datetime import datetime
import pandas as pd
import re
import os
import constants as ct
import astro as ap
from astro import lumin, physics_age, pheno_age
# location of the data path
data_path = os.path.dirname(os.path.abspath(__file__))+'/data/'
class SuperNova(object):
"""Class of SN. Each SN of the Bietenholz dataset is one instance of this class
"""
def __init__(self):
self.name = ''
self.is_limit = np.asarray([], dtype=bool) # whether it's a limit
self.year = np.asarray([]) # year of data point
self.month = np.asarray([]) # month of the data point
self.day = np.asarray([]) # day of the data point
# days since explosion, will be updated in gen_time_axis()
self.t = np.asarray([])
self.telescope = np.asarray([]) # the telescope id
# the freq of observation,4-10 GHz, except for 1987A (2.3 GHz)
self.freq = np.asarray([])
self.flux = np.asarray([]) # [mJy]
self.dflux = np.asarray([]) # [mJy]
self.comment = np.asarray([]) # extra comment
# extended info
self.type = None
self.galaxy = None # galaxy of residence
self.dist = None # dist [Mpc]
self.explosion_date = None # date of explosion [yyyy mm dd]
self.number_of_measurements = None
self.detected = None
self.has_explosion_time = False
def sanity_check():
# TODO: add some sanity checks
return
def gen_time_axis(self):
"""Generate time array, each point is the time since explosion [day]
"""
try:
t_explosion = datetime.strptime(self.explosion_date, "%Y %m %d")
t_since_explosion = []
for i in range(len(self.year)):
t_i = datetime.strptime(
"%d-%d-%d" % (self.year[i], self.month[i], self.day[i]), "%Y-%m-%d")
t_since_explosion.append((t_i - t_explosion).days)
self.t = np.asarray(t_since_explosion)
self.has_explosion_time = True
return np.asarray(t_since_explosion)
except TypeError:
pass
def load_Bietenholz(path=data_path+"Table1_complete_ascii.txt"):
"""Load Bietenholz table 1 (note that it's not all 294 SNe. only those 100+ new SNe)
:param path: path of the data file
"""
res = {}
num_of_SN = 0
ex = ""
with open(path, 'r') as f:
for i in range(30):
next(f)
for line in f:
words = line.split()
current_SN_name = words[0]
# determine if it's a new SN
if current_SN_name != ex:
if num_of_SN > 0:
res[ex] = SN # save previous SN
SN = SuperNova()
num_of_SN += 1
ex = words[0]
SN.name = words[0]
if ('L' in line[10]):
SN.is_limit = np.append(SN.is_limit, True)
else:
SN.is_limit = np.append(SN.is_limit, False)
SN.year = np.append(SN.year, int(line[12:16]))
SN.month = np.append(SN.month, int(line[17:19]))
SN.day = np.append(SN.day, float(line[20:25]))
SN.telescope = np.append(SN.telescope, line[26:33])
SN.freq = np.append(SN.freq, float(line[35:40]))
SN.flux = np.append(SN.flux, float(line[41:49]))
SN.dflux = np.append(SN.dflux, float(line[50:56]))
SN.comment = np.append(SN.comment, line[57:63])
res[words[0]] = SN
return res
def load_table2(path=data_path+"Table2_extended_SN_info.txt"):
"""load Bietenholz Table 2 data. Returns array rows
"""
res = []
with open(path, 'r') as f:
for line in f:
words = line.split('&')
# clean up white spaces
words_clean = np.asarray([x.strip() for x in words], dtype=object)
# clean up the white space in name
name_str = words_clean[0]
name_arr = name_str.split()
name_new = ''
for name_part in name_arr:
name_new = name_new + name_part
words_clean[0] = name_new
# clean up distance to number
dist = float(words_clean[3])
words_clean[3] = dist
# save
res.append(words_clean)
res = np.asarray(res, dtype=object)
# save into a dictionary
res_dct = {}
for i in range(len(res)):
res_dct[res[i, 0]] = res[i]
# return np.asarray(res, dtype=object)
return res_dct
def update_Bietenholz_with_table2(SNe_dct, table2_dct):
"""Update the result of Table 1 with meta-info from Table 2
:param SNe_dct: the result from load_Bietenholz() return
:param table2_dct: the dict from load_table2() return
"""
for key, SN in SNe_dct.items():
try:
entry = table2_dct[key]
SN.type = entry[1]
SN.galaxy = entry[2]
SN.distance = entry[3]
SN.explosion_date = entry[4]
except KeyError:
print('%s failed updating' % key)
return
def clean_white_spaces(string):
"""Gets rid of white spaces in the string
:param string: string to be processed
"""
try:
# in case it is in byte value
string = string.decode('utf-8')
except:
pass
res = ''
words = string.split()
for word in words:
res = res + str(word)
return res
def update_Bietenholz_with_coord(SNe_dct, use_Simbad=False):
"""query simbad to get SN coordinates. Requires network when use_Simbad is True
"""
names = list(SNe_dct.keys())
if use_Simbad:
# use online query
query = Simbad.query_objects(names)
query_res = query.as_array()
with open(data_path+'Table_SN_coord.txt', 'w') as f:
for i in range(len(query_res)):
entry = query_res[i]
name = entry[0]
name = clean_white_spaces(name)
RA = entry[1]
DEC = entry[2]
try:
SN = SNe_dct[name]
except:
print('%s failed the query' % (names[i]))
SN.RA = RA
SN.DEC = DEC
try:
SN.l, SN.b = simbad_to_galactic(RA, DEC)
except TypeError:
print(name)
print(RA)
print(DEC)
# TODO: save the query result
f.write('%s, %s, %s\n' % (name, RA, DEC))
else:
# use offline file
i = 0
try:
with open(data_path+'Table_SN_coord.txt', 'r') as f:
for line in f:
words = line.split(',')
name = words[0].strip()
RA = words[1].strip()
DEC = words[2].strip()
try:
SN = SNe_dct[name]
except:
print('%s failed the query' % (names[i]))
SN.RA = RA
SN.DEC = DEC
SN.l, SN.b = simbad_to_galactic(RA, DEC)
i += 1
except:
raise Exception(
'Error in using local cache. Try to run with use_Simbad=True to regenerate the cache in {}Table_SN_coord.txt'.format(data_path))
return
def simbad_to_galactic(RA, DEC):
"""For simbad by default returns RA "h:m:s" and DEC "d:m:s" in ICRS(ep=J2000,eq=2000), this is a utility function that converts (RA, DEC) to (l, b) in galactic coordinate system.
"""
RA_arr = RA.split()
DEC_arr = DEC.split()
if len(DEC_arr) == 3:
coord = SkyCoord('%sh%sm%ss' % tuple(RA_arr),
'%sd%sm%ss' % tuple(DEC_arr))
else:
# some of the records (SN1982F and SN1980O) only have d-m not d-m-s
coord = SkyCoord('%sh%sm%ss' % tuple(RA_arr),
'%sd%sm' % tuple(DEC_arr))
coord_gal = coord.galactic
return (coord_gal.l, coord_gal.b)
# def test():
# url = 'http://simbad.u-strasbg.fr/simbad/sim-basic?Ident=SN1997eg&submit=SIMBAD+search'
# #myobj = {'somekey': 'somevalue'}
# x = requests.get(url) # , data=myobj)
# print(x.text)
# return x
def test2():
res = Simbad.query_objects(
["SN1995ad", "SN1996L", "SN2012a", "SN1987A", "SN1982F", "SN1980O"])
return res
def gen_SN_with_table2(table2_dct):
SNe_dct = {}
for key, entry in table2_dct.items():
try:
SN = SuperNova()
SN.type = entry[1]
SN.galaxy = entry[2]
SN.distance = entry[3]
SN.explosion_date = entry[4]
SNe_dct[key] = SN
except KeyError:
print('%s failed updating' % key)
return SNe_dct
#
# SNR
#
class SuperNovaRemnant(object):
""" The SNR class
"""
def __init__(self):
self.no_dist = True # whether the record has a distance meas
self.no_flux = True # whether the record has a flux meas.
self.is_complete = False # the completeness condition from Green 2005
self.is_spectral_certain = False
self.is_flux_certain = False
self.is_type_certain = False
def __repr__(self):
return "<SNR intance '%s'>" % self.name
def __str__(self):
return "<SNR intance '%s'>" % self.name
def set_coord(self, l, sign, b):
"""set the snr coordinate
:param l: longitude in galactic frame
:param b: height in galactic frame
"""
if l is not None:
self.l = float(l)
if b is not None:
self.b = float(b)
if sign == '-':
self.b *= -1.
def set_flux_density(self, Snu, is_flux_certain='y'):
"""Set the flux density according
:param Snu: flux density at 1 GHz [Jy]
:param is_flux_certain: flag to show whether it has large uncertainty in distance (default: 'y')
"""
self.snu_at_1GHz = float(Snu)
if is_flux_certain == "?":
self.is_flux_certain = False
else:
self.is_flux_certain = True
self.no_flux = False
return
def set_sr(self, sr):
"""Set the size of the SNR with square-radian. Allows to be None so that it will be computed based on the two-phase model.
:param sr: square radian
"""
self.sr = sr
def set_size(self, length, width=None):
"""Set the size of the SNR. Some data points are in a x b format, i.e. rectangle, while some have only one number. In the former case, we take the geometric mean as the angular size [arcmin]. The sr value is also updated
:param length: the length in [arcmin]
:param width: the width [arcmin]
"""
length = float(length)
try:
width = float(width)
except:
pass
if width is not None:
self.ang_size = np.sqrt(length * width)
else:
self.ang_size = length
ang_size_in_rad = self.ang_size / 60 * np.pi / 180
self.sr = ct.angle_to_solid_angle(ang_size_in_rad)
def set_distance(self, dist_arr):
"""Some data points have multiple distance measurements. We take average in these cases
:param dist_arr: distance measurement array from different tracers [kpc]
"""
dist_arr = np.asarray(dist_arr).astype(float)
if dist_arr.ndim == 0:
dist_arr = dist_arr[None]
dist_arr_clean = []
for x in dist_arr:
# some sanity check to filter out rare junk that arises from the regex parsing
if x < 50:
dist_arr_clean.append(x)
res = np.mean(dist_arr)
self.distance = res
self.no_dist = False
def set_name(self, name):
"""name the SNR
:param name: string of name
"""
self.name = name
def set_type(self, snr_type, is_certain):
"""set SNR type, S: shell, F: fill, C: combination
:param snr_type: type of SNR
:param is_certain: flag to show if it's certain. "?" set to False.
"""
self.type = snr_type
if is_certain is not None:
if is_certain != "?":
# print(is_certain)
self.is_type_certain = True
def set_spectral(self, alpha, is_certain):
"""set the spectral index alpha, as defined in S_nu\propto \nu^{-\alpha}
:param alpha: spectral index
:param is_certain: flag to show if it's certain
"""
self.alpha = float(alpha)
if is_certain == "?":
self.is_spectral_certain = False
else:
self.is_spectral_certain = True
def set_age(self, age):
"""set up the age of the SNR
"""
self.age = float(age)
def get_age(self):
try:
return self.age
except Exception:
return None
def get_luminosity(self):
"""get the luminosity of the SNR in [erg/s/Hz]
"""
if self.no_dist is False and self.no_flux is False:
dist = self.distance
snu = self.snu_at_1GHz
lum = lumin(dist, snu)
self.lum = lum
else:
self.lum = -1 # use -1 to indicate unknown luminosity
return self.lum
def get_diameter(self):
"""compute the diameter of the SNR [pc]
"""
if self.no_dist is False:
dist = self.distance
diam = dist * self.ang_size / 60. * np.pi/180. * ct._kpc_over_pc_
self.diam = diam
else:
self.diam = -1 # use -1 to indicate unknown diameter
return self.diam
def get_radius(self):
"""compute the radius of the SNR [pc]
"""
if self.no_dist is False:
dist = self.distance
radius = (dist * self.ang_size / 60. *
np.pi/180. * ct._kpc_over_pc_)/2.
self.radius = radius
else:
self.radius = -1 # use -1 to indicate unknown diameter
return self.radius
def get_SB(self):
"""get the surface brightness [Jy/sr]
"""
sr = self.sr
Sigma = self.snu_at_1GHz / sr
self.Sigma = Sigma
# set the flag of SB completeness
Sigma_in_SI = Sigma * ct._Jy_over_SI_
if Sigma_in_SI > 1.e-20:
self.is_complete = True
return Sigma
def get_spectral_index(self):
"""get the spectral index alpha
"""
try:
return self.alpha
except AttributeError:
return None
def get_gamma(self):
try:
return ap.gamma_from_alpha(self.alpha)
except AttributeError:
return None
def get_type(self):
try:
return self.type
except AttributeError:
return None
def get_distance(self):
if self.no_dist is False:
return self.distance
else:
raise Exception('no distance for %s SNR' % self.name)
def get_coord(self):
return (self.l, self.b)
def get_longitude(self):
return self.l
def get_latitude(self):
return self.b
def get_size(self):
try:
return self.sr
except AttributeError:
return None
def get_flux_density(self):
"""get the flux density [Jy]
"""
if self.no_flux is False:
return self.snu_at_1GHz
else:
return -1
def load_Green_catalogue_names(path=data_path+'snr_website/www.mrao.cam.ac.uk/surveys/snrs/snrs.list.html'):
# first load the file list
file_snrs_list = []
with open(path, 'r') as f:
for i in range(49):
next(f)
for line in f:
file_snrs_list.append(line)
# now load the names
snr_name_arr = []
for entry in file_snrs_list:
m = re.search('.*snrs\.(.*\..*)\.html.*', entry)
if m is not None:
# print(m.group(1))
name = m.group(1)
snr_name_arr.append(name)
return snr_name_arr
def load_Green_catalogue(snr_name_arr, pathroot=data_path+'snr_website/www.mrao.cam.ac.uk/surveys/snrs/', verbose=0):
"""Load the Green 2019 catalog
:param snr_name_arr: array of SNR names
:param pathroot: directory where the SNR catalog is saved
"""
snrs_dct = {}
# pathroot = '/home/chen/Downloads/snr_website/www.mrao.cam.ac.uk/surveys/snrs/'
for snr_name in snr_name_arr:
path = os.path.join(pathroot, 'snrs.'+snr_name+'.html')
try:
snr_obj = snrs_dct[snr_name]
except KeyError:
flg_new = True
snr_obj = SuperNovaRemnant()
snr_obj.set_name(snr_name)
# parse coordinate
m = re.search(
'G(\d\d\d\.\d|\d\d\.\d|\d\.\d)([+-])(\d\d\.\d|\d\.\d)', snr_name)
l = m.group(1)
sign = m.group(2)
b = m.group(3)
snr_obj.set_coord(l, sign, b)
with open(path, 'r') as f:
for line in f:
# parse coordinate
# m = re.search('.*Right Ascension:(.*\d).*Declination:.*(&minus|\+).*(\d\d \d\d)\n', line)
# if m is not None:
# ra = m.group(1)
# if m.group(2) == '&minus':
# dec = '-' + m.group(3)
# else:
# dec = '+' + m.group(3)
# snr_obj.set_coord(ra, dec)
# parse flux density
m = re.search(
'.*Flux density at 1 GHz \(/Jy\): (\d+\.\d+|\d+)([?]*).*', line)
if m is not None:
snu_at_1GHz = m.group(1)
is_flux_certain = m.group(2)
snr_obj.set_flux_density(snu_at_1GHz, is_flux_certain)
# parse size aa x bb format for rectangle
is_square = True
m = re.search(
'Size \(/arcmin\): (\d+\.\d+|\d+)×(\d+\.\d+|\d+)', line)
if m is not None:
is_square = False
length = m.group(1)
width = m.group(2)
snr_obj.set_size(length, width)
# parse size for square data points
if is_square:
m = re.search('Size \(/arcmin\): (\d+\.\d+|\d+)', line)
if m is not None:
length = m.group(1)
snr_obj.set_size(length)
# parse distance
#m = re.search('Distance.*?(\d+.\d+|\d+) kpc.*?(\d+.\d+|\d+) kpc.*?(\d+.\d+|\d+) kpc', line)
#m = re.search('Distance.*?(\d+.\d+|\d+) kpc.*?(\d+.\d+|\d+) kpc', line)
# three distances
m = re.search(
'^Distance.*?(\d+\.\d+).*?(\d+\.\d+).*?(\d+\.\d+)(.*?)$', line)
if m is not None:
dist1 = m.group(1)
dist2 = m.group(2)
dist3 = m.group(3)
snr_obj.set_distance([dist1, dist2, dist3])
if verbose > 4:
print(m.group(4))
# two distances
if snr_obj.no_dist is True:
m = re.search(
'Distance.*?(\d+\.\d+).*?(\d+\.\d+)(.*?)$', line)
if m is not None:
dist1 = m.group(1)
dist2 = m.group(2)
snr_obj.set_distance([dist1, dist2])
if verbose > 4:
print(m.group(3))
# one distance
if snr_obj.no_dist is True:
m = re.search('Distance.*?(\d+\.\d+)(.*?)$', line)
if m is not None:
dist1 = m.group(1)
snr_obj.set_distance([dist1])
if verbose > 4:
print(m.group(2))
# one distance, with \pm sign
if snr_obj.no_dist is True:
m = re.search('Distance.*?(\d+)±\d+(.*?)$', line)
if m is not None:
dist1 = m.group(1)
snr_obj.set_distance([dist1])
if verbose > 4:
print(m.group(2))
# parse type - first, all that have flux set to certain ones:
m = re.search('Type: (S|C|F)', line)
if m is not None:
snr_type = m.group(1)
snr_obj.set_type(snr_type, is_certain=True)
# parse type - next, find the uncertain ones
m = re.search('Type: (S|C|F)(\?)', line)
if m is not None:
snr_type = m.group(1)
is_certain = m.group(2)
snr_obj.set_type(snr_type, is_certain)
# parse snr spectral index
# x.yy type
m = re.search('Spectral Index: (\d.\d\d)([?]*).*', line)
if m is not None:
alpha = m.group(1)
is_certain = m.group(2)
snr_obj.set_spectral(alpha, is_certain)
# snr_obj.set_is_spectral_certain(is_certain)
else:
# x.y type
mm = re.search('Spectral Index: (\d.\d)([?]*).*', line)
if mm is not None:
alpha = mm.group(1)
is_certain = mm.group(2)
snr_obj.set_spectral(alpha, is_certain)
# parse the remnant with known age
m = re.search('AD</SPAN>(\d\d\d\d).*$', line)
if m is not None:
if verbose > 0:
print('%s is suggested to be related to SN explosion at AD:%s' % (
snr_name, m.group(1)))
snr_obj.set_age(2021 - float(m.group(1)))
else:
m = re.search('AD</SPAN>(\d\d\d).*$', line)
if m is not None:
if verbose > 0:
print('%s is suggested to be related to SN explosion at AD:%s' % (
snr_name, m.group(1)))
snr_obj.set_age(2021 - float(m.group(1)))
# the following doesn't yield anything so skip it for now.
# else:
# m = re.search('AD</SPAN>(\d\d).*$', line)
# if m is not None:
# print('%s exploded at AD:%s' %
# (snr_name, m.group(1)))
# else:
# m = re.search('AD</SPAN>(\d).*$', line)
# if m is not None:
# print('%s exploded at AD:%s' %
# (snr_name, m.group(1)))
else:
m = re.search('.*remnant of(.*)', line)
if m is not None:
if verbose > 0:
print('%s could be related to %s' %
(snr_name, m.group(1)))
# the special one, Cas A, not in standard format
if snr_obj.name == 'G111.7-2.1':
snr_obj.set_age(2021 - 1700)
if flg_new:
snrs_dct[snr_name] = snr_obj
age = snr_obj.get_age()
if age is not None:
if verbose > 0:
print('it is about %.0f years old.' % age)
return snrs_dct
def name_expand(name):
"""
A simple function that expands the names of the Green's Catalog by adding 0's, in order to put it in the form 'GLLL.L[+-]BB.B'.
"""
m = re.search('G(\d\d\d\.\d|\d\d\.\d|\d\.\d)([+-])(\d\d\.\d|\d\.\d)',
name)
lon = m.group(1)
sig = m.group(2)
lat = m.group(3)
new_lon = (5-len(lon))*'0'+lon
new_lat = (4-len(lat))*'0'+lat
new_name = 'G'+new_lon+sig+new_lat
return new_name
# -------------------------------------------------
##########
# SNR Ages
##########
# http://snrcat.physics.umanitoba.ca/SNRtable.php
# Table downloaded from: http://snrcat.physics.umanitoba.ca/SNRdownload.php?
# The SNR catalog found there:
snr_cat = pd.read_csv(data_path+"SNRcat/SNRcat20211019-SNR.csv", sep=";", skiprows=2, index_col='G')
# Keeping only those entries with known ages:
snr_ages = snr_cat[['age_min (yr)', 'age_max (yr)']].dropna()
# Adding a new age value that is the geometric mean (conservative value; plus less order-of-magnitude dependendent):
snr_ages = snr_ages.assign(geom_mean=sqrt(snr_ages['age_min (yr)']*snr_ages['age_max (yr)']))
# Adding the mean, for bookkeeping purposes
snr_ages = snr_ages.assign(arith_mean=(snr_ages['age_min (yr)']+snr_ages['age_max (yr)'])/2.)
# -------------------------------------------------
#######################
# SNR GREEN'S CATALOG #
#######################
# Loading Green's catalog:
# First let's parse snrs.list.html
# Names:
snr_name_arr = load_Green_catalogue_names()
# Catalog:
snrs_dct = load_Green_catalogue(snr_name_arr)
# Curating Green's catalog
snrs_cut = {}
snrs_age = {}
snrs_age_only = {}
for name, snr in snrs_dct.items():
try:
snr.age
snrs_age_only[name] = snr
except:
new_name = name_expand(name)
try:
# finding the age in the SNRcat catalog:
found_age = snr_ages.loc[new_name]['geom_mean']
# found_age = snr_ages.loc[new_name]['age_min (yr)']
snr.set_age(found_age)
snrs_age_only[name] = snr
except:
pass
try:
snr.distance
except:
continue
try:
snr.alpha
except:
continue
# ignoring SNRs without known flux
if snr.get_flux_density() == -1:
continue
# ignoring SNRs with uncertain flux
# if not snr.is_flux_certain:
# print("uncertain flux: "+str(name))
# continue
# The SNR passed all the critical cuts: add it to the basic dictionary
snrs_cut[name] = snr
# check if age is known...
try:
snr.age
except:
new_name = name_expand(name)
try:
# finding the age in the SNRcat catalog:
found_age = snr_ages.loc[new_name]['geom_mean']
# found_age = snr_ages.loc[new_name]['age_min (yr)']
snr.set_age(found_age)
except:
continue
# Add the SNR to the dictionary of those with known age
snrs_age[name] = snr
# ----------------------------------------------
# AGE COMPUTATION
# -----------------
TR_arr = []
for name, snr in snrs_age_only.items():
if snr.no_dist:
continue
try:
R = snr.get_radius()
TR_arr.append([snr.age, R])
except:
continue
TR_arr = np.array(TR_arr)
# sorting array according to age:
sorted_age_idx = TR_arr[:,0].argsort()
TR_arr = TR_arr[sorted_age_idx]
# training sample, of shape (n_samples, n_features)
X = TR_arr[:, 1].reshape((-1, 1))
y = TR_arr[:, 0] # target values, of shape (, n_samples)
# fitting linear regression:
reg_lin = LR().fit(X, y)
reg_log = LR().fit(log10(X), log10(y))
del X, y
def lin_reg_pred(R, method='lin'):
"""
Linear regression for the age [years] as a function of the radius [pc], fitted on the SNR with known age.
"""
R_arr = np.array(R).reshape(-1, 1)
if method == 'lin':
return np.squeeze(reg_lin.predict(R_arr))
elif method == 'log':
return np.squeeze(10.**reg_log.predict(log10(R_arr)))
else:
raise ValueError(
"method={} cannot be used. Use 'lin' or 'log'".format(method))
def age_from_radius(R, method=None, **kwargs):
"""
Computes the age [years] of a SNR based on its radius [pc].
"""
if method in ['lin', 'log']:
age = lin_reg_pred(R, method=method)
elif method in ['estimate', 'TM99-0', 'TM99-simple']:
age = physics_age(R, model=method, **kwargs)
elif method in ['pheno', 'phenomenological']:
age = pheno_age(R, **kwargs)
else:
raise ValueError("method={} not currently supported.".format(method))
return age