File: allskymap_cr_example.py

package info (click to toggle)
basemap 1.2.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 212,272 kB
  • sloc: python: 9,541; ansic: 266; makefile: 39; sh: 23
file content (230 lines) | stat: -rw-r--r-- 8,247 bytes parent folder | download | duplicates (3)
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
from __future__ import (absolute_import, division, print_function)

"""
Example of astronomical use of AllSkyMap class in allskymap.py module

Plot an all-sky map showing locations of the 27 highest-energy ultra-high
energy cosmic rays detected by the Auger (south) experiment as of Aug 2007,
and locations of 18 (fictitious!) candidate sources.  Indicate CR direction
uncertainties and source scattering scales with tissots, and show the
nearest candidate source to each CR with geodesics.

Created 2011-02-07 by Tom Loredo
"""

try:
    from cStringIO import StringIO
except:
    from io import StringIO
import numpy as np
from numpy import cos, sin, arccos, deg2rad, rad2deg
import csv, re, sys

import matplotlib.pyplot as plt
from allskymap import AllSkyMap
from matplotlib.colors import Normalize
from matplotlib.colorbar import ColorbarBase
import matplotlib.ticker as ticker


class Source:
    """
    Parse and store data for a celestial source.
    """
    
    int_re = re.compile(r'^[-+]?[0-9]+$')
    # float_re = re.compile(r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$')

    def __init__(self, id, year, day, l, b, sig=None, **kwds):
        self.id = int(id)
        self.year = int(year)
        self.day = int(day)
        self.l = float(l)
        self._l = deg2rad(self.l)  # radians
        self.b = float(b)
        self._b = deg2rad(self.b)  # radians
        if sig is not None:
            self.sig = float(sig)
            self._sig = deg2rad(self.sig)
        else:
            self.sig, self._sig = None, None
        # If extra values are specified as keywords, set them as
        # attributes.  The quick way is to use self.__dict__.update(kwds),
        # but we want to convert to int or float values if possible.
        # *** Consider using matplotlib.cbook.converter.
        if kwds:
            for key, val in kwds.items():
                try:
                    nval = float(val)
                    # It's a number; but it may be an int.
                    if self.int_re.match(str(val)):
                        nval = int(val)
                    setattr(self, key, nval)
                except ValueError:  # non-numerical value
                    setattr(self, key, val)

    def gcangle(self, src):
        """
        Calculate the great circle angle to another source.
        """
        # Use the law of cosines; note it is usually expressed with colattitude.
        c = sin(self._b)*sin(src._b) + \
            cos(self._b)*cos(src._b)*cos(self._l - src._l)
        return rad2deg(arccos(c))


# Auger UHE cosmic ray data, Jan 2004 to Aug 2007
# From Appendix A of Abraham et al. (2008); "Correlation of the highest-energy
# cosmic rays with the positions of nearby active galactic nuclei,"
# Astropart.Phys.29:188-204,2008; Erratum-ibid.30:45,2008

# Year	day 	 ang	S(1000)	E (EeV)	RA	Dec	Longitude	Latitude
# * = w/i 3.2 deg of AGN
AugerData = StringIO(
"""2004	125	47.7	252	70	267.1	-11.4	15.4	8.4
2004	142	59.2	212	84	199.7	-34.9	-50.8	27.6	*
2004	282	26.5	328	66	208.0	-60.3	-49.6	1.7	*
2004	339	44.7	316	83	268.5	-61.0	-27.7	-17.0	*
2004	343	23.4	323	63	224.5	-44.2	-34.4	13.0	*
2005	54	35.0	373	84	17.4	-37.9	-75.6	-78.6	*
2005	63	54.5	214	71	331.2	-1.2	58.8	-42.4	*
2005	81	17.2	308	58	199.1	-48.6	-52.8	14.1	*
2005	295	15.4	311	57	332.9	-38.2	4.2	-54.9	*
2005	306	40.1	248	59	315.3	-0.3	48.8	-28.7	*
2005	306	14.2	445	84	114.6	-43.1	-103.7	-10.3
2006	35	30.8	398	85	53.6	-7.8	-165.9	-46.9	*
2006	55	37.9	255	59	267.7	-60.7	-27.6	-16.5	*
2006	81	34.0	357	79	201.1	-55.3	-52.3	7.3
2006	185	59.1	211	83	350.0	9.6	88.8	-47.1	*
2006	296	54.0	208	69	52.8	-4.5	-170.6	-45.7	*
2006	299	26.0	344	69	200.9	-45.3	-51.2	17.2	*
2007	13	14.3	762	148	192.7	-21.0	-57.2	41.8
2007	51	39.2	247	58	331.7	2.9	63.5	-40.2	*
2007	69	30.4	332	70	200.2	-43.4	-51.4	19.2	**
2007	84	17.3	340	64	143.2	-18.3	-109.4	23.8	*
2007	145	23.9	392	78	47.7	-12.8	-163.8	-54.4	*
2007	186	44.8	248	64	219.3	-53.8	-41.7	5.9
2007	193	18.0	469	90	325.5	-33.5	12.1	-49.0	*
2007	221	35.3	318	71	212.7	-3.3	-21.8	54.1	*
2007	234	33.2	365	80	185.4	-27.9	-65.1	34.5
2007	235	42.6	276	69	105.9	-22.9	-125.2	-7.7
""")
AugerTable = csv.reader(AugerData, dialect='excel-tab')
CRs = {}
for id, row in enumerate(AugerTable):
    # Make an integer ID from Year+Day (presumes none on same day!).
    src = Source(id, row[0], row[1], row[7], row[8], E=float(row[4]))
    CRs[src.id] = src
