zenithangle.py
1 |
#! /usr/bin/env python
|
---|---|
2 |
# ==========================================================================
|
3 |
# This script computes a visibility cube
|
4 |
#
|
5 |
# Copyright (C) 2016 Juergen Knoedlseder
|
6 |
#
|
7 |
# This program is free software: you can redistribute it and/or modify
|
8 |
# it under the terms of the GNU General Public License as published by
|
9 |
# the Free Software Foundation, either version 3 of the License, or
|
10 |
# (at your option) any later version.
|
11 |
#
|
12 |
# This program is distributed in the hope that it will be useful,
|
13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15 |
# GNU General Public License for more details.
|
16 |
#
|
17 |
# You should have received a copy of the GNU General Public License
|
18 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
19 |
#
|
20 |
# ==========================================================================
|
21 |
import math |
22 |
import gammalib |
23 |
import matplotlib.pyplot as plt |
24 |
import itertools |
25 |
|
26 |
|
27 |
# =========================== #
|
28 |
# Compute position of the Sun #
|
29 |
# =========================== #
|
30 |
def sun(jd): |
31 |
"""
|
32 |
From https://en.wikipedia.org/wiki/Position_of_the_Sun
|
33 |
"""
|
34 |
# Number of days since Greenwich noon, Terrestrial Time, on 1 January 2000
|
35 |
n = jd - 2451545.0
|
36 |
|
37 |
# Mean longitude of the Sun, corrected for the aberration of light
|
38 |
L = 280.460 + 0.9856474 * n |
39 |
while L < 0.0: |
40 |
L += 360.0
|
41 |
while L >= 360.0: |
42 |
L -= 360.0
|
43 |
|
44 |
# Mean anomaly of the Sun
|
45 |
g = 357.528 + 0.9856003 * n |
46 |
while g < 0.0: |
47 |
g += 360.0
|
48 |
while g >= 360.0: |
49 |
g -= 360.0
|
50 |
|
51 |
# Ecliptic longitude of the Sun
|
52 |
lam = L + 1.915 * math.sin(g*gammalib.deg2rad) + \
|
53 |
0.020 * math.sin(2.0*g*gammalib.deg2rad) |
54 |
|
55 |
# Compute Right Ascension and Declination
|
56 |
ra = math.atan(math.cos(23.43711*gammalib.deg2rad) *
|
57 |
math.tan(lam*gammalib.deg2rad))*gammalib.rad2deg |
58 |
dec = math.asin(math.sin(23.43711*gammalib.deg2rad) *
|
59 |
math.sin(lam*gammalib.deg2rad))*gammalib.rad2deg |
60 |
|
61 |
# Put ra in the right quadrant (same quadrant as lam)
|
62 |
while lam < 0.0: |
63 |
lam += 360.0
|
64 |
while lam >= 360.0: |
65 |
lam -= 360.0
|
66 |
while ra < 0.0: |
67 |
ra += 360.0
|
68 |
while ra >= 360.0: |
69 |
ra -= 360.0
|
70 |
q_lam = int(lam / 90.0) |
71 |
q_ra = int(ra / 90.0) |
72 |
ra += (q_lam-q_ra)*90.0
|
73 |
print(q_lam,q_ra,q_lam-q_ra,(q_lam-q_ra)*90.0)
|
74 |
|
75 |
# Return
|
76 |
return (ra, dec)
|
77 |
|
78 |
|
79 |
# ========================== #
|
80 |
# Compute Sun constraint map #
|
81 |
# ========================== #
|
82 |
def sun_constraint(lim=100.0): |
83 |
"""
|
84 |
Compute Sun constraint map
|
85 |
"""
|
86 |
# Initialise sky map
|
87 |
map = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180) |
88 |
|
89 |
# Set Julian days for one "orbit" of the Sun
|
90 |
nsteps = 1000
|
91 |
jds = [float(i)*365.25636/float(nsteps) for i in range(nsteps)] |
92 |
norm = 1.0/float(nsteps) |
93 |
|
94 |
# Loop over JDs
|
95 |
for jd in jds: |
96 |
|
97 |
# Get position of sun
|
98 |
ra, dec = sun(jd) |
99 |
print(jd,ra,dec) |
100 |
sundir = gammalib.GSkyDir() |
101 |
sundir.radec_deg(ra, dec) |
102 |
|
103 |
# Compute for all pixels distance to Sun
|
104 |
for inx in range(map.npix()): |
105 |
dir = map.inx2dir(inx)
|
106 |
if sundir.dist_deg(dir) > lim: |
107 |
map[inx] += norm
|
108 |
|
109 |
# Save map
|
110 |
map.save('sun.fits', True) |
111 |
|
112 |
|
113 |
# ================= #
|
114 |
# Plot zenith angle #
|
115 |
# ================= #
|
116 |
def plot_zenith_angle(lat=-24.4, lim=60.0): |
117 |
"""
|
118 |
Plot zenith angle.
|
119 |
|
120 |
The zenith angle of a position (ra,dec) depends on the declination and
|
121 |
the hour angle h and is given by
|
122 |
|
123 |
zenith(ra,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h) )
|
124 |
|
125 |
The hour angle (or local hour angle, LHA) is defined as the difference
|
126 |
between local siderial time (LST) and the Right Ascension
|
127 |
|
128 |
h = LST - ra
|
129 |
|
130 |
LST is the difference between the Greenwhich siderial time (GST) and the
|
131 |
geographic longitude of the observer
|
132 |
|
133 |
LST = GST - lon
|
134 |
|
135 |
where lon is counted positive west from the meridian. Hence
|
136 |
|
137 |
h = GST - lon - ra
|
138 |
|
139 |
and
|
140 |
|
141 |
zenith(ra,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(GST-lon-ra) )
|
142 |
|
143 |
The zenith angle of the Sun depends of the hour angle in the local solar
|
144 |
time h_sol, the current declination of the Sun dec_sol and the geographic
|
145 |
latitude
|
146 |
|
147 |
zenith_sol = arccos( sin(lat) * sin(dec_sol) + cos(lat) * cos(dec_sol) * cos(h_sol) )
|
148 |
|
149 |
"""
|
150 |
# Initialise sky map
|
151 |
map = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180,60) |
152 |
|
153 |
# Set hour angle array in radians
|
154 |
hours = [float(i)*0.1 for i in range(3600)] |
155 |
norm = 24.0 / float(len(hours)) * 365.0 # hours per year per time step |
156 |
|
157 |
# Create figure
|
158 |
plt.figure(1)
|
159 |
plt.title('Zenith angles')
|
160 |
|
161 |
# Loop over declinations
|
162 |
decs = [float(i)-89.5 for i in range(180)] |
163 |
visible = [0.0 for i in range(180)] |
164 |
for k, dec in enumerate(decs): |
165 |
|
166 |
# Compute zenith angles for a given declination as function of hour
|
167 |
# angle
|
168 |
cos_dec = math.cos(dec*gammalib.deg2rad) |
169 |
sin_dec = math.sin(dec*gammalib.deg2rad) |
170 |
cos_phi = math.cos(lat*gammalib.deg2rad) |
171 |
sin_phi = math.sin(lat*gammalib.deg2rad) |
172 |
zeniths = [math.acos(sin_phi*sin_dec + |
173 |
cos_phi*cos_dec*math.cos(h*gammalib.deg2rad))* |
174 |
gammalib.rad2deg for h in hours] |
175 |
|
176 |
# Select only zenith angles < lim
|
177 |
x = [] |
178 |
y = [] |
179 |
for i in range(len(hours)): |
180 |
if zeniths[i] <= lim:
|
181 |
x.append(hours[i]) |
182 |
y.append(zeniths[i]) |
183 |
|
184 |
# Update visibility
|
185 |
visible[k] = float(len(x)) * norm |
186 |
|
187 |
# Plot zenith angle
|
188 |
if len(x) > 0: |
189 |
plt.plot(x, y, 'r-')
|
190 |
|
191 |
# Fill visibility cube for all hour angles. For each hour angle compute
|
192 |
# the appropriate zenith angle index iz, and set all Right Ascensions.
|
193 |
# This implies that we loop uniformly over all local siderial times.
|
194 |
#
|
195 |
# In reality, however, the Sun constraint (Sun under horizon) translates
|
196 |
# into a set of time intervals during which observations are performed.
|
197 |
for i in range(len(hours)): |
198 |
iz = int(zeniths[i]/1.0) |
199 |
if iz < 60: |
200 |
for ira in range(360): |
201 |
index = ira + 360 * k
|
202 |
map[index,iz] += norm
|
203 |
|
204 |
# Set axes
|
205 |
plt.xlabel('Hour angle (degrees)')
|
206 |
plt.ylabel('Zenith angle (degrees)')
|
207 |
|
208 |
# Create figure
|
209 |
plt.figure(2)
|
210 |
plt.title('Visibility')
|
211 |
plt.plot(decs, visible, 'r-')
|
212 |
plt.xlabel('Declination (degrees)')
|
213 |
plt.ylabel('Visibility (hours)')
|
214 |
|
215 |
# Save sky map
|
216 |
map.save('viscube_north.fits', True) |
217 |
|
218 |
# Show plot
|
219 |
plt.show() |
220 |
|
221 |
# Return
|
222 |
return
|
223 |
|
224 |
|
225 |
# ======================== #
|
226 |
# Compute zenith angle map #
|
227 |
# ======================== #
|
228 |
def zenith_angle_map(lat=-24.4): |
229 |
"""
|
230 |
Compute zenith angle map
|
231 |
|
232 |
The zenith angle of a position (ra,dec) depends on the declination and
|
233 |
the hour angle h and is given by
|
234 |
|
235 |
zenith(h,dec) = arccos( sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(h) )
|
236 |
|
237 |
The hour angle h (or local hour angle, LHA) is defined as the difference
|
238 |
between local siderial time (LST) and the Right Ascension
|
239 |
|
240 |
h = LST - ra
|
241 |
|
242 |
LST is the difference between the Greenwich siderial time (GST) and the
|
243 |
geographic longitude of the observer
|
244 |
|
245 |
LST = GST - lon
|
246 |
|
247 |
where lon is counted positive west from the meridian. Hence
|
248 |
|
249 |
h = GST - lon - ra
|
250 |
|
251 |
The map is computed for h=-ra which is equivalent to GST=lon (or LST=0).
|
252 |
In other words, the map corresponds to the time when ra=0 passes the
|
253 |
local meridian.
|
254 |
"""
|
255 |
# Initialise zenith angle map
|
256 |
map = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180) |
257 |
|
258 |
# Set hour angle and declination vectors
|
259 |
hours = [float(i) for i in range(360)] |
260 |
decs = [float(i)-89.5 for i in range(180)] |
261 |
|
262 |
# Precompute latitude terms
|
263 |
cos_lat = math.cos(lat*gammalib.deg2rad) |
264 |
sin_lat = math.sin(lat*gammalib.deg2rad) |
265 |
|
266 |
# Loop over all declination and hour angles and compute the zenith
|
267 |
# angle. Store the zenith angle in the map
|
268 |
index = 0
|
269 |
for dec in decs: |
270 |
cos_dec = math.cos(dec*gammalib.deg2rad) |
271 |
sin_dec = math.sin(dec*gammalib.deg2rad) |
272 |
for h in hours: |
273 |
cos_h = math.cos(h*gammalib.deg2rad) |
274 |
zenith = math.acos(sin_lat*sin_dec + |
275 |
cos_lat*cos_dec*cos_h)*gammalib.rad2deg |
276 |
map[index] = zenith
|
277 |
index += 1
|
278 |
|
279 |
# Save zenith angle map
|
280 |
map.save('zenith_angles.fits', True) |
281 |
|
282 |
# Return
|
283 |
return
|
284 |
|
285 |
|
286 |
# ======================= #
|
287 |
# Compute visibility cube #
|
288 |
# ======================= #
|
289 |
def visibility_cube(): |
290 |
"""
|
291 |
Compute visibility cube by displacing the zenith angle map for all
|
292 |
hour angles. The visibility cube contains the number of hours a given
|
293 |
celestial position is visible under a given zenith angle interval.
|
294 |
Summing over all zenith angle intervals specifies for how long a given
|
295 |
celestial position will be visible.
|
296 |
"""
|
297 |
# Set zenith angle range and bin size
|
298 |
dz = 1.0
|
299 |
nz = int(60.0/dz) |
300 |
|
301 |
# Initialise visibility cube
|
302 |
cube = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,360,180,nz) |
303 |
|
304 |
# Load zenith angle map
|
305 |
map = gammalib.GSkyMap('zenith_angles.fits')
|
306 |
|
307 |
# Setup hour angle weights. They specify for how many hours a given
|
308 |
# hour angle will be observed during one year.
|
309 |
hours = hour_angle_weight() |
310 |
|
311 |
# Compute normalisation factor
|
312 |
norm = 365.0/float(len(hours)) |
313 |
|
314 |
# Loop over all hour angle weights
|
315 |
for h, weight in enumerate(hours): |
316 |
|
317 |
# Compute temporal shift
|
318 |
shift = 360.0/float(len(hours)) * float(h) |
319 |
|
320 |
# Initialise shifted zenith angle map
|
321 |
map_shift = gammalib.GSkyMap('CAR','CEL',shift,0.0,-1.0,1.0,360,180) |
322 |
|
323 |
# Merge zenith angle map into shifted map
|
324 |
map_shift += map
|
325 |
map_shift.save('zenith_angles_shifted.fits', True) |
326 |
|
327 |
# Loop over all pixels of shifted map
|
328 |
for i, zenith in enumerate(map_shift): |
329 |
iz = int(zenith/dz)
|
330 |
if iz < nz:
|
331 |
cube[i,iz] += weight * norm |
332 |
|
333 |
# Save visibility cube
|
334 |
cube.save('viscube.fits', True) |
335 |
|
336 |
# Return
|
337 |
return
|
338 |
|
339 |
|
340 |
# ======================= #
|
341 |
# Compute visibility cube #
|
342 |
# ======================= #
|
343 |
def hour_angle_weight(): |
344 |
"""
|
345 |
Compute during which time the array was observing due to hour angle
|
346 |
constraints. We take here a very simple model where the night lasts
|
347 |
between 6 and 10 hours (or 90-150 degrees), with a sinusoidal variation
|
348 |
along the year. Every day the hour angle of the Sun set is advancing by
|
349 |
4 minutes.
|
350 |
"""
|
351 |
# Set number of hour angle bins
|
352 |
nsteps = 1000
|
353 |
d_min = 6.0/24.0*nsteps |
354 |
d_max = 10.0/24.0*nsteps |
355 |
|
356 |
# Initialise hour angle array and weight
|
357 |
hours = [0.0 for i in range(nsteps)] |
358 |
weight = 24.0/float(nsteps) # Weight per hour angle step |
359 |
|
360 |
# Loop over all days in one year
|
361 |
for day in range(nsteps): |
362 |
h_set = day |
363 |
h_rise = day + int(d_min + (d_max-d_min)*math.sin(day/float(nsteps)*gammalib.pi)) |
364 |
for i in range(h_set,h_rise): |
365 |
if i >= nsteps:
|
366 |
hours[i-nsteps] += weight |
367 |
else:
|
368 |
hours[i] += weight |
369 |
|
370 |
# Return
|
371 |
return hours
|
372 |
|
373 |
|
374 |
#==========================#
|
375 |
# Main routine entry point #
|
376 |
#==========================#
|
377 |
if __name__ == '__main__': |
378 |
|
379 |
# Plot zenith angle
|
380 |
#zenith_angle_map(lat=-24.4) # South
|
381 |
zenith_angle_map(lat=+28.7569) # North |
382 |
#visibility_cube()
|
383 |
visibility_cube() |
384 |
|