Configuration File

TABASCAL makes use of a configuration file to fully define the model used, what satellites to include and what inference to do. The configuration file is in YAML format using only the most basic constructs and types. The configuration file is broken up into multiple sections. Each section will be described below.

Model

The forward model used by TABASCAL is modular and configurable directly in the model section of the configuration file. An example of this section is shown below.

model:
  name: Custom
  components:
    # RFI Phase
    - trajectory:KeplerOrbit
    - trajectory:PhaseCalculationRFI
    # RFI Signal
    - rfi_signal:ComplexRFIVarAnt
    # RFI Visibilty
    - rfi_vis:RiemannVis
    # Astronomical Visibility
    - ast_vis:GPVisAst
    # Gains
    - gains:UnitaryGains

The components should be given in order of dependency. For example, trajectory:KeplerOrbit is specified before trajectory:PhaseCalculationRFI because the later depends on the output of the former. Each component is a class which defines the parameters (if any), their initialisation, their prior distribution, and its own forward model for the component. The component modules are located in tabascal/components/. The model component given in the configuration file should use the module name and then the class name. For example the component trajectory:KeplerOrbit is a class named KeplerOrbit that resides in the module file tabascal/components/trajectory.py

Data

The data section of the configuration file includes only a few options to select the data to use. An exhaustive example is given below.

data:
  sim_dir:
  ms_path: path/to/ms_file.ms
  data_col: DATA
  freq: 0
  corr: xx
  noise:
  • sim_dir: Simulation directory created when using sim-vis to simulate a dataset. This can also be given at runtime of tabascal with the -s flag.

  • ms_path: Path to the Measurement Set (MS) to run on. This can also be given at runtime of tabascal with the -ms flag.

  • data_col: The data column within the MS file to use as the observed data. Default is DATA but can be any column that exists in the MS file.

  • freq: This is the frequency channel to run on. Default is to run on all frequency channels.

  • corr: This is the correlation product to run on, default xx. It is matched against the MS’s POLARIZATION::CORR_TYPE by identity, not by position, so it names the correlation you want rather than an axis index: yy selects YY whether the MS holds all four correlations or only that one. Linear (xx, xy, yx, yy), circular (rr, rl, lr, ll) and Stokes (i, q, u, v) names are accepted. Requesting a correlation the MS does not hold is an error naming what it does hold.

  • noise: The per-visibility noise in Jy. Leave it null to use the MS’s own SIGMA column, which is read per baseline rather than averaged to one number: the antennas of a real array differ in sensitivity, and a single value mis-weights every visibility. On EDA2 the per-baseline SIGMA spans a factor of ~30, so a scalar under-weights the quietest baselines by up to ~200x. It matters most when fitting gains, because the per-antenna noise correlates with the per-antenna gain (measured sigma_a ~ amplitude_a^0.76, R = 0.96) — a uniform-noise likelihood cannot tell a loud antenna from a noisy one, so the fitted gain absorbs the noise structure.

    A scalar may still be given, applying to every baseline. A path to an .npz is also accepted, for a noise measured out of band; it must carry one of:

    key

    shape

    meaning

    sigma_bl

    (n_bl,)

    per-baseline noise, used as given

    s_ant

    (n_ant,)

    per-antenna noise, combined as sqrt(s_p^2 + s_q^2) / sqrt(2)

    Baselines whose SIGMA is non-positive or non-finite — the dead ones — take the median of the rest rather than a zero that would divide the likelihood by nothing. They are flagged out of it anyway.

Plots

The plots section defines which plots of predictions to create. These produced plots are saved in the plots/ directory alongside the MS file TABASCAL was run on. These are defined as follows

plots:
  init: True
  truth: False
  prior: True
  prior_samples: 100

The above configuration will plot the predictions for the initial parameters and the prior distributions. To plot the prediction from the prior, 100 samples will be drawn from the prior and then pushed through the forward model to get a predicition. The truth plots can only be included if TABASCAL is being run on a simulation created with sim-vis.

Inference

The inference section defines the type of inference that will be done. An example is given below.

inference:
  opt: True
  mcmc: False
  fisher: False
  • opt: Optimisation will be done to find the maximum a posteriori (MAP) point.

  • mcmc: Markov Chain Monte Carlo (MCMC) will be run to draw samples from the posterior after some number of warmup iterations. If opt is True then the MCMC chains will be initialised from the MAP point, otherwise initialisation will be done according to the definitions in the appropriate sections.

  • fisher: A Laplace approximation of the covariance will be performed about the MAP point.

Optimisation

The opt section gives the parameters for the optimiser. An example is given below.

