Skip to content

virgil.gains

Calibration gains correlated across the channels of a frame, as low-rank blocks of the visibility covariance that the likelihood marginalizes analytically. Add them with OIData.with_gains, and fit their widths with the noise terms vis_gain_telescope, vis_gain_baseline, vis_gain_chromatic and vis_gain_modes (e.g. fit(..., noise={"vis_gain_telescope": dist.LogUniform(1e-4, 0.1)})). Widths are scale parameters, so their default (Jeffreys) prior is log-uniform on stated bounds; a Uniform(0, ...) would favour large widths.

Closure-phase offsets common to a frame's channels work the same way, on the whitened closure phases: add them with OIData.with_closure_offsets and fit their widths with phi_offset_baseline, phi_offset_triangle and phi_offset_modes. They are off by default.

Calibration gains correlated across channels, marginalized analytically.

The calibration of spectro-interferometric data (the transfer function, its drift between calibrators, injection and piston losses) multiplies every channel of a frame's baseline by much the same factor. Independent error bars cannot describe this: an error common to all channels averages down in them, but not in the data. Here each such error is a gain on log |V|,

log |V|_obs = log |V| + Σ_j τ_j z_j m_j,    z_j ~ N(0, 1),

where m_j is a mode (a shape over the data's visibility samples) and τ_j its width. The modes come in groups, with one width each:

  • telescope: one gain per (frame, telescope), on the frame's baselines that include that telescope;
  • baseline: one gain per (frame, baseline), on all its channels;
  • chromatic: one per (frame, baseline) shaped (λ_ref/λ)², the first order of a coherence loss exp(−a/λ²) from piston jitter;
  • modes: shapes supplied from outside, per 1σ, e.g. from a calibrator PCA (as virgil-vlti makes), whose width scales them (1 = as given).

For small gains (τ ≲ 0.2) the observable changes linearly, by J m_j with J = dObs/dlog|V| = 2V² for squared visibilities, |V| for amplitudes and 1 for log-amplitudes, taken from the model (so the covariance does not depend on the noisy data; Lachaume 2021). The gains are then Gaussian and are marginalized analytically: the visibility covariance becomes

C = D + U Uᵀ,   U = J τ m  (one column per mode),

with D the diagonal of squared errors. Modes that share no sample are independent, so C is block diagonal, one block per connected group of modes (one per frame, for the built-in groups).

Whitening. Each block is whitened by successive rank-one steps, the shared machinery for linear marginalization in virgil._linear (whiten_blocks). Only square roots of scalars appear, so the gradients stay smooth, also where modes are degenerate and where widths go to zero.

GAIN_GROUPS = ('telescope', 'baseline', 'chromatic', 'modes') module-attribute

OFFSET_GROUPS = ('baseline', 'triangle', 'modes') module-attribute

GainModes

Bases: Module

Low-rank gain modes on the visibility observables.

Built by gain_modes (or OIData.with_gains); see the module notes for the model.

Modes within one frame are kept in blocks, the connected groups of such modes (one per frame for the built-in groups), padded to a common size. Modes that span frames (only supplied ones can) are kept apart, as dense columns, and whitened after the blocks. That way, a few modes across frames do not merge every frame into one dense block.

Attributes:

Name Type Description
rows Array

(n_block, n_row) int32: the visibility observables of each block (rows of OIData.vis), padded with n_vis (out of range). If every mode spans frames, one empty block of padding.

shapes Array

(n_block, n_row, n_mode): each mode's shape on log |V| for unit width, zero on padding.

group Array

(n_block, n_mode) int32: each mode's group, an index into groups.

spanning Array or None

(n_vis, n_spanning): the shapes of modes that span frames, or None if there are none. Never a zero-size array: XLA's Shardy pass segfaults compiling a jax.pmap (numpyro's parallel chains) that captures one (JAX 0.11.2).

spanning_group Array or None

(n_spanning,) int32: their groups (None with spanning).

widths Array

The default width of each group.

groups tuple of str

The groups present, a subset of GAIN_GROUPS.

n_vis int

The number of visibility observables.

rows instance-attribute

shapes instance-attribute

group instance-attribute

spanning instance-attribute

spanning_group instance-attribute

widths instance-attribute

groups = eqx.field(static=True) class-attribute instance-attribute

n_vis = eqx.field(static=True) class-attribute instance-attribute

widths_for(terms=None)

Each group's width, with vis_gain_<group> terms replacing them.

whiten(x, jacobian, widths)

Whiten residuals for the covariance I + Σ w_j w_jᵀ.

Parameters:

Name Type Description Default
x array - like

Visibility residuals divided by their errors, (n_vis,).

required
jacobian array - like

dObs/dlog|V| of the model divided by the errors, (n_vis,).

required
widths array - like

Each group's width (see widths_for).

required

Returns:

Type Description
tuple

The whitened residuals (n_vis,), and per observable the log of the factor its effective error grows by: they sum to half the log-determinant of the covariance, ½ Σ log(1 + w_jᵀw_j).

