RFI Signal Components
- class tabascal.components.rfi_signal.BaseGPRFI[source]
- build_mask_constants() dict[source]
Constants the signal mask needs, or
{}when nothing is masked.Kept separate from each component’s own
build_constantsso the None-check lives in one place. The mask is a constant rather than a closed-over array because it is indexed byn_rfi:distributed.pyshards constants named inRFI_AXIS_NAMESalong the source axis, and a captured array would instead be replicated, pullingrfi_Aback to a full copy on every device.
- build_masked_signal() Callable[source]
Return the signal-domain mask to apply at the end of a forward.
Sibling of
masked_forward_transform(), which zeroes the padded dummy sources in the latent k-space. A time window cannot be expressed there: zeroing global Fourier modes cannot produce a time-limited signal, so the elevation mask has to be applied torfi_Aafterlatent_to_signal.Both follow the same contract – a base-class hook every component applies unconditionally, which degrades to the identity when there is nothing to mask (no elevation cut here, a single device there). Resolving the branch here rather than inside the traced function means a run without an elevation cut emits no mask op at all and pays nothing.
The returned function takes any array whose leading axis is
n_rfiand whose trailing axis isn_time_fine, so a component that keeps the antenna axis broadcast rather than materialised can mask the smaller(n_rfi, n_freq_fine, n_time_fine)array before expanding it.
- config_params: Dict[str, Param] = {'rfi.corr_freq': Param(types=(<class 'int'>, <class 'float'>), default=1000000.0, choices=(), item=None, gt=0, ge=None, lt=None, le=None, null_ok=True, doc='correlation bandwidth of the RFI signal in Hz; null derives it'), 'rfi.corr_time': Param(types=(<class 'int'>, <class 'float'>), default=24, choices=(), item=None, gt=0, ge=None, lt=None, le=None, null_ok=True, doc='correlation time of the RFI signal in seconds; null derives it'), 'rfi.est': Param(types=(<class 'str'>,), default=None, choices=(), item=None, gt=None, ge=None, lt=None, le=None, null_ok=False, doc="light-curve file used by init/mean 'est'; see docs/config.md"), 'rfi.init': Param(types=(), default='sample', choices=('prior', 'est', 'truth', 'sample', 'zeros', 0, 'ones', 1), item=None, gt=None, ge=None, lt=None, le=None, null_ok=False, doc='how the RFI signal parameters are initialised'), 'rfi.mean': Param(types=(), default=0, choices=('data', 'est', 'zeros', 0), item=None, gt=None, ge=None, lt=None, le=None, null_ok=False, doc='mean of the prior over the RFI signal'), 'rfi.r_seed': Param(types=(<class 'int'>,), default=123, choices=(), item=None, gt=None, ge=None, lt=None, le=None, null_ok=False, doc='seed for RFI samples drawn from the prior'), 'rfi.var': Param(types=(<class 'int'>, <class 'float'>), default=<derived from the data>, choices=(), item=None, gt=0, ge=None, lt=None, le=None, null_ok=False, doc='variance of the RFI signal in Jy; null estimates it from the data')}
Config parameters this component reads, keyed by their dotted path in the config file. Declared here, next to the code that reads them, so a component and its requirements can never drift apart. Only the components named in
model.componentscontribute to a run’s schema – seetabascal.config_schema.collect_params(). Subclasses extend (and may override) what their base declared; the parameters read outside any component live ontabascal.config.TabConfig.
- resolve_data_params(tab_config: TabConfig) Dict[source]
Resolve the
rfiparameters that are derived from the data.The schema has already checked everything it can; what is left is the parameters declared
FROM_DATA, whose defaults come from the observed visibility amplitude and the extent of the observation’s own axes.Returns the resolved values rather than writing them back into
tab_config.args, so a second component reading the same section still sees what was configured.
- class tabascal.components.rfi_signal.ComplexRFIConstAnt[source]
- class tabascal.components.rfi_signal.ComplexRFIVarAnt[source]
- build_constants()[source]
Return arrays that do not change during the forward pass.
Returns a dict of array_name -> array_value. These will be stored in constants as “_c/<ClassName>/array_name” by Model.__init__.
- build_forward()[source]
Return pure, JIT-compatible function
The latent-to-signal transform is scanned over antennas rather than vmapped. A double vmap over
(n_rfi, n_ant)lowers to a single batched cuFFT ofn_rfi * n_anttransforms on the zero-padded grid, and cuFFT sizes its plan work area for the whole batch. At 32 channels that reached a 12.6 GiB request which aborted the process from inside XLA – aCheck failure, not a catchable Python OOM, so there was no graceful degradation. Scanning the antenna axis reduces that batch byn_ant.checkpointon the body is load-bearing rather than decorative:lax.scanstacks the body’s residuals across iterations for reverse-mode AD, which would rebuild much of what the vmap was holding, so without it the scan fixes the cuFFT plan and not the autodiff tape.Measured on a 64-antenna / 32-channel / 4-satellite problem, single precision: peak device memory 35.80 -> 14.62 GB (2.45x) for a 4% runtime cost, with the optimised chi^2 unchanged to ~6 significant figures.
- tabascal.components.rfi_signal.read_light_curves(est_path: str, norad_ids: List[int], times_mjd: NDArray, freqs: NDArray) Array[source]
Read an RFI light curve estimate onto the observation grid.
File structure
A
.zarrstore (read withxarray.open_zarr()) or a.npz, holdinglight_curves(n_src, n_time, n_freq). One light curve per source, in the same units the RFI visibility amplitude is squared from.norad_ids(n_src,). NORAD id labelling each row oflight_curves.times(n_time,). Modified Julian Date, in days, strictly increasing.freqs(n_freq,). Frequency in Hz, strictly increasing.
In the zarr form the last three are coordinates of
light_curves.All four are required. The format is deliberately strict: this is the interchange standard between tabascal and whatever measures the light curves, and every loose alternative it could accept instead fails silently. Matching rows by position rather than by id attaches a curve to the wrong satellite without changing its shape; assuming the file’s sampling matches the observation’s resamples it wrongly by an unknown amount. Neither shows up as an error, only as a worse fit.
Times are absolute (MJD) rather than seconds from the start of a particular observation, so a light curve is interpretable on its own and can be reused across measurement sets covering the same pass.
Resampling
Light curves are interpolated linearly onto
times_mjdandfreqs. Samples outside the file’s coverage are zero, on either axis – the file says nothing there, which is the same “no signal known” convention the elevation mask uses. An axis of length 1 is held constant instead, since a single sample carries no gradient to interpolate along.Partial coverage
Satellites with no light curve in the file are zero, so an estimate only has to cover the satellites it was actually measured for rather than every satellite in the fit. Those are named in a warning. It is an error for no configured satellite to be found, which otherwise silently degrades the whole estimate to zeros.
- returns:
Light curves on the observation grid, in
norad_idsorder, with unmatched satellites zero and NaNs replaced by zero.- rtype:
Array (n_rfi, n_freq, n_time)