Skip to content

Commit 97fe0b7

Browse files
committed
Making uvcalibrate work w/ wideband gains solns
1 parent 2ca5e6f commit 97fe0b7

File tree

3 files changed

+122
-84
lines changed

3 files changed

+122
-84
lines changed

src/pyuvdata/utils/uvcalibrate.py

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,6 @@ def uvcalibrate(
210210
Returns if not inplace
211211
212212
"""
213-
if uvcal.cal_type == "gain" and uvcal.wide_band:
214-
raise ValueError(
215-
"uvcalibrate currently does not support wide-band calibrations"
216-
)
217213
if uvcal.cal_type == "delay" and uvcal.Nspws > 1:
218214
# To fix this, need to make UVCal.convert_to_gain support multiple spws
219215
raise ValueError(
@@ -319,7 +315,7 @@ def uvcalibrate(
319315
)
320316

321317
uvdata_times, uvd_time_ri = np.unique(uvdata.time_array, return_inverse=True)
322-
downselect_cal_times = False
318+
uvcal_times_to_keep = None
323319
# time_range supercedes time_array.
324320
if uvcal.time_range is not None:
325321
if np.min(uvdata_times) < np.min(uvcal.time_range[:, 0]) or np.max(
@@ -402,43 +398,44 @@ def uvcalibrate(
402398
raise ValueError(
403399
f"Time {this_time} exists on UVData but not on UVCal."
404400
)
405-
if len(uvcal_times_to_keep) < uvcal.Ntimes:
406-
downselect_cal_times = True
407401

408-
downselect_cal_freq = False
409-
if uvcal.freq_array is not None:
410-
uvdata_freq_arr_use = uvdata.freq_array
411-
uvcal_freq_arr_use = uvcal.freq_array
412-
try:
413-
freq_arr_match = np.allclose(
414-
np.sort(uvcal_freq_arr_use),
415-
np.sort(uvdata_freq_arr_use),
416-
atol=uvdata._freq_array.tols[1],
417-
rtol=uvdata._freq_array.tols[0],
418-
)
419-
except ValueError:
420-
freq_arr_match = False
421-
422-
if freq_arr_match is False:
423-
# check more carefully
424-
uvcal_freqs_to_keep = []
425-
for this_freq in uvdata_freq_arr_use:
426-
wh_freq_match = np.nonzero(
427-
np.isclose(
428-
uvcal.freq_array - this_freq,
429-
0,
402+
uvcal_chans_to_keep = None if uvcal.wide_band else []
403+
uvcal_spws_to_keep = [] if uvcal.wide_band else None
404+
for idx, spw in enumerate(uvcal.spw_array):
405+
if spw in uvdata.spw_array:
406+
if uvcal.wide_band:
407+
freq_array_spw = uvdata.freq_array[uvdata.flex_spw_id_array == spw]
408+
min_freq = min(uvcal.freq_range[idx])
409+
max_freq = max(uvcal.freq_range[idx])
410+
if all((freq_array_spw < min_freq) | (freq_array_spw > max_freq)):
411+
raise ValueError(
412+
"SPWs between UVData and UVCal overlap, but the frequencies of "
413+
"channels are inconsistent with frequency ranges."
414+
)
415+
uvcal_spws_to_keep.append(spw)
416+
else:
417+
uvcal_chans = np.where(uvcal.flex_spw_id_array == spw)[0]
418+
uvdata_chans = np.where(uvdata.flex_spw_id_array == spw)[0]
419+
uvcal_freqs = uvcal.freq_array[uvcal_chans]
420+
uvdata_freqs = uvdata.freq_array[uvdata_chans]
421+
for indv_freq in uvdata_freqs:
422+
freq_match = np.isclose(
423+
uvcal_freqs,
424+
indv_freq,
430425
atol=uvdata._freq_array.tols[1],
431426
rtol=uvdata._freq_array.tols[0],
432427
)
433-
)
434-
if wh_freq_match[0].size > 0:
435-
uvcal_freqs_to_keep.append(uvcal.freq_array[wh_freq_match][0])
436-
else:
437-
raise ValueError(
438-
f"Frequency {this_freq} exists on UVData but not on UVCal."
439-
)
440-
if len(uvcal_freqs_to_keep) < uvcal.Nfreqs:
441-
downselect_cal_freq = True
428+
if any(freq_match):
429+
uvcal_chans_to_keep.append(uvcal_chans[freq_match][0])
430+
else:
431+
raise ValueError(
432+
f"Frequency {indv_freq} exists on UVData but not on UVCal."
433+
)
434+
435+
if np.array_equal(uvcal_chans_to_keep, np.arange(uvcal.Nfreqs)):
436+
uvcal_chans_to_keep = None
437+
if np.array_equal(uvcal_spws_to_keep, uvcal.spw_array):
438+
uvcal_spws_to_keep = None
442439

443440
# check if x_orientation-equivalent in uvdata isn't set (it's required for uvcal)
444441
uvd_x = uvdata.telescope.get_x_orientation_from_feeds()
@@ -468,21 +465,20 @@ def uvcalibrate(
468465
)
469466

470467
# downselect UVCal times, frequencies
471-
if downselect_cal_freq or downselect_cal_times:
472-
if not downselect_cal_times:
473-
uvcal_times_to_keep = None
474-
elif not downselect_cal_freq:
475-
uvcal_freqs_to_keep = None
476-
468+
new_uvcal = not (
469+
uvcal_times_to_keep is None
470+
and uvcal_chans_to_keep is None
471+
and uvcal_spws_to_keep is None
472+
)
473+
uvcal_use = uvcal
474+
if new_uvcal:
477475
uvcal_use = uvcal.select(
478-
times=uvcal_times_to_keep, frequencies=uvcal_freqs_to_keep, inplace=False
476+
times=uvcal_times_to_keep,
477+
freq_chans=uvcal_chans_to_keep,
478+
spws=uvcal_spws_to_keep,
479+
inplace=False,
479480
)
480481

481-
new_uvcal = True
482-
else:
483-
uvcal_use = uvcal
484-
new_uvcal = False
485-
486482
# input checks
487483
if uvcal_use.cal_type == "delay":
488484
if not new_uvcal:
@@ -525,6 +521,7 @@ def uvcalibrate(
525521
strict=False,
526522
)
527523
)
524+
uvc_spw_map = {spw: idx for idx, spw in enumerate(uvcal_use.spw_array)}
528525

529526
# iterate over keys
530527
for key in uvdata.get_antpairpols():
@@ -573,20 +570,29 @@ def uvcalibrate(
573570
gain = gain[trange_ind_arr[blt_inds], :]
574571
flag = flag[trange_ind_arr[blt_inds], :]
575572

573+
# Use a slice operator to expand out the flags and gains with a wide_band
574+
# calibration solution, otherwise use the Ellipsis to select the whole
575+
# array when using a channel-based soln.
576+
gain_slice = ...
577+
if uvcal_use.wide_band:
578+
gain_slice = np.s_[
579+
:, [uvc_spw_map[spw] for spw in uvdata.flex_spw_id_array]
580+
]
581+
576582
# propagate flags
577583
if prop_flags:
578584
mask = np.isclose(gain, 0.0) | flag
579585
gain[mask] = 1.0
580-
uvdata.flag_array[blt_inds, :, pol_ind] += mask
586+
uvdata.flag_array[blt_inds, :, pol_ind] |= mask[gain_slice]
581587

582588
# apply to data
583589
mult_gains = uvcal_use.gain_convention == "multiply"
584590
if undo:
585591
mult_gains = not mult_gains
586592
if mult_gains:
587-
uvdata.data_array[blt_inds, :, pol_ind] *= gain
593+
uvdata.data_array[blt_inds, :, pol_ind] *= gain[gain_slice]
588594
else:
589-
uvdata.data_array[blt_inds, :, pol_ind] /= gain
595+
uvdata.data_array[blt_inds, :, pol_ind] /= gain[gain_slice]
590596

591597
# update attributes
592598
uvdata.history += "\nCalibrated with pyuvdata.utils.uvcalibrate."

src/pyuvdata/uvdata/mir.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ def _init_from_mir_parser(
714714
self.uvw_array = (-1.0) * uvw_array
715715

716716
self.vis_units = "Jy"
717-
self.pol_convention = "avg"
717+
# self.pol_convention = "avg"
718718

719719
isource = np.unique(mir_data.in_data["isource"])
720720
for sou_id in isource:

tests/utils/test_uvcalibrate.py

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99
import pytest
1010

11-
from pyuvdata import UVCal, utils
11+
from pyuvdata import UVCal, UVData, utils
1212
from pyuvdata.datasets import fetch_data
1313
from pyuvdata.testing import check_warnings
1414
from pyuvdata.utils import uvcalibrate
@@ -698,35 +698,6 @@ def test_uvcalibrate_x_orientation_mismatch(uvcalibrate_data):
698698
uvcalibrate(uvd, uvc, inplace=False)
699699

700700

701-
def test_uvcalibrate_wideband_gain(uvcalibrate_data):
702-
uvd, uvc = uvcalibrate_data
703-
704-
uvc.flex_spw_id_array = None
705-
uvc._set_wide_band()
706-
uvc.spw_array = np.array([1, 2, 3])
707-
uvc.Nspws = 3
708-
uvc.gain_array = uvc.gain_array[:, 0:3, :, :]
709-
uvc.flag_array = uvc.flag_array[:, 0:3, :, :]
710-
uvc.quality_array = uvc.quality_array[:, 0:3, :, :]
711-
uvc.total_quality_array = uvc.total_quality_array[0:3, :, :]
712-
713-
uvc.freq_range = np.zeros((uvc.Nspws, 2), dtype=uvc.freq_array.dtype)
714-
uvc.freq_range[0, :] = uvc.freq_array[[0, 2]]
715-
uvc.freq_range[1, :] = uvc.freq_array[[2, 4]]
716-
uvc.freq_range[2, :] = uvc.freq_array[[4, 6]]
717-
718-
uvc.channel_width = None
719-
uvc.freq_array = None
720-
uvc.Nfreqs = 1
721-
722-
uvc.check()
723-
with pytest.raises(
724-
ValueError,
725-
match="uvcalibrate currently does not support wide-band calibrations",
726-
):
727-
uvcalibrate(uvd, uvc, inplace=False)
728-
729-
730701
@pytest.mark.filterwarnings("ignore:Fixing auto-correlations to be be real-only")
731702
@pytest.mark.filterwarnings("ignore:The uvw_array does not match the expected values")
732703
@pytest.mark.filterwarnings("ignore:Nfreqs will be required to be 1 for wide_band cals")
@@ -892,3 +863,64 @@ def test_uvdata_pol_array_in_stokes(uvcalibrate_data):
892863
),
893864
):
894865
uvcalibrate(uvd, uvc)
866+
867+
868+
@pytest.mark.filterwarnings("ignore:> 25 ms errors detected reading in LST values")
869+
@pytest.mark.filterwarnings("ignore:The lst_array is not self-consistent")
870+
@pytest.mark.filterwarnings("ignore:Setting telescope_location to value in known")
871+
@pytest.mark.filterwarnings("ignore:Unknown polarization basis for solutions")
872+
@pytest.mark.filterwarnings("ignore: Unknown x_orientation basis for solutions")
873+
def test_uvcalibrate_wideband_gains_errs():
874+
uvd = UVData.from_file(fetch_data("sma_mir"))
875+
uvc = UVCal.from_file(fetch_data("sma_pha_gcal"))
876+
877+
# Condition the UVCal object so as to avoid additional warnings
878+
uvc.gain_scale = "Jy"
879+
uvc.pol_convention = "avg"
880+
uvc.time_array[:] = uvd.time_array[0]
881+
882+
with pytest.raises(ValueError, match="SPWs between UVData and UVCal overlap"):
883+
uvcalibrate(uvd, uvc)
884+
885+
886+
@pytest.mark.filterwarnings("ignore:> 25 ms errors detected reading in LST values")
887+
@pytest.mark.filterwarnings("ignore:The lst_array is not self-consistent")
888+
@pytest.mark.filterwarnings("ignore:Setting telescope_location to value in known")
889+
@pytest.mark.filterwarnings("ignore:Unknown polarization basis for solutions")
890+
@pytest.mark.filterwarnings("ignore: Unknown x_orientation basis for solutions")
891+
def test_uvcalibrate_wideband_gains():
892+
uvd = UVData.from_file(fetch_data("sma_mir"))
893+
uvc = UVCal.from_file(fetch_data("sma_pha_gcal"))
894+
895+
# Condition the UVCal object so as to avoid additional warnings
896+
uvc.gain_scale = "Jy"
897+
uvc.pol_convention = "avg"
898+
899+
# Set up gains so that the values change based on SPW number
900+
uvc.gain_array[:, :, 0, :] = uvc.spw_array[None, :, None]
901+
902+
# Flag every other window to test flagging cascading
903+
uvc.flag_array[:, ::2, 0, :] = True
904+
905+
# Get one timestamp to align
906+
uvd.time_array[:] = uvc.time_array[0]
907+
908+
# Make it easier to compare things by setting everything to one
909+
uvd.data_array[:] = 1.0
910+
911+
uvc.spw_array = np.array([-6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6])
912+
uvc.freq_range[2:-2, 0] = np.min(uvd.freq_array.reshape(8, -1), axis=1)
913+
uvc.freq_range[2:-2, 1] = np.max(uvd.freq_array.reshape(8, -1), axis=1)
914+
915+
new_uvd = uvcalibrate(uvdata=uvd, uvcal=uvc, inplace=False)
916+
917+
# Make sure no flagged solns got applied
918+
assert np.all(new_uvd.data_array.reshape(8, 16384, 2)[::2] == 1)
919+
# Confirm all the bad things are flagged
920+
assert np.all(new_uvd.flag_array.reshape(8, 16384, 2)[::2])
921+
922+
# Calculate the expected data based on spw number
923+
exp_data = np.repeat((uvd.spw_array**2)[:, None], 16384 * 2).reshape(1, -1, 2)
924+
925+
# Check that either things agree with expectations or that data are flagged
926+
assert np.all((exp_data == new_uvd.data_array) | new_uvd.flag_array)

0 commit comments

Comments
 (0)