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,572 @@
1
+ import dask.array as da
2
+ import numpy as np
3
+ import xarray as xr
4
+ from dask.delayed import delayed
5
+ from dask.array.core import Array
6
+ from tabsim.jax import interferometry as itf
7
+
8
+
9
+ def astro_vis(sources: Array, uvw: Array, lmn: Array, freqs: Array) -> Array:
10
+ """Calculate visibilities from sources, uvw, lmn, and freqs.
11
+
12
+ Parameters:
13
+ -----------
14
+ sources: Array (n_src, n_time, n_freq)
15
+ Array of point source intensities in Jy.
16
+ uvw: Array (ntime, n_bl, 3)
17
+ (u,v,w) coordinates of each baseline.
18
+ lmn: Array (n_src, 3)
19
+ (l,m,n) coordinate of each source.
20
+ freqs: Array (n_freq,)
21
+ Frequencies in Hz.
22
+
23
+ Returns:
24
+ --------
25
+ vis: Array (n_time, n_bl, n_freq)
26
+ """
27
+ n_time, n_bl = uvw.shape[:2]
28
+ n_freq = freqs.shape[0]
29
+
30
+ time_chunk, bl_chunk = uvw.chunksize[:2]
31
+ freq_chunk = freqs.chunksize[0]
32
+
33
+ src_n_time = sources.shape[1]
34
+ if src_n_time != n_time and src_n_time != 1:
35
+ ValueError(
36
+ "The size of the time dimension for the astronomical sources must be n_time_fine or 1."
37
+ )
38
+
39
+ t = "time" if src_n_time is n_time else "time1"
40
+ I = {"I": (["src", f"{t}", "freq"], sources)}
41
+
42
+ input = xr.Dataset(
43
+ {
44
+ **I,
45
+ "uvw": (["time", "bl", "space"], uvw),
46
+ "lmn": (["src", "space"], lmn),
47
+ "freqs": (["freq"], freqs),
48
+ }
49
+ )
50
+ output = xr.Dataset(
51
+ {
52
+ "vis": (
53
+ ["time", "bl", "freq"],
54
+ da.zeros(
55
+ shape=(n_time, n_bl, n_freq),
56
+ chunks=(time_chunk, bl_chunk, freq_chunk),
57
+ dtype=complex,
58
+ ),
59
+ )
60
+ }
61
+ )
62
+
63
+ def _astro_vis(ds):
64
+ vis = delayed(itf.astro_vis)(
65
+ ds.I.data, ds.uvw.data, ds.lmn.data, ds.freqs.data
66
+ ).compute()
67
+ ds_out = xr.Dataset({"vis": (["time", "bl", "freq"], vis)})
68
+ return ds_out
69
+
70
+ ds = xr.map_blocks(_astro_vis, input, template=output)
71
+
72
+ return ds.vis.data
73
+
74
+
75
+ def astro_vis_gauss(
76
+ sources: Array,
77
+ major: Array,
78
+ minor: Array,
79
+ pos_angle: Array,
80
+ uvw: Array,
81
+ lmn: Array,
82
+ freqs: Array,
83
+ ) -> Array:
84
+ """Calculate visibilities from sources, uvw, lmn, and freqs.
85
+
86
+ Parameters:
87
+ -----------
88
+ sources: Array (n_src, n_time, n_freq)
89
+ Array of point source intensities in Jy.
90
+ shapes: array_like (n_src,)
91
+ Array of standard deviations of the gaussian shape sources. These are
92
+ assumed to be circular gaussians for now.
93
+ uvw: Array (ntime, n_bl, 3)
94
+ (u,v,w) coordinates of each baseline.
95
+ lmn: Array (n_src, 3)
96
+ (l,m,n) coordinate of each source.
97
+ freqs: Array (n_freq,)
98
+ Frequencies in Hz.
99
+
100
+ Returns:
101
+ --------
102
+ vis: Array (n_time, n_bl, n_freq)
103
+ """
104
+ n_time, n_bl = uvw.shape[:2]
105
+ n_freq = freqs.shape[0]
106
+
107
+ time_chunk, bl_chunk = uvw.chunksize[:2]
108
+ freq_chunk = freqs.chunksize[0]
109
+
110
+ input = xr.Dataset(
111
+ {
112
+ "I": (["src", "time", "freq"], sources),
113
+ "major": (["src"], major),
114
+ "minor": (["src"], minor),
115
+ "pos_angle": (["src"], pos_angle),
116
+ "uvw": (["time", "bl", "space"], uvw),
117
+ "lmn": (["src", "space"], lmn),
118
+ "freqs": (["freq"], freqs),
119
+ }
120
+ )
121
+ output = xr.Dataset(
122
+ {
123
+ "vis": (
124
+ ["time", "bl", "freq"],
125
+ da.zeros(
126
+ shape=(n_time, n_bl, n_freq),
127
+ chunks=(time_chunk, bl_chunk, freq_chunk),
128
+ dtype=complex,
129
+ ),
130
+ )
131
+ }
132
+ )
133
+
134
+ def _astro_vis_gauss(ds):
135
+ vis = delayed(itf.astro_vis_gauss)(
136
+ ds.I.data,
137
+ ds.major.data,
138
+ ds.minor.data,
139
+ ds.pos_angle.data,
140
+ ds.uvw.data,
141
+ ds.lmn.data,
142
+ ds.freqs.data,
143
+ ).compute()
144
+ ds_out = xr.Dataset({"vis": (["time", "bl", "freq"], vis)})
145
+ return ds_out
146
+
147
+ ds = xr.map_blocks(_astro_vis_gauss, input, template=output)
148
+
149
+ return ds.vis.data
150
+
151
+
152
+ def astro_vis_exp(
153
+ sources: Array, shapes: Array, uvw: Array, lmn: Array, freqs: Array
154
+ ) -> Array:
155
+ """Calculate visibilities from sources, uvw, lmn, and freqs.
156
+
157
+ Parameters:
158
+ -----------
159
+ sources: Array (n_src, n_time, n_freq)
160
+ Array of point source intensities in Jy.
161
+ shapes: array_like (n_src,)
162
+ Array of shape parameters for the exp sources. These are
163
+ assumed to be circular.
164
+ uvw: Array (ntime, n_bl, 3)
165
+ (u,v,w) coordinates of each baseline.
166
+ lmn: Array (n_src, 3)
167
+ (l,m,n) coordinate of each source.
168
+ freqs: Array (n_freq,)
169
+ Frequencies in Hz.
170
+
171
+ Returns:
172
+ --------
173
+ vis: Array (n_time, n_bl, n_freq)
174
+ """
175
+ n_time, n_bl = uvw.shape[:2]
176
+ n_freq = freqs.shape[0]
177
+
178
+ time_chunk, bl_chunk = uvw.chunksize[:2]
179
+ freq_chunk = freqs.chunksize[0]
180
+
181
+ input = xr.Dataset(
182
+ {
183
+ "I": (["src", "time", "freq"], sources),
184
+ "sigmas": (["src"], shapes),
185
+ "uvw": (["time", "bl", "space"], uvw),
186
+ "lmn": (["src", "space"], lmn),
187
+ "freqs": (["freq"], freqs),
188
+ }
189
+ )
190
+ output = xr.Dataset(
191
+ {
192
+ "vis": (
193
+ ["time", "bl", "freq"],
194
+ da.zeros(
195
+ shape=(n_time, n_bl, n_freq),
196
+ chunks=(time_chunk, bl_chunk, freq_chunk),
197
+ dtype=complex,
198
+ ),
199
+ )
200
+ }
201
+ )
202
+
203
+ def _astro_vis_exp(ds):
204
+ vis = delayed(itf.astro_vis_exp)(
205
+ ds.I.data, ds.sigmas.data, ds.uvw.data, ds.lmn.data, ds.freqs.data
206
+ ).compute()
207
+ ds_out = xr.Dataset({"vis": (["time", "bl", "freq"], vis)})
208
+ return ds_out
209
+
210
+ ds = xr.map_blocks(_astro_vis_exp, input, template=output)
211
+
212
+ return ds.vis.data
213
+
214
+
215
+ def rfi_vis(
216
+ app_amplitude: Array,
217
+ c_distances: Array,
218
+ freqs: Array,
219
+ a1: Array,
220
+ a2: Array,
221
+ ) -> Array:
222
+ """Calculate visibilities from sources, uvw, lmn, and freqs.
223
+
224
+ Parameters:
225
+ -----------
226
+ app_amplitude: Array (n_src, n_time, n_int, n_ant, n_freq)
227
+ Apparent amplitude of the sources at each antenna.
228
+ c_distances: Array (n_src, n_time, n_int, n_ant)
229
+ The phase corrected distances between the rfi sources and the antennas in metres.
230
+ freqs: Array (n_freq,)
231
+ Frequencies in Hz.
232
+ a1: Array (n_bl,)
233
+ Antenna 1 indexes, between 0 and n_ant-1.
234
+ a2: Array (n_bl,)
235
+ Antenna 2 indexes, between 0 and n_ant-1.
236
+
237
+ Returns:
238
+ --------
239
+ vis: Array (n_time, n_bl, n_freq)
240
+ """
241
+ n_time = app_amplitude.shape[1]
242
+ n_freq = freqs.shape[0]
243
+ n_bl = a1.shape[0]
244
+
245
+ time_chunk = app_amplitude.chunksize[1]
246
+ freq_chunk = freqs.chunksize[0]
247
+ bl_chunk = a1.chunksize[0]
248
+
249
+ input = xr.Dataset(
250
+ {
251
+ "app_amplitude": (["src", "time", "int", "ant", "freq"], app_amplitude),
252
+ "c_distances": (["src", "time", "int", "ant"], c_distances),
253
+ "freqs": (["freq"], freqs),
254
+ "a1": (["bl"], a1),
255
+ "a2": (["bl"], a2),
256
+ }
257
+ )
258
+ output = xr.Dataset(
259
+ {
260
+ "vis": (
261
+ ["time", "bl", "freq"],
262
+ da.zeros(
263
+ shape=(n_time, n_bl, n_freq),
264
+ chunks=(time_chunk, bl_chunk, freq_chunk),
265
+ dtype=complex,
266
+ ),
267
+ )
268
+ }
269
+ )
270
+
271
+ def _rfi_vis(ds):
272
+ vis = delayed(itf.rfi_vis)(
273
+ ds.app_amplitude.data,
274
+ ds.c_distances.data,
275
+ ds.freqs.data,
276
+ ds.a1.data,
277
+ ds.a2.data,
278
+ ).compute()
279
+ ds_out = xr.Dataset({"vis": (["time", "bl", "freq"], vis)})
280
+ return ds_out
281
+
282
+ ds = xr.map_blocks(_rfi_vis, input, template=output)
283
+
284
+ return ds.vis.data
285
+
286
+
287
+ def ants_to_bl(G: Array, a1: Array, a2: Array) -> Array:
288
+ n_time, _, n_freq = G.shape
289
+ n_bl = a1.shape[0]
290
+
291
+ time_chunk, _, freq_chunk = G.chunksize
292
+ bl_chunk = a1.chunksize[0]
293
+
294
+ input = xr.Dataset(
295
+ {"G": (["time", "ant", "freq"], G), "a1": (["bl"], a1), "a2": (["bl"], a2)}
296
+ )
297
+
298
+ output = xr.Dataset(
299
+ {
300
+ "G_bl": (
301
+ ["time", "bl", "freq"],
302
+ da.zeros(
303
+ (n_time, n_bl, n_freq), chunks=(time_chunk, bl_chunk, freq_chunk)
304
+ ),
305
+ )
306
+ }
307
+ )
308
+
309
+ def _ants_to_bl(ds):
310
+ G_bl = delayed(itf.ants_to_bl)(ds.G.data, ds.a1.data, ds.a2.data).compute()
311
+ ds_out = xr.Dataset({"G_bl": (["time", "bl", "freq"], G_bl)})
312
+ return ds_out
313
+
314
+ ds = xr.map_blocks(_ants_to_bl, input, template=output)
315
+
316
+ return ds.G_bl.data
317
+
318
+
319
+ ants_to_bl.__doc__ = itf.ants_to_bl.__doc__
320
+
321
+
322
+ def airy_beam(theta, freqs, dish_d):
323
+ n_src, n_time, n_ant = theta.shape
324
+ n_freq = freqs.shape[0]
325
+
326
+ src_chunk, time_chunk, ant_chunk = theta.chunksize
327
+ freq_chunk = freqs.chunksize[0]
328
+
329
+ input = xr.Dataset(
330
+ {
331
+ "theta": (["src", "time", "ant"], theta),
332
+ "freqs": (["freq"], freqs),
333
+ "dish_d": (["space_0"], da.from_array([dish_d])),
334
+ }
335
+ )
336
+
337
+ output = xr.Dataset(
338
+ {
339
+ "beam": (
340
+ ["src", "time", "ant", "freq"],
341
+ da.zeros(
342
+ (n_src, n_time, n_ant, n_freq),
343
+ chunks=(src_chunk, time_chunk, ant_chunk, freq_chunk),
344
+ ),
345
+ )
346
+ }
347
+ )
348
+
349
+ def _airy_beam(ds):
350
+ beam = delayed(itf.airy_beam)(
351
+ ds.theta.data, ds.freqs.data, ds.dish_d.data
352
+ ).compute()
353
+ ds_out = xr.Dataset({"beam": (["src", "time", "ant", "freq"], beam)})
354
+ return ds_out
355
+
356
+ ds = xr.map_blocks(_airy_beam, input, template=output)
357
+
358
+ return ds.beam.data
359
+
360
+
361
+ airy_beam.__doc__ = itf.airy_beam.__doc__
362
+
363
+
364
+ def Pv_to_Sv(Pv, d):
365
+ n_src, _, n_freq = Pv.shape
366
+ n_time, n_ant = d.shape[1:]
367
+
368
+ src_chunk, _, freq_chunk = Pv.chunksize
369
+ time_chunk, ant_chunk = d.chunksize[1:]
370
+
371
+ input = xr.Dataset(
372
+ {"Pv": (["src", "time", "freq"], Pv), "d": (["src", "time", "ant"], d)}
373
+ )
374
+
375
+ output = xr.Dataset(
376
+ {
377
+ "Sv": (
378
+ ["src", "time", "ant", "freq"],
379
+ da.zeros(
380
+ (n_src, n_time, n_ant, n_freq),
381
+ chunks=(src_chunk, time_chunk, ant_chunk, freq_chunk),
382
+ ),
383
+ )
384
+ }
385
+ )
386
+
387
+ def _Pv_to_Sv(ds):
388
+ Sv = delayed(itf.Pv_to_Sv)(ds.Pv.data, ds.d.data).compute()
389
+ ds_out = xr.Dataset({"Sv": (["src", "time", "ant", "freq"], Sv)})
390
+ return ds_out
391
+
392
+ ds = xr.map_blocks(_Pv_to_Sv, input, template=output)
393
+
394
+ return ds.Sv.data
395
+
396
+
397
+ Pv_to_Sv.__doc__ = itf.Pv_to_Sv.__doc__
398
+
399
+
400
+ def add_noise(vis: Array, noise_std: float, key: int):
401
+ rng = np.random.default_rng(key)
402
+ noise = rng.normal(0, noise_std, size=vis.shape) + 1.0j * rng.normal(
403
+ 0, noise_std, size=vis.shape
404
+ )
405
+ return vis + noise, noise
406
+
407
+
408
+ def SEFD_to_noise_std(SEFD, chan_width, t_int):
409
+ noise_std = SEFD / da.sqrt(chan_width * t_int)
410
+ return noise_std
411
+
412
+
413
+ # def int_sample_times(times, n_int_samples, int_time):
414
+ # times_fine = da.from_array(
415
+ # itf.int_sample_times(times, n_int_samples, int_time), chunks=times.chunksize
416
+ # )
417
+ # return times_fine
418
+
419
+
420
+ def int_sample_times(times, n_int_samples: int, int_time: float = None):
421
+
422
+ n_time = len(times)
423
+ time_range = times[-1] - times[0]
424
+ int_time = time_range / (n_time - 1) if not int_time else int_time
425
+ n_time_fine = n_time * n_int_samples
426
+ times_fine = da.linspace(
427
+ -int_time / 2, time_range + int_time / 2, n_time_fine, endpoint=False
428
+ )
429
+
430
+ return times[0] + int_time / (2 * n_int_samples) + times_fine
431
+
432
+
433
+ def generate_gains(
434
+ G0_mean: float,
435
+ G0_std: float,
436
+ Gt_std_amp: float,
437
+ Gt_std_phase: float,
438
+ times: np.ndarray,
439
+ n_ant: int,
440
+ n_freq: int,
441
+ random_seed: int,
442
+ ) -> Array:
443
+ rng = np.random.default_rng(random_seed)
444
+ times = times[:, None, None] - times[0]
445
+
446
+ # Generate the initial gain values
447
+ G0 = G0_mean * da.exp(
448
+ 1.0j * rng.uniform(low=-np.pi / 2, high=np.pi / 2, size=(1, n_ant, n_freq))
449
+ ) + (
450
+ rng.normal(scale=G0_std, size=(1, n_ant, n_freq))
451
+ + 1.0j * rng.normal(scale=G0_std, size=(1, n_ant, n_freq))
452
+ )
453
+
454
+ # Generate the gain variations
455
+ gain_amp = rng.normal(scale=Gt_std_amp, size=(1, n_ant, 1)) * times
456
+ gain_phase = rng.normal(scale=Gt_std_phase, size=(1, n_ant, 1)) * times
457
+ # Generate the gain time series
458
+ gain_ants = G0 + gain_amp * da.exp(1.0j * gain_phase)
459
+ # Set the gain on the last antenna to have zero phase (reference antenna)
460
+ gain_ants[:, -1, :] = da.abs(gain_ants[:, -1, :])
461
+
462
+ return gain_ants
463
+
464
+
465
+ def generate_fourier_gains(
466
+ G0_mean: float,
467
+ G0_std: float,
468
+ Gt_std_amp: float,
469
+ Gt_std_phase: float,
470
+ Gt_corr_amp: float,
471
+ Gt_corr_phase: float,
472
+ times_jd: np.ndarray,
473
+ n_ant: int,
474
+ n_freq: int,
475
+ random_seed: int,
476
+ ) -> Array:
477
+
478
+ rng = np.random.default_rng(random_seed)
479
+ times = times_jd[None, :, None, None] * 24
480
+
481
+ # Generate the initial gain values
482
+ G0 = G0_mean * da.exp(
483
+ 1.0j * rng.uniform(low=-np.pi / 2, high=np.pi / 2, size=(1, n_ant, n_freq))
484
+ ) + (
485
+ rng.normal(scale=G0_std / 100, size=(1, n_ant, n_freq))
486
+ + 1.0j * rng.normal(scale=G0_std / 100, size=(1, n_ant, n_freq))
487
+ )
488
+
489
+ N = 1000
490
+ mode_size = (N, 1, n_ant, 1)
491
+
492
+ # Generate the gain variations
493
+ gain_amp = np.mean(
494
+ rng.normal(scale=Gt_std_amp / 100, size=mode_size)
495
+ * np.cos(
496
+ 2
497
+ * np.pi
498
+ * rng.normal(scale=1 / (2 * np.pi * Gt_corr_amp), size=mode_size)
499
+ * times
500
+ + rng.uniform(low=0.0, high=2 * np.pi, size=mode_size)
501
+ ),
502
+ axis=0,
503
+ ) * np.sqrt(2 * N)
504
+ gain_phase = np.mean(
505
+ rng.normal(scale=np.deg2rad(Gt_std_phase), size=mode_size)
506
+ * np.cos(
507
+ 2
508
+ * np.pi
509
+ * rng.normal(scale=1 / (2 * np.pi * Gt_corr_phase), size=mode_size)
510
+ * times
511
+ + rng.uniform(low=0.0, high=2 * np.pi, size=mode_size)
512
+ ),
513
+ axis=0,
514
+ ) * np.sqrt(2 * N)
515
+ # Generate the gain time series
516
+ gain_ants = G0 + gain_amp * da.exp(1.0j * gain_phase)
517
+ # Set the gain on the last antenna to have zero phase (reference antenna)
518
+ gain_ants[:, -1, :] = da.abs(gain_ants[:, -1, :])
519
+
520
+ return gain_ants
521
+
522
+
523
+ def apply_gains(
524
+ vis_ast: Array,
525
+ vis_rfi: Array,
526
+ gains: Array,
527
+ a1: Array,
528
+ a2: Array,
529
+ ) -> Array:
530
+ n_time, n_bl, n_freq = vis_ast.shape
531
+
532
+ time_chunk, bl_chunk, freq_chunk = vis_ast.chunksize
533
+
534
+ input = xr.Dataset(
535
+ {
536
+ "vis_ast": (["time", "bl", "freq"], vis_ast),
537
+ "vis_rfi": (["time", "bl", "freq"], vis_rfi),
538
+ "gains": (["time", "ant", "freq"], gains),
539
+ "a1": (["bl"], a1),
540
+ "a2": (["bl"], a2),
541
+ }
542
+ )
543
+
544
+ output = xr.Dataset(
545
+ {
546
+ "vis_obs": (
547
+ ["time", "bl", "freq"],
548
+ da.zeros(
549
+ (n_time, n_bl, n_freq),
550
+ chunks=(time_chunk, bl_chunk, freq_chunk),
551
+ dtype=vis_ast.dtype,
552
+ ),
553
+ )
554
+ }
555
+ )
556
+
557
+ def _apply_gains(ds):
558
+ vis_obs = delayed(itf.apply_gains)(
559
+ ds.vis_ast.data, ds.vis_rfi.data, ds.gains.data, ds.a1.data, ds.a2.data
560
+ ).compute()
561
+ ds_out = xr.Dataset({"vis_obs": (["time", "bl", "freq"], vis_obs)})
562
+ return ds_out
563
+
564
+ ds = xr.map_blocks(_apply_gains, input, template=output)
565
+
566
+ return ds.vis_obs.data
567
+
568
+
569
+ def time_avg(vis, n_int_samples):
570
+ return da.mean(
571
+ da.reshape(vis, (-1, n_int_samples, vis.shape[1], vis.shape[2])), axis=1
572
+ )