covariance(errors, jacobian, widths)

The dense visibility covariance D + U Uᵀ (for checks; O(n²)).

jacobian is dObs/dlog|V| itself here, not divided by the errors.

sample(key, widths)

Draw the gains: log |V| offset of each visibility observable.

subset(keep)

The modes on the visibility observables where keep is True.

A Gaussian's marginal on a subset of its variables is the same covariance restricted to them, so dropping rows is exact.

labels_shared(labels)

Whether any mode touches observables with different labels.

labels (one per visibility observable) partitions the data, e.g. into epochs; a partition's likelihoods add up to the whole only if no gain is shared between its parts.

ClosureOffsets

Bases: Module

Closure-phase offsets common to the channels of a frame, marginalized.

Built by closure_offsets (or OIData.with_closure_offsets). Each offset is a mode m over the closure phases, in radians per unit width: φ_obs = φ + Σ τ_j z_j m_j with z_j ~ N(0, 1).

The likelihood of correlated closure phases whitens their sines with OIData.cp_noise (see likelihood._whiten). The offsets add τ² m mᵀ to that covariance: a small-phase approximation, since an offset δ changes sin Δ by about δ cos Δ. It holds for offsets (and residuals) below about 0.3 rad; larger widths are not marginalized exactly. Each mode is whitened the same way, one closure-phase group (frame and channel) at a time, and the modes of a frame then form one block of the rank-one whitening (GainModes has the details). The periodic penalty rows are left as they are.

Attributes:

Name Type Description
groups ndarray

(n_block, n_group) int32: the cp_noise groups of each block.

group_mask ndarray

(n_block, n_group): True for real groups (the rest is padding).

values ndarray

(n_block, n_mode, n_group, m): each mode's value at each slot of each group (as cp_noise.groups), zero on padding.

group ndarray

(n_block, n_mode) int32: each mode's width group.

rows ndarray

(n_block, n_group · k) int32: the whitened row of each (group, basis row), padded with n_out (out of range).

widths Array

The default width of each group, in radians.

groups_present tuple of str

The groups present, a subset of OFFSET_GROUPS.

n_out int

The number of whitened closure phases (cp_noise.size).

groups instance-attribute

group_mask instance-attribute

values instance-attribute

group instance-attribute

rows instance-attribute

widths instance-attribute

groups_present = eqx.field(static=True) class-attribute instance-attribute

n_out = eqx.field(static=True) class-attribute instance-attribute

widths_for(terms=None)

Each group's width, with phi_offset_<group> terms replacing them.

whiten(cp_noise, x, sigma, widths)

Whiten the closure-phase sines x (already whitened by cp_noise).

Returns the whitened values and, per value, the log of the factor its effective error grows by (summing to ½ log det).

modes(cp_noise, n_phase, widths=None)

The modes as dense columns over the closure phases (for checks).

sample(key, cp_noise, n_phase, widths)

Draw the offsets: radians added to each closure phase.

gain_modes(data, telescope=None, baseline=None, chromatic=None, modes=None)

Gain modes for a dataset, with default widths.

Parameters:

Name Type Description Default
data OIData

Unprojected data, with frames (frame) and, for telescope, baseline and chromatic gains, station pairs (stations), as read from OIFITS.

required
telescope float

Widths (1σ, on log |V|, so 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)² with λ_ref the median wavelength. None leaves that group out. A group can be given a nominal width here and fitted with the noise term vis_gain_<group>.

None
baseline float

Widths (1σ, on log |V|, so 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)² with λ_ref the median wavelength. None leaves that group out. A group can be given a nominal width here and fitted with the noise term vis_gain_<group>.

None
chromatic float

Widths (1σ, on log |V|, so 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)² with λ_ref the median wavelength. None leaves that group out. A group can be given a nominal width here and fitted with the noise term vis_gain_<group>.

None
modes array - like

Further modes, (n_mode, n_sample): shapes on log |V| per 1σ, one value per sample of the data (the order of data.u; flagged samples are ignored). Their width (default 1) scales them all.

None

Returns:

Type Description
GainModes

closure_offsets(data, baseline=None, triangle=None, modes=None)

Closure-phase offsets for a dataset, with default widths.

Parameters:

Name Type Description Default
data OIData

Closure phases from four or more telescopes (cp_noise), not projected, with frames and, for baseline and triangle offsets, station pairs, as read from OIFITS.

required
baseline float

Width (radians) of a phase offset per (frame, baseline), common to all channels, which reaches the closure phases as T·e (T the triangle-by-baseline signs). The design's default form.

None
triangle float

Width (radians) of an offset per (frame, triangle), common to all channels: what a pair test of consecutive frames measures.

None
modes array - like

Further modes, (n_mode, n_phase), in radians per 1σ, one value per closure phase of data.phi. Each must lie within one frame. Their width (default 1) scales them all.

None

Returns:

Type Description
ClosureOffsets