Source code for orca.resources.subband_config

"""Subband processing configuration for OVRO-LWA pipeline.

Contains node-to-subband mapping, imaging parameters, reference file paths,
and all configuration needed by the subband processing Celery tasks.

All settings live within the orca package for use with the Celery-based
subband pipeline.
"""
from astropy.coordinates import EarthLocation
import astropy.units as u

# --- Observatory Location (single source of truth) ---
[docs] OVRO_LOC = EarthLocation(lat=37.23977727*u.deg, lon=-118.2816667*u.deg, height=1222*u.m)
# --- Conda env required for running the pipeline worker ---
[docs] REQUIRED_CONDA_ENV = 'py38_orca_nkosogor'
# --------------------------------------------------------------------------- # Node ↔ Subband mapping # Each calim server has local NVMe at /fast/pipeline/ that is NOT shared. # Subbands are pinned to specific nodes so data stays local. # ---------------------------------------------------------------------------
[docs] NODE_SUBBAND_MAP = { '18MHz': 'lwacalim00', '23MHz': 'lwacalim00', '27MHz': 'lwacalim01', '32MHz': 'lwacalim01', '36MHz': 'lwacalim02', '41MHz': 'lwacalim02', '46MHz': 'lwacalim03', '50MHz': 'lwacalim03', '55MHz': 'lwacalim04', '59MHz': 'lwacalim05', '64MHz': 'lwacalim06', '69MHz': 'lwacalim07', '73MHz': 'lwacalim08', '78MHz': 'lwacalim09', '82MHz': 'lwacalim00', }
# Reverse map: node → list of subbands
[docs] SUBBAND_NODE_MAP = {}
for _sb, _node in NODE_SUBBAND_MAP.items(): SUBBAND_NODE_MAP.setdefault(_node, []).append(_sb) # All unique calim nodes
[docs] CALIM_NODES = sorted(set(NODE_SUBBAND_MAP.values()))
# --------------------------------------------------------------------------- # Dynamic dispatch — node pool # Used with ``--dynamic`` mode. Any node in this list can process any # subband. Edit this list to match currently active nodes. # ---------------------------------------------------------------------------
[docs] DYNAMIC_NODE_POOL = [ 'calim00', 'calim01', 'calim03', 'calim04', 'calim05', 'calim06', 'calim07', 'calim08', 'calim09', ]
[docs] def get_queue_for_subband(subband: str) -> str: """Return the Celery queue name for a given subband. Args: subband: e.g. '73MHz' Returns: Queue name, e.g. 'calim08' """ node = NODE_SUBBAND_MAP.get(subband) if node is None: raise ValueError(f"Unknown subband: {subband}") # Queue name = last part of hostname: lwacalim08 → calim08 return node.replace('lwa', '')
# --------------------------------------------------------------------------- # Directory layout # ---------------------------------------------------------------------------
[docs] NVME_BASE_DIR = '/fast/pipeline' # Local NVMe on each calim node
[docs] LUSTRE_ARCHIVE_DIR = '/lustre/pipeline/images' # Shared Lustre storage
# Where archive MS files live (input)
[docs] LUSTRE_NIGHTTIME_DIR = '/lustre/pipeline/night-time/averaged'
# Where final products go
[docs] LUSTRE_PRODUCTS_DIR = '/lustre/pipeline/products'
# --------------------------------------------------------------------------- # Reference files (on shared Lustre, accessible from all nodes) # ---------------------------------------------------------------------------
[docs] PEELING_PARAMS = { 'sky_env': 'julia060', 'rfi_env': 'ttcal_dev', 'sky_model': '/lustre/gh/calibration/pipeline/reference/sources/sources.json', 'rfi_model': '/lustre/gh/calibration/pipeline/reference/sources/rfi_43.2_ver20251101.json', 'beam': 'constant', 'minuvw': 5, 'maxiter': 5, 'tolerance': '1e-4', 'args': '--beam constant --minuvw 5 --maxiter 5 --tolerance 1e-4', }
[docs] AOFLAGGER_STRATEGY = '/lustre/ghellbourg/AOFlagger_strat_opt/LWA_opt_GH1.lua'
[docs] VLSSR_CATALOG = '/lustre/gh/calibration/pipeline/reference/surveys/FullVLSSCatalog.text'
[docs] BEAM_MODEL_H5 = '/lustre/gh/calibration/pipeline/reference/beams/OVRO-LWA_MROsoil_updatedheight.h5'
# --------------------------------------------------------------------------- # Calibrator flux scale (Scaife & Heald 2012 + Perley-Butler 2017) # --------------------------------------------------------------------------- from astropy.coordinates import SkyCoord
[docs] CALIB_DATA = { '3C48': {'coords': SkyCoord('01h37m41.3s', '+33d09m35s'), 'scale': 'SH12', 'coeffs': [64.768, -0.387, -0.420, 0.181]}, '3C147': {'coords': SkyCoord('05h42m36.1s', '+49d51m07s'), 'scale': 'SH12', 'coeffs': [66.738, -0.022, -1.012, 0.549]}, '3C196': {'coords': SkyCoord('08h13m36.0s', '+48d13m03s'), 'scale': 'SH12', 'coeffs': [83.084, -0.699, -0.110]}, '3C286': {'coords': SkyCoord('13h31m08.3s', '+30d30m33s'), 'scale': 'SH12', 'coeffs': [27.477, -0.158, 0.032, -0.180]}, '3C295': {'coords': SkyCoord('14h11m20.5s', '+52d12m10s'), 'scale': 'SH12', 'coeffs': [97.763, -0.582, -0.298, 0.583, -0.363]}, '3C380': {'coords': SkyCoord('18h29m31.8s', '+48d44m46s'), 'scale': 'SH12', 'coeffs': [77.352, -0.767]}, '3C123': {'coords': SkyCoord('04h37m04.4s', '+29d40m14s'), 'scale': 'PB17', 'coeffs': [1.8017, -0.7884, -0.1035, -0.0248, 0.0090]}, }
# --------------------------------------------------------------------------- # Hot baseline analysis parameters # ---------------------------------------------------------------------------
[docs] HOT_BASELINE_PARAMS = { 'run_uv_analysis': True, 'run_heatmap_analysis': True, 'uv_sigma': 7.0, 'heatmap_sigma': 5.0, 'bad_antenna_threshold': 0.25, 'uv_cut_lambda': 4.0, 'uv_window_size': 100, 'apply_flags': True, }
# --------------------------------------------------------------------------- # Imaging configurations # Imaging configurations # ---------------------------------------------------------------------------
[docs] SNAPSHOT_PARAMS = { 'suffix': 'Pilot-Snapshot', 'args': [ '-log-time', '-pol', 'IV', '-niter', '0', '-mem', '20', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-no-dirty', '-make-psf', '-no-update-model-required', ], }
# Stokes-I-only CLEANed snapshots (produced IN ADDITION to dirty pilots). # Optimised per Marin Torchiarolo's wsclean benchmarks: # auto-mask=5 (sweet spot), mgain=0.9999 (~2 major cycles), # auto-threshold=1 (safe floor, negligible time impact). # Output goes to snapshots_clean/ and is always fpack-compressed.
[docs] SNAPSHOT_CLEAN_I_PARAMS = { 'suffix': 'Clean-Snapshot', 'args': [ '-log-time', '-pol', 'I', '-niter', '50000', '-mgain', '0.9999', '-auto-mask', '5', '-auto-threshold', '1', '-local-rms', '-horizon-mask', '10deg', '-mem', '50', '-no-dirty', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-no-update-model-required', ], }
[docs] IMAGING_STEPS = [ # --- STOKES I --- { 'pol': 'I', 'category': 'deep', 'suffix': 'I-Deep-Taper-Robust-0.75', 'args': [ '-log-time', '-pol', 'I', '-multiscale', '-multiscale-scale-bias', '0.8', '-niter', '500000', '-mgain', '0.95', '-horizon-mask', '10deg', '-mem', '50', '-auto-threshold', '0.5', '-auto-mask', '3', '-local-rms', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '-0.75', '-no-update-model-required', ], }, { 'pol': 'I', 'category': 'deep', 'suffix': 'I-Deep-Taper-Robust-0', 'args': [ '-log-time', '-pol', 'I', '-multiscale', '-multiscale-scale-bias', '0.8', '-niter', '500000', '-mgain', '0.95', '-horizon-mask', '10deg', '-mem', '50', '-auto-threshold', '0.5', '-auto-mask', '3', '-local-rms', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-no-update-model-required', ], }, { 'pol': 'I', 'category': 'deep', 'suffix': 'I-Deep-NoTaper-Robust-0.75', 'args': [ '-log-time', '-pol', 'I', '-multiscale', '-multiscale-scale-bias', '0.8', '-niter', '150000', '-mgain', '0.95', '-horizon-mask', '10deg', '-mem', '50', '-auto-threshold', '0.5', '-auto-mask', '3', '-local-rms', '-size', '4096', '4096', '-scale', '0.03125', '-weight', 'briggs', '-0.75', '-no-update-model-required', ], }, { 'pol': 'I', 'category': 'deep', 'suffix': 'I-Deep-NoTaper-Robust-0', 'args': [ '-log-time', '-pol', 'I', '-multiscale', '-multiscale-scale-bias', '0.8', '-niter', '150000', '-mgain', '0.95', '-horizon-mask', '10deg', '-mem', '50', '-auto-threshold', '0.5', '-auto-mask', '3', '-local-rms', '-size', '4096', '4096', '-scale', '0.03125', '-weight', 'briggs', '0', '-no-update-model-required', ], }, { 'pol': 'I', 'category': '10min', 'suffix': 'I-Taper-10min', 'args': [ '-log-time', '-pol', 'I', '-multiscale', '-multiscale-scale-bias', '0.8', '-niter', '50000', '-mgain', '0.95', '-horizon-mask', '10deg', '-mem', '50', '-auto-threshold', '0.5', '-auto-mask', '3', '-local-rms', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-intervals-out', '6', '-no-update-model-required', ], }, # --- STOKES V --- { 'pol': 'V', 'category': 'deep', 'suffix': 'V-Taper-Deep', 'args': [ '-log-time', '-pol', 'V', '-niter', '0', '-horizon-mask', '10deg', '-mem', '50', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-no-dirty', '-no-update-model-required', ], }, { 'pol': 'V', 'category': '10min', 'suffix': 'V-Taper-10min', 'args': [ '-log-time', '-pol', 'V', '-niter', '0', '-horizon-mask', '10deg', '-mem', '50', '-size', '4096', '4096', '-scale', '0.03125', '-taper-inner-tukey', '30', '-weight', 'briggs', '0', '-no-dirty', '-intervals-out', '6', '-no-update-model-required', ], }, ]
# --------------------------------------------------------------------------- # Resource management for shared calim nodes # --------------------------------------------------------------------------- # Nodes with 2 subbands (e.g. calim00: 18+23 MHz) share 32 cores / 128 GB. # Nodes with 1 subband get all resources. _DUAL_SUBBAND_NODES = { n for n, subs in SUBBAND_NODE_MAP.items() if len(subs) > 1 } # --------------------------------------------------------------------------- # Per-subband pixel scaling # Lower-frequency subbands have wider beams → fewer pixels needed. # This speeds up imaging significantly for the lowest bands. # --------------------------------------------------------------------------- _SUBBAND_PIXEL_SIZE = { '18MHz': 1024, '23MHz': 1024, '27MHz': 1024, '32MHz': 1024, '36MHz': 1024, '41MHz': 2048, '46MHz': 2048, '50MHz': 2048, '55MHz': 2048, '59MHz': 2048, '64MHz': 4096, '69MHz': 4096, '73MHz': 4096, '78MHz': 4096, '82MHz': 4096, } # Pixel scale (deg/pixel) paired with _SUBBAND_PIXEL_SIZE so that # npix * scale = const (≈128°), preserving full FoV at every tier. _SUBBAND_PIXEL_SCALE = { '18MHz': 0.125, '23MHz': 0.125, '27MHz': 0.125, '32MHz': 0.125, '36MHz': 0.125, '41MHz': 0.0625, '46MHz': 0.0625, '50MHz': 0.0625, '55MHz': 0.0625, '59MHz': 0.0625, '64MHz': 0.03125,'69MHz': 0.03125,'73MHz': 0.03125,'78MHz': 0.03125,'82MHz': 0.03125, }
[docs] def get_pixel_size(subband: str) -> int: """Return the image pixel dimension for a given subband. Lower subbands use fewer pixels (wider beam → coarser resolution): 18-36 MHz → 1024 (4096/4) 41-59 MHz → 2048 (4096/2) 64-82 MHz → 4096 Args: subband: e.g. '55MHz' Returns: Pixel dimension (square images: NxN). """ return _SUBBAND_PIXEL_SIZE.get(subband, 4096)
[docs] def get_pixel_scale(subband: str) -> float: """Return the pixel scale (deg/pixel) paired with :func:`get_pixel_size`. The product ``get_pixel_size(sb) * get_pixel_scale(sb)`` is constant (~128°) so that the field-of-view is preserved across frequency tiers. 18-36 MHz → 0.125 (0.03125 * 4) 41-59 MHz → 0.0625 (0.03125 * 2) 64-82 MHz → 0.03125 Args: subband: e.g. '55MHz' Returns: Pixel scale in degrees. """ return _SUBBAND_PIXEL_SCALE.get(subband, 0.03125)
[docs] def get_image_resources(subband: str): """Return (cpus, mem_gb, wsclean_j) for a given subband. In dynamic dispatch mode any subband can land on any node, so we always allocate full node resources (44 cores). The old dual-node halving (22 cores) is no longer used. Args: subband: e.g. '73MHz' Returns: Tuple of (cpus: int, mem_gb: int, wsclean_j: int). """ return 44, 120, 44