|
2 | 2 | # Licensed under the 2-clause BSD License |
3 | 3 |
|
4 | 4 | import re |
| 5 | +from dataclasses import dataclass |
5 | 6 |
|
6 | 7 | import numpy as np |
7 | 8 | import pytest |
|
12 | 13 | from pyuvdata import AiryBeam, GaussianBeam, ShortDipoleBeam, UniformBeam, UVBeam |
13 | 14 | from pyuvdata.analytic_beam import AnalyticBeam, UnpolarizedAnalyticBeam |
14 | 15 | from pyuvdata.testing import check_warnings |
| 16 | +from pyuvdata.utils.types import FloatArray |
15 | 17 |
|
16 | 18 |
|
17 | 19 | def test_airy_beam_values(az_za_deg_grid): |
@@ -190,30 +192,137 @@ def test_short_dipole_beam(az_za_deg_grid): |
190 | 192 |
|
191 | 193 | efield_vals = beam.efield_eval(az_array=az_vals, za_array=za_vals, freq_array=freqs) |
192 | 194 |
|
193 | | - expected_data = np.zeros((2, 2, n_freqs, nsrcs), dtype=float) |
| 195 | + expected_efield = np.zeros((2, 2, n_freqs, nsrcs), dtype=float) |
194 | 196 |
|
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) |
199 | 201 |
|
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) |
201 | 203 |
|
202 | 204 | 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) |
204 | 206 |
|
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 |
209 | 211 |
|
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) |
211 | 213 |
|
212 | 214 | assert ( |
213 | 215 | beam.__repr__() == "ShortDipoleBeam(feed_array=array(['x', 'y'], dtype='<U1'), " |
214 | 216 | "feed_angle=array([1.57079633, 0. ]), mount_type='fixed')" |
215 | 217 | ) |
216 | 218 |
|
| 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 | + |
217 | 326 |
|
218 | 327 | def test_shortdipole_feed_error(): |
219 | 328 | with pytest.raises( |
|
0 commit comments