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.
@@ -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)