fftvis 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.
- fftvis/__init__.py +1 -0
- fftvis/beams.py +75 -0
- fftvis/simulate.py +330 -0
- fftvis/tests/__init__.py +165 -0
- fftvis/tests/test_compare_matvis.py +122 -0
- fftvis/tests/test_compare_pyuvsim.py +129 -0
- fftvis/tests/test_utils.py +6 -0
- fftvis/utils.py +57 -0
- fftvis-0.0.1.dist-info/METADATA +76 -0
- fftvis-0.0.1.dist-info/RECORD +12 -0
- fftvis-0.0.1.dist-info/WHEEL +5 -0
- fftvis-0.0.1.dist-info/top_level.txt +1 -0
fftvis/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import beams, utils, simulate
|
fftvis/beams.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from pyuvdata import UVBeam
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _evaluate_beam(
|
|
6
|
+
A_s: np.ndarray,
|
|
7
|
+
beam: UVBeam,
|
|
8
|
+
az: np.ndarray,
|
|
9
|
+
za: np.ndarray,
|
|
10
|
+
polarized: bool,
|
|
11
|
+
freq: float,
|
|
12
|
+
check: bool = False,
|
|
13
|
+
spline_opts: dict = None,
|
|
14
|
+
):
|
|
15
|
+
"""Evaluate the beam on the CPU. Simplified version of the `_evaluate_beam_cpu` function
|
|
16
|
+
in matvis.
|
|
17
|
+
|
|
18
|
+
This function will either interpolate the beam to the given coordinates tx, ty,
|
|
19
|
+
or evaluate the beam there if it is an analytic beam.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
A_s
|
|
24
|
+
Array of shape (nax, nfeed, nsrcs_up) that will be filled with beam
|
|
25
|
+
values.
|
|
26
|
+
beam
|
|
27
|
+
UVBeam object to evaluate.
|
|
28
|
+
tx, ty
|
|
29
|
+
Coordinates to evaluate the beam at, in sin-projection.
|
|
30
|
+
polarized
|
|
31
|
+
Whether to use beam polarization.
|
|
32
|
+
freq
|
|
33
|
+
Frequency to interpolate beam to.
|
|
34
|
+
check
|
|
35
|
+
Whether to check that the beam has no inf/nan values. Set to False if you are
|
|
36
|
+
sure that the beam is valid, as it will be faster.
|
|
37
|
+
spline_opts
|
|
38
|
+
Extra options to pass to the RectBivariateSpline class when interpolating.
|
|
39
|
+
"""
|
|
40
|
+
# Primary beam pattern using direct interpolation of UVBeam object
|
|
41
|
+
kw = (
|
|
42
|
+
{
|
|
43
|
+
"reuse_spline": True,
|
|
44
|
+
"check_azza_domain": False,
|
|
45
|
+
"spline_opts": spline_opts,
|
|
46
|
+
}
|
|
47
|
+
if isinstance(beam, UVBeam)
|
|
48
|
+
else {}
|
|
49
|
+
)
|
|
50
|
+
if isinstance(beam, UVBeam) and not beam.future_array_shapes:
|
|
51
|
+
beam.use_future_array_shapes()
|
|
52
|
+
|
|
53
|
+
interp_beam = beam.interp(
|
|
54
|
+
az_array=az,
|
|
55
|
+
za_array=za,
|
|
56
|
+
freq_array=np.atleast_1d(freq),
|
|
57
|
+
**kw,
|
|
58
|
+
)[0]
|
|
59
|
+
|
|
60
|
+
if polarized:
|
|
61
|
+
interp_beam = interp_beam[:, :, 0, :]
|
|
62
|
+
else:
|
|
63
|
+
# Here we have already asserted that the beam is a power beam and
|
|
64
|
+
# has only one polarization, so we just evaluate that one.
|
|
65
|
+
interp_beam = np.sqrt(interp_beam[0, 0, 0, :])
|
|
66
|
+
|
|
67
|
+
A_s[:, :] = interp_beam
|
|
68
|
+
|
|
69
|
+
# Check for invalid beam values
|
|
70
|
+
if check:
|
|
71
|
+
sm = np.sum(A_s)
|
|
72
|
+
if np.isinf(sm) or np.isnan(sm):
|
|
73
|
+
raise ValueError("Beam interpolation resulted in an invalid value")
|
|
74
|
+
|
|
75
|
+
return A_s
|
fftvis/simulate.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import finufft
|
|
4
|
+
import numpy as np
|
|
5
|
+
from matvis import conversions
|
|
6
|
+
|
|
7
|
+
from . import utils, beams
|
|
8
|
+
|
|
9
|
+
# Default accuracy for the non-uniform fast fourier transform based on precision
|
|
10
|
+
default_accuracy_dict = {
|
|
11
|
+
1: 6e-8,
|
|
12
|
+
2: 1e-13,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def simulate_vis(
|
|
17
|
+
ants: dict,
|
|
18
|
+
fluxes: np.ndarray,
|
|
19
|
+
ra: np.ndarray,
|
|
20
|
+
dec: np.ndarray,
|
|
21
|
+
freqs: np.ndarray,
|
|
22
|
+
lsts: np.ndarray,
|
|
23
|
+
beam,
|
|
24
|
+
baselines: list[tuple] = None,
|
|
25
|
+
precision: int = 2,
|
|
26
|
+
polarized: bool = False,
|
|
27
|
+
latitude: float = -0.5361913261514378,
|
|
28
|
+
eps: float = None,
|
|
29
|
+
use_feed: str = "x",
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Parameters:
|
|
33
|
+
----------
|
|
34
|
+
ants : dict
|
|
35
|
+
Dictionary of antenna positions
|
|
36
|
+
fluxes : np.ndarray
|
|
37
|
+
Intensity distribution of sources/pixels on the sky, assuming intensity
|
|
38
|
+
(Stokes I) only. The Stokes I intensity will be split equally between
|
|
39
|
+
the two linear polarization channels, resulting in a factor of 0.5 from
|
|
40
|
+
the value inputted here. This is done even if only one polarization
|
|
41
|
+
channel is simulated.
|
|
42
|
+
ra, dec : array_like
|
|
43
|
+
Arrays of source RA and Dec positions in radians. RA goes from [0, 2 pi]
|
|
44
|
+
and Dec from [-pi, +pi].
|
|
45
|
+
freqs : np.ndarray
|
|
46
|
+
Frequencies to evaluate visibilities in Hz.
|
|
47
|
+
lsts : np.ndarray
|
|
48
|
+
Local sidereal time in radians. Range is [0, 2 pi].
|
|
49
|
+
beam : UVBeam
|
|
50
|
+
Beam object to use for the array. Per-antenna beams are not yet supported.
|
|
51
|
+
baselines : list of tuples, default = None
|
|
52
|
+
If provided, only the baselines within the list will be simulated and array of shape
|
|
53
|
+
(nbls, nfreqs, ntimes) will be returned if polarized is False, and (nbls, nfreqs, ntimes, 2, 2) if polarized is True.
|
|
54
|
+
precision : int, optional
|
|
55
|
+
Which precision level to use for floats and complex numbers
|
|
56
|
+
Allowed values:
|
|
57
|
+
- 1: float32, complex64
|
|
58
|
+
- 2: float64, complex128
|
|
59
|
+
polarized : bool, optional
|
|
60
|
+
Whether to simulate polarized visibilities. If True, the output will have
|
|
61
|
+
shape (nfreqs, ntimes, 2, 2, nants, nants), and if False, the output will
|
|
62
|
+
have shape (nfreqs, ntimes, nants, nants).
|
|
63
|
+
latitude : float, optional
|
|
64
|
+
Latitude of the array in radians. The default is the
|
|
65
|
+
HERA latitude = -30.7215 * pi / 180.
|
|
66
|
+
eps : float, default = None
|
|
67
|
+
Desired accuracy of the non-uniform fast fourier transform. If None, the default accuracy
|
|
68
|
+
for the given precision will be used. For precision 1, the default accuracy is 6e-8, and for
|
|
69
|
+
precision 2, the default accuracy is 1e-12.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
-------
|
|
73
|
+
vis : np.ndarray
|
|
74
|
+
Array of shape (nfreqs, ntimes, nants, nants) if polarized is False, and
|
|
75
|
+
(nfreqs, ntimes, 2, 2, nants, nants) if polarized is True.
|
|
76
|
+
"""
|
|
77
|
+
# Get the accuracy for the given precision if not provided
|
|
78
|
+
if eps is None:
|
|
79
|
+
eps = default_accuracy_dict[precision]
|
|
80
|
+
|
|
81
|
+
# Source coordinate transform, from equatorial to Cartesian
|
|
82
|
+
crd_eq = conversions.point_source_crd_eq(ra, dec)
|
|
83
|
+
|
|
84
|
+
# Make sure antpos has the right format
|
|
85
|
+
ants = {k: np.array(v) for k, v in ants.items()}
|
|
86
|
+
|
|
87
|
+
# Get coordinate transforms as a function of LST
|
|
88
|
+
eq2tops = np.array([conversions.eci_to_enu_matrix(lst, latitude) for lst in lsts])
|
|
89
|
+
|
|
90
|
+
return simulate(
|
|
91
|
+
ants=ants,
|
|
92
|
+
freqs=freqs,
|
|
93
|
+
fluxes=fluxes,
|
|
94
|
+
beam=beam,
|
|
95
|
+
crd_eq=crd_eq,
|
|
96
|
+
eq2tops=eq2tops,
|
|
97
|
+
baselines=baselines,
|
|
98
|
+
precision=precision,
|
|
99
|
+
polarized=polarized,
|
|
100
|
+
eps=eps,
|
|
101
|
+
use_feed=use_feed,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def simulate(
|
|
106
|
+
ants: dict,
|
|
107
|
+
freqs: np.ndarray,
|
|
108
|
+
fluxes: np.ndarray,
|
|
109
|
+
beam,
|
|
110
|
+
crd_eq: np.ndarray,
|
|
111
|
+
eq2tops: np.ndarray,
|
|
112
|
+
baselines: list[tuple] = None,
|
|
113
|
+
precision: int = 2,
|
|
114
|
+
polarized: bool = False,
|
|
115
|
+
eps: float = 1e-13,
|
|
116
|
+
use_feed: str = "x",
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Parameters:
|
|
120
|
+
----------
|
|
121
|
+
ants : dict
|
|
122
|
+
Dictionary of antenna positions in the form {ant_index: np.array([x,y,z])}.
|
|
123
|
+
freqs : np.ndarray
|
|
124
|
+
Frequencies to evaluate visibilities at in Hz.
|
|
125
|
+
fluxes : np.ndarray
|
|
126
|
+
Intensity distribution of sources/pixels on the sky, assuming intensity
|
|
127
|
+
(Stokes I) only. The Stokes I intensity will be split equally between
|
|
128
|
+
the two linear polarization channels, resulting in a factor of 0.5 from
|
|
129
|
+
the value inputted here. This is done even if only one polarization
|
|
130
|
+
channel is simulated.
|
|
131
|
+
beam : UVBeam
|
|
132
|
+
Beam object to use for the array. Per-antenna beams are not yet supported.
|
|
133
|
+
crd_eq : np.ndarray
|
|
134
|
+
Cartesian unit vectors of sources in an ECI (Earth Centered
|
|
135
|
+
Inertial) system, which has the Earth's center of mass at
|
|
136
|
+
the origin, and is fixed with respect to the distant stars.
|
|
137
|
+
The components of the ECI vector for each source are:
|
|
138
|
+
(cos(RA) cos(Dec), sin(RA) cos(Dec), sin(Dec)).
|
|
139
|
+
Shape=(3, NSRCS).
|
|
140
|
+
eq2tops : np.ndarray
|
|
141
|
+
Set of 3x3 transformation matrices to rotate the RA and Dec
|
|
142
|
+
cosines in an ECI coordinate system (see `crd_eq`) to
|
|
143
|
+
topocentric ENU (East-North-Up) unit vectors at each
|
|
144
|
+
time/LST/hour angle in the dataset. Shape=(NTIMES, 3, 3).
|
|
145
|
+
baselines : list of tuples, default = None
|
|
146
|
+
If provided, only the baselines within the list will be simulated and array of shape
|
|
147
|
+
(nbls, nfreqs, ntimes) will be returned
|
|
148
|
+
precision : int, optional
|
|
149
|
+
Which precision level to use for floats and complex numbers
|
|
150
|
+
Allowed values:
|
|
151
|
+
- 1: float32, complex64
|
|
152
|
+
- 2: float64, complex128
|
|
153
|
+
eps : float, default = 6e-8
|
|
154
|
+
Desired accuracy of the non-uniform fast fourier transform.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
-------
|
|
159
|
+
vis : np.ndarray
|
|
160
|
+
Array of shape (nfreqs, ntimes, nants, nants) if polarized is False, and
|
|
161
|
+
(nfreqs, ntimes, 2, 2, nants, nants) if polarized is True.
|
|
162
|
+
"""
|
|
163
|
+
# Get sizes of inputs
|
|
164
|
+
nfreqs = np.size(freqs)
|
|
165
|
+
nants = len(ants)
|
|
166
|
+
ntimes = len(eq2tops)
|
|
167
|
+
|
|
168
|
+
if polarized:
|
|
169
|
+
nax = nfeeds = 2
|
|
170
|
+
else:
|
|
171
|
+
nax = nfeeds = 1
|
|
172
|
+
|
|
173
|
+
if precision == 1:
|
|
174
|
+
real_dtype = np.float32
|
|
175
|
+
complex_dtype = np.complex64
|
|
176
|
+
else:
|
|
177
|
+
real_dtype = np.float64
|
|
178
|
+
complex_dtype = np.complex128
|
|
179
|
+
|
|
180
|
+
# Get the redundant groups - TODO handle this better
|
|
181
|
+
if not baselines:
|
|
182
|
+
reds = utils.get_pos_reds(ants, include_autos=True)
|
|
183
|
+
baselines = [red[0] for red in reds]
|
|
184
|
+
nbls = len(baselines)
|
|
185
|
+
bl_to_red_map = {red[0]: np.array(red) for red in reds}
|
|
186
|
+
expand_vis = True
|
|
187
|
+
else:
|
|
188
|
+
nbls = len(baselines)
|
|
189
|
+
expand_vis = False
|
|
190
|
+
|
|
191
|
+
# Prepare the beam
|
|
192
|
+
beam = conversions.prepare_beam(beam, polarized=polarized, use_feed=use_feed)
|
|
193
|
+
|
|
194
|
+
# Check if the beam is complex
|
|
195
|
+
beam_values, _ = beam.interp(
|
|
196
|
+
az_array=np.array([0]),
|
|
197
|
+
za_array=np.array([0]),
|
|
198
|
+
freq_array=np.array([freqs[0]]),
|
|
199
|
+
)
|
|
200
|
+
is_beam_complex = np.issubdtype(beam_values.dtype, np.complexfloating)
|
|
201
|
+
|
|
202
|
+
# Convert to correct precision
|
|
203
|
+
crd_eq = crd_eq.astype(real_dtype)
|
|
204
|
+
eq2tops = eq2tops.astype(real_dtype)
|
|
205
|
+
|
|
206
|
+
# Factor of 0.5 accounts for splitting Stokes between polarization channels
|
|
207
|
+
Isky = (0.5 * fluxes).astype(complex_dtype)
|
|
208
|
+
|
|
209
|
+
# Compute baseline vectors
|
|
210
|
+
blx, bly = np.array([ants[bl[1]] - ants[bl[0]] for bl in baselines])[
|
|
211
|
+
:, :2
|
|
212
|
+
].T.astype(real_dtype)
|
|
213
|
+
|
|
214
|
+
# Generate visibility array
|
|
215
|
+
if expand_vis:
|
|
216
|
+
vis = np.zeros(
|
|
217
|
+
(ntimes, nants, nants, nfeeds, nfeeds, nfreqs), dtype=complex_dtype
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
vis = np.zeros((ntimes, nbls, nfeeds, nfeeds, nfreqs), dtype=complex_dtype)
|
|
221
|
+
|
|
222
|
+
# Loop over time samples
|
|
223
|
+
for ti, eq2top in enumerate(eq2tops):
|
|
224
|
+
# Convert to topocentric coordinates
|
|
225
|
+
tx, ty, tz = np.dot(eq2top, crd_eq)
|
|
226
|
+
|
|
227
|
+
# Only simulate above the horizon
|
|
228
|
+
above_horizon = tz > 0
|
|
229
|
+
tx = tx[above_horizon]
|
|
230
|
+
ty = ty[above_horizon]
|
|
231
|
+
|
|
232
|
+
# Number of above horizon points
|
|
233
|
+
nsim_sources = above_horizon.sum()
|
|
234
|
+
|
|
235
|
+
# Form the visibility array
|
|
236
|
+
_vis = np.zeros((nfeeds, nfeeds, nbls, nfreqs), dtype=complex_dtype)
|
|
237
|
+
|
|
238
|
+
if is_beam_complex:
|
|
239
|
+
_vis_negatives = np.zeros(
|
|
240
|
+
(nfeeds, nfeeds, nbls, nfreqs), dtype=complex_dtype
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Compute azimuth and zenith angles
|
|
244
|
+
az, za = conversions.enu_to_az_za(enu_e=tx, enu_n=ty, orientation="uvbeam")
|
|
245
|
+
|
|
246
|
+
for fi in range(nfreqs):
|
|
247
|
+
# Compute uv coordinates
|
|
248
|
+
u, v = (
|
|
249
|
+
blx * freqs[fi] / utils.speed_of_light,
|
|
250
|
+
bly * freqs[fi] / utils.speed_of_light,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Compute beams - only single beam is supported
|
|
254
|
+
A_s = np.zeros((nax, nfeeds, nsim_sources), dtype=complex_dtype)
|
|
255
|
+
A_s = beams._evaluate_beam(A_s, beam, az, za, polarized, freqs[fi])
|
|
256
|
+
A_s = A_s.transpose((1, 0, 2))
|
|
257
|
+
beam_product = np.einsum("abs,cbs->acs", A_s.conj(), A_s)
|
|
258
|
+
beam_product = beam_product.reshape(nax * nfeeds, nsim_sources)
|
|
259
|
+
|
|
260
|
+
# Compute sky beam product
|
|
261
|
+
i_sky = beam_product * Isky[above_horizon, fi]
|
|
262
|
+
|
|
263
|
+
# Compute visibilities w/ non-uniform FFT
|
|
264
|
+
_vis_here = finufft.nufft2d3(
|
|
265
|
+
2 * np.pi * tx,
|
|
266
|
+
2 * np.pi * ty,
|
|
267
|
+
i_sky,
|
|
268
|
+
u,
|
|
269
|
+
v,
|
|
270
|
+
modeord=0,
|
|
271
|
+
eps=eps,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Expand out the visibility array
|
|
275
|
+
_vis[..., fi] = _vis_here.reshape(nfeeds, nfeeds, nbls)
|
|
276
|
+
|
|
277
|
+
# If beam is complex, we need to compute the reverse negative frequencies
|
|
278
|
+
# TODO: no way to store this in the loop
|
|
279
|
+
if is_beam_complex:
|
|
280
|
+
# Compute
|
|
281
|
+
_vis_here_neg = finufft.nufft2d3(
|
|
282
|
+
2 * np.pi * tx,
|
|
283
|
+
2 * np.pi * ty,
|
|
284
|
+
i_sky,
|
|
285
|
+
-u,
|
|
286
|
+
-v,
|
|
287
|
+
modeord=0,
|
|
288
|
+
eps=eps,
|
|
289
|
+
)
|
|
290
|
+
_vis_negatives[..., fi] = _vis_here_neg.reshape(nfeeds, nfeeds, nbls)
|
|
291
|
+
|
|
292
|
+
# Expand out the visibility array in antenna by antenna matrix
|
|
293
|
+
if expand_vis:
|
|
294
|
+
for bi, bls in enumerate(baselines):
|
|
295
|
+
np.add.at(
|
|
296
|
+
vis,
|
|
297
|
+
(ti, bl_to_red_map[bls][:, 0], bl_to_red_map[bls][:, 1]),
|
|
298
|
+
_vis[..., bi, :],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Add the conjugate, avoid auto baselines twice
|
|
302
|
+
if bls[0] != bls[1]:
|
|
303
|
+
if is_beam_complex:
|
|
304
|
+
np.add.at(
|
|
305
|
+
vis,
|
|
306
|
+
(ti, bl_to_red_map[bls][:, 1], bl_to_red_map[bls][:, 0]),
|
|
307
|
+
_vis_negatives[..., bi, :],
|
|
308
|
+
)
|
|
309
|
+
else:
|
|
310
|
+
np.add.at(
|
|
311
|
+
vis,
|
|
312
|
+
(ti, bl_to_red_map[bls][:, 1], bl_to_red_map[bls][:, 0]),
|
|
313
|
+
_vis[..., bi, :].conj(),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
else:
|
|
317
|
+
vis[ti] = np.swapaxes(_vis, 2, 0)
|
|
318
|
+
|
|
319
|
+
if expand_vis:
|
|
320
|
+
return (
|
|
321
|
+
np.transpose(vis, (5, 0, 3, 4, 1, 2))
|
|
322
|
+
if polarized
|
|
323
|
+
else np.moveaxis(vis[..., 0, 0, :], 3, 0)
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
return (
|
|
327
|
+
np.transpose(vis, (4, 0, 2, 3, 1))
|
|
328
|
+
if polarized
|
|
329
|
+
else np.moveaxis(vis[..., 0, 0, :], 2, 0)
|
|
330
|
+
)
|
fftvis/tests/__init__.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Tests."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from astropy import units as un
|
|
5
|
+
from astropy.coordinates import EarthLocation, Latitude, Longitude
|
|
6
|
+
from astropy.time import Time
|
|
7
|
+
from astropy.units import Quantity
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from pyradiosky import SkyModel
|
|
10
|
+
from pyuvdata import UVBeam
|
|
11
|
+
from pyuvsim import AnalyticBeam, simsetup
|
|
12
|
+
from pyuvsim.telescope import BeamList
|
|
13
|
+
|
|
14
|
+
from matvis import DATA_PATH, conversions
|
|
15
|
+
|
|
16
|
+
nfreq = 1
|
|
17
|
+
ntime = 5 # 20
|
|
18
|
+
nants = 3 # 4
|
|
19
|
+
nsource = 15 # 250
|
|
20
|
+
beam_file = DATA_PATH / "NF_HERA_Dipole_small.fits"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_standard_sim_params(
|
|
24
|
+
use_analytic_beam: bool,
|
|
25
|
+
polarized: bool,
|
|
26
|
+
nants=nants,
|
|
27
|
+
nfreq=nfreq,
|
|
28
|
+
ntime=ntime,
|
|
29
|
+
nsource=nsource,
|
|
30
|
+
first_source_antizenith=False,
|
|
31
|
+
):
|
|
32
|
+
"""Create some standard random simulation parameters for use in tests."""
|
|
33
|
+
hera_lat = -30.7215
|
|
34
|
+
hera_lon = 21.4283
|
|
35
|
+
hera_alt = 1073.0
|
|
36
|
+
obstime = Time("2018-08-31T04:02:30.11", format="isot", scale="utc")
|
|
37
|
+
|
|
38
|
+
# HERA location
|
|
39
|
+
location = EarthLocation.from_geodetic(lat=hera_lat, lon=hera_lon, height=hera_alt)
|
|
40
|
+
|
|
41
|
+
np.random.seed(1)
|
|
42
|
+
|
|
43
|
+
# Beam model
|
|
44
|
+
if use_analytic_beam:
|
|
45
|
+
n_freq = nfreq
|
|
46
|
+
beam = AnalyticBeam("gaussian", diameter=14.0)
|
|
47
|
+
else:
|
|
48
|
+
n_freq = min(nfreq, 2)
|
|
49
|
+
# This is a peak-normalized e-field beam file at 100 and 101 MHz,
|
|
50
|
+
# downsampled to roughly 4 square-degree resolution.
|
|
51
|
+
beam = UVBeam()
|
|
52
|
+
beam.read_beamfits(beam_file)
|
|
53
|
+
if not polarized:
|
|
54
|
+
uvsim_beam = beam.copy()
|
|
55
|
+
beam.efield_to_power(calc_cross_pols=False, inplace=True)
|
|
56
|
+
beam.select(polarizations=["xx"], inplace=True)
|
|
57
|
+
|
|
58
|
+
# Now, the beam we have on file doesn't actually properly wrap around in azimuth.
|
|
59
|
+
# This is fine -- the uvbeam.interp() handles the interpolation well. However, when
|
|
60
|
+
# comparing to the GPU interpolation, which first has to interpolate to a regular
|
|
61
|
+
# grid that ends right at 2pi, it's better to compare like for like, so we
|
|
62
|
+
# interpolate to such a grid here.
|
|
63
|
+
beam = beam.interp(
|
|
64
|
+
az_array=np.linspace(0, 2 * np.pi, 181),
|
|
65
|
+
za_array=np.linspace(0, np.pi / 2, 46),
|
|
66
|
+
az_za_grid=True,
|
|
67
|
+
new_object=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if polarized or use_analytic_beam:
|
|
71
|
+
uvsim_beams = BeamList([beam] * nants)
|
|
72
|
+
else:
|
|
73
|
+
uvsim_beams = BeamList([uvsim_beam] * nants)
|
|
74
|
+
|
|
75
|
+
# beams = [AnalyticBeam('uniform') for i in range(len(ants.keys()))]
|
|
76
|
+
beam_dict = {str(i): i for i in range(nants)}
|
|
77
|
+
|
|
78
|
+
# Random antenna locations
|
|
79
|
+
x = np.random.random(nants) * 400.0 # Up to 400 metres
|
|
80
|
+
y = np.random.random(nants) * 400.0
|
|
81
|
+
z = np.random.random(nants) * 0.0
|
|
82
|
+
ants = {i: (x[i], y[i], z[i]) for i in range(nants)}
|
|
83
|
+
|
|
84
|
+
# Observing parameters in a UVData object
|
|
85
|
+
uvdata = simsetup.initialize_uvdata_from_keywords(
|
|
86
|
+
Nfreqs=n_freq,
|
|
87
|
+
start_freq=100e6,
|
|
88
|
+
channel_width=97.3e3,
|
|
89
|
+
start_time=obstime.jd,
|
|
90
|
+
integration_time=182.0, # Just over 3 mins between time samples
|
|
91
|
+
Ntimes=ntime,
|
|
92
|
+
array_layout=ants,
|
|
93
|
+
polarization_array=np.array(["XX", "YY", "XY", "YX"]),
|
|
94
|
+
telescope_location=(hera_lat, hera_lon, hera_alt),
|
|
95
|
+
telescope_name="test_array",
|
|
96
|
+
phase_type="drift",
|
|
97
|
+
vis_units="Jy",
|
|
98
|
+
complete=True,
|
|
99
|
+
write_files=False,
|
|
100
|
+
)
|
|
101
|
+
lsts = np.unique(uvdata.lst_array)
|
|
102
|
+
|
|
103
|
+
# One fixed source plus random other sources
|
|
104
|
+
sources = [
|
|
105
|
+
[
|
|
106
|
+
300 if first_source_antizenith else 125.7,
|
|
107
|
+
-30.72,
|
|
108
|
+
2,
|
|
109
|
+
0,
|
|
110
|
+
], # Fix a single source near zenith
|
|
111
|
+
]
|
|
112
|
+
if nsource > 1: # Add random other sources
|
|
113
|
+
ra = np.random.uniform(low=0.0, high=360.0, size=nsource - 1)
|
|
114
|
+
dec = -30.72 + np.random.random(nsource - 1) * 10.0
|
|
115
|
+
flux = np.random.random(nsource - 1) * 4
|
|
116
|
+
sources.extend([ra[i], dec[i], flux[i], 0] for i in range(nsource - 1))
|
|
117
|
+
sources = np.array(sources)
|
|
118
|
+
|
|
119
|
+
# Source locations and frequencies
|
|
120
|
+
ra_dec = np.deg2rad(sources[:, :2])
|
|
121
|
+
freqs = np.unique(uvdata.freq_array)
|
|
122
|
+
|
|
123
|
+
# Correct source locations so that matvis uses the right frame
|
|
124
|
+
ra_new, dec_new = conversions.equatorial_to_eci_coords(
|
|
125
|
+
ra_dec[:, 0], ra_dec[:, 1], obstime, location, unit="rad", frame="icrs"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Calculate source fluxes for matvis
|
|
129
|
+
flux = ((freqs[:, np.newaxis] / freqs[0]) ** sources[:, 3].T * sources[:, 2].T).T
|
|
130
|
+
|
|
131
|
+
# Stokes for the first frequency only. Stokes for other frequencies
|
|
132
|
+
# are calculated later.
|
|
133
|
+
stokes = np.zeros((4, 1, ra_dec.shape[0]))
|
|
134
|
+
stokes[0, 0] = sources[:, 2]
|
|
135
|
+
reference_frequency = np.full(len(ra_dec), freqs[0])
|
|
136
|
+
|
|
137
|
+
# Set up sky model
|
|
138
|
+
sky_model = SkyModel(
|
|
139
|
+
name=[str(i) for i in range(len(ra_dec))],
|
|
140
|
+
ra=Longitude(ra_dec[:, 0], "rad"),
|
|
141
|
+
dec=Latitude(ra_dec[:, 1], "rad"),
|
|
142
|
+
frame="icrs",
|
|
143
|
+
spectral_type="spectral_index",
|
|
144
|
+
spectral_index=sources[:, 3],
|
|
145
|
+
stokes=stokes * un.Jy,
|
|
146
|
+
reference_frequency=Quantity(reference_frequency, "Hz"),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Calculate stokes at all the frequencies.
|
|
150
|
+
sky_model.at_frequencies(Quantity(freqs, "Hz"), inplace=True)
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
sky_model,
|
|
154
|
+
ants,
|
|
155
|
+
flux,
|
|
156
|
+
ra_new,
|
|
157
|
+
dec_new,
|
|
158
|
+
freqs,
|
|
159
|
+
lsts,
|
|
160
|
+
[beam],
|
|
161
|
+
uvsim_beams,
|
|
162
|
+
beam_dict,
|
|
163
|
+
hera_lat,
|
|
164
|
+
uvdata,
|
|
165
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import matvis
|
|
2
|
+
import pytest
|
|
3
|
+
import numpy as np
|
|
4
|
+
from fftvis import simulate
|
|
5
|
+
from pyuvsim.analyticbeam import AnalyticBeam
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_simulate():
|
|
9
|
+
""" """
|
|
10
|
+
# Simulation parameters
|
|
11
|
+
ntimes = 10
|
|
12
|
+
nfreqs = 5
|
|
13
|
+
nants = 3
|
|
14
|
+
nsrcs = 20
|
|
15
|
+
|
|
16
|
+
# Set random set
|
|
17
|
+
np.random.seed(42)
|
|
18
|
+
|
|
19
|
+
# Define frequency and time range
|
|
20
|
+
freqs = np.linspace(100e6, 200e6, nfreqs)
|
|
21
|
+
lsts = np.linspace(0, np.pi, ntimes)
|
|
22
|
+
|
|
23
|
+
# Set up the antenna positions
|
|
24
|
+
antpos = {k: np.array([k * 10, 0, 0]) for k in range(nants)}
|
|
25
|
+
|
|
26
|
+
# Define a Gaussian beam
|
|
27
|
+
beam = AnalyticBeam("gaussian", diameter=14.0)
|
|
28
|
+
|
|
29
|
+
# Set sky model
|
|
30
|
+
ra = np.linspace(0.0, 2.0 * np.pi, nsrcs)
|
|
31
|
+
dec = np.linspace(-0.5 * np.pi, 0.5 * np.pi, nsrcs)
|
|
32
|
+
sky_model = np.random.uniform(0, 1, size=(nsrcs, 1)) * (freqs[None] / 150e6) ** -2.5
|
|
33
|
+
|
|
34
|
+
# Use matvis as a reference
|
|
35
|
+
mvis = matvis.simulate_vis(
|
|
36
|
+
antpos, sky_model, ra, dec, freqs, lsts, beams=[beam], precision=2
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Use fftvis to simulate visibilities
|
|
40
|
+
fvis = simulate.simulate_vis(
|
|
41
|
+
antpos, sky_model, ra, dec, freqs, lsts, beam, precision=2, eps=1e-10
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Should have shape (nfreqs, ntimes, nants, nants)
|
|
45
|
+
assert fvis.shape == (nfreqs, ntimes, nants, nants)
|
|
46
|
+
|
|
47
|
+
# Check that the results are the same
|
|
48
|
+
assert np.allclose(mvis, fvis, atol=1e-5)
|
|
49
|
+
|
|
50
|
+
# Test polarized visibilities
|
|
51
|
+
# Use matvis as a reference
|
|
52
|
+
mvis = matvis.simulate_vis(
|
|
53
|
+
antpos,
|
|
54
|
+
sky_model,
|
|
55
|
+
ra,
|
|
56
|
+
dec,
|
|
57
|
+
freqs,
|
|
58
|
+
lsts,
|
|
59
|
+
beams=[beam],
|
|
60
|
+
precision=2,
|
|
61
|
+
polarized=True,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Use fftvis to simulate visibilities
|
|
65
|
+
fvis = simulate.simulate_vis(
|
|
66
|
+
antpos,
|
|
67
|
+
sky_model,
|
|
68
|
+
ra,
|
|
69
|
+
dec,
|
|
70
|
+
freqs,
|
|
71
|
+
lsts,
|
|
72
|
+
beam,
|
|
73
|
+
precision=2,
|
|
74
|
+
eps=1e-10,
|
|
75
|
+
polarized=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Should have shape (nfreqs, ntimes, nfeeds, nfeeds, nants, nants)
|
|
79
|
+
assert fvis.shape == (nfreqs, ntimes, 2, 2, nants, nants)
|
|
80
|
+
|
|
81
|
+
# Check that the polarized results are the same
|
|
82
|
+
assert np.allclose(mvis, fvis, atol=1e-5)
|
|
83
|
+
|
|
84
|
+
# Simulate with specified baselines
|
|
85
|
+
sim_baselines = [(0, 1), (0, 2), (1, 2)]
|
|
86
|
+
fvis = simulate.simulate_vis(
|
|
87
|
+
antpos,
|
|
88
|
+
sky_model,
|
|
89
|
+
ra,
|
|
90
|
+
dec,
|
|
91
|
+
freqs,
|
|
92
|
+
lsts,
|
|
93
|
+
beam,
|
|
94
|
+
baselines=sim_baselines,
|
|
95
|
+
precision=2,
|
|
96
|
+
eps=1e-10,
|
|
97
|
+
polarized=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Should have shape (nfreqs, ntimes, 2, 2, len(sim_baselines))
|
|
101
|
+
assert fvis.shape == (nfreqs, ntimes, 2, 2, len(sim_baselines))
|
|
102
|
+
|
|
103
|
+
# Check that the polarized results are the same
|
|
104
|
+
for bi, bl in enumerate(sim_baselines):
|
|
105
|
+
assert np.allclose(fvis[:, :, :, :, bi], mvis[:, :, :, :, bl[0], bl[1]])
|
|
106
|
+
|
|
107
|
+
# Test with precision 1
|
|
108
|
+
# Use matvis as a reference
|
|
109
|
+
mvis = matvis.simulate_vis(
|
|
110
|
+
antpos, sky_model, ra, dec, freqs, lsts, beams=[beam], precision=1
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Use fftvis to simulate visibilities
|
|
114
|
+
fvis = simulate.simulate_vis(
|
|
115
|
+
antpos, sky_model, ra, dec, freqs, lsts, beam, precision=1, eps=6e-8
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Should have shape (nfreqs, ntimes, nants, nants)
|
|
119
|
+
assert fvis.shape == (nfreqs, ntimes, nants, nants)
|
|
120
|
+
|
|
121
|
+
# Check that the results are the same
|
|
122
|
+
assert np.allclose(mvis, fvis, atol=1e-5)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Compare matvis with pyuvsim visibilities.
|
|
4
|
+
|
|
5
|
+
Tests copied from matvis/tests/test_compare_pyuvsim.py
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from pyuvsim import simsetup, uvsim
|
|
13
|
+
|
|
14
|
+
from fftvis.simulate import simulate_vis
|
|
15
|
+
from matvis import simulate_vis as simulate_vis_matvis
|
|
16
|
+
|
|
17
|
+
from . import get_standard_sim_params, nants
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize("use_analytic_beam", (True, False))
|
|
21
|
+
@pytest.mark.parametrize("polarized", (True, False))
|
|
22
|
+
def test_compare_pyuvsim(polarized, use_analytic_beam):
|
|
23
|
+
"""Compare matvis and pyuvsim simulated visibilities."""
|
|
24
|
+
print("Polarized=", polarized, "Analytic Beam =", use_analytic_beam)
|
|
25
|
+
(
|
|
26
|
+
sky_model,
|
|
27
|
+
ants,
|
|
28
|
+
flux,
|
|
29
|
+
ra,
|
|
30
|
+
dec,
|
|
31
|
+
freqs,
|
|
32
|
+
lsts,
|
|
33
|
+
cpu_beams,
|
|
34
|
+
uvsim_beams,
|
|
35
|
+
beam_dict,
|
|
36
|
+
hera_lat,
|
|
37
|
+
uvdata,
|
|
38
|
+
) = get_standard_sim_params(use_analytic_beam, polarized)
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# (1) Run matvis
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
vis_fftvis = simulate_vis(
|
|
43
|
+
ants=ants,
|
|
44
|
+
fluxes=flux,
|
|
45
|
+
ra=ra,
|
|
46
|
+
dec=dec,
|
|
47
|
+
freqs=freqs,
|
|
48
|
+
lsts=lsts,
|
|
49
|
+
beam=cpu_beams[0],
|
|
50
|
+
polarized=polarized,
|
|
51
|
+
precision=2,
|
|
52
|
+
latitude=hera_lat * np.pi / 180.0,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# (2) Run pyuvsim
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# uvd_uvsim = uvsim.run_uvdata_uvsim(
|
|
59
|
+
# uvdata,
|
|
60
|
+
# uvsim_beams,
|
|
61
|
+
# beam_dict=beam_dict,
|
|
62
|
+
# catalog=simsetup.SkyModelData(sky_model),
|
|
63
|
+
# )
|
|
64
|
+
vis_matvis = simulate_vis_matvis(
|
|
65
|
+
ants=ants,
|
|
66
|
+
fluxes=flux,
|
|
67
|
+
ra=ra,
|
|
68
|
+
dec=dec,
|
|
69
|
+
freqs=freqs,
|
|
70
|
+
lsts=lsts,
|
|
71
|
+
beams=cpu_beams,
|
|
72
|
+
polarized=polarized,
|
|
73
|
+
precision=2,
|
|
74
|
+
latitude=hera_lat * np.pi / 180.0,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# Compare
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Loop over baselines and compare
|
|
81
|
+
diff_re = 0.0
|
|
82
|
+
diff_im = 0.0
|
|
83
|
+
rtol = 2e-4 if use_analytic_beam else 0.01
|
|
84
|
+
atol = 5e-4
|
|
85
|
+
|
|
86
|
+
# If it passes this test, but fails the following tests, then its probably an
|
|
87
|
+
# ordering issue.
|
|
88
|
+
for i in range(nants):
|
|
89
|
+
for j in range(i, nants):
|
|
90
|
+
for if1, feed1 in enumerate(("X", "Y") if polarized else ("X",)):
|
|
91
|
+
for if2, feed2 in enumerate(("X", "Y") if polarized else ("X",)):
|
|
92
|
+
# d_uvsim = uvd_uvsim.get_data(
|
|
93
|
+
# (i, j, feed1 + feed2)
|
|
94
|
+
# ).T # pyuvsim visibility
|
|
95
|
+
d_uvsim = (
|
|
96
|
+
vis_matvis[:, :, if1, if2, i, j]
|
|
97
|
+
if polarized
|
|
98
|
+
else vis_fftvis[:, :, i, j]
|
|
99
|
+
)
|
|
100
|
+
d_fftvis = (
|
|
101
|
+
vis_fftvis[:, :, if1, if2, i, j]
|
|
102
|
+
if polarized
|
|
103
|
+
else vis_fftvis[:, :, i, j]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Keep track of maximum difference
|
|
107
|
+
delta = d_uvsim - d_fftvis
|
|
108
|
+
if np.max(np.abs(delta.real)) > diff_re:
|
|
109
|
+
diff_re = np.max(np.abs(delta.real))
|
|
110
|
+
if np.max(np.abs(delta.imag)) > diff_im:
|
|
111
|
+
diff_im = np.abs(np.max(delta.imag))
|
|
112
|
+
|
|
113
|
+
err = f"\nMax diff: {diff_re:10.10e} + 1j*{diff_im:10.10e}\n"
|
|
114
|
+
err += f"Baseline: ({i},{j},{feed1}{feed2})\n"
|
|
115
|
+
err += f"Avg. diff: {delta.mean():10.10e}\n"
|
|
116
|
+
err += f"Max values: \n uvsim={d_uvsim.max():10.10e}"
|
|
117
|
+
err += f"\n matvis={d_fftvis.max():10.10e}"
|
|
118
|
+
assert np.allclose(
|
|
119
|
+
d_uvsim.real,
|
|
120
|
+
d_fftvis.real,
|
|
121
|
+
rtol=rtol if feed1 == feed2 else rtol * 100,
|
|
122
|
+
atol=atol,
|
|
123
|
+
), err
|
|
124
|
+
assert np.allclose(
|
|
125
|
+
d_uvsim.imag,
|
|
126
|
+
d_fftvis.imag,
|
|
127
|
+
rtol=rtol if feed1 == feed2 else rtol * 100,
|
|
128
|
+
atol=atol,
|
|
129
|
+
), err
|
fftvis/utils.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
IDEALIZED_BL_TOL = 1e-8 # bl_error_tol for redcal.get_reds when using antenna positions calculated from reds
|
|
4
|
+
speed_of_light = 299792458.0 # m/s
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_pos_reds(antpos, decimals=3, include_autos=True):
|
|
8
|
+
"""
|
|
9
|
+
Figure out and return list of lists of redundant baseline pairs. This function is a modified version of the
|
|
10
|
+
get_pos_reds function in redcal. It is used to calculate the redundant baseline groups from antenna positions
|
|
11
|
+
rather than from a list of baselines. This is useful for simulating visibilities with fftvis.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
----------
|
|
15
|
+
antpos: dict
|
|
16
|
+
dictionary of antenna positions in the form {ant_index: np.array([x,y,z])}.
|
|
17
|
+
decimals: int, optional
|
|
18
|
+
Number of decimal places to round to when determining redundant baselines. default is 3.
|
|
19
|
+
include_autos: bool, optional
|
|
20
|
+
if True, include autos in the list of pos_reds. default is False
|
|
21
|
+
Returns:
|
|
22
|
+
-------
|
|
23
|
+
reds: list of lists of redundant tuples of antenna indices (no polarizations),
|
|
24
|
+
sorted by index with the first index of the first baseline the lowest in the group.
|
|
25
|
+
"""
|
|
26
|
+
# Create a dictionary of redundant baseline groups
|
|
27
|
+
uv_to_red_key = {}
|
|
28
|
+
reds = {}
|
|
29
|
+
|
|
30
|
+
# Compute baseline lengths and round to specified precision
|
|
31
|
+
baselines = np.round(
|
|
32
|
+
[
|
|
33
|
+
antpos[aj] - antpos[ai]
|
|
34
|
+
for ai in antpos
|
|
35
|
+
for aj in antpos
|
|
36
|
+
if ai < aj or include_autos and ai == aj
|
|
37
|
+
],
|
|
38
|
+
decimals,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
ci = 0
|
|
42
|
+
for ai in antpos:
|
|
43
|
+
for aj in antpos:
|
|
44
|
+
if ai < aj or include_autos and ai == aj:
|
|
45
|
+
u, v, _ = baselines[ci]
|
|
46
|
+
|
|
47
|
+
if (u, v) not in uv_to_red_key and (-u, -v) not in uv_to_red_key:
|
|
48
|
+
reds[(ai, aj)] = [(ai, aj)]
|
|
49
|
+
uv_to_red_key[(u, v)] = (ai, aj)
|
|
50
|
+
elif (-u, -v) in uv_to_red_key:
|
|
51
|
+
reds[uv_to_red_key[(-u, -v)]].append((aj, ai))
|
|
52
|
+
elif (u, v) in uv_to_red_key:
|
|
53
|
+
reds[uv_to_red_key[(u, v)]].append((ai, aj))
|
|
54
|
+
|
|
55
|
+
ci += 1
|
|
56
|
+
|
|
57
|
+
return [reds[k] for k in reds]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fftvis
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A package for simulating visibilities using FFTs
|
|
5
|
+
Home-page: https://github.com/tyler-a-cox/fftvis
|
|
6
|
+
Author: Tyler Cox
|
|
7
|
+
Author-email: tyler.a.cox@berkeley.edu
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: matvis
|
|
27
|
+
Requires-Dist: finufft
|
|
28
|
+
Requires-Dist: pyuvdata
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mpi4py ; extra == 'dev'
|
|
31
|
+
Requires-Dist: pyuvsim[sim] ; extra == 'dev'
|
|
32
|
+
Requires-Dist: pyradiosky ; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
34
|
+
Requires-Dist: pre-commit ; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
36
|
+
Requires-Dist: hera-sim ; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-xdist ; extra == 'dev'
|
|
38
|
+
|
|
39
|
+
# fftvis: A Non-Uniform Fast Fourier Transform-based Visibility Simulator
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
`fftvis` is a fast Python package designed for simulating interferometric visibilities using the Non-Uniform Fast Fourier Transform (NUFFT). It provides a convenient and efficient way to generate simulated visibilities.
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- Utilizes the Flatiron Institute NUFFT (finufft) [algorithm](https://arxiv.org/abs/1808.06736) for fast visibility simulations that agree with similar methods ([`matvis`](https://github.com/HERA-team/matvis)) to nearly machine precision.
|
|
50
|
+
- Designed to be a near drop-in replacement to `matvis` with a ~10x improvement in runtime
|
|
51
|
+
|
|
52
|
+
## Limitations
|
|
53
|
+
- Currently no support for per-antenna beams
|
|
54
|
+
- Currently no support for polarized sky emission
|
|
55
|
+
- Currently no GPU support
|
|
56
|
+
- Diffuse sky models must be pixelized
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
You can install `fftvis` via pip:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/tyler-a-cox/fftvis
|
|
64
|
+
cd fftvis
|
|
65
|
+
pip install .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Contributing
|
|
69
|
+
Contributions to `fftvis` are welcome! If you find any issues, have feature requests, or want to contribute improvements, please open an issue or submit a pull request on the GitHub repository: `fftvis` on GitHub
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
74
|
+
|
|
75
|
+
## Acknowledgments
|
|
76
|
+
This package relies on the `finufft` implementation provided by [finufft](https://github.com/flatironinstitute/finufft) library. Special thanks to the contributors and maintainers of open-source libraries used in this project.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
fftvis/__init__.py,sha256=8tSRabRYy3YZ8Oiu0yP-KhV2Pti6CNumjqrs4Qll6tw,37
|
|
2
|
+
fftvis/beams.py,sha256=JP6jFyh2ulXCZvFLoiLRQ8GfYMLx0XWNkijqDTyQJU4,2143
|
|
3
|
+
fftvis/simulate.py,sha256=YtyhNCAwq2ysggf2rl2Haur7qRyaU61SqjkVugYM4Oo,11422
|
|
4
|
+
fftvis/utils.py,sha256=4nDW2pm5NCJofsxMB1DlSjyTAhYCUSxfuOmNwD2Bh3g,2159
|
|
5
|
+
fftvis/tests/__init__.py,sha256=Dy1vOmUe-Q4lKQsNGE2vB0YR9n5XJn030IG6V55LCo8,5353
|
|
6
|
+
fftvis/tests/test_compare_matvis.py,sha256=fRc2pvCS_jHV4Mb-5zCia7MMUZ3outlTBkLglDe3joU,3236
|
|
7
|
+
fftvis/tests/test_compare_pyuvsim.py,sha256=N49B_903gjjtoeddGmu5niWNWxNM_heCQQxBITdtW2s,4367
|
|
8
|
+
fftvis/tests/test_utils.py,sha256=gBmBDW-FFqEBOsh_315xALdpLk26RlkEQnmc8pw4hE8,62
|
|
9
|
+
fftvis-0.0.1.dist-info/METADATA,sha256=-dbnCfVKg3oZ2idpm3MYliEYxw_kAkGQ2A2r5nDuOqQ,3177
|
|
10
|
+
fftvis-0.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
11
|
+
fftvis-0.0.1.dist-info/top_level.txt,sha256=p91SGcGpU_MXL32mier1gBGXt2_nxk5H8hn1MwNki2I,7
|
|
12
|
+
fftvis-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fftvis
|