Revision d745e74710ab581d489e815095d0dd4ee91e9c35 authored by Bryna Hazelton on 15 September 2025, 18:00:43 UTC, committed by Jonathan Pober on 15 September 2025, 18:31:58 UTC
1 parent ea19da5
test_pol.py
# Copyright (c) 2024 Radio Astronomy Software Group
# Licensed under the 2-clause BSD License
"""Tests for polarization utility functions."""
import copy
import numpy as np
import pytest
from pyuvdata import utils
from pyuvdata.testing import check_warnings
from pyuvdata.uvbeam.uvbeam import _convert_feeds_to_pols
def test_pol_funcs():
"""Test utility functions to convert between polarization strings and numbers"""
pol_nums = [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4]
pol_str = ["yx", "xy", "yy", "xx", "lr", "rl", "ll", "rr", "pI", "pQ", "pU", "pV"]
assert pol_nums == utils.polstr2num(pol_str)
assert pol_str == utils.polnum2str(pol_nums)
# Check individuals
assert utils.polstr2num("YY") == -6
assert utils.polnum2str(4) == "pV"
# Check errors
pytest.raises(KeyError, utils.polstr2num, "foo")
pytest.raises(ValueError, utils.polstr2num, 1)
pytest.raises(ValueError, utils.polnum2str, 7.3)
# Check parse
assert utils.parse_polstr("xX") == "xx"
assert utils.parse_polstr("XX") == "xx"
assert utils.parse_polstr("i") == "pI"
def test_pol_funcs_x_orientation():
"""Test functions to convert between pol strings and numbers with x_orientation."""
pol_nums = [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4]
x_orient1 = "e"
pol_str = ["ne", "en", "nn", "ee", "lr", "rl", "ll", "rr", "pI", "pQ", "pU", "pV"]
assert pol_nums == utils.polstr2num(pol_str, x_orientation=x_orient1)
assert pol_str == utils.polnum2str(pol_nums, x_orientation=x_orient1)
# Check individuals
assert utils.polstr2num("NN", x_orientation=x_orient1) == -6
assert utils.polnum2str(4) == "pV"
# Check errors
pytest.raises(KeyError, utils.polstr2num, "foo", x_orientation=x_orient1)
pytest.raises(ValueError, utils.polstr2num, 1, x_orientation=x_orient1)
pytest.raises(ValueError, utils.polnum2str, 7.3, x_orientation=x_orient1)
# Check parse
assert utils.parse_polstr("eE", x_orientation=x_orient1) == "ee"
assert utils.parse_polstr("xx", x_orientation=x_orient1) == "ee"
assert utils.parse_polstr("NN", x_orientation=x_orient1) == "nn"
assert utils.parse_polstr("yy", x_orientation=x_orient1) == "nn"
assert utils.parse_polstr("i", x_orientation=x_orient1) == "pI"
x_orient2 = "n"
pol_str = ["en", "ne", "ee", "nn", "lr", "rl", "ll", "rr", "pI", "pQ", "pU", "pV"]
assert pol_nums == utils.polstr2num(pol_str, x_orientation=x_orient2)
assert pol_str == utils.polnum2str(pol_nums, x_orientation=x_orient2)
# Check individuals
assert utils.polstr2num("EE", x_orientation=x_orient2) == -6
assert utils.polnum2str(4) == "pV"
# Check errors
pytest.raises(KeyError, utils.polstr2num, "foo", x_orientation=x_orient2)
pytest.raises(ValueError, utils.polstr2num, 1, x_orientation=x_orient2)
pytest.raises(ValueError, utils.polnum2str, 7.3, x_orientation=x_orient2)
# Check parse
assert utils.parse_polstr("nN", x_orientation=x_orient2) == "nn"
assert utils.parse_polstr("xx", x_orientation=x_orient2) == "nn"
assert utils.parse_polstr("EE", x_orientation=x_orient2) == "ee"
assert utils.parse_polstr("yy", x_orientation=x_orient2) == "ee"
assert utils.parse_polstr("i", x_orientation=x_orient2) == "pI"
# check warnings for non-recognized x_orientation
with check_warnings(UserWarning, "x_orientation not recognized"):
assert utils.polstr2num("xx", x_orientation="foo") == -5
with check_warnings(UserWarning, "x_orientation not recognized"):
assert utils.polnum2str(-6, x_orientation="foo") == "yy"
def test_jones_num_funcs():
"""Test functions to convert between jones polarization strings and numbers."""
jnums = [-8, -7, -6, -5, -4, -3, -2, -1]
jstr = ["Jyx", "Jxy", "Jyy", "Jxx", "Jlr", "Jrl", "Jll", "Jrr"]
assert jnums == utils.jstr2num(jstr)
assert jstr, utils.jnum2str(jnums)
# Check shorthands
jstr = ["yx", "xy", "yy", "y", "xx", "x", "lr", "rl", "ll", "l", "rr", "r"]
jnums = [-8, -7, -6, -6, -5, -5, -4, -3, -2, -2, -1, -1]
assert jnums == utils.jstr2num(jstr)
# Check individuals
assert utils.jstr2num("jyy") == -6
assert utils.jnum2str(-7) == "Jxy"
# Check errors
pytest.raises(KeyError, utils.jstr2num, "foo")
pytest.raises(ValueError, utils.jstr2num, 1)
pytest.raises(ValueError, utils.jnum2str, 7.3)
# check parse method
assert utils.pol.parse_jpolstr("x") == "Jxx"
assert utils.pol.parse_jpolstr("xy") == "Jxy"
assert utils.pol.parse_jpolstr("XY") == "Jxy"
def test_jones_num_funcs_x_orientation():
"""Test functions to convert jones pol strings and numbers with x_orientation."""
jnums = [-8, -7, -6, -5, -4, -3, -2, -1]
x_orient1 = "east"
jstr = ["Jne", "Jen", "Jnn", "Jee", "Jlr", "Jrl", "Jll", "Jrr"]
assert jnums == utils.jstr2num(jstr, x_orientation=x_orient1)
assert jstr == utils.jnum2str(jnums, x_orientation=x_orient1)
# Check shorthands
jstr = ["ne", "en", "nn", "n", "ee", "e", "lr", "rl", "ll", "l", "rr", "r"]
jnums = [-8, -7, -6, -6, -5, -5, -4, -3, -2, -2, -1, -1]
assert jnums == utils.jstr2num(jstr, x_orientation=x_orient1)
# Check individuals
assert utils.jstr2num("jnn", x_orientation=x_orient1) == -6
assert utils.jnum2str(-7, x_orientation=x_orient1) == "Jen"
# Check errors
pytest.raises(KeyError, utils.jstr2num, "foo", x_orientation=x_orient1)
pytest.raises(ValueError, utils.jstr2num, 1, x_orientation=x_orient1)
pytest.raises(ValueError, utils.jnum2str, 7.3, x_orientation=x_orient1)
# check parse method
assert utils.pol.parse_jpolstr("e", x_orientation=x_orient1) == "Jee"
assert utils.pol.parse_jpolstr("x", x_orientation=x_orient1) == "Jee"
assert utils.pol.parse_jpolstr("y", x_orientation=x_orient1) == "Jnn"
assert utils.pol.parse_jpolstr("en", x_orientation=x_orient1) == "Jen"
assert utils.pol.parse_jpolstr("NE", x_orientation=x_orient1) == "Jne"
jnums = [-8, -7, -6, -5, -4, -3, -2, -1]
x_orient2 = "north"
jstr = ["Jen", "Jne", "Jee", "Jnn", "Jlr", "Jrl", "Jll", "Jrr"]
assert jnums == utils.jstr2num(jstr, x_orientation=x_orient2)
assert jstr == utils.jnum2str(jnums, x_orientation=x_orient2)
# Check shorthands
jstr = ["en", "ne", "ee", "e", "nn", "n", "lr", "rl", "ll", "l", "rr", "r"]
jnums = [-8, -7, -6, -6, -5, -5, -4, -3, -2, -2, -1, -1]
assert jnums == utils.jstr2num(jstr, x_orientation=x_orient2)
# Check individuals
assert utils.jstr2num("jee", x_orientation=x_orient2) == -6
assert utils.jnum2str(-7, x_orientation=x_orient2) == "Jne"
# Check errors
pytest.raises(KeyError, utils.jstr2num, "foo", x_orientation=x_orient2)
pytest.raises(ValueError, utils.jstr2num, 1, x_orientation=x_orient2)
pytest.raises(ValueError, utils.jnum2str, 7.3, x_orientation=x_orient2)
# check parse method
assert utils.pol.parse_jpolstr("e", x_orientation=x_orient2) == "Jee"
assert utils.pol.parse_jpolstr("x", x_orientation=x_orient2) == "Jnn"
assert utils.pol.parse_jpolstr("y", x_orientation=x_orient2) == "Jee"
assert utils.pol.parse_jpolstr("en", x_orientation=x_orient2) == "Jen"
assert utils.pol.parse_jpolstr("NE", x_orientation=x_orient2) == "Jne"
# check warnings for non-recognized x_orientation
with check_warnings(UserWarning, "x_orientation not recognized"):
assert utils.jstr2num("x", x_orientation="foo") == -5
with check_warnings(UserWarning, "x_orientation not recognized"):
assert utils.jnum2str(-6, x_orientation="foo") == "Jyy"
def test_conj_pol():
"""Test function to conjugate pols"""
pol_nums = [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4]
cpol_nums = [-7, -8, -6, -5, -3, -4, -2, -1, 1, 2, 3, 4]
assert pol_nums == utils.conj_pol(cpol_nums)
assert utils.conj_pol(pol_nums) == cpol_nums
# fmt: off
pol_str = ['yx', 'xy', 'yy', 'xx', 'ee', 'nn', 'en', 'ne', 'lr', 'rl', 'll',
'rr', 'pI', 'pQ', 'pU', 'pV']
cpol_str = ['xy', 'yx', 'yy', 'xx', 'ee', 'nn', 'ne', 'en', 'rl', 'lr', 'll',
'rr', 'pI', 'pQ', 'pU', 'pV']
# fmt: on
assert pol_str == utils.conj_pol(cpol_str)
assert utils.conj_pol(pol_str) == cpol_str
assert [pol_str, pol_nums] == utils.conj_pol([cpol_str, cpol_nums])
# Test error with jones
cjstr = ["Jxy", "Jyx", "Jyy", "Jxx", "Jrl", "Jlr", "Jll", "Jrr"]
assert pytest.raises(KeyError, utils.conj_pol, cjstr)
# Test invalid pol
with pytest.raises(
ValueError, match="Polarization not recognized, cannot be conjugated."
):
utils.conj_pol(2.3)
def test_reorder_conj_pols_non_list():
pytest.raises(ValueError, utils.pol.reorder_conj_pols, 4)
def test_reorder_conj_pols_strings():
pols = ["xx", "xy", "yx"]
corder = utils.pol.reorder_conj_pols(pols)
assert np.array_equal(corder, [0, 2, 1])
def test_reorder_conj_pols_ints():
pols = [-5, -7, -8] # 'xx', 'xy', 'yx'
corder = utils.pol.reorder_conj_pols(pols)
assert np.array_equal(corder, [0, 2, 1])
def test_reorder_conj_pols_missing_conj():
pols = ["xx", "xy"] # Missing 'yx'
pytest.raises(ValueError, utils.pol.reorder_conj_pols, pols)
def test_determine_pol_order_err():
with pytest.raises(ValueError, match='order must be either "AIPS" or "CASA".'):
utils.pol.determine_pol_order([], order="ABC")
@pytest.mark.parametrize(
"pols,aips_order,casa_order",
[
[[-8, -7, -6, -5], [3, 2, 1, 0], [3, 1, 0, 2]],
[[-5, -6, -7, -8], [0, 1, 2, 3], [0, 2, 3, 1]],
[[1, 2, 3, 4], [0, 1, 2, 3], [0, 1, 2, 3]],
],
)
@pytest.mark.parametrize("order", ["CASA", "AIPS"])
def test_pol_order(pols, aips_order, casa_order, order):
check = utils.pol.determine_pol_order(pols, order=order)
if order == "CASA":
assert all(check == casa_order)
if order == "AIPS":
assert all(check == aips_order)
def test_x_orientation_pol_map():
assert utils.x_orientation_pol_map("north") == {"x": "n", "y": "e"}
@pytest.mark.parametrize(
("input_kwargs", "output"),
[
({"feed_array": ["x"]}, [-5]),
({"feed_array": ["x", "y"]}, [-5, -6, -7, -8]),
({"feed_array": ["r", "l"]}, [-1, -2, -3, -4]),
({"feed_array": ["x", "y"], "include_cross_pols": False}, [-5, -6]),
(
{
"feed_array": ["e", "n"],
"x_orientation": "east",
"return_feed_pol_order": True,
},
([-5, -6, -7, -8], [(0, 0), (1, 1), (0, 1), (1, 0)]),
),
(
{
"feed_array": ["n", "e"],
"x_orientation": "north",
"return_feed_pol_order": True,
},
([-5, -6, -7, -8], [(0, 0), (1, 1), (0, 1), (1, 0)]),
),
(
{
"feed_array": ["e", "n"],
"x_orientation": "north",
"return_feed_pol_order": True,
},
([-6, -5, -8, -7], [(0, 0), (1, 1), (0, 1), (1, 0)]),
),
],
)
def test_convert_feeds_to_pols(input_kwargs, output):
ret_val = utils.pol.convert_feeds_to_pols(**input_kwargs)
if not isinstance(output, tuple):
assert ret_val.tolist() == output
else:
assert ret_val[0].tolist() == output[0]
assert ret_val[1] == output[1]
dep_kwargs = copy.deepcopy(input_kwargs)
dep_kwargs["calc_cross_pols"] = dep_kwargs.pop("include_cross_pols", True)
del dep_kwargs["return_feed_pol_order"]
with check_warnings(
DeprecationWarning,
match="This method (uvbeam._convert_feeds_to_pols) is deprecated in "
"favor of utils.pol.convert_feeds_to_pols. This will become an error "
"in version 3.3",
):
dep_ret_val = _convert_feeds_to_pols(**dep_kwargs)
assert dep_ret_val[0].tolist() == output[0]
assert dep_ret_val[1] == output[1]
def test_convert_feeds_to_pols_errors():
with pytest.raises(
ValueError, match="feed_array contains 3 feeds. Only 1 or 2 feeds is supported."
):
utils.pol.convert_feeds_to_pols(["x", "y", "z"])
with pytest.raises(
ValueError, match="feed_array contains 0 feeds. Only 1 or 2 feeds is supported."
):
utils.pol.convert_feeds_to_pols([])
@pytest.mark.parametrize(
"kwargs,err_msg",
[
[{"feeds": ["l", "r"], "x_orientation": "xyz"}, "x_orientation not recognized"],
[
{"feeds": ["l", "r", "x"], "x_orientation": "n"},
"feeds must be a list or tuple of length 1 or 2.",
],
[
{"feeds": ["u", "v"], "x_orientation": "n"},
'feeds must contain only "x", "y", "l", and/or "r".',
],
],
)
def test_get_feeds_from_x_orientation_errors(kwargs, err_msg):
with pytest.raises(ValueError, match=err_msg):
utils.pol.get_feeds_from_x_orientation(nants=1, **kwargs)
@pytest.mark.parametrize(
"kwargs,exp_feed_array,exp_feed_angle,has_warn",
[
[{"x_orientation": "e", "nants": 1}, [["x", "y"]], [[np.pi / 2, 0]], True],
[{"x_orientation": None, "nants": 2}, None, None, False],
[{"x_orientation": "n", "nants": 1, "feeds": "x"}, [["x"]], [[0]], False],
[
{
"x_orientation": "east",
"nants": 2,
"polarization_array": [-5, -6, -7, -8],
},
[["x", "y"], ["x", "y"]],
[[np.pi / 2, 0], [np.pi / 2, 0]],
False,
],
[
{
"x_orientation": "east",
"nants": 2,
"flex_polarization_array": [-1, -2, -3, -4],
"polarization_array": [0],
},
[["l", "r"], ["l", "r"]],
[[0, 0], [0, 0]],
False,
],
],
)
def test_get_feeds_from_x_orientation(kwargs, exp_feed_array, exp_feed_angle, has_warn):
with check_warnings(
UserWarning if has_warn else None,
match=("Unknown polarization basis" if has_warn else None),
):
Nfeeds, feed_array, feed_angle = utils.pol.get_feeds_from_x_orientation(
**kwargs
)
if exp_feed_angle is None:
assert Nfeeds is None
assert feed_array is None
assert feed_angle is None
else:
assert np.array_equal(feed_array, exp_feed_array)
assert np.array_equal(feed_angle, exp_feed_angle)
assert Nfeeds == feed_array.shape[1]
assert Nfeeds == feed_angle.shape[1]

Computing file changes ...