opt:
  epsilon: 1e-1
  max_iter: 10000
  dual_run: True
  • epsilon: This is the optimiser step size

  • max_iter: This is the number of iterations to run the optimiser for in each optimisation run.

  • dual_run: This determines whether the optimiser will be run a second time starting from where the previous run left off but with an epsilon that is 10x smaller.

Fisher

This section gives the parameters for the Laplace approximation. An example is given below.

fisher:
  n_samples: 1
  max_cg_iter: 10_000

The covariance approximation is not performed in the traditional way of evaluating the negastive inverse Hessian. Rather, the Gaussian approximation of the posterior is sampled around the MAP point. The inverse of the posterior covariance is implicitly defined and then applied to samples from \(\mathcal{N}(\boldsymbol{0}, \boldsymbol{\Sigma}^{-1})\). Therefore, the number of samples is defined in n_samples and when applying the inverse covariance to the samples the conjugate gradient method is used when max_cg_iter defines the number of iteration used in the conjugate gradient method. Increasing both of these values leads to a greater computational load but also improves the accuracy of the resulting posterior samples.

Astronomical Signal

The ast section defines the prior distrbution, intialisation and forward model parameters for the astronomical signal. An example is given below.

ast:
  init: prior
  mean: 0
  freq_pad_factor: 2.0
  time_pad_factor: 2.0
  pow_spec:
    p0: 3e3
    k0_freq: 1
    fov_deg: 5
    gammas: [5, 5]
    cutoff: 1e-6
  • init: This gives the initialisation of the parameters. In the sample above prior is given so then the parameters will be initialised with the mean of the prior distribution. Other options include est to estimate the best initialisation, sample to draw a sample from the prior distribution, and truth to initialise at the true values. truth is only possible when running on a dataset simulated with sim-vis.

  • mean: This is the mean value of the prior distribution.

  • freq_pad_factor: This defines the size of the padding used when modelling the signal in the Fourier domain. The signal is modelled in the Fourier domain where periodicity is assumed on some interval. If freq_pad_factor: 1.0 is given then the interval is the interval of the data itself and will lead to periodic solutions.

  • time_pad_factor: This defines the padding used in the time axis of the signal. It is the time axis equivalent to freq_pad_factor.

  • pow_spec: This is the section that defines the prior covariance of the signal. The signal is modelled in the Fourier domain so the prior covariance is given by the power spectrum of the signal.

The parameters for the power spectrum are defined as

  • p0: Mean power of the signal.

  • k0_freq: Inverse correlation scale along the frequency axis.

  • fov_deg: The field of view in degrees used to set the maximum astronomical fringe rate (the knee k0 of the time-axis power spectrum). It is the full field of view, i.e. the angular diameter out to the first null of the primary beam; the maximum source offset from the phase centre is fov_deg / 2. When omitted, it defaults to the primary-beam field of view of the telescope, 2 * 1.22 * lambda / D (null-to-null), from the dish diameter D and frequency read from the MS file.

  • gammas: The rate of drop off in the power spectrum. As \(\gamma \rightarrow \infty\), the power spectrum tends to a Gaussian with width given by k0_freq in the frequency axis and inferred from fov_deg in the time axis.

  • cutoff: This is the relative cutoff for Fourier components. The power spectrum is calculated and then Fourier components, where the power spectrum value is less than p0 * cutoff, are removed and not modelled. This reduces the number of parameters to fit.

RFI signal

The rfi section defines the prior distribution over the RFI signal. An example of this section is given below.

rfi:
  init: sample
  mean: 0
  min_elevation: 0
  freq_pad_factor: 2.0
  time_pad_factor: 2.0
  freq_int_samples: 1
  time_int_factor: 1
  pow_spec:
    p0: 1e3
    k0s: [1e0, 1e-2]
    gammas: [5, 5]
    cutoff: 1e-6

