Skip to content

Commit 72cfa02

Browse files
committed
add testing for analytic beam decomposed types
1 parent b24b559 commit 72cfa02

2 files changed

Lines changed: 130 additions & 17 deletions

File tree

src/pyuvdata/analytic_beam.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,15 +391,17 @@ def _get_empty_data_array(
391391
self, grid_shape: tuple[int, int], beam_type: str = "efield"
392392
) -> FloatArray:
393393
"""Get the empty data to fill in the eval methods."""
394-
if beam_type == "efield":
394+
if beam_type in ["efield", "feed_projection"]:
395395
return np.zeros(
396396
(self.Naxes_vec, self.Nfeeds, *grid_shape), dtype=np.complex128
397397
)
398-
else:
398+
elif beam_type == "power":
399399
return np.zeros(
400400
(1, self.Npols, *grid_shape),
401401
dtype=np.complex128 if self.Npols > self.Nfeeds else np.float64,
402402
)
403+
elif beam_type == "feed_iresponse":
404+
return np.zeros((1, self.Nfeeds, *grid_shape), dtype=np.complex128)
403405

404406
def efield_eval(
405407
self, *, az_array: FloatArray, za_array: FloatArray, freq_array: FloatArray
@@ -546,7 +548,7 @@ def feed_iresponse_eval(
546548
az_grid=az_grid, za_grid=za_grid, f_grid=f_grid
547549
).astype(complex)
548550
else:
549-
efield_vals = self.efield_eval(
551+
efield_vals = self._efield_eval(
550552
az_grid=az_grid, za_grid=za_grid, f_grid=f_grid
551553
)
552554

@@ -592,12 +594,14 @@ def feed_projection_eval(
592594
az_grid=az_grid, za_grid=za_grid, f_grid=f_grid
593595
).astype(complex)
594596
else:
595-
efield_vals = self.efield_eval(
597+
efield_vals = self._efield_eval(
596598
az_grid=az_grid, za_grid=za_grid, f_grid=f_grid
597599
)
598600

599601
# set f to the magnitude of the I response, assume no time delays
600-
f_vals = self.feed_iresponse_eval()
602+
f_vals = self.feed_iresponse_eval(
603+
az_array=az_array, za_array=za_array, freq_array=freq_array
604+
)
601605
data_array = self._get_empty_data_array(az_grid.shape)
602606

603607
for fn in np.arange(self.Nfeeds):

tests/test_analytic_beam.py

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Licensed under the 2-clause BSD License
33

44
import re
5+
from dataclasses import dataclass
56

67
import numpy as np
78
import pytest
@@ -12,6 +13,7 @@
1213
from pyuvdata import AiryBeam, GaussianBeam, ShortDipoleBeam, UniformBeam, UVBeam
1314
from pyuvdata.analytic_beam import AnalyticBeam, UnpolarizedAnalyticBeam
1415
from pyuvdata.testing import check_warnings
16+
from pyuvdata.utils.types import FloatArray
1517

1618

1719
def test_airy_beam_values(az_za_deg_grid):
@@ -190,30 +192,137 @@ def test_short_dipole_beam(az_za_deg_grid):
190192

191193
efield_vals = beam.efield_eval(az_array=az_vals, za_array=za_vals, freq_array=freqs)
192194

193-
expected_data = np.zeros((2, 2, n_freqs, nsrcs), dtype=float)
195+
expected_efield = np.zeros((2, 2, n_freqs, nsrcs), dtype=float)
194196

195-
expected_data[0, 0] = -np.sin(az_vals)
196-
expected_data[0, 1] = np.cos(az_vals)
197-
expected_data[1, 0] = np.cos(za_vals) * np.cos(az_vals)
198-
expected_data[1, 1] = np.cos(za_vals) * np.sin(az_vals)
197+
expected_efield[0, 0] = -np.sin(az_vals)
198+
expected_efield[0, 1] = np.cos(az_vals)
199+
expected_efield[1, 0] = np.cos(za_vals) * np.cos(az_vals)
200+
expected_efield[1, 1] = np.cos(za_vals) * np.sin(az_vals)
199201

200-
np.testing.assert_allclose(efield_vals, expected_data, atol=1e-15, rtol=0)
202+
np.testing.assert_allclose(efield_vals, expected_efield, atol=1e-15, rtol=0)
201203

202204
power_vals = beam.power_eval(az_array=az_vals, za_array=za_vals, freq_array=freqs)
203-
expected_data = np.zeros((1, 4, n_freqs, nsrcs), dtype=float)
205+
expected_power = np.zeros((1, 4, n_freqs, nsrcs), dtype=float)
204206

205-
expected_data[0, 0] = 1 - np.sin(za_vals) ** 2 * np.cos(az_vals) ** 2
206-
expected_data[0, 1] = 1 - np.sin(za_vals) ** 2 * np.sin(az_vals) ** 2
207-
expected_data[0, 2] = -(np.sin(za_vals) ** 2) * np.sin(2.0 * az_vals) / 2.0
208-
expected_data[0, 3] = -(np.sin(za_vals) ** 2) * np.sin(2.0 * az_vals) / 2.0
207+
expected_power[0, 0] = 1 - np.sin(za_vals) ** 2 * np.cos(az_vals) ** 2
208+
expected_power[0, 1] = 1 - np.sin(za_vals) ** 2 * np.sin(az_vals) ** 2
209+
expected_power[0, 2] = -(np.sin(za_vals) ** 2) * np.sin(2.0 * az_vals) / 2.0
210+
expected_power[0, 3] = -(np.sin(za_vals) ** 2) * np.sin(2.0 * az_vals) / 2.0
209211

210-
np.testing.assert_allclose(power_vals, expected_data, atol=1e-15, rtol=0)
212+
np.testing.assert_allclose(power_vals, expected_power, atol=1e-15, rtol=0)
211213

212214
assert (
213215
beam.__repr__() == "ShortDipoleBeam(feed_array=array(['x', 'y'], dtype='<U1'), "
214216
"feed_angle=array([1.57079633, 0. ]), mount_type='fixed')"
215217
)
216218

219+
iresponse_vals = beam.feed_iresponse_eval(
220+
az_array=az_vals, za_array=za_vals, freq_array=freqs
221+
)
222+
223+
expected_iresponse = np.zeros((1, 2, n_freqs, nsrcs), dtype=float)
224+
225+
expected_iresponse[0, 0] = np.sqrt(
226+
np.sum(np.abs(expected_efield[:, 0]) ** 2, axis=0)
227+
)
228+
expected_iresponse[0, 1] = np.sqrt(
229+
np.sum(np.abs(expected_efield[:, 1]) ** 2, axis=0)
230+
)
231+
232+
np.testing.assert_allclose(iresponse_vals, expected_iresponse, atol=1e-15, rtol=0)
233+
234+
projection_vals = beam.feed_projection_eval(
235+
az_array=az_vals, za_array=za_vals, freq_array=freqs
236+
)
237+
238+
expected_projval = np.zeros((2, 2, n_freqs, nsrcs), dtype=float)
239+
240+
expected_projval[0, 0] = -np.sin(az_vals) / expected_iresponse[0, 0]
241+
expected_projval[0, 1] = np.cos(az_vals) / expected_iresponse[0, 1]
242+
expected_projval[1, 0] = (
243+
np.cos(za_vals) * np.cos(az_vals) / expected_iresponse[0, 0]
244+
)
245+
expected_projval[1, 1] = (
246+
np.cos(za_vals) * np.sin(az_vals) / expected_iresponse[0, 1]
247+
)
248+
249+
np.testing.assert_allclose(projection_vals, expected_projval, atol=1e-14, rtol=0)
250+
251+
252+
def test_defined_decomp_methods(az_za_deg_grid):
253+
@dataclass(kw_only=True)
254+
class CosEfield(UnpolarizedAnalyticBeam):
255+
"""A test class to test handling for defined decomposition eval methods."""
256+
257+
width: float
258+
259+
def _efield_eval(
260+
self, *, az_grid: FloatArray, za_grid: FloatArray, f_grid: FloatArray
261+
) -> FloatArray:
262+
"""Evaluate the efield at the given coordinates."""
263+
data_array = self._get_empty_data_array(az_grid.shape)
264+
265+
for feed_i in np.arange(self.Nfeeds):
266+
# For power beams the first axis is shallow
267+
data_array[0, feed_i, :, :] = np.cos(self.width * za_grid) / np.sqrt(2)
268+
data_array[1, feed_i, :, :] = np.cos(self.width * za_grid) / np.sqrt(2)
269+
270+
return data_array
271+
272+
def _feed_iresponse_eval(
273+
self, *, az_grid: FloatArray, za_grid: FloatArray, f_grid: FloatArray
274+
) -> FloatArray:
275+
data_array = self._get_empty_data_array(
276+
az_grid.shape, beam_type="feed_iresponse"
277+
)
278+
279+
# set f to the magnitude of the I response, assume no time delays
280+
for feed_i in range(self.Nfeeds):
281+
data_array[0, feed_i] = np.abs(np.cos(self.width * za_grid))
282+
283+
return data_array
284+
285+
def _feed_projection_eval(
286+
self, *, az_grid: FloatArray, za_grid: FloatArray, f_grid: FloatArray
287+
) -> FloatArray:
288+
data_array = self._get_empty_data_array(
289+
az_grid.shape, beam_type="feed_projection"
290+
)
291+
292+
data_array.fill(1 / np.sqrt(2))
293+
# find where it goes negative
294+
wh_neg = np.nonzero(np.cos(self.width * za_grid) < 0)
295+
for bv_i in range(self.Naxes_vec):
296+
for feed_i in range(self.Nfeeds):
297+
data_array[bv_i, feed_i, *wh_neg] *= -1
298+
299+
return data_array
300+
301+
az_vals, za_vals, freqs = az_za_deg_grid
302+
303+
this_cosbeam = CosEfield(width=3.0)
304+
this_iresponse = this_cosbeam.feed_iresponse_eval(
305+
az_array=az_vals, za_array=za_vals, freq_array=freqs
306+
)
307+
308+
from pyuvdata.data.test_analytic_beam import CosEfieldTest
309+
310+
test_cosbeam = CosEfieldTest(width=3.0)
311+
test_iresponse = test_cosbeam.feed_iresponse_eval(
312+
az_array=az_vals, za_array=za_vals, freq_array=freqs
313+
)
314+
315+
np.testing.assert_allclose(this_iresponse, test_iresponse, atol=1e-14, rtol=0)
316+
317+
this_feed_proj = this_cosbeam.feed_projection_eval(
318+
az_array=az_vals, za_array=za_vals, freq_array=freqs
319+
)
320+
test_feed_proj = test_cosbeam.feed_projection_eval(
321+
az_array=az_vals, za_array=za_vals, freq_array=freqs
322+
)
323+
324+
np.testing.assert_allclose(this_feed_proj, test_feed_proj, atol=1e-14, rtol=0)
325+
217326

218327
def test_shortdipole_feed_error():
219328
with pytest.raises(

0 commit comments

Comments
 (0)