sys.stdout.write('Parsed data for %s UHE CRs...\n'%len(CRs))

# Partly fictitious candidate source locations.
# src.id src.l_deg	src.b_deg	src.xProj	src.yProj
# tab-delimited
CandData = StringIO(
"""1	270.	-28.
2	229.	-80.
3	141.	-47.
4	172.	-51.
5	251.	-51.
6	241.	-36.
7	281.	26.
8	241.	64.
9	240.	64.
10	148.	70.
11	305.	13.
12	98.	79.
13	309.	19.
14	104.	68.
15	104.	68.
16	321.	15.
17	328.	-14.
18	177.5	-35.
""")
# Add this line above to see a tissot overlapping the map limb.
CandTable = csv.reader(CandData, dialect='excel-tab')
cands = {}
for row in CandTable:
    src = Source(row[0], 0, 0, row[1], row[2])
    cands[src.id] = src
sys.stdout.write('Parsed data for %s candidate sources...\n' % len(cands))

# Calculate the separation matrix; track the closest candidate to each CR.
sepn = {}
for cr in CRs.values():
    id, sep = None, 181.
    for cand in cands.values():
        val = cr.gcangle(cand)
        sepn[cr.id, cand.id] = val
        if val < sep:
            id, sep = cand.id, val
    # Store the closest candidate id and separation as a CR attribute.
    cr.near_id = id
    cr.near_ang = sep


# Note that Hammer & Mollweide projections enforce a 2:1 aspect ratio.
# Choose figure size for a 2:1 plot, with room at bottom for colorbar.
fig = plt.figure(figsize=(12,7))
main_ax = plt.axes([0.05, .19, .9, .75])  # rect=L,B,W,H

# Set up the projection and draw a grid.
m = AllSkyMap(ax=main_ax, projection='hammer')
m.drawmapboundary(fill_color='white')
m.drawparallels(np.arange(-75,76,15), linewidth=0.5, dashes=[1,2],
    labels=[1,0,0,0], fontsize=9)
m.drawmeridians(np.arange(-150,151,30), linewidth=0.5, dashes=[1,2])

# Label a subset of meridians.
lons = np.arange(-150,151,30)
m.label_meridians(lons, fontsize=9, vnudge=1,
                halign='left', hnudge=-1)  # nudge<0 shifts to right

# Plot CR directions.
lvals = [src.l for src in CRs.values()]
bvals = [src.b for src in CRs.values()]
x, y = m(lvals, bvals)
# These symbols will be covered by opaque tissots; plot them anyway
# so there is a collection for the legend.
cr_pts = m.scatter(x, y, s=8, c='r', marker='o', linewidths=.5,
    edgecolors='none')

# Plot tissots showing uncertainties, colored by energy.
# We use 1 deg uncertainties, which are probably ~2 sigma for most events.
Evals = np.array([src.E for src in CRs.values()])
norm_E = Normalize(Evals.min()-10, Evals.max()+20)  # -+ for jet_r for brt clrs
# jet_r color map is in spectral sequence, with a small unsaturated
# green range, helping to distinguish CRs from candidates.
cmap = plt.cm.get_cmap('jet_r')
for cr in CRs.values():
    color = cmap(norm_E(cr.E))[0:3]  # ignore alpha
    m.tissot(cr.l, cr.b, 2., 30, ec='none', color=color, alpha=1)

# Plot candidate directions.
lvals = [src.l for src in cands.values()]
bvals = [src.b for src in cands.values()]
x, y = m(lvals, bvals)
cand_pts = m.scatter(x, y, marker='+', linewidths=.5, 
    edgecolors='k', facecolors='none', zorder=10)  # hi zorder -> top

# Plot tissots showing possible scale of candidate scatter.
for l, b in zip(lvals, bvals):
    m.tissot(l, b, 5., 30, ec='none', color='g', alpha=0.25)

# Show the closest candidate to each CR.
for cr in CRs.values():
    cand = cands[cr.near_id]
    m.geodesic(cr.l, cr.b, cand.l, cand.b, lw=0.5, ls='-', c='g')

plt.title('UHE Cosmic Rays and Candidate Sources')
plt.legend([cr_pts, cand_pts], ['UHE CR', 'Candidate'],
    frameon=False, loc='lower right', scatterpoints=1)

# Plot a colorbar for the CR energies.
cb_ax = plt.axes([0.25, .1, .5, .03], frameon=False)  # rect=L,B,W,H
#bar = ColorbarBase(cb_ax, cmap=cmap, orientation='horizontal', drawedges=False)
vals = np.linspace(Evals.min(), Evals.max(), 100)
bar = ColorbarBase(cb_ax, values=vals, norm=norm_E, cmap=cmap, 
    orientation='horizontal', drawedges=False)
bar.set_label('CR Energy (EeV)')

plt.show()