virgil.oidata
Data containers and observable helpers for interferometric observations.
OIData maps each supported data product into a likelihood comparison vector.
For ordinary OIFITS-style data, this standardized vector concatenates the
visibility and phase observables. For AMIGO mixed-DISCO products, it is the
single mixed log-complex vector defined by the stored log-amplitude and phase
projection operators. In this context, "standardize" means "put data and model
predictions into the same comparison basis", not z-score normalization.
Every (baseline, wavelength) sample is one entry of u, v and wavel, so
data with several wavelength channels need no special handling in models.
Flagged samples are left out of the observables (see vis_index and
phi_index). residuals wraps phase residuals into [-π, π) for display.
Likelihoods and fits use
whitened_residuals instead. For
ordinary unprojected phases, chord residuals 2 sin(Δ/2) give a squared
contribution that is smooth across phase wraps. Correlated closure phases are
the exception: their residuals are wrapped into [-π, π), combined, and
whitened together as sines sin Δ, with a periodic penalty
2 sin²(Δ/2)/σ per closure phase, so the likelihood is continuous
everywhere (the correlated Gaussian for small residuals, with no false
minimum at Δ = π).
Closure phases from four or more telescopes are correlated: the triangles
of one frame and channel share baselines, and only some of them are
independent (three of four, for four telescopes). The likelihood keeps only
the independent combinations and whitens them with their covariance, built
from independent noise on the baseline phases. n_independent counts the
observables that remain (the degrees of freedom); n_residuals is the
length of whitened_residuals, which adds the penalty residuals.
Calibration nuisances
with_wavelength_scale and with_north_angle are what the noise= terms
wavel_scale and north_angle of fit and
numpyro_model apply. A North angle δ
means the data see every position angle as the true one plus δ: a source at
(dra, ddec) appears at R(δ) (dra, ddec) with
R(δ) = [[cos δ, sin δ], [-sin δ, cos δ]]. Because the Fourier kernel is
u dra + v ddec, models are evaluated at R(-δ) (u, v), as for
Rotated(model, δ).
A wavelength scale rescales the spatial frequencies (u and v are in
metres), so it magnifies the sky by 1 / scale: it is also the plate-scale
term, and a sky magnified by m is wavel_scale = 1/m. With a single dataset
it is degenerate with every angular size, so its prior is the systematic
error (dist.Normal(1.0, 2e-4) is about right for GRAVITY).
Neither has a default prior. Their invariant priors (uniform on the circle
for δ) leave them degenerate with the scene's orientation and size for a
single dataset, so a Gaussian prior is strong information and its width
should come from the instrument's astrometric calibration. Long-baseline
interferometers know their baselines' orientation, so leave north_angle
out there. The same terms for published positions are in
PositionData.term; they follow
Octofitter (Thompson et al. 2023, AJ 166, 164).
with_closure_offsets marginalizes closure-phase offsets as a linear
perturbation: an offset δ changes the whitened sine sin Δ by about
δ cos Δ, which holds while the offsets and residuals are small
(δ ≲ 0.3 rad, 17°); larger widths are not marginalized exactly.
Attributes
uv_grid: aUVGridfor AMIGO DISCO products, whose samples lie on a regular (possibly rotated) lattice: the mixed-DISCO product, and dictionaries with adisco_vis_matordisco_phi_matwhose single-wavelength samples lie on one to 1e-5 cells (their float32 rounding). It isNonefor all other data, even on a lattice: virgil does not search ordinary data for one. Matching models, such as anImageor aFlaredDiskwith the samerotation_deg, then evaluate faster with the same results. For other lattice-sampled data set it withfind_uv_grid, e.g.eqx.tree_at(lambda d: d.uv_grid, data, find_uv_grid(data.u, data.v), is_leaf=lambda x: x is None).dt,t_ref,frame,mjd: data read from OIFITS keep each sample's time and exposure.framenumbers the exposures,mjdgives each time, andepochsandsplit_by_epochgroup the frames into nights. The time is stored asdt, days since the static float64t_ref, so that models of time see small numbers that keep their precision in float32 (about 5 s over 1000 days).stationsholds each sample's station pair (STA_INDEX).gainsandphase_offsets: calibration gains (GainModes, fromwith_gains) and closure-phase offsets (ClosureOffsets, fromwith_closure_offsets) that the likelihood marginalizes;Noneby default.extras: further observables, read withextras=(OI_FLUX, T3AMP, VISAMP beside V², differential VISPHI beside closure phases): a tuple of blocks fromvirgil.observables, which follow the phases in the data vector in the order ofobservables.KINDS. Empty by default.
Dictionary input
OIData(dict) takes these keys:
u,v(metres) andwavel(metres): per sample, oru/vper baseline withvisof shape(n_baseline, n_wavel)for several channels.vis,d_vis: squared visibilities or amplitudes.phi,d_phi: closure or absolute phases, withphi_unit("rad", the default, or"deg";phase_unitis an alias). For several channels, shape(n_triangle, n_wavel)or(n_baseline, n_wavel).i_cps1,i_cps2,i_cps3(optional): for each closure phase, the baselines(a, b),(b, c)and(a, c)of its triangle.v2_flag(default True):visholds squared visibilities; otherwise amplitudes.cp_flag(default: True when closure indices are given):phiholds closure phases; otherwise absolute phases.vis_flag,phi_flag(optional): boolean masks, True for bad samples. Samples with non-finite values or errors are flagged automatically.vis_mode("auto","v2","amp"or"logamp"): the visibility channel that data and model are compared in;"auto"keeps the channel ofvis. The keyobservable_vis_modeis read as an old name for it. Neither sets thevis_encodingattribute, which says howvisandphiare laid out ("split", or"mixed_log_complex"for an AMIGO mixed-DISCO product), not what they measure.mjd,frame(optional): the time (days) and an integer exposure label of each sample (or of each baseline, for several channels). Withoutframe, samples with the samemjdform one frame.stations(optional): the station pair(a, b)of each sample, or of each baseline, forwith_gains.vis_mat,phi_mat(optional): linear operators of shape(n_out, n_in)projecting the channels into, e.g., kernel or DISCO observables. Thedisco_vis_mat/disco_phi_matspellings also check that the projected covariance is diagonal. Only diagonal uncertainties are propagated.flux/nflux,visamp,t3amp,visphi(optional): extra observables as dictionaries in the layoutread_oifitsreturns.visampandvisphigive per value thesampleit belongs to, withvalue,errorandflag(visampalsoamptyp);t3amphas one value per closure phase;fluxgiveswavel,value,error,flagand optionallyrow,mjdandstation.
A record with disco_coefficients is read as an AMIGO mixed-DISCO product
(see load_oi_data); its u and v are
negated to match the virgil sign convention.
The bundled data/calibrated_visibility.npy fixture is synthetic; see the
AMIGO DISCO tutorial and virgil.amigo for loading it.
Classes
Bases: Base
Store and transform optical-interferometry observables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict, str, os.PathLike, astropy.io.fits.HDUList, or a list of files
|
An OIFITS file (a list of them is concatenated; read with
|
required |
target
|
str or int
|
For OIFITS input, the target to keep (name or |
None
|
extras
|
sequence of str
|
For OIFITS input, extra observables to read (as |
()
|
bandwidth
|
bool
|
For OIFITS input, keep the channel widths ( |
False
|
insname
|
str or sequence of str
|
For OIFITS input, the |
None
|
Notes
Every (baseline, wavelength) sample is one element of the flat u, v
(metres) and wavel (metres) arrays; models are evaluated on them. The
observables are vis/d_vis (squared visibilities or amplitudes) and
phi/d_phi (closure or absolute phases, radians), optionally
projected through vis_mat/phi_mat. Flagged samples are left out;
vis_index and phi_index list those that remain (None when all
are used). Visibility-only data have an empty phase block
(has_phases is False). The other attributes (uv_grid, times,
gains, phase_offsets, extras) are described in
virgil.oidata.
has_phases
property
Whether the data hold any phase observables. Visibility-only data (e.g. V² without closure phases) have an empty phase block.
unflagged
property
Which samples an observable uses, as a boolean array shaped like u.
Flagged samples (and those not finite or without a positive error) keep
their u, v and wavel but no observable uses them; the image
resolution and field come from the unflagged samples only. Projected data
(kernel, DISCO) cannot be flagged per sample, so all their samples count.
n_independent
property
Number of independent observables, for degrees of freedom.
Equal to the size of :meth:flatten_data, except for closure phases
from four or more telescopes, where only the independent
combinations count (three of the four triangles of a frame and
channel, for four telescopes). The residual vector of
whitened_residuals is
longer for such data; see :attr:n_residuals.
n_residuals
property
Length of whitened_residuals.
Equal to n_independent, except for correlated closure phases
(four or more telescopes), which add one periodic penalty residual
per closure phase that keeps the likelihood continuous where a
residual crosses ±π. Use n_independent for degrees of freedom.
has_model_covariance
property
Whether the likelihood's covariance depends on the model: with
gains, or with marginalized flux scales (extras). Least squares
then does not give the likelihood, so fit uses L-BFGS.
mjd
property
Time of each sample (days, float64), or None if unknown.
__init__(data, target=None, extras=(), bandwidth=False, insname=None)
Initialize from an OIFITS file or explicit arrays (see the class).
flatten_data()
Return the data vector and its uncertainties.
Returns:
| Type | Description |
|---|---|
tuple[array - like, array - like]
|
The visibility observables followed by the phases (radians),
in the order of
|
standardize_model(cvis)
Map model complex visibilities (one per sample) to the data vector.
The result lines up with the first vector of :meth:flatten_data.
to_vis(cvis)
Convert model complex visibilities to the visibility observables.
The channel follows vis_mode (V², amplitude or log-amplitude);
flagged samples are dropped, and vis_mat is applied if set.
to_phases(cvis)
Convert complex visibilities to closure or absolute phases in radians.
model(model)
Compute the model visibilities and phases for the given model object.
A model that changes with time (see
SourceModel.at) is evaluated at
each sample's own time, which the data must have (mjd), with the
direct Fourier transform: a uv_grid (AMIGO DISCO data, which
carry no times) is not used for it.
residuals(prediction, reference=None)
Return prediction - reference with phase residuals wrapped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prediction
|
array - like
|
Model vector, e.g. from |
required |
reference
|
array - like
|
Vector to compare against; by default the data
(the first vector of :meth: |
None
|
Returns:
| Type | Description |
|---|---|
array - like
|
Residual vector. Unprojected phase residuals are wrapped into
|
Notes
This is for display. Likelihoods and fits use
whitened_residuals,
which is smooth where phases wrap.
with_model(model, key=None, noise_scale=1.0)
Return a copy populated from a model with optional Gaussian noise.
Sampling, uncertainties, conventions, closure indices, and linear
observable operators are preserved from this object. With key,
marginalized gains and extra-observable nuisance modes are drawn
along with diagonal noise.
with_error_scale(factor)
A copy of the data with its uncertainties multiplied by factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
float or dict
|
Positive scale for every uncertainty, or a dictionary of positive
scales by kind of observable ( |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
For a factor that is not finite and positive, or a dictionary key that is not a kind of observable. |
Examples:
>>> data = OIData({"u": [1.0, 2.0], "v": [0.0, 1.0], "wavel": 1e-6,
... "vis": [0.9, 0.5], "d_vis": [0.01, 0.02]})
>>> [round(float(e), 3) for e in data.with_error_scale(3.0).d_vis]
[0.03, 0.06]
with_error_floor(absolute=None, relative=None)
A copy of the data with a minimum uncertainty per observable.
Each uncertainty becomes max(σ, absolute, relative × |data|). This is a
fixed change to the data; the fitted counterpart is the noise= terms
(see inflated_errors).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
absolute
|
dict
|
Floors per observable: |
None
|
relative
|
dict
|
Floors per observable: |
None
|
Returns:
| Type | Description |
|---|---|
OIData
|
|
Notes
Closure phases from four or more telescopes are floored before they are whitened; the periodic penalty rows keep their effective error 1/√(2π).
Examples:
>>> data = OIData({"u": [1.0, 2.0], "v": [0.0, 1.0], "wavel": 1e-6,
... "vis": [0.9, 0.5], "d_vis": [0.001, 0.02]})
>>> floored = data.with_error_floor(relative={"vis": 0.01})
>>> [round(float(e), 4) for e in floored.d_vis]
[0.009, 0.02]
with_continuum(continuum=None, lines=None, order=None, prior_width=None)
A copy with the continuum and line windows of the extra spectra.
They set how differential phases ("visphi") and normalized spectra
("nflux") are normalized; see
DifferentialPhase.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
continuum
|
sequence of (lo, hi)
|
Wavelength ranges (metres); each defaults to the complement of the other. Differential phases are fitted over the continuum and kept in the lines; normalized spectra use the continuum. |
None
|
lines
|
sequence of (lo, hi)
|
Wavelength ranges (metres); each defaults to the complement of the other. Differential phases are fitted over the continuum and kept in the lines; normalized spectra use the continuum. |
None
|
order
|
int
|
The continuum polynomial in wavenumber: 1 by default for differential phases, 0 for spectra. |
None
|
prior_width
|
float or (float, float)
|
For differential phases: marginalize each baseline and frame's offset (and slope) under Gaussian priors of these widths (radians), instead of projecting them out (the default). |
None
|
Examples:
data.with_continuum([(2.150e-6, 2.162e-6)], lines=[(2.163e-6, 2.169e-6)]).
with_flux_scale(scale=None, per='dataset', poly_order=0, poly_width=0.1, kinds=None)
A copy with new grey-scale nuisances for the extra spectra.
Each spectrum (OI_FLUX, or correlated fluxes) is known up to a scale,
marginalized analytically under a Gaussian prior that you state (see
FluxSpectrum).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
(float, float)
|
The prior |
None
|
per
|
(dataset, row, frame, station)
|
One scale for the whole dataset (default), or one per spectrum (row), exposure, or telescope. |
"dataset"
|
poly_order
|
int
|
Also marginalize a polynomial in λ of this order times the model spectrum. |
0
|
poly_width
|
float
|
Prior width of each polynomial coefficient, relative to the scale's prior mean. |
0.1
|
kinds
|
sequence of str
|
Which blocks to change: |
None
|
with_gains(telescope=None, baseline=None, chromatic=None, modes=None)
A copy of the data with calibration gains correlated across channels.
The likelihood marginalizes gains on log |V| per frame analytically (see
virgil.gains), with these widths unless fitted as the
noise terms vis_gain_telescope, vis_gain_baseline,
vis_gain_chromatic or vis_gain_modes (log-uniform priors on stated
bounds). The covariance then depends on the model, so fits use L-BFGS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
telescope
|
float
|
Widths (1σ on log |V|) of gains per (frame, telescope), per (frame,
baseline), and per (frame, baseline) shaped (λ_ref/λ)². |
None
|
baseline
|
float
|
Widths (1σ on log |V|) of gains per (frame, telescope), per (frame,
baseline), and per (frame, baseline) shaped (λ_ref/λ)². |
None
|
chromatic
|
float
|
Widths (1σ on log |V|) of gains per (frame, telescope), per (frame,
baseline), and per (frame, baseline) shaped (λ_ref/λ)². |
None
|
modes
|
array - like
|
Further modes, |
None
|
Returns:
| Type | Description |
|---|---|
OIData
|
The data with |
with_wavelength_scale(scale=1.0, offset=0.0)
A copy of the data whose wavelengths are scale · λ + offset.
A wavelength (or plate-scale) calibration error: models are evaluated at
the corrected wavelengths, which rescales the spatial frequencies. Fit it
with the noise terms wavel_scale and wavel_offset, which call this;
see calibration nuisances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
float
|
Factor on the wavelengths (default 1). |
1.0
|
offset
|
float
|
Shift added after scaling, in metres (default 0). |
0.0
|
Returns:
| Type | Description |
|---|---|
OIData
|
The data with |
with_bandwidth(dlambda)
A copy of the data with channel widths dlambda (bandwidth smearing).
For simulated data, which have no EFF_BAND, or to override one. Models
are then smeared over each channel (see
smeared_model).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dlambda
|
(float, array - like or None)
|
Full channel width in metres: one value, or one per sample. |
required |
Returns:
| Type | Description |
|---|---|
OIData
|
The data with |
with_north_angle(angle)
A copy of the data that see the sky rotated by angle degrees.
An error in the instrument's North: every position angle these data
measure is the true one plus angle (North through East). Fit it with
the noise term north_angle, which calls this; its prior must be stated.
See calibration nuisances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
float
|
Rotation of the sky as these data see it, in degrees. |
required |
Returns:
| Type | Description |
|---|---|
OIData
|
The data with |
with_closure_offsets(baseline=None, triangle=None, modes=None)
A copy of the data with closure-phase offsets per frame.
The likelihood marginalizes offsets common to the channels of a frame
analytically (see ClosureOffsets), with
these widths unless fitted as the noise terms phi_offset_baseline,
phi_offset_triangle or phi_offset_modes (log-uniform priors, see
conventions). Off by default; use it only if
calibrators show such offsets. A small-offset approximation (δ ≲ 0.3
rad); three or more telescopes are supported. With three, the log
likelihood is not comparable with that of the data without offsets
(it differs by about log(1 + σ²/8) per closure phase).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
baseline
|
float
|
Width (radians) of a phase offset per (frame, baseline). |
None
|
triangle
|
float
|
Width (radians) of an offset per (frame, triangle). |
None
|
modes
|
array - like
|
Further modes, |
None
|
Returns:
| Type | Description |
|---|---|
OIData
|
The data with |
epochs(gap_days=0.5)
Label each sample with its epoch: a run of frames with no gap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gap_days
|
float
|
Frames whose mean times are more than this far apart (days) are in different epochs; the default separates nights. A frame is never split between epochs. |
0.5
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Integer epoch of each sample, numbered in time order from 0. |
split_by_epoch(gap_days=0.5)
One OIData per epoch, in time order.
See :meth:epochs. Each part keeps its own samples, observables and
closure phases, so it can be fitted on its own or with a model per
epoch. Not available for projected (kernel, DISCO) observables, nor
for data with a gain mode shared between epochs (a supplied mode
spanning frames): the parts' likelihoods would then not add up to
the whole.
select(wavel_min=None, wavel_max=None, *, ranges=None, exclude=None, observables=None)
These data restricted to wavelength windows and observables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wavel_min
|
float
|
Keep |
None
|
wavel_max
|
float
|
Keep |
None
|
ranges
|
sequence of (lo, hi)
|
Keep the samples in any of these ranges (metres, inclusive), instead
of |
None
|
exclude
|
sequence of (lo, hi)
|
Then drop the samples in any of these ranges, e.g. a line. |
None
|
observables
|
str or sequence of str
|
The observables to keep: |
None
|
Returns:
| Type | Description |
|---|---|
OIData
|
The samples in range (closure triangles are kept whole). Not available for projected (kernel, DISCO) observables, or after closure-phase offsets are added. |
Examples:
data.select(2.05e-6, 2.16e-6) keeps a continuum window;
data.select(exclude=[(2.162e-6, 2.170e-6)]) drops a line.
uv grids
AMIGO DISCO data are sampled on a regular uv lattice; OIData.uv_grid
records it, so that a matching Image or FlaredDisk can use an exact
matrix Fourier transform. Other data get uv_grid=None, even on a lattice; set it with
find_uv_grid (see OIData) to use the fast transform for them.
virgil.oidata.UVGrid
Bases: Module
uv samples on a regular lattice, possibly rotated on the sky (see :func:find_uv_grid).
Sample k is at grid-frame (u_axis[index[k] % nu], v_axis[index[k] // nu])
(metres), and on the sky at rotate(u_g, v_g, rotation_deg).
u_axis
instance-attribute
v_axis
instance-attribute
index
instance-attribute
rotation_deg = eqx.field(static=True)
class-attribute
instance-attribute
virgil.oidata.find_uv_grid(u, v, tolerance=1e-06)
Return the lattice that (u, v) samples lie on, or None.
A first guess at the axes comes from the shortest sample separation;
the origin, pitches and rotation are then fitted by least squares to
every sample, so that rounding of the coordinates (float32 products)
does not accumulate across the lattice. Every sample must lie within
tolerance cells of a point of the fitted lattice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
array - like
|
Sample coordinates (concrete arrays, e.g. in metres). |
required |
v
|
array - like
|
Sample coordinates (concrete arrays, e.g. in metres). |
required |
tolerance
|
float
|
Largest allowed offset from a lattice point, in cells. |
1e-06
|
Returns:
| Type | Description |
|---|---|
UVGrid or None
|
|
Functions
closure_phases(cvis, index_cps1, index_cps2, index_cps3)
Calculate closure phases from complex visibilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cvis
|
array - like
|
Complex visibilities, one per sample. |
required |
index_cps1
|
array - like
|
For each closure phase, the sample of baseline |
required |
index_cps2
|
array - like
|
For each closure phase, the sample of baseline |
required |
index_cps3
|
array - like
|
For each closure phase, the sample of baseline |
required |
Returns:
| Type | Description |
|---|---|
array - like
|
Closure phases |
cp_indices(vis_sta_index, cp_sta_index)
Map closure-triangle station indices to baseline indices.
Baselines are matched on station indices alone; for several epochs or
channels use read_oifits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vis_sta_index
|
array - like
|
Station index pairs |
required |
cp_sta_index
|
array - like
|
Station index triplets |
required |
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray, ndarray]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a triangle needs a baseline that is missing, or stored only in the reversed orientation. |