tabsim 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tabsim/__init__.py +0 -0
- tabsim/config.py +904 -0
- tabsim/dask/__init__.py +0 -0
- tabsim/dask/coordinates.py +469 -0
- tabsim/dask/extras.py +60 -0
- tabsim/dask/interferometry.py +572 -0
- tabsim/dask/observation.py +969 -0
- tabsim/data/EDA2.itrf.txt +256 -0
- tabsim/data/MWA.itrf.txt +128 -0
- tabsim/data/MeerKAT.itrf.txt +64 -0
- tabsim/data/__init__.py +0 -0
- tabsim/data/aux_data/calibrator.skymodel +2 -0
- tabsim/data/aux_data/norad_satellite.rfimodel +61711 -0
- tabsim/data/aux_data/satellite.ole +3 -0
- tabsim/data/aux_data/satellite.rfimodel +3 -0
- tabsim/data/aux_data/stationary.loc +4 -0
- tabsim/data/aux_data/stationary.rfimodel +4 -0
- tabsim/data/config_files/sim_config_base.yaml +172 -0
- tabsim/data/config_files/tab_config_base.yaml +86 -0
- tabsim/jax/__init__.py +0 -0
- tabsim/jax/coordinates.py +1082 -0
- tabsim/jax/interferometry.py +667 -0
- tabsim/jax/utils.py +90 -0
- tabsim/plot.py +323 -0
- tabsim/scripts/__init__.py +1 -0
- tabsim/scripts/ast_region.py +56 -0
- tabsim/scripts/flag_data.py +195 -0
- tabsim/scripts/sim_vis.py +136 -0
- tabsim/scripts/tle_sat_region.py +191 -0
- tabsim/sky.py +137 -0
- tabsim/tle.py +753 -0
- tabsim/tools.py +111 -0
- tabsim/write.py +715 -0
- tabsim-0.0.1.dist-info/METADATA +991 -0
- tabsim-0.0.1.dist-info/RECORD +39 -0
- tabsim-0.0.1.dist-info/WHEEL +5 -0
- tabsim-0.0.1.dist-info/entry_points.txt +5 -0
- tabsim-0.0.1.dist-info/licenses/LICENSE +674 -0
- tabsim-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import jax.numpy as jnp
|
|
4
|
+
from jax import jit, random, config
|
|
5
|
+
from jax.lax import scan
|
|
6
|
+
from scipy.special import jv
|
|
7
|
+
|
|
8
|
+
from functools import partial
|
|
9
|
+
|
|
10
|
+
from tabsim.jax.utils import jit_with_doc
|
|
11
|
+
|
|
12
|
+
config.update("jax_enable_x64", True)
|
|
13
|
+
|
|
14
|
+
c = 2.99792458e8
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@jit_with_doc
|
|
18
|
+
def rfi_vis(app_amplitude, c_distances, freqs, a1, a2):
|
|
19
|
+
"""
|
|
20
|
+
Calculate visibilities from distances to rfi sources.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
app_amplitude: array_like (n_src, n_time, n_ant, n_freq)
|
|
25
|
+
Apparent amplitude at the antennas.
|
|
26
|
+
c_distances: array_like (n_src, n_time, n_ant)
|
|
27
|
+
The phase corrected distances between the rfi sources and the antennas in metres.
|
|
28
|
+
freqs: array_like (n_freq,)
|
|
29
|
+
Frequencies in Hz.
|
|
30
|
+
a1: array_like (n_bl,)
|
|
31
|
+
Antenna 1 indexes, between 0 and n_ant-1.
|
|
32
|
+
a2: array_like (n_bl,)
|
|
33
|
+
Antenna 2 indexes, between 0 and n_ant-1.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
vis: array_like (n_time, n_bl, n_freq)
|
|
38
|
+
The visibilities.
|
|
39
|
+
"""
|
|
40
|
+
n_src = app_amplitude.shape[0]
|
|
41
|
+
vis = _rfi_vis(app_amplitude[0, None], c_distances[0, None], freqs, a1, a2)
|
|
42
|
+
|
|
43
|
+
# This is a scan over the sources, but we can't use scan it unless we jit decorate this function
|
|
44
|
+
def _add_vis(vis, i):
|
|
45
|
+
return (
|
|
46
|
+
vis + _rfi_vis(app_amplitude[i, None], c_distances[i, None], freqs, a1, a2),
|
|
47
|
+
i,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return scan(_add_vis, vis, jnp.arange(1, n_src))[0]
|
|
51
|
+
# return _rfi_vis(app_amplitude, c_distances, freqs, a1, a2)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@jit_with_doc
|
|
55
|
+
def astro_vis(sources, uvw, lmn, freqs):
|
|
56
|
+
"""
|
|
57
|
+
Calculate visibilities from a set of point sources using DFT.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
sources: array_like (n_src, n_time, n_freq)
|
|
62
|
+
Array of point source intensities in Jy.
|
|
63
|
+
uvw: array_like (ntime, n_bl, 3)
|
|
64
|
+
(u,v,w) coordinates of each baseline.
|
|
65
|
+
lmn: array_like (n_src, 3)
|
|
66
|
+
(l,m,n) coordinate of each source.
|
|
67
|
+
freqs: array_like (n_freq,)
|
|
68
|
+
Frequencies in Hz.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
vis: array_like (n_time, n_bl, n_freq)
|
|
73
|
+
Visibilities of the given set of sources and baselines.
|
|
74
|
+
"""
|
|
75
|
+
n_src = sources.shape[0]
|
|
76
|
+
vis = _astro_vis(sources[0, None], uvw, lmn[0, None], freqs)
|
|
77
|
+
|
|
78
|
+
# This is a scan over the sources, but we can't use scan it unless we jit decorate this function
|
|
79
|
+
@jit
|
|
80
|
+
def _add_vis(vis, i):
|
|
81
|
+
return vis + _astro_vis(sources[i, None], uvw, lmn[i, None], freqs), i
|
|
82
|
+
|
|
83
|
+
return scan(_add_vis, vis, jnp.arange(1, n_src))[0]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@jit_with_doc
|
|
87
|
+
def astro_vis_gauss(sources, major, minor, pos_angle, uvw, lmn, freqs):
|
|
88
|
+
"""
|
|
89
|
+
Calculate visibilities from a set of point sources using DFT.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
sources: array_like (n_src, n_time, n_freq)
|
|
94
|
+
Array of point source intensities in Jy.
|
|
95
|
+
shapes: array_like (n_src,)
|
|
96
|
+
Array of standard deviations of the gaussian shape sources. These are
|
|
97
|
+
assumed to be circular gaussians for now.
|
|
98
|
+
uvw: array_like (ntime, n_bl, 3)
|
|
99
|
+
(u,v,w) coordinates of each baseline.
|
|
100
|
+
lmn: array_like (n_src, 3)
|
|
101
|
+
(l,m,n) coordinate of each source.
|
|
102
|
+
freqs: array_like (n_freq,)
|
|
103
|
+
Frequencies in Hz.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
vis: array_like (n_time, n_bl, n_freq)
|
|
108
|
+
Visibilities of the given set of sources and baselines.
|
|
109
|
+
"""
|
|
110
|
+
n_src = sources.shape[0]
|
|
111
|
+
vis = _astro_vis_gauss(
|
|
112
|
+
sources[0, None],
|
|
113
|
+
major[0, None],
|
|
114
|
+
minor[0, None],
|
|
115
|
+
pos_angle[0, None],
|
|
116
|
+
uvw,
|
|
117
|
+
lmn[0, None],
|
|
118
|
+
freqs,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# This is a scan over the sources, but we can't use scan it unless we jit decorate this function
|
|
122
|
+
def _add_vis(vis, i):
|
|
123
|
+
return (
|
|
124
|
+
vis
|
|
125
|
+
+ _astro_vis_gauss(
|
|
126
|
+
sources[i, None],
|
|
127
|
+
major[i, None],
|
|
128
|
+
minor[i, None],
|
|
129
|
+
pos_angle[i, None],
|
|
130
|
+
uvw,
|
|
131
|
+
lmn[i, None],
|
|
132
|
+
freqs,
|
|
133
|
+
),
|
|
134
|
+
i,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return scan(_add_vis, vis, jnp.arange(1, n_src))[0]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@jit_with_doc
|
|
141
|
+
def astro_vis_exp(sources, shapes, uvw, lmn, freqs):
|
|
142
|
+
"""
|
|
143
|
+
Calculate visibilities from a set of point sources using DFT.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
sources: array_like (n_src, n_time, n_freq)
|
|
148
|
+
Array of point source intensities in Jy.
|
|
149
|
+
shapes: array_like (n_src,)
|
|
150
|
+
Array of standard deviations of the gaussian shape sources. These are
|
|
151
|
+
assumed to be circular gaussians for now.
|
|
152
|
+
uvw: array_like (ntime, n_bl, 3)
|
|
153
|
+
(u,v,w) coordinates of each baseline.
|
|
154
|
+
lmn: array_like (n_src, 3)
|
|
155
|
+
(l,m,n) coordinate of each source.
|
|
156
|
+
freqs: array_like (n_freq,)
|
|
157
|
+
Frequencies in Hz.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
vis: array_like (n_time, n_bl, n_freq)
|
|
162
|
+
Visibilities of the given set of sources and baselines.
|
|
163
|
+
"""
|
|
164
|
+
n_src = sources.shape[0]
|
|
165
|
+
vis = _astro_vis_exp(sources[0, None], shapes[0, None], uvw, lmn[0, None], freqs)
|
|
166
|
+
|
|
167
|
+
# This is a scan over the sources, but we can't use scan it unless we jit decorate this function
|
|
168
|
+
def _add_vis(vis, i):
|
|
169
|
+
return (
|
|
170
|
+
vis
|
|
171
|
+
+ _astro_vis_exp(
|
|
172
|
+
sources[i, None], shapes[i, None], uvw, lmn[i, None], freqs
|
|
173
|
+
),
|
|
174
|
+
i,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return scan(_add_vis, vis, jnp.arange(1, n_src))[0]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def ants_to_bl(G, a1, a2):
|
|
181
|
+
"""
|
|
182
|
+
Calculate the complex gains for each baseline given the per antenna gains.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
G: array_like (n_time, n_ant, n_freq)
|
|
187
|
+
Complex gains at each antenna over time.
|
|
188
|
+
a1: array_like (n_bl,)
|
|
189
|
+
Antenna 1 indexes, between 0 and n_ant-1.
|
|
190
|
+
a2: array_like (n_bl,)
|
|
191
|
+
Antenna 2 indexes, between 0 and n_ant-1.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
G_bl: array_like (n_time, n_bl, n_freq)
|
|
196
|
+
Complex gains on each baseline over time.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
return _ants_to_bl(G, a1, a2)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@jit_with_doc
|
|
203
|
+
def minus_two_pi_over_lamda(freqs):
|
|
204
|
+
"""Calculate -2pi/lambda for each frequency.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
freqs (jnp.ndarray): Frequencies in Hz. (n_freq,)
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
jnp.ndarray: -2pi/lambda for each frequency. (n_freq,)
|
|
211
|
+
"""
|
|
212
|
+
return -2.0 * jnp.pi * freqs / c
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@jit_with_doc
|
|
216
|
+
def amp_to_intensity(amps, a1, a2):
|
|
217
|
+
"""Calculate intensity on a baseline ffrom the amplitudes at each antenna.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
amps (jnp.ndarray): Amplitudes at the antennas. (n_src, n_time, n_int, n_ant, n_freq)
|
|
221
|
+
a1 (jnp.ndarray): Antenna 1 indexes, between 0 and n_ant-1. (n_bl,)
|
|
222
|
+
a2 (jnp.ndarray): Antenna 2 indexes, between 0 and n_ant-1. (n_bl,)
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
jnp.ndarray: Intensity on baselines.
|
|
226
|
+
"""
|
|
227
|
+
return amps[:, :, :, a1] * jnp.conjugate(amps[:, :, :, a2])
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@jit_with_doc
|
|
231
|
+
def phase_from_distances(distances, a1, a2, freqs):
|
|
232
|
+
"""Calculate phase differences between antennas from distances.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
distances (jnp.ndarray): Distances to antennas. (n_src, n_time, n_int, n_ant)
|
|
236
|
+
a1 (jnp.ndarray): Antenna 1 indexes, between 0 and n_ant-1. (n_bl,)
|
|
237
|
+
a2 (jnp.ndarray): Antenna 2 indexes, between 0 and n_ant-1. (n_bl,)
|
|
238
|
+
freqs (jnp.ndarray): Frequencies in Hz. (n_freq,)
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
jnp.ndarray: Phases on baselines.
|
|
242
|
+
"""
|
|
243
|
+
# Create array of shape (n_src, n_time, n_bl, n_freq)
|
|
244
|
+
freqs = freqs[None, None, None, None, :]
|
|
245
|
+
distances = distances[:, :, :, :, None]
|
|
246
|
+
|
|
247
|
+
phases = minus_two_pi_over_lamda(freqs) * (
|
|
248
|
+
distances[:, :, :, a1, :] - distances[:, :, :, a2, :]
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return phases
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@jit
|
|
255
|
+
def _rfi_vis(app_amplitude, c_distances, freqs, a1, a2):
|
|
256
|
+
# Create array of shape (n_src, n_time, n_bl, n_freq), then sum over n_src
|
|
257
|
+
|
|
258
|
+
app_amplitude = jnp.asarray(app_amplitude)
|
|
259
|
+
c_distances = jnp.asarray(c_distances)
|
|
260
|
+
freqs = jnp.asarray(freqs)
|
|
261
|
+
a1 = jnp.asarray(a1)
|
|
262
|
+
a2 = jnp.asarray(a2)
|
|
263
|
+
|
|
264
|
+
phase = phase_from_distances(c_distances, a1, a2, freqs)
|
|
265
|
+
intensity = amp_to_intensity(app_amplitude, a1, a2)
|
|
266
|
+
|
|
267
|
+
vis = jnp.sum(intensity * jnp.exp(1.0j * phase), axis=0)
|
|
268
|
+
vis_avg = jnp.mean(vis, axis=1)
|
|
269
|
+
|
|
270
|
+
return vis_avg
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@jit_with_doc
|
|
274
|
+
def _astro_vis(sources, uvw, lmn, freqs):
|
|
275
|
+
# Create array of shape (n_src, n_time, n_bl, n_freq), then sum over n_src
|
|
276
|
+
|
|
277
|
+
sources = jnp.asarray(sources[:, :, None, :]) # (n_src, 1, 1, n_freq)
|
|
278
|
+
freqs = jnp.asarray(freqs[None, None, None, :]) # (1, 1, 1, n_freq)
|
|
279
|
+
uvw = jnp.asarray(uvw[None, :, :, None, :]) # (1, n_time, n_bl, 1, 3)
|
|
280
|
+
lmn = jnp.asarray(lmn[:, None, None, None, :]) # (n_src, 1, 1, 1, 3)
|
|
281
|
+
s0 = jnp.array([0, 0, 1])[None, None, None, None, :] # (1, 1, 1, 1, 3)
|
|
282
|
+
|
|
283
|
+
phase = minus_two_pi_over_lamda(freqs) * jnp.sum(uvw * (lmn - s0), axis=-1)
|
|
284
|
+
|
|
285
|
+
vis = jnp.sum(sources * jnp.exp(-1.0j * phase), axis=0)
|
|
286
|
+
|
|
287
|
+
return vis
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@jit_with_doc
|
|
291
|
+
def gauss(uvw, shapes, freqs):
|
|
292
|
+
uv_mag = jnp.linalg.norm(uvw[..., :-1], axis=-1) / (c / freqs)
|
|
293
|
+
|
|
294
|
+
sigmas = shapes / (2.0 * jnp.sqrt(2.0 * jnp.log(2)))
|
|
295
|
+
|
|
296
|
+
sigmas_uv = 1.0 / (2.0 * jnp.pi * sigmas)
|
|
297
|
+
|
|
298
|
+
return jnp.exp(-((uv_mag / sigmas_uv) ** 2))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@jit_with_doc
|
|
302
|
+
def source_to_abc(major, minor, pa):
|
|
303
|
+
"""Calculate the coefficients of the quadratic for a Gaussian source."""
|
|
304
|
+
sigma_factor = 2 * jnp.sqrt(2 * jnp.log(2))
|
|
305
|
+
sigma_x = jnp.deg2rad(minor / 3600) / sigma_factor
|
|
306
|
+
sigma_y = jnp.deg2rad(major / 3600) / sigma_factor
|
|
307
|
+
theta = jnp.deg2rad(pa)
|
|
308
|
+
|
|
309
|
+
a = jnp.cos(theta) ** 2 / (2 * sigma_x**2) + jnp.sin(theta) ** 2 / (2 * sigma_y**2)
|
|
310
|
+
b = jnp.sin(2 * theta) / (4 * sigma_x**2) - jnp.sin(2 * theta) / (4 * sigma_y**2)
|
|
311
|
+
c = jnp.sin(theta) ** 2 / (2 * sigma_x**2) + jnp.cos(theta) ** 2 / (2 * sigma_y**2)
|
|
312
|
+
|
|
313
|
+
return a, b, c
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@jit_with_doc
|
|
317
|
+
def gauss_uv(uvw, major, minor, pos_a, freqs):
|
|
318
|
+
|
|
319
|
+
cc = 2.99792458e8
|
|
320
|
+
lamda = cc / freqs
|
|
321
|
+
u = uvw[..., 0] / lamda
|
|
322
|
+
v = uvw[..., 1] / lamda
|
|
323
|
+
a, b, c = source_to_abc(major, minor, pos_a)
|
|
324
|
+
det = (a * c - b**2) / (4 * jnp.pi**2)
|
|
325
|
+
|
|
326
|
+
return jnp.exp(-(c * u**2 - 2 * b * u * v + a * v**2) / (4 * det))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@jit_with_doc
|
|
330
|
+
def gauss_lm(l, m, a, b, c):
|
|
331
|
+
return jnp.exp(-(a * l**2 + 2 * b * l * m + c * m**2))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@jit_with_doc
|
|
335
|
+
def _astro_vis_gauss(sources, major, minor, pos_angle, uvw, lmn, freqs):
|
|
336
|
+
# Create array of shape (n_src, n_time, n_bl, n_freq), then sum over n_src
|
|
337
|
+
|
|
338
|
+
sources = jnp.asarray(sources[:, :, None, :]) # (n_src, n_time, 1, n_freq)
|
|
339
|
+
major = jnp.asarray(major[:, None, None, None]) # (n_src, 1, 1, 1)
|
|
340
|
+
minor = jnp.asarray(minor[:, None, None, None]) # (n_src, 1, 1, 1)
|
|
341
|
+
pos_angle = jnp.asarray(pos_angle[:, None, None, None]) # (n_src, 1, 1, 1)
|
|
342
|
+
freqs = jnp.asarray(freqs[None, None, None, :]) # (1, 1, 1, n_freq)
|
|
343
|
+
uvw = jnp.asarray(uvw[None, :, :, None, :]) # (1, n_time, n_bl, 1, 3)
|
|
344
|
+
lmn = jnp.asarray(lmn[:, None, None, None, :]) # (n_src, 1, 1, 1, 3)
|
|
345
|
+
s0 = jnp.array([0, 0, 1])[None, None, None, None, :] # (1, 1, 1, 1, 3)
|
|
346
|
+
|
|
347
|
+
phase = minus_two_pi_over_lamda(freqs) * jnp.sum(uvw * (lmn - s0), axis=-1)
|
|
348
|
+
|
|
349
|
+
uv_filter = gauss_uv(uvw, major, minor, pos_angle, freqs)
|
|
350
|
+
|
|
351
|
+
vis = jnp.sum(uv_filter * sources * jnp.exp(-1.0j * phase), axis=0)
|
|
352
|
+
|
|
353
|
+
return vis
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@jit_with_doc
|
|
357
|
+
def exp_uv(uvw, shapes, freqs):
|
|
358
|
+
U = jnp.linalg.norm(uvw[..., :-1], axis=-1) / (c / freqs)
|
|
359
|
+
|
|
360
|
+
return 1.0 / (1.0 + (2 * jnp.pi * shapes * U) ** 2) ** 1.5
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@jit_with_doc
|
|
364
|
+
def _astro_vis_exp(sources, shapes, uvw, lmn, freqs):
|
|
365
|
+
# Create array of shape (n_src, n_time, n_bl, n_freq), then sum over n_src
|
|
366
|
+
|
|
367
|
+
sources = jnp.asarray(sources[:, :, None, :]) # (n_src, n_time, 1, n_freq)
|
|
368
|
+
shapes = jnp.asarray(shapes[:, None, None, None]) # (n_src, 1, 1, 1)
|
|
369
|
+
freqs = jnp.asarray(freqs[None, None, None, :]) # (1, 1, 1, n_freq)
|
|
370
|
+
uvw = jnp.asarray(uvw[None, :, :, None, :]) # (1, n_time, n_bl, 1, 3)
|
|
371
|
+
lmn = jnp.asarray(lmn[:, None, None, None, :]) # (n_src, 1, 1, 1, 3)
|
|
372
|
+
s0 = jnp.array([0, 0, 1])[None, None, None, None, :] # (1, 1, 1, 1, 3)
|
|
373
|
+
|
|
374
|
+
phase = minus_two_pi_over_lamda(freqs) * jnp.sum(uvw * (lmn - s0), axis=-1)
|
|
375
|
+
|
|
376
|
+
vis = jnp.sum(exp_uv(uvw, shapes, freqs) * sources * jnp.exp(-1.0j * phase), axis=0)
|
|
377
|
+
|
|
378
|
+
return vis
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@jit_with_doc
|
|
382
|
+
def _ants_to_bl(G, a1, a2):
|
|
383
|
+
G_bl = G[:, a1, :] * jnp.conjugate(G[:, a2, :])
|
|
384
|
+
|
|
385
|
+
return G_bl
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def airy_beam(theta: jnp.ndarray, freqs: jnp.ndarray, dish_d: float):
|
|
389
|
+
"""
|
|
390
|
+
Calculate the primary beam voltage at a given angular distance from the
|
|
391
|
+
pointing direction. The beam intensity model is the Airy disk as
|
|
392
|
+
defined by the dish diameter. This is the same a the CASA default.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
theta: (n_src, n_time, n_ant)
|
|
397
|
+
The angular separation (in degrees) between the pointing direction and the
|
|
398
|
+
source.
|
|
399
|
+
freqs: (n_freq,)
|
|
400
|
+
The frequencies at which to calculate the beam in Hz.
|
|
401
|
+
dish_d: float
|
|
402
|
+
The diameter of the dish in meters.
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
E: ndarray (n_src, n_time, n_ant, n_freq)
|
|
407
|
+
The beam voltage at each frequency.
|
|
408
|
+
"""
|
|
409
|
+
theta = jnp.asarray(theta[:, :, :, None])
|
|
410
|
+
freqs = jnp.asarray(freqs)
|
|
411
|
+
dish_d = jnp.asarray(dish_d).flatten()[0]
|
|
412
|
+
# mask = jnp.where(theta > 90.0, 0, 1)
|
|
413
|
+
theta = jnp.deg2rad(theta)
|
|
414
|
+
x = jnp.where(
|
|
415
|
+
theta == 0.0,
|
|
416
|
+
sys.float_info.epsilon,
|
|
417
|
+
jnp.pi * freqs[None, None, None, :] * dish_d * jnp.sin(theta) / c,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return 2 * jv(1, x) / x
|
|
421
|
+
# return (2 * jv(1, x) / x) * mask
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# @jit_with_doc
|
|
425
|
+
def Pv_to_Sv(Pv: jnp.ndarray, d: jnp.ndarray) -> jnp.ndarray:
|
|
426
|
+
"""
|
|
427
|
+
Convert emission power to received intensity in Jy. Assumes constant
|
|
428
|
+
power across the bandwidth.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
Pv: ndarray (n_src, n_time, n_freq)
|
|
433
|
+
Specific emission power in W/Hz.
|
|
434
|
+
d: ndarray (n_src, n_time, n_ant)
|
|
435
|
+
Distances from source to receiving antennas in m.
|
|
436
|
+
|
|
437
|
+
Returns
|
|
438
|
+
-------
|
|
439
|
+
Sv: ndarray (n_src, n_time, n_ant, n_freq)
|
|
440
|
+
Spectral flux density at the receiving antennas in Jy.
|
|
441
|
+
"""
|
|
442
|
+
Pv = jnp.asarray(Pv)
|
|
443
|
+
d = jnp.asarray(d)
|
|
444
|
+
return Pv[:, :, None, :] / (4 * jnp.pi * d[:, :, :, None] ** 2) * 1e26
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# @jit_with_doc
|
|
448
|
+
def add_noise(vis: jnp.ndarray, noise_std: jnp.ndarray, key: jnp.ndarray):
|
|
449
|
+
"""
|
|
450
|
+
Add complex gaussian noise to the integrated visibilities. The real and
|
|
451
|
+
imaginary components will each get this level of noise.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
vis: ndarray (n_time, n_bl, n_freq)
|
|
456
|
+
The visibilities to add noise to.
|
|
457
|
+
noise_std: (n_freq, )
|
|
458
|
+
Standard deviation of the complex noise.
|
|
459
|
+
key: jax.random.PRNGKey
|
|
460
|
+
Random number generator key.
|
|
461
|
+
"""
|
|
462
|
+
vis = jnp.asarray(vis)
|
|
463
|
+
noise_std = jnp.asarray(noise_std)
|
|
464
|
+
key = jnp.asarray(key)
|
|
465
|
+
noise = (
|
|
466
|
+
random.normal(key, shape=vis.shape, dtype=jnp.complex128)
|
|
467
|
+
* noise_std[None, None, :]
|
|
468
|
+
)
|
|
469
|
+
return vis + noise, noise
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# @jit_with_doc
|
|
473
|
+
def SEFD_to_noise_std(
|
|
474
|
+
SEFD: jnp.ndarray, chan_width: jnp.ndarray, int_time: jnp.ndarray
|
|
475
|
+
):
|
|
476
|
+
"""Calculate the standard deviation of the complex noise in a visibility
|
|
477
|
+
given the system equivalent flux density, the channel width and integration time.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
SEFD: ndarray (n_freq, )
|
|
482
|
+
System equivalent flux density in Jy.
|
|
483
|
+
chan_width: ndarray (n_time, n_ant, n_freq)
|
|
484
|
+
Channel width in Hz.
|
|
485
|
+
int_time: float
|
|
486
|
+
Integration time in seconds.
|
|
487
|
+
|
|
488
|
+
Returns
|
|
489
|
+
-------
|
|
490
|
+
noise_std: ndarray (n_time, n_ant, n_freq)
|
|
491
|
+
Standard deviation of the complex noise in a visibility.
|
|
492
|
+
"""
|
|
493
|
+
SEFD = jnp.asarray(SEFD)
|
|
494
|
+
chan_width = jnp.asarray(chan_width)
|
|
495
|
+
int_time = jnp.asarray(int_time)
|
|
496
|
+
return SEFD / jnp.sqrt(2 * chan_width * int_time)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
# @jit_with_doc
|
|
500
|
+
def int_sample_times(times: jnp.ndarray, n_int_samples: int = 1):
|
|
501
|
+
"""Calculate the times at which to sample the visibilities given the time centroids.
|
|
502
|
+
This shoudl produce `n_int_samples` times per integration time that are evenly
|
|
503
|
+
spaced around the time centroid.
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
times: ndarray (n_time, )
|
|
508
|
+
The time centroids at which to sample the visibilities.
|
|
509
|
+
n_int_samples: int
|
|
510
|
+
The number of samples to take per integration time.
|
|
511
|
+
|
|
512
|
+
Returns
|
|
513
|
+
-------
|
|
514
|
+
times_fine: ndarray (n_time * n_int_samples, )
|
|
515
|
+
The times at which to sample the visibilities.
|
|
516
|
+
"""
|
|
517
|
+
times = jnp.asarray(times)
|
|
518
|
+
n_int_samples = jnp.asarray(n_int_samples)
|
|
519
|
+
int_time = times[1] - times[0]
|
|
520
|
+
times_fine = (
|
|
521
|
+
int_time / (2 * n_int_samples)
|
|
522
|
+
+ jnp.arange(
|
|
523
|
+
times[0] - int_time / 2,
|
|
524
|
+
times[-1] + int_time / 2,
|
|
525
|
+
int_time / n_int_samples,
|
|
526
|
+
)[: n_int_samples * len(times)]
|
|
527
|
+
)
|
|
528
|
+
return times_fine
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# @jit_with_doc
|
|
532
|
+
def generate_gains(
|
|
533
|
+
G0_mean: complex,
|
|
534
|
+
G0_std: float,
|
|
535
|
+
Gt_std_amp: float,
|
|
536
|
+
Gt_std_phase: float,
|
|
537
|
+
times: jnp.ndarray,
|
|
538
|
+
n_ant: int,
|
|
539
|
+
n_freq: int,
|
|
540
|
+
key: jnp.ndarray,
|
|
541
|
+
):
|
|
542
|
+
"""
|
|
543
|
+
Generate complex antenna gains. Gain amplitudes and phases
|
|
544
|
+
are modelled as linear time-variates. Gains for all antennas at t = 0
|
|
545
|
+
are randomly sampled from a Gaussian described by the G0 parameters.
|
|
546
|
+
The rate of change of both ampltudes and phases are sampled from a zero
|
|
547
|
+
mean Gaussian with standard deviation as provided.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
G0_mean: complex
|
|
552
|
+
Mean of Gaussian at t = 0.
|
|
553
|
+
G0_std: float
|
|
554
|
+
Standard deviation of Gaussian at t = 0.
|
|
555
|
+
Gt_std_amp: float
|
|
556
|
+
Standard deviation of Gaussian describing the rate of change in the
|
|
557
|
+
gain amplitudes in 1/seconds.
|
|
558
|
+
Gt_std_phase: float
|
|
559
|
+
Standard deviation of Gaussian describing the rate of change in the
|
|
560
|
+
gain phases in rad/seconds.
|
|
561
|
+
key: jax.random.PRNGKey
|
|
562
|
+
Random number generator key.
|
|
563
|
+
"""
|
|
564
|
+
G0_mean = jnp.asarray(G0_mean)
|
|
565
|
+
G0_std = jnp.asarray(G0_std)
|
|
566
|
+
Gt_std_amp = jnp.asarray(Gt_std_amp)
|
|
567
|
+
Gt_std_phase = jnp.asarray(Gt_std_phase)
|
|
568
|
+
times = jnp.asarray(times) - jnp.asarray(times[0])
|
|
569
|
+
n_ant = jnp.asarray(n_ant)
|
|
570
|
+
n_freq = jnp.asarray(n_freq)
|
|
571
|
+
key = jnp.asarray(key)
|
|
572
|
+
G0 = G0_mean * jnp.exp(
|
|
573
|
+
1.0j * jnp.pi * (random.uniform(key, (1, n_ant, n_freq)) - 0.5)
|
|
574
|
+
)
|
|
575
|
+
key, subkey = random.split(key)
|
|
576
|
+
gains_noise = G0_std * random.normal(key, (n_ant, n_freq), dtype=jnp.complex128)
|
|
577
|
+
key, subkey = random.split(key)
|
|
578
|
+
|
|
579
|
+
gains_amp = Gt_std_amp * random.normal(key, (1, n_ant, 1)) * (times)[:, None, None]
|
|
580
|
+
key, subkey = random.split(key)
|
|
581
|
+
gains_phase = (
|
|
582
|
+
Gt_std_phase * random.normal(key, (1, n_ant, 1)) * (times)[:, None, None]
|
|
583
|
+
)
|
|
584
|
+
key, subkey = random.split(key)
|
|
585
|
+
gains_ants = G0 + gains_noise + gains_amp * jnp.exp(1.0j * gains_phase)
|
|
586
|
+
gains_ants = gains_ants.at[:, -1, :].set(jnp.abs(gains_ants[:, -1, :]))
|
|
587
|
+
return gains_ants
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
@jit_with_doc
|
|
591
|
+
def apply_gains(
|
|
592
|
+
vis_ast: jnp.ndarray,
|
|
593
|
+
vis_rfi: jnp.ndarray,
|
|
594
|
+
gains: jnp.ndarray,
|
|
595
|
+
a1: jnp.ndarray,
|
|
596
|
+
a2: jnp.ndarray,
|
|
597
|
+
):
|
|
598
|
+
"""Apply antenna gains to visibilities.
|
|
599
|
+
|
|
600
|
+
Parameters
|
|
601
|
+
----------
|
|
602
|
+
vis_ast: ndarray (n_time, n_bl, n_freq)
|
|
603
|
+
The astronomical visibilities.
|
|
604
|
+
vis_rfi: ndarray (n_time, n_bl, n_freq)
|
|
605
|
+
The RFI visibilities.
|
|
606
|
+
gains: ndarray (n_time, n_ant, n_freq)
|
|
607
|
+
The antenna gains.
|
|
608
|
+
a1: ndarray (n_bl,)
|
|
609
|
+
The first antenna index for each baseline.
|
|
610
|
+
a2: ndarray (n_bl,)
|
|
611
|
+
The second antenna index for each baseline.
|
|
612
|
+
|
|
613
|
+
Returns
|
|
614
|
+
-------
|
|
615
|
+
vis_obs: ndarray (n_time, n_bl, n_freq)
|
|
616
|
+
The visibilities with gains applied.
|
|
617
|
+
"""
|
|
618
|
+
vis_ast = jnp.asarray(vis_ast)
|
|
619
|
+
vis_rfi = jnp.asarray(vis_rfi)
|
|
620
|
+
gains = jnp.asarray(gains)
|
|
621
|
+
a1 = jnp.asarray(a1)
|
|
622
|
+
a2 = jnp.asarray(a2)
|
|
623
|
+
vis_obs = gains[:, a1] * (vis_ast + vis_rfi) * jnp.conj(gains[:, a2])
|
|
624
|
+
return vis_obs
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@partial(jit, static_argnums=(1,))
|
|
628
|
+
def time_avg(vis: jnp.ndarray, n_int_samples: int = 1):
|
|
629
|
+
"""Average visibilities in time.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
vis: ndarray (n_time_fine, n_bl, n_freq)
|
|
634
|
+
The visibilities to average in time.
|
|
635
|
+
n_int_samples: int
|
|
636
|
+
The number of samples to take per integration time.
|
|
637
|
+
|
|
638
|
+
Returns
|
|
639
|
+
-------
|
|
640
|
+
vis_avg: ndarray (n_time, n_bl, n_freq)
|
|
641
|
+
The averaged visibilities.
|
|
642
|
+
"""
|
|
643
|
+
vis = jnp.asarray(vis)
|
|
644
|
+
# n_int_samples = jnp.asarray(n_int_samples)
|
|
645
|
+
vis_avg = jnp.mean(
|
|
646
|
+
jnp.reshape(vis, (-1, n_int_samples, vis.shape[1], vis.shape[2])),
|
|
647
|
+
axis=1,
|
|
648
|
+
)
|
|
649
|
+
return vis_avg
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
# @jit_with_doc
|
|
653
|
+
def db_to_lin(dB: float):
|
|
654
|
+
"""
|
|
655
|
+
Convert deciBels to linear units.
|
|
656
|
+
|
|
657
|
+
Parameters
|
|
658
|
+
----------
|
|
659
|
+
dB: float, ndarray
|
|
660
|
+
deciBel value to convert.
|
|
661
|
+
|
|
662
|
+
Returns
|
|
663
|
+
-------
|
|
664
|
+
lin: float, ndarray
|
|
665
|
+
"""
|
|
666
|
+
dB = jnp.asarray(dB)
|
|
667
|
+
return 10.0 ** (dB / 10.0)
|