Source code for flint.selfcal.utils
"""Gemeral help functions that could be used for self-calibration
across different packages.
"""
from __future__ import annotations
from math import ceil
from pathlib import Path
from flint.logging import logger
from flint.ms import get_freqs_from_ms
from flint.options import MS
from flint.utils import remove_files_folders
[docs]
def create_and_check_caltable_path(
ms: MS, channel_range: tuple[int, int] | None = None, remove_if_exists: bool = False
) -> Path:
"""Create the output name path for the gaincal solutions table.
If the table already exists it will be removed.
Args:
cal_ms (MS): A description of the measurement set
channel_range (tuple[int,int] | None, optional): Channel start and end, which will be appended. Defaults to None.
remove_if_exists (bool, optional): If ``True`` and the table exists, remove it. Defaults to False.
Returns:
Path: Output path of the solutions table
"""
cal_suffix = ".caltable"
if channel_range:
cal_suffix += f".ch{channel_range[0]:04d}-{channel_range[1]:04d}"
cal_table_name = ms.path.with_suffix(cal_suffix)
cal_table = ms.path.absolute().parent / cal_table_name
logger.info(f"Will create calibration table {cal_table}.")
if remove_if_exists and cal_table.exists():
logger.warning(f"Removing {cal_table!s}")
remove_files_folders(cal_table)
return cal_table
[docs]
def get_channel_ranges_given_nspws(
num_channels: int, nspws: int
) -> tuple[tuple[int, int], ...]:
"""Given the number of channels construct the start and end channel indices
for the specified number of spectral windows. The interval step size will be ceiled
should the the ``num_channels / nspw`` not be whole. In this case the last
interval will be smaller than the others.
Args:
num_channels (int): The number of channels spanning some band
nspws (int): The number of segments across the band
Returns:
tuple[tuple[int,int]]: The start and end channel index spanning the number of channels.
"""
step = ceil(num_channels / nspws)
starts = list(range(0, num_channels, step))
ends = [min(start + step - 1, num_channels - 1) for start in starts]
return tuple(zip(starts, ends))
[docs]
def get_channel_ranges_given_nspws_for_ms(
ms: MS | Path, nspw: int
) -> tuple[tuple[int, int], ...]:
"""Construct channel range intervals for the channels in a measurement set
given a desired number of spectral windows
Args:
ms (MS | Path): The measurement set to construct channel ranges for
nspw (int): Number of channel intervals to construct
Returns:
tuple[tuple[int,int], ...]: The collection of start and end channel intervals. The length will be ``nspw``
"""
ms = MS.cast(ms=ms)
logger.info(f"Considering {ms.path}, obtaining channel ranges for {nspw=}")
freqs = get_freqs_from_ms(ms=ms)
return get_channel_ranges_given_nspws(num_channels=len(freqs), nspws=nspw)
[docs]
def consider_skip_selfcal_on_round(
current_round: int, skip_selfcal_on_rounds: int | list[int] | None
) -> bool:
"""Consider whether the self-calibration process (derive and applying solutions)
should be skipped on a particular imaging round.
Should `current_round` be in `skip_selfcal_on_round` then the self-calibration
should be skipped and a `True` is returned.
Args:
current_round (int): The current imaging round being considered
skip_selfcal_on_rounds (Union[int, List[int], None]): The set of rounds that should be considerede for skipping. If None a False is returned.
Returns:
bool: Whether the round is skipped (True) or performed (False)
"""
# Consider whether this is unset, in which case all rounds should be self-cal
if skip_selfcal_on_rounds is None:
return False
# For sanity consider a single int case
skip_selfcal_on_rounds = (
skip_selfcal_on_rounds
if isinstance(skip_selfcal_on_rounds, list)
else [skip_selfcal_on_rounds]
)
return current_round in skip_selfcal_on_rounds