source: trunk/beamsgenerator.py@ 49

Last change on this file since 49 was 43, checked in by JohnLightfoot, 10 years ago

calculates beams only over truncated fts_wn

File size: 7.7 KB
Line 
1from __future__ import absolute_import
2
3import collections
4import numpy as np
5import pp
6
7import common.commonobjects as co
8
9def calculate_dirty_beam(npix, rpix, mult, bmax, bxbylist, wnmax, wn):
10 lamb = 1.0 / (wn * 100.0)
11 lambda_min = 1.0 / (wnmax * 100.0)
12 maxbx = bmax / lambda_min
13
14 uv = numpy.zeros([mult*npix,mult*npix])
15
16 for bxby in bxbylist:
17 ibx = int(round(((bxby[0] / lamb) / maxbx) * mult * rpix))
18 if ibx < 0:
19 ibx = mult*npix + ibx
20 iby = int(round(((bxby[1] / lamb) / maxbx) * mult * rpix))
21 if iby < 0:
22 iby = mult*npix + iby
23 uv[ibx,iby] = 1.0
24
25 temp = numpy.fft.fftshift(uv)
26 temp = numpy.fft.fft2(temp)
27 temp = numpy.fft.fftshift(temp)
28 temp = numpy.power(temp, 2)
29 # convert to real, should be real anway as FT of symmetric function
30 dirty_beam = numpy.abs(temp)
31 # and normalise
32 dirty_beam /= numpy.max(dirty_beam)
33
34 # truncate dirty beam pattern
35 dirty_beam = dirty_beam[
36 (mult-1)*rpix:(mult+1)*rpix, (mult-1)*rpix:(mult+1)*rpix]
37
38 return dirty_beam
39
40def calculate_primary_beam(npix, rpix, mult, mx, my, bmax, m1_diameter,
41 wnmax, wn):
42 # assume uniform illumination of primary. uv goes out to
43 # +- bmax / lambda_min, fill uv plane appropriate to primary
44 # size
45 mirror = numpy.ones([mult*npix,mult*npix])
46 lambda_min = 1.0 / (wnmax * 100.0)
47 lamb = 1.0 / (wn * 100.0)
48 mirror[(numpy.sqrt(mx*mx + my*my) / (mult*rpix)) *
49 (bmax / lambda_min) > (0.5 * m1_diameter / lamb)] = 0.0
50
51 # fftshift mirror to be centred at [0,0],
52 # do a 2d ftt of it,
53 # fftshift transform so that 0 freq is in centre of array
54 temp = numpy.fft.fftshift(mirror)
55 temp = numpy.fft.fft2(temp)
56 temp = numpy.fft.fftshift(temp)
57 # convert to real, should be real anyway as FT of symmetric function
58 # square to give intensity response
59 primary_amplitude_beam = numpy.real(temp)
60 primary_intensity_beam = numpy.power(numpy.abs(temp), 2)
61 # and normalise
62 primary_amplitude_beam /= numpy.max(primary_amplitude_beam)
63 primary_intensity_beam /= numpy.max(primary_intensity_beam)
64
65 # truncate primary beam pattern
66 primary_amplitude_beam = primary_amplitude_beam[
67 (mult-1)*rpix:(mult+1)*rpix,
68 (mult-1)*rpix:(mult+1)*rpix]
69 primary_intensity_beam = primary_intensity_beam[
70 (mult-1)*rpix:(mult+1)*rpix,
71 (mult-1)*rpix:(mult+1)*rpix]
72
73 return primary_intensity_beam, primary_amplitude_beam
74
75
76class BeamsGenerator(object):
77 """Class to generate the primary beam(s) of the simulated observation.
78 """
79
80 def __init__(self, previous_results):
81 self.previous_results = previous_results
82 self.result = collections.OrderedDict()
83
84 # start parallel python (pp), find the number of CPUS available
85 ppservers = ()
86 self.job_server = pp.Server(ppservers=ppservers)
87 print 'Sampler starting pp with %s workers' % \
88 self.job_server.get_ncpus()
89
90 def run(self):
91 print 'BeamsGenerator.run'
92
93 uvmapgen = self.previous_results['uvmapgenerator']
94 fts = self.previous_results['fts']
95 telescope = self.previous_results['loadparameters']['substages']\
96 ['Telescope']
97
98 # number of mirror positions sampled by FTS
99 nsample = fts['ftsnsample']
100 fts_wn = fts['fts_wn']
101 fts_wn_truncated = fts['fts_wn_truncated']
102
103 # max pixel size (radians) that will fully sample the image.
104 # Sampling freq is 2 * Nyquist freq.
105 # So sampling freq = 2 * b_max / lambda_min.
106 bmax = uvmapgen['bmax']
107 self.result['max baseline [m]'] = bmax
108
109 lambda_min = 1.0 / (np.max(fts_wn) * 100.0)
110 max_pixsize = lambda_min / (2.0 * bmax)
111 self.result['max pixsize [rad]'] = max_pixsize
112
113 # Number of pixels per beam
114 # radius of first null of Airy disk is theta=1.22lambda/d. Number of
115 # pixels is beam diameter/max_pixsize. Calculate this for the
116 # longest wavelength that has flux (at wnmin), largest beam
117
118 max_beam_radius = 1.22 * (1.0 / (np.min(fts_wn_truncated) * 100.0)) /\
119 telescope['Primary mirror diameter']
120 self.result['beam diam [rad]'] = 2.0 * max_beam_radius
121
122 # go out to radius of first null
123 rpix = int(max_beam_radius / max_pixsize)
124 npix = 2 * rpix
125 self.result['npix'] = npix
126
127 # spatial axes same for all wavelengths
128 axis = np.arange(-rpix, rpix, dtype=np.float)
129 axis *= max_pixsize
130 axis *= 206264.806247
131 self.result['spatial axis [arcsec]'] = axis
132 axis1 = co.Axis(data=-axis, title='RA offset', units='arcsec')
133 axis2 = co.Axis(data=axis, title='Dec offset', units='arcsec')
134 axis3 = co.Axis(data=fts_wn_truncated, title='Frequency', units='cm-1')
135
136 # calculate beams for each point on fts_wn_truncated
137 # oversample uv grid by mult to minimise aliasing
138 mult = 5
139 mx,my = np.meshgrid(np.arange(-mult*rpix, mult*rpix),
140 np.arange(-mult*rpix, mult*rpix))
141 self.result['primary beam'] = collections.OrderedDict()
142 self.result['primary amplitude beam'] = collections.OrderedDict()
143 self.result['dirty beam'] = collections.OrderedDict()
144
145 wnmax = np.max(fts_wn_truncated)
146 m1_diameter = telescope['Primary mirror diameter']
147
148 # first calculate primary beam for each wn
149 jobs = {}
150 for wn in fts_wn_truncated:
151 # submit jobs
152 indata = (npix, rpix, mult, mx, my, bmax, m1_diameter, wnmax,
153 wn,)
154 jobs[wn] = self.job_server.submit(calculate_primary_beam,
155 indata, (), ('numpy',))
156
157 primary_amplitude_beam = np.zeros([npix,npix,len(fts_wn_truncated)],
158 np.complex)
159 for iwn,wn in enumerate(fts_wn_truncated):
160 # collect and store results
161 primary_intensity_beam, primary_amplitude_beam[:,:,iwn] = jobs[wn]()
162 image = co.Image(data=primary_intensity_beam, axes=[axis1, axis2],
163 title='Primary Beam %06.4g cm-1' % wn)
164 self.result['primary beam'][wn] = image
165
166 cube = co.Cube(data=primary_amplitude_beam, axes=[axis1, axis2, axis3],
167 title='Amplitude Primary Beam %06.4g cm-1' % wn)
168 self.result['primary amplitude beam'] = cube
169
170 # second calculate dirty beam, same use of pp
171 for wn in fts_wn_truncated:
172 #
173 # 1. uv plane covered by discrete points so should use a
174 # slow dft rather than fft, which requires evenly spaced
175 # samples. Perhaps a bit slow though as calcs done in Python
176 # layer.
177 # 2. Could use shift theorem to give even-spaced equivalent
178 # of each discrete point, then use ffts. Equivalent to 1
179 # in speed.
180 # 3. Oversample uv plane and accept small error in point
181 # positions. Fastest but adequate?
182
183 indata = (npix, rpix, mult, bmax, uvmapgen['bxby'], wnmax, wn,)
184 jobs[wn] = self.job_server.submit(calculate_dirty_beam,
185 indata, (), ('numpy',))
186
187
188 # axes for dirty beam
189 axis = np.arange(-rpix, rpix, dtype=np.float)
190 axis *= (lambda_min / bmax)
191 axis *= 206264.806247
192 axis1 = co.Axis(data=-axis, title='RA offset', units='arcsec')
193 axis2 = co.Axis(data=axis, title='Dec offset', units='arcsec')
194
195 for wn in fts_wn_truncated:
196 dirty_beam = jobs[wn]()
197 image = co.Image(data=dirty_beam, axes=[axis1, axis2],
198 title='Dirty Beam %06.4g cm-1' % wn)
199 self.result['dirty beam'][wn] = image
200
201 return self.result
202
203 def __repr__(self):
204 return 'PrimaryBeamGenerator'
205
Note: See TracBrowser for help on using the repository browser.