All parameters in this section that overlap with those of the ast section have the same definition. The only additional parameters are

  • min_elevation: Elevation in degrees below which a satellite’s RFI signal is held at zero, so it is only modelled while it is up. The default is 0, which masks a satellite exactly while it is below the geometric horizon. Set it to null to disable masking entirely and model every satellite over the whole observation.

    While a satellite is below the horizon it contributes no signal, but an unmasked model still carries a full set of free parameters for it over those times. Those parameters have no signal of their own to constrain them, so they are free to absorb signal that belongs elsewhere — the astronomical sky, or another RFI source — to the extent that the RFI signal prior admits it and the fringe rates overlap. Masking removes the parameters rather than relying on the fit to leave them alone. This is why 0 rather than null is the default: a satellite below the horizon is not a modelling choice, it is simply not there.

    Each satellite gets its own in-view window, evaluated on the observation time grid and expanded over each integration, so an integration is never partially masked. Setup fails if a satellite is never above the cut, since it would then be modelled nowhere.

    Raising the cut above 0 additionally excludes the low-elevation part of each pass, where the fringe rate is lowest and the overlap with other components is therefore greatest. How far to raise it is observation-dependent and is not currently calibrated, so no value above 0 is recommended here. Note that masking is about which parameters exist, not about subtraction quality, and reduced \(\chi^2\) is largely insensitive to it — judge the effect on the recovered sky model.

  • freq_int_samples: This is the amount of over-sampling in the frequency domain that is used and then averaged back down to the data sampling rate. It therefore determines the number of samples per frequency channel that are used in the averaging to correctly calculate the fringe-winding loss (band-smearing). Band-smearing can be caused by both the phase variation over the channel width due to the geometric phase as well as the intrinsic signal of the RFI sources.

  • time_int_factor: In the time axis the number of integration samples needed to accurately model fringe-winding loss (time-smearing) is calculated based solely on the fringe rate due to the movement of the RFI source as well as the signal to noise ratio with

\[N^T_\text{int} \geq \pi \nu_F \Delta t \sqrt{\frac{\lvert V^\text{RFI}_\text{inst} \rvert}{6 \sigma_n}}\]

where \(N^T_\text{int}\) is the number of integration samples used per time step, \(\Delta t\) is the integration time for a single sample, \(\nu_F\) is the fringe frequency of the source due to its movement, \(\lvert V^\text{RFI}_\text{inst} \rvert\) is the instantaneous RFI visibility amplitude, and \(\sigma_n\) is the visibility noise of a single data point. This parameter (time_int_factor) determines the factor by which to increase this oversampling.

RFI light curve estimates

rfi.est points at a measured light curve file, used by init: est and mean: est to seed the RFI signal. This is the interchange format between tabascal and whatever measures the light curves, so it is deliberately strict.

The file is either a .zarr store (read with xarray.open_zarr) or a .npz, and must contain all four of

name

shape

contents

light_curves

(n_src, n_time, n_freq)

apparent flux per source, in Jy

norad_ids

(n_src,)

NORAD id of each row of light_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, whose dimensions must be exactly norad_ids, times and freqs — declared in any order, since they are identified by name and transposed on read. A minimal writer:

import numpy as np, xarray as xr

xr.Dataset(
    {"light_curves": (("norad_ids", "times", "freqs"), curves)},
    coords={"norad_ids": np.array([25544, 27386]), "times": times_mjd, "freqs": freqs_hz},
).to_zarr("light_curves.zarr")

Rows are matched to satellites by NORAD id, never by position, so the order of sources in the file does not have to match satellites.norad_ids. Samples are interpolated onto the observation’s own time and frequency grid, so the file’s sampling does not have to match the observation either.

Both are strict because their failure modes are silent. A light curve attached to the wrong satellite still has the right shape and still optimises — it just seeds the prior from another satellite. A file whose sampling is assumed rather than declared is resampled wrongly by an unknown amount. Neither surfaces as an error, only as a worse fit, so a file that cannot state which satellite and which sample times it describes is rejected rather than guessed at.

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.

light_curves is a flux in Jy, not the modelled amplitude rfi_A. The RFI visibility is quadratic in rfi_A (\(V^\text{RFI}_{pq} = A_p A_q^* e^{i\Delta\phi}\)), so rfi_A carries units of \(\sqrt{\text{Jy}}\) and the estimate is seeded with \(\sqrt{\lvert \text{light\_curves} \rvert}\). Supplying an amplitude where a flux is expected is squared away silently, so the value is wrong rather than the shape — give the flux the source would show in the visibilities, on the same scale as rfi.var.

Some further details:

  • Each satellite must appear exactly once. A repeated NORAD id has no single answer to which row belongs to it, and resolving that by file order is the thing id-matching exists to avoid, so it is rejected — merge the passes or drop one before using the file as an estimate.

  • Labels that are not integer NORAD ids never match a satellite and are dropped, so a file may carry named sources (e.g. Fornax A) alongside the satellites without filtering beforehand, and those may repeat freely.

  • 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; a single-frequency light curve therefore applies across the whole band rather than being zeroed outside it.

  • The file does not have to cover every satellite in the fit. Satellites with no light curve are initialised at zero and named in a warning, so light curves can be measured for a subset — the bright or well-characterised sources — while the rest are still modelled and fitted, just without an informative starting point. It is an error only if no configured satellite is found, which would otherwise silently reduce the whole estimate to zeros.

Satellites

The satellites section determines which satelites to include in the model and the prior distribution to use for there trajectories. An example is given below.

satellites:
  norad_ids: [20452, 38833, 45854]
  norad_ids_path: null
  extra_orbit_dir: null
  extra_orbit_max_age_days: null
  remote_max_age_days: 3
  cache_reuse_max_age_days: 1
  ric_std: 1e2
  • norad_ids: List of the NORAD IDs of the satellites to include. TABASCAL requests the record whose epoch is closest to the observation from the IAU CPS SatChecker service — its get-nearest-omm endpoint for observations from 2026-07-12 onwards and get-nearest-tle before that, falling back to the other archive if the first has nothing acceptable. Cache misses run concurrently with a bounded five-worker pool; no account or credentials are required. Every ID listed here must resolve to an acceptable record: otherwise preflight stops before reading the visibilities and names each failure. TABASCAL never silently drops a configured satellite from the RFI model.

    An empty list (or null) is valid only for a model that does not use a satellite trajectory component — a stationary-RFI or astronomical-only run. If the model.components list includes one that consumes orbital records (FixedOrbit, KeplerOrbit, Orbit, NoDragOrbit), configuring no IDs is a configuration error rather than a run that models nothing. Either separator form of a component reference is recognised, so trajectory:FixedOrbit and trajectory.FixedOrbit behave identically here.

  • norad_ids_path: Optional path to a text file of NORAD IDs, one per line; blank lines and # comments are ignored and malformed lines are reported with their line number. When set it takes precedence over norad_ids, and the -np/--norad-path CLI flag takes precedence over both.

  • extra_orbit_dir: Optional path to an additional directory of local orbit files, searched per NORAD ID before the managed cache and SatChecker. Every *.json file in the directory is considered; files must be pandas-oriented JSON tables carrying either NORAD_CAT_ID, TLE_LINE1 and TLE_LINE2, or NORAD_CAT_ID, EPOCH and the seven OMM element columns. The kind is inferred, so a Space-Track gp/gp_history export drops in unconverted. For each requested satellite the valid record whose epoch is closest to the observation is chosen — by epoch distance, regardless of format — and, if it is accepted (see extra_orbit_max_age_days), it wins outright and no service call is made for that satellite. Files that cannot be read or lack either required column set are skipped. Records that fail validation are rejected, allowing that satellite to fall through to the managed cache and SatChecker. Legacy date-named files and the bundled Space-Track fixtures remain supported. This directory can be given at runtime with the --extra-orbit-dir flag. (The ORBIT_CACHE_DIR environment variable is a different thing: it relocates where the managed cache is stored, and is not an additional source of records.)

  • extra_orbit_max_age_days: Maximum allowed absolute difference, in days, between an extra_orbit_dir record’s epoch and the observation epoch. null (default) applies no age limit, preserving exact replay of used_orbits_*.json; 0 accepts an epoch match within TLE precision. A rejected local record falls through to the managed cache and SatChecker. The age comes from the record itself — line 1 for a TLE, the EPOCH field for an OMM — not from the filename or modification time.

  • remote_max_age_days: Hard ceiling, in days, on how far a SatChecker or managed-cache record may be from the observation. A TLE’s epoch is re-derived locally from line 1; an OMM has no lines, so its EPOCH field is used after being range-checked. Every accepted remote record’s source, provider, endpoint, signed offset and absolute age is logged. null explicitly removes the ceiling.

    This ceiling is also what makes the endpoint fallback work. Neither SatChecker endpoint reports that it has nothing near the epoch requested — get-nearest-omm answers a pre-2026 request with its earliest record — so an over-age response is the signal that the record belongs to the other archive, and the other endpoint is then asked.

    The default of 3 is provisional. It is a hard backstop against obviously unsuitable remote records — for one observation, SatChecker’s per-satellite fallback silently returned records ~31 days old, worth ~9,663 km of ISS position error — and not a claim that a three-day-old element set gives adequate positional accuracy. The calibrated, observation-specific suitability policy that should replace it is tracked in issue #101; it may end up rejecting records younger than three days for some orbits and baselines, or accepting older ones where independently justified.

  • cache_reuse_max_age_days: Request-avoidance threshold for the per-NORAD cache (default 1). A cached record this close to the observation avoids a request. An older cached record triggers an exact-epoch nearest lookup — including against the fallback archive, since holding a stale record is not the same as the archive having answered — but remains an offline fallback if it is within remote_max_age_days. A response replaces it only when strictly closer to the observation. null always reuses the nearest acceptable cached record. When both limits are set, this value must not exceed the hard ceiling.

  • ric_std: The error in the orbital elements is not provided as part of the element sets. When estimated positions are analysed the error is calculated in a local reference frame of the satellite. This is the radial, in-track, and cross-track (RIC) frame. This parameter gives a factor by which to scale the RIC covariance that is stored internally which is taken from a paper where the average errors are calculated.

Gains