6
6
import astropy .units as un
7
7
import torch
8
8
from astropy .constants import c
9
- from astropy .coordinates import AltAz , Angle , EarthLocation , SkyCoord , Longitude
9
+ from astropy .coordinates import AltAz , Angle , EarthLocation , Longitude , SkyCoord
10
10
from astropy .time import Time
11
11
from tqdm .autonotebook import tqdm
12
12
13
13
from pyvisgen .layouts import layouts
14
14
from pyvisgen .simulation .array import Array
15
15
16
-
17
16
DEFAULT_POL_KWARGS = {
18
17
"delta" : 0 ,
19
18
"amp_ratio" : 0.5 ,
30
29
31
30
@dataclass
32
31
class Baselines :
32
+ """Baselines dataclass.
33
+
34
+ Attributes
35
+ ----------
36
+ st1 : :func:`~torch.tensor`
37
+ st2 : :func:`~torch.tensor`
38
+ u : :func:`~torch.tensor`
39
+ v : :func:`~torch.tensor`
40
+ w : :func:`~torch.tensor`
41
+ valid : :func:`~torch.tensor`
42
+ time : :func:`~torch.tensor`
43
+ q1 : :func:`~torch.tensor`
44
+ q2 : :func:`~torch.tensor`
45
+ """
46
+
33
47
st1 : torch .tensor
34
48
st2 : torch .tensor
35
49
u : torch .tensor
@@ -112,6 +126,30 @@ def get_valid_subset(self, num_baselines, device):
112
126
113
127
@dataclass ()
114
128
class ValidBaselineSubset :
129
+ """Valid baselines subset dataclass.
130
+
131
+ Attributes
132
+ ----------
133
+ u_start : :func:`~torch.tensor`
134
+ u_stop : :func:`~torch.tensor`
135
+ u_valid : :func:`~torch.tensor`
136
+ v_start : :func:`~torch.tensor`
137
+ v_stop : :func:`~torch.tensor`
138
+ v_valid : :func:`~torch.tensor`
139
+ w_start : :func:`~torch.tensor`
140
+ w_stop : :func:`~torch.tensor`
141
+ w_valid : :func:`~torch.tensor`
142
+ baseline_nums : :func:`~torch.tensor`
143
+ date : :func:`~torch.tensor`
144
+ q1_start : :func:`~torch.tensor`
145
+ q1_stop : :func:`~torch.tensor`
146
+ q1_valid : :func:`~torch.tensor`
147
+ q2_start : :func:`~torch.tensor`
148
+ q2_stop : :func:`~torch.tensor`
149
+ q2_valid : :func:`~torch.tensor`
150
+
151
+ """
152
+
115
153
u_start : torch .tensor
116
154
u_stop : torch .tensor
117
155
u_valid : torch .tensor
@@ -207,6 +245,74 @@ def _lexsort(self, a, dim=-1):
207
245
208
246
209
247
class Observation :
248
+ """Main observation simulation class.
249
+ The :class:`~pyvisgen.simulation.Observation` class
250
+ simulates the baselines and time steps during the
251
+ observation.
252
+
253
+ Parameters
254
+ ----------
255
+ src_ra : float
256
+ Source right ascension coordinate.
257
+ src_dec : float
258
+ Source declination coordinate.
259
+ start_time : datetime
260
+ Observation start time.
261
+ scan_duration : int
262
+ Scan duration.
263
+ num_scans : int
264
+ Number of scans.
265
+ scan_separation : int
266
+ Scan separation.
267
+ integration_time : int
268
+ Integration time.
269
+ ref_frequency : float
270
+ Reference frequency.
271
+ frequency_offsets : list
272
+ Frequency offsets.
273
+ bandwidths : list
274
+ Frequency bandwidth.
275
+ fov : float
276
+ Field of view.
277
+ image_size : int
278
+ Image size of the sky distribution.
279
+ array_layout : str
280
+ Name of an existing array layout. See :mod:`~pyvisgen.layouts`.
281
+ corrupted : bool
282
+ If ``True``, apply corruption during the vis loop.
283
+ device : str
284
+ Torch device to select for computation.
285
+ dense : bool, optional
286
+ If ``True``, apply dense baseline calculation of a perfect
287
+ interferometer. Default: ``False``
288
+ sensitivity_cut : float, optional
289
+ Sensitivity threshold, where only pixels above the value
290
+ are kept. Default: ``1e-6``
291
+ polarisation : str, optional
292
+ Choose between ``'linear'`` or ``'circular'`` or ``None`` to
293
+ simulate different types of polarisations or disable
294
+ the simulation of polarisation. Default: ``None``
295
+ pol_kwargs : dict, optional
296
+ Additional keyword arguments for the simulation
297
+ of polarisation. Default:
298
+ ``{'delta': 0,'amp_ratio': 0.5,'random_state': 42}``
299
+ field_kwargs : dict, optional
300
+ Additional keyword arguments for the random polarisation
301
+ field that is applied when simulating polarisation.
302
+ Default:
303
+ ``{'order': [1, 1],'scale': [0, 1],'threshold': None,'random_state': 42}``
304
+ show_progress : bool, optional
305
+ If ``True``, show a progress bar during the iteration over the
306
+ scans. Default: ``False``
307
+
308
+ Notes
309
+ -----
310
+ See :class:`~pyvisgen.simulation.Polarisation` and
311
+ :class:`~pyvisgen.simulation.Polarisation.rand_polarisation_field`
312
+ for more information on the keyword arguments in ``pol_kwargs``
313
+ and ``field_kwargs``, respectively.
314
+ """
315
+
210
316
def __init__ (
211
317
self ,
212
318
src_ra : float ,
@@ -260,21 +366,21 @@ def __init__(
260
366
image_size : int
261
367
Image size of the sky distribution.
262
368
array_layout : str
263
- Name of an existing array layout. See `~pyvisgen.layouts`.
369
+ Name of an existing array layout. See :mod: `~pyvisgen.layouts`.
264
370
corrupted : bool
265
- If `True`, apply corruption during the vis loop.
371
+ If `` True` `, apply corruption during the vis loop.
266
372
device : str
267
373
Torch device to select for computation.
268
374
dense : bool, optional
269
- If `True`, apply dense baseline calculation of a perfect
270
- interferometer. Default: `False`
375
+ If `` True` `, apply dense baseline calculation of a perfect
376
+ interferometer. Default: `` False` `
271
377
sensitivity_cut : float, optional
272
378
Sensitivity threshold, where only pixels above the value
273
- are kept. Default: 1e-6
379
+ are kept. Default: `` 1e-6``
274
380
polarisation : str, optional
275
- Choose between `'linear'` or `'circular'` or `None` to
381
+ Choose between `` 'linear'`` or `` 'circular'`` or `` None` ` to
276
382
simulate different types of polarisations or disable
277
- the simulation of polarisation. Default: `None`
383
+ the simulation of polarisation. Default: `` None` `
278
384
pol_kwargs : dict, optional
279
385
Additional keyword arguments for the simulation
280
386
of polarisation. Default: `{
@@ -292,15 +398,15 @@ def __init__(
292
398
"random_state": 42
293
399
}`
294
400
show_progress : bool, optional
295
- If `True`, show a progress bar during the iteration over the
296
- scans. Default: False
401
+ If `` True` `, show a progress bar during the iteration over the
402
+ scans. Default: `` False``
297
403
298
404
Notes
299
405
-----
300
- See `~pyvisgen.simulation.visibility .Polarisation` and
301
- `~pyvisgen.simulation.visibility .Polarisation.rand_polarisation_field`
302
- for more information on the keyword arguments in `pol_kwargs`
303
- and `field_kwargs`, respectively.
406
+ See :class: `~pyvisgen.simulation.Polarisation` and
407
+ :class: `~pyvisgen.simulation.Polarisation.rand_polarisation_field`
408
+ for more information on the keyword arguments in `` pol_kwargs` `
409
+ and `` field_kwargs` `, respectively.
304
410
"""
305
411
self .ra = torch .tensor (src_ra ).double ()
306
412
self .dec = torch .tensor (src_dec ).double ()
@@ -396,6 +502,10 @@ def calc_time_steps(self):
396
502
return time , time .mjd * (60 * 60 * 24 )
397
503
398
504
def calc_dense_baselines (self ):
505
+ """Calculates the baselines of a densely-built
506
+ antenna array, which would provide full coverage of the
507
+ uv space.
508
+ """
399
509
N = self .img_size
400
510
fov = self .fov * pi / (3600 * 180 )
401
511
delta = fov ** (- 1 ) * c .value / self .ref_frequency
@@ -437,9 +547,11 @@ def calc_dense_baselines(self):
437
547
)
438
548
439
549
def calc_baselines (self ):
440
- """Initializes Baselines dataclass object and
441
- calls self.get_baselines to compute the contents of
442
- the Baselines dataclass.
550
+ """Initializes :class:`~pyvisgen.simulation.Baselines`
551
+ dataclass object and calls
552
+ :py:func:`~pyvisgen.simulation.Observation.get_baselines`
553
+ to compute the contents of the :class:`~pyvisgen.simulation.Baselines`
554
+ dataclass.
443
555
"""
444
556
self .baselines = Baselines (
445
557
torch .tensor ([]), # st1
@@ -534,7 +646,22 @@ def get_baselines(self, times):
534
646
535
647
return baselines
536
648
537
- def calc_ref_elev (self , time = None ):
649
+ def calc_ref_elev (self , time = None ) -> tuple :
650
+ """Calculates the station elevations for given
651
+ time steps.
652
+
653
+ Parameters
654
+ ----------
655
+ time : array_like or None, optional
656
+ Array containing observation time steps.
657
+ Default: ``None``
658
+
659
+ Returns
660
+ -------
661
+ tuple
662
+ Tuple containing tensors of the Greenwich hour angle,
663
+ antenna-local hour angles, and the elevations.
664
+ """
538
665
if time is None :
539
666
time = self .times
540
667
if time .shape == ():
@@ -592,7 +719,8 @@ def calc_feed_rotation(self, ha: Angle) -> Angle:
592
719
593
720
.. math::
594
721
595
- q = \atan\left(\frac{\sin h}{\cos\delta \tan\varphi - \sin\delta \cos h\right),
722
+ q = \atan\left(\frac{\sin h}{\cos\delta
723
+ \tan\varphi - \sin\delta \cos h\right),
596
724
597
725
where $h$ is the local hour angle, $\varphi$ the geographical latitude
598
726
of the observer, and $\delta$ the declination of the source.
@@ -612,30 +740,6 @@ def calc_feed_rotation(self, ha: Angle) -> Angle:
612
740
613
741
return q
614
742
615
- def calc_direction_cosines (self , ha , el_st , delta_x , delta_y , delta_z ):
616
- src_dec = torch .deg2rad (self .dec )
617
- ha = torch .deg2rad (ha )
618
-
619
- u = (torch .sin (ha ) * delta_x + torch .cos (ha ) * delta_y ).reshape (- 1 )
620
- v = (
621
- - torch .sin (src_dec ) * torch .cos (ha ) * delta_x
622
- + torch .sin (src_dec ) * torch .sin (ha ) * delta_y
623
- + torch .cos (src_dec ) * delta_z
624
- ).reshape (- 1 )
625
- w = (
626
- torch .cos (src_dec ) * torch .cos (ha ) * delta_x
627
- - torch .cos (src_dec ) * torch .sin (ha ) * delta_y
628
- + torch .sin (src_dec ) * delta_z
629
- ).reshape (- 1 )
630
-
631
- if not (u .shape == v .shape == w .shape ):
632
- raise ValueError (
633
- "Expected u, v, and w to have the same shapes: "
634
- f"{ u .shape } , { v .shape } , { w .shape } "
635
- )
636
-
637
- return u , v , w
638
-
639
743
def create_rd_grid (self ):
640
744
"""Calculates RA and Dec values for a given fov around a source position
641
745
0 commit comments