Skip to content

Commit 687cb7c

Browse files
Merge pull request #26 from HERA-Team/fix_uvbeam_norm
Remove spurious beam normalisation logic in `uvbeam_to_lm`
2 parents a8a45ca + 074f3a2 commit 687cb7c

11 files changed

Lines changed: 174 additions & 25 deletions

File tree

.coveragerc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[run]
33
branch = True
44
source = vis_cpu
5-
# omit = bad_file.py
5+
omit = */vis_gpu.py
66

77
[paths]
88
source =

CHANGELOG.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22
Changelog
33
=========
44

5+
Version 0.2.3
6+
=============
7+
8+
Fixed
9+
-----
10+
11+
- Fix issue with spurious beam normalization when a pixel beam
12+
interpolation grid is generated from a UVBeam object
13+
- Fix bug where the imaginary part of complex pixel beams was
14+
being discarded
15+
- Fix bug that was causing polarized calculations to fail with
16+
``simulate_vis``
17+
- CI paths fixed so coverage reports are linked properly
18+
19+
Added
20+
-----
21+
22+
- New units tests
23+
524
Version 0.2.2
625
=============
726

ci/test-env.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies:
1313
- matplotlib>=3.3.4,<4
1414
- ipython>=7.22,<8
1515
- h5py>=3.2,<4
16+
- ffmpeg
1617
- pip:
1718
- pyuvsim[sim]>=1.2,<1.4
1819
- pyradiosky>=0.1.1,<0.3

codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fixes:
2+
- "/home/runner/work/vis_cpu/::"

src/vis_cpu/conversions.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ def uvbeam_to_lm(uvbeam, freqs, n_pix_lm=63, polarized=False, **kwargs):
266266
drift-scan telescope). For a vector in East-North-Up (ENU) coordinates
267267
vec{p}, we therefore have ``l = vec{p}.hat{e}`` etc.
268268
269+
N.B. This function does not perform any beam normalization.
270+
269271
Parameters
270272
----------
271273
uvbeam : UVBeam object
@@ -301,19 +303,10 @@ def uvbeam_to_lm(uvbeam, freqs, n_pix_lm=63, polarized=False, **kwargs):
301303
else:
302304
bm = efield_beam[0, 0, 1, :, :] # (phi, e) == 'xx' component
303305

304-
# Peak normalization and reshape output
306+
# Reshape output
305307
if polarized:
306308
Naxes = bm.shape[0] # polarization vector axes
307309
Nfeeds = bm.shape[1] # polarized feeds
308-
309-
# Separately normalize each polarization channel
310-
for i in range(Naxes):
311-
for j in range(Nfeeds):
312-
if np.max(bm[i, j]) > 0.0:
313-
bm /= np.max(bm[i, j])
314310
return bm.reshape((Naxes, Nfeeds, len(freqs), n_pix_lm, n_pix_lm))
315311
else:
316-
# Normalize single polarization channel
317-
if np.max(bm) > 0.0:
318-
bm /= np.max(bm)
319312
return bm.reshape((len(freqs), n_pix_lm, n_pix_lm))

src/vis_cpu/plot.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ def _source_az_za_beam(
3434
Value of the beam (E-field, not power, unless the beam object contains
3535
only the power beam) for each source.
3636
"""
37-
# Equatorial to topocentric conversion at given LST
38-
eq2tops = conversions.get_eq2tops(np.atleast_1d(lst), latitude=latitude)
39-
eq2top = eq2tops[0]
37+
# Get coordinate transforms as a function of LST
38+
eq2top = conversions.eci_to_enu_matrix(lst, latitude)
4039

41-
# Get source az, za
40+
# Get source az, za (note the azimuth convention used by UVBeam)
4241
tx, ty, tz = np.dot(eq2top, crd_eq)
43-
az, za = conversions.lm_to_az_za(tx, ty)
42+
az, za = conversions.enu_to_az_za(enu_e=tx, enu_n=ty, orientation="uvbeam")
4443

4544
# Get beam values
4645
interp_beam = beam.interp(az, za, np.atleast_1d(ref_freq))[0]

src/vis_cpu/vis_cpu.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def vis_cpu(
163163

164164
if beam_list is None:
165165
bm_pix = bm_cube.shape[-1]
166-
complex_bm_pix = np.iscomplex(bm_pix)
166+
complex_bm_cube = np.any(np.iscomplex(bm_cube))
167167
if polarized:
168168
assert bm_cube.shape == (nax, nfeed, nant, bm_pix, bm_pix), (
169169
"bm_cube must have shape (NAXES, NFEEDS, NANTS, BM_PIX, BM_PIX) "
@@ -202,7 +202,7 @@ def vis_cpu(
202202
# Precompute splines using pixelized beams
203203
if beam_list is None:
204204
splines_re = construct_pixel_beam_spline(bm_cube.real)
205-
if complex_bm_pix:
205+
if complex_bm_cube:
206206
splines_im = construct_pixel_beam_spline(bm_cube.imag)
207207

208208
# Loop over time samples
@@ -222,7 +222,7 @@ def vis_cpu(
222222
# The beam pixel grid has been reshaped in the order
223223
# ty,tx, which implies m,l order
224224
A_s[p1, p2, i] = splines_re[p1][p2][i](ty, tx, grid=False)
225-
if complex_bm_pix:
225+
if complex_bm_cube:
226226
A_s[p1, p2, i] += 1.0j * splines_im[p1][p2][i](
227227
ty, tx, grid=False
228228
)

src/vis_cpu/wrapper.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ def simulate_vis(
8787
naxes = beams[0].Naxes_vec
8888
nfeeds = beams[0].Nfeeds
8989
except AttributeError:
90-
# If Naxes_vec and Nfeeds properties aren't set, assume no pol.
91-
naxes = nfeeds = 1
90+
# If Naxes_vec and Nfeeds properties aren't set, assume all pol.
91+
naxes = nfeeds = 2
9292

9393
# Antenna x,y,z positions
9494
antpos = np.array([ants[k] for k in ants.keys()])
@@ -122,18 +122,27 @@ def simulate_vis(
122122
for i in range(freqs.size):
123123

124124
if pixel_beams:
125-
vis[i] = vis_cpu(
125+
126+
# Get per-freq. pixel beam
127+
bm = beam_cube[:, :, :, i, :, :] if polarized else beam_cube[:, i, :, :]
128+
129+
# Run vis_cpu
130+
v = vis_cpu(
126131
antpos,
127132
freqs[i],
128133
eq2tops,
129134
crd_eq,
130135
fluxes[:, i],
131-
bm_cube=beam_cube[:, i, :, :],
136+
bm_cube=bm,
132137
precision=precision,
133138
polarized=polarized,
134139
)
140+
if polarized:
141+
vis[:, :, i] = v # v.shape: (nax, nfeed, ntimes, nant, nant)
142+
else:
143+
vis[i] = v # v.shape: (ntimes, nant, nant)
135144
else:
136-
vis[i] = vis_cpu(
145+
v = vis_cpu(
137146
antpos,
138147
freqs[i],
139148
eq2tops,
@@ -143,5 +152,9 @@ def simulate_vis(
143152
precision=precision,
144153
polarized=polarized,
145154
)
155+
if polarized:
156+
vis[:, :, i] = v # v.shape: (nax, nfeed, ntimes, nant, nant)
157+
else:
158+
vis[i] = v # v.shape: (ntimes, nant, nant)
146159

147160
return vis

tests/test_compare_pyuvsim.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def test_compare_pyuvsim():
5050
phase_type="drift",
5151
vis_units="Jy",
5252
complete=True,
53+
write_files=False,
5354
)
5455
lsts = np.unique(uvdata.lst_array)
5556

tests/test_plot.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Compare vis_cpu with pyuvsim visibilities."""
2+
import numpy as np
3+
from pyuvsim.analyticbeam import AnalyticBeam
4+
5+
from vis_cpu import conversions, plot
6+
7+
nsource = 10
8+
9+
10+
def test_source_az_za_beam():
11+
"""Test function that calculates the Az and ZA positions of sources."""
12+
# Observation latitude and LST
13+
hera_lat = -30.7215
14+
lst = 0.78
15+
16+
# Add random sources
17+
ra = np.random.uniform(low=0.0, high=360.0, size=nsource - 1)
18+
dec = -30.72 + np.random.random(nsource - 1) * 10.0
19+
ra = np.deg2rad(ra)
20+
dec = np.deg2rad(dec)
21+
22+
# Point source coordinate transform, from equatorial to Cartesian
23+
crd_eq = conversions.point_source_crd_eq(ra, dec)
24+
25+
# Beam model
26+
beam = AnalyticBeam(type="gaussian", diameter=14.0)
27+
28+
# Calculate source locations and positions
29+
az, za, beamval = plot._source_az_za_beam(
30+
lst, crd_eq, beam, ref_freq=100.0e6, latitude=np.deg2rad(hera_lat)
31+
)
32+
assert np.all(np.isfinite(az))
33+
assert np.all(np.isfinite(za))
34+
# (Values of beamval should be NaN below the horizon)
35+
36+
37+
def test_animate_source_map():
38+
"""Test function that animates source positions vs LST."""
39+
# Observation latitude and LSTs
40+
hera_lat = -30.7215
41+
lsts = np.linspace(0.0, 2.0 * np.pi, 5)
42+
43+
# Add random sources
44+
ra = np.random.uniform(low=0.0, high=360.0, size=nsource - 1)
45+
dec = -30.72 + np.random.random(nsource - 1) * 10.0
46+
ra = np.deg2rad(ra)
47+
dec = np.deg2rad(dec)
48+
49+
# Beam model
50+
beam = AnalyticBeam(type="gaussian", diameter=14.0)
51+
52+
# Generate animation
53+
anim = plot.animate_source_map(
54+
ra,
55+
dec,
56+
lsts,
57+
beam,
58+
interval=200,
59+
ref_freq=100.0e6,
60+
latitude=np.deg2rad(hera_lat),
61+
)
62+
assert anim is not None

0 commit comments

Comments
 (0)