Skip to content

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 per dataset. 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 δ, which is a rotation of (u, v) by −δ. A plate scale needs no term of its own: a sky magnified by m is wavel_scale = 1/m. 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. The same terms for published positions are in PositionData.term; they follow Octofitter (Thompson et al. 2023, AJ 166, 164).

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 path, or a file opened with astropy.io.fits or pyoifits), a list of such files (concatenated), or a dictionary of arrays; see __init__.

required
target str or int

For OIFITS input, the target to keep (by name or TARGET_ID). Required when the file contains more than one target.

None
Notes

Every (baseline, wavelength) sample is one element of the flat u, v (metres) and wavel (metres) arrays; wavel has a single element when all samples share one wavelength. Models are evaluated on these samples. The observables are:

  • vis/d_vis: squared visibilities (v2_flag=True) or amplitudes, or their projection through vis_mat.
  • phi/d_phi: closure phases (cp_flag=True) built from the samples i_cps1 + i_cps2 - i_cps3, or absolute phases; always in radians, optionally projected through phi_mat.

Visibility-only data (no OI_T3 or VISPHI, no phi in a dictionary, or every closure phase flagged) have an empty phase block: has_phases is False and fits use the visibilities alone.

Flagged samples are left out of the observables. vis_index (and phi_index for absolute phases) then lists the samples that are observed; they are None when every sample is used.

uv_grid is a UVGrid for AMIGO DISCO products, whose samples lie on a regular (possibly rotated) lattice, and None for all other data, even if their samples happen to lie on a lattice: virgil does not search ordinary data for one. Models that can use the lattice, such as an Image with matching rotation_deg, then evaluate faster; the results are the same. To use it for other lattice-sampled data, set it with find_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).

Data read from OIFITS also keep their time and exposure per sample: frame numbers the exposures (frames), :attr:mjd gives each sample's time, and :meth:epochs and :meth:split_by_epoch group the frames into nights. The time is stored as dt, days since the static float64 t_ref, so that models of time see small numbers that keep their precision in float32 (about 5 s over 1000 days). stations holds each sample's station pair (STA_INDEX).

gains holds calibration gains correlated across channels (GainModes, set with with_gains), which the likelihood marginalizes; None by default. phase_offsets likewise holds closure-phase offsets per frame (ClosureOffsets, set with with_closure_offsets).

extras holds further observables, read with extras= (OI_FLUX, T3AMP, VISAMP beside V², differential VISPHI beside closure phases): a tuple of blocks from virgil.observables, which follow the phases in the data vector, in the order of observables.KINDS. It is empty by default.

has_phases property

Whether the data hold any phase observables. Visibility-only data (e.g. V² without closure phases) have an empty phase block.

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=())

Initialize from an OIFITS file or explicit arrays.

Parameters:

Name Type Description Default
data dict, str, os.PathLike, astropy.io.fits.HDUList, or a list of files

An OIFITS file or a list of them, read with virgil.oifits.read_oifits (several files, wavelength channels, tables and epochs, and FLAG columns, are supported). Or a dictionary with keys:

  • u, v (metres) and wavel (metres): per sample, or u/v per baseline with vis of shape (n_baseline, n_wavel) for several channels.
  • vis, d_vis: squared visibilities or amplitudes.
  • phi, d_phi: closure or absolute phases, with phi_unit ("rad", the default, or "deg"; phase_unit is 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): vis holds squared visibilities; otherwise amplitudes.
  • cp_flag (default: True when closure indices are given): phi holds closure phases; otherwise absolute phases.
  • vis_flag, phi_flag (optional): boolean masks, True for bad samples, shaped like vis and phi. Samples with non-finite values or errors are flagged automatically.
  • vis_mode ("auto", "v2", "amp" or "logamp"; observable_kind is an alias): the visibility channel that data and model are compared in. "auto" keeps the channel of vis.
  • mjd, frame (optional): the time (days) and an integer exposure label of each sample (or of each baseline, for several channels). Without frame, samples with the same mjd form one frame. (OIFITS input also compares each row's TIME: see read_oifits.)
  • stations (optional): the station pair (a, b) (STA_INDEX) of each sample, or of each baseline for several channels, for telescope and baseline gains (with_gains).
  • vis_mat, phi_mat (optional): linear operators of shape (n_out, n_in) projecting the channels into, e.g., kernel or DISCO observables. The disco_vis_mat/disco_phi_mat spellings also check that the projected covariance is diagonal. Only diagonal uncertainties are propagated.

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. * flux/nflux, visamp, t3amp, visphi (optional): extra observables, as dictionaries in the layout read_oifits returns (see extras). visamp and visphi give per value the sample it belongs to, with value, error and flag (visamp also amptyp); t3amp has one value per closure phase; flux gives wavel, value, error, flag and optionally row, mjd and station.

required
target str or int

For OIFITS input, the target to keep.

None
extras sequence of str

For OIFITS input, the extra observables to read (see read_oifits).

()

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 model, and matching one-sigma uncertainties.

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 model.

required
reference array - like

Vector to compare against; by default the data (the first vector of :meth:flatten_data).

None

Returns:

Type Description
array - like

Residual vector. Unprojected phase residuals are wrapped into [-π, π), so that a closure phase of π - ε against a model of -π + ε counts as a small residual rather than 2π.

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.

Use it when the error bars are known to be too large or too small, for example with a factor from error_scale. The uncertainties are those of the observables as fitted (after any projection), so the whitened residuals simply scale by 1 / factor.

A dictionary scales each kind of observable by its own factor, as returned by error_scale(..., by_observable=True): "vis" (d_vis), "phi" (d_phi) and the kinds of extras ("flux", "nflux", "corrflux", "visamp", "t3amp", "visphi"). Kinds missing from the dictionary are left unchanged, and so are kinds these data lack, so one dictionary can rescale several datasets.

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]})
>>> scaled = data.with_error_scale({"vis": 3.0})
>>> [round(float(e), 3) for e in scaled.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|), as PMOIRED's min error and min relative error do. This is a fixed change to the data; the fitted counterpart, added in quadrature and relative to the model, is the noise= terms (see inflated_errors). Both use one rule, _utils.inflate_errors.

Parameters:

Name Type Description Default
absolute dict

Floors per observable: "vis" (in the units of vis, e.g. V²), "phi" (radians), and any of extras by kind: "flux", "nflux", "corrflux", "visamp", "t3amp" and "visphi" (radians). Relative floors are not defined for phases.

None
relative dict

Floors per observable: "vis" (in the units of vis, e.g. V²), "phi" (radians), and any of extras by kind: "flux", "nflux", "corrflux", "visamp", "t3amp" and "visphi" (radians). Relative floors are not defined for phases.

None

Returns:

Type Description
OIData
Notes

Closure phases from four or more telescopes are floored before they are whitened: the whitened rows use the floored errors, while their periodic penalty rows keep their effective error 1/√(2π) (the penalty itself is 2 sin²(Δ/2)/σ with the floored σ). The differential phases are floored per channel, before their covariance N D Nᵀ is formed.

Examples:

>>> import numpy as onp
>>> 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). Differential phases are fitted over the continuum and kept in the lines (each defaults to the complement of the other); normalized spectra use the continuum.

None
lines sequence of (lo, hi)

Wavelength ranges (metres). Differential phases are fitted over the continuum and kept in the lines (each defaults to the complement of the other); normalized spectra use the continuum.

None
order int

The continuum polynomial in wavenumber: 1 (a mean and a slope) by default for differential phases, 0 (a mean) 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), using the channels of both windows, instead of projecting them out as the pipeline does (the default). See DifferentialPhase.

None

Examples:

Keep the differential phase across Brγ, normalized on either side: data.with_continuum([(2.150e-6, 2.162e-6), (2.170e-6, 2.180e-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). It is never taken from the data, and it is a proposal for the scale's Jeffreys prior 1/k (see virgil.observables).

Parameters:

Name Type Description Default
scale (float, float)

The prior (mean, sd) of the scale k, in the data's units (e.g. a calibrated OI_FLUX in Jy: a broad prior about the expected level). Required for "flux" and "corrflux" before fitting; (1, 0.1) by default for "nflux". It applies to "flux" and "corrflux" unless kinds says otherwise; blocks without it keep their own.

None
per (dataset, row, frame, station)

One scale for the whole dataset (default), or one per spectrum (row), per exposure, or per telescope (baseline, for correlated fluxes). Fibre injection varies per telescope and exposure, so per row is the safest for uncalibrated spectra.

"dataset"
poly_order int

Also marginalize a polynomial in λ of this order times the model spectrum (a chromatic calibration).

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: by default "flux" and "corrflux" when scale is given, and every spectrum (also "nflux") otherwise.

None

with_gains(telescope=None, baseline=None, chromatic=None, modes=None)

A copy of the data with calibration gains correlated across channels.

The likelihood then marginalizes gains on log |V| per frame analytically (see virgil.gains), with these widths unless they are fitted as the noise terms vis_gain_telescope, vis_gain_baseline, vis_gain_chromatic or vis_gain_modes (scale parameters: give them log-uniform priors on stated bounds, e.g. dist.LogUniform(1e-4, 0.3)). The covariance then depends on the model, so fits use L-BFGS.

Parameters:

Name Type Description Default
telescope float

Widths (1σ on log |V|; 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)², a coherence loss. None leaves a group out.

None
baseline float

Widths (1σ on log |V|; 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)², a coherence loss. None leaves a group out.

None
chromatic float

Widths (1σ on log |V|; 0.01 is a 1% amplitude or 2% V² gain) of gains per (frame, telescope), per (frame, baseline), and per (frame, baseline) shaped (λ_ref/λ)², a coherence loss. None leaves a group out.

None
modes array - like

Further modes, (n_mode, n_sample): shapes on log |V| per 1σ, one value per sample (the order of u), e.g. a calibrator PCA's. Their width, 1 by default, scales them.

None

Returns:

Type Description
OIData

The data with gains set (replacing any earlier ones).

with_wavelength_scale(scale=1.0, offset=0.0)

A copy of the data whose wavelengths are scale · λ + offset.

A wavelength calibration error: models are evaluated at the corrected wavelengths, which rescales the spatial frequencies (u and v are in metres) and moves the spectra together, as a wrong wavelength scale does. Angular sizes scale with it, so with a single dataset the scale is degenerate with every size, and its prior is the systematic error. Its effect on the spatial frequencies is that of magnifying the sky by 1 / scale, so it is also the plate-scale term for interferometric data (see with_north_angle). Fit it with the noise terms wavel_scale and wavel_offset (e.g. noise={"wavel_scale": dist.Normal(1.0, 2e-4)}, about right for GRAVITY), which call this.

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 wavel replaced. A uv_grid is kept: it is in metres and divided by the wavelength where it is used, so it still matches the samples.

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). A source at (dra, ddec) appears at R(angle) (dra, ddec), with R(δ) = [[cos δ, sin δ], [-sin δ, cos δ]], so a companion at PA θ appears at PA θ + angle. Because virgil's Fourier kernel is u dra + v ddec, rotating the sky rotates (u, v) the same way, and models are evaluated at R(-angle) (u, v), as for Rotated(model, angle). Fit it with the noise term north_angle, which calls this.

Its prior must be stated, as for every noise term: there is no default width. The invariant prior of a rotation is uniform on the circle, under which, with one dataset, the angle is degenerate with every position angle in the scene. A Gaussian prior (e.g. noise=[{}, {"north_angle": dist.Normal(0.0, 0.5)}]) is strong information, and its width should come from the instrument's astrometric calibration. Long-baseline interferometers know their baselines' orientation geometrically, so leave the term out there; it is for imaging, masking and kernel-phase data.

A plate scale needs no term of its own: a sky magnified by a factor m is with_wavelength_scale(1 / m) (the noise term wavel_scale). Per-dataset North-angle and plate-scale calibration terms follow Octofitter (Thompson et al. 2023, AJ 166, 164).

Parameters:

Name Type Description Default
angle float

Rotation of the sky as these data see it, in degrees, North through East.

required

Returns:

Type Description
OIData

The data with u and v rotated. A uv_grid is dropped, since its rotation is static, so models are evaluated by the direct transform, which gives the same visibilities.

with_closure_offsets(baseline=None, triangle=None, modes=None)

A copy of the data with closure-phase offsets per frame.

Calibration can leave closure phases that do not close. The likelihood then marginalizes offsets common to the channels of a frame analytically (see ClosureOffsets), with these widths unless they are fitted as the noise terms phi_offset_baseline, phi_offset_triangle or phi_offset_modes (scale parameters: log-uniform priors on stated bounds). Use them only if calibrators show such offsets: they are off by default. Needs closure phases from four or more telescopes.

This is a small-offset approximation: an offset δ changes the whitened sine sin Δ by about δ cos Δ, which is treated as linear in δ. It holds while the offsets (and the residuals) are small, about δ ≲ 0.3 rad (17°); larger widths are not marginalized exactly.

Parameters:

Name Type Description Default
baseline float

Width (radians) of a phase offset per (frame, baseline), which reaches the closure phases through the triangles' signs.

None
triangle float

Width (radians) of an offset per (frame, triangle).

None
modes array - like

Further modes, (n_mode, n_phase), radians per 1σ, one value per closure phase, each within one frame (e.g. a calibrator PCA's). Their width, 1 by default, scales them.

None

Returns:

Type Description
OIData

The data with phase_offsets set. Split or select the data first: data with offsets cannot be split.

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 the samples with wavel_min <= wavel <= wavel_max (metres); either bound may be left open.

None
wavel_max float

Keep the samples with wavel_min <= wavel <= wavel_max (metres); either bound may be left open.

None
ranges sequence of (lo, hi)

Keep the samples in any of these wavelength ranges (metres, inclusive), instead of wavel_min and wavel_max.

None
exclude sequence of (lo, hi)

Then drop the samples in any of these ranges (metres, inclusive), e.g. a line or a telluric band.

None
observables str or sequence of str

The observables to keep: "vis", "phi" and the kinds of extras (observables.KINDS); the others are dropped at every wavelength. All are kept by default. Without the closure phases, differential phases keep their closure part (closure_free=False), which is then counted only once. Calibration gains go with the visibilities.

None

Returns:

Type Description
OIData

The samples in range, with their observables and closure phases (a closure triangle's legs share a wavelength, so triangles are kept whole). Not available for projected (kernel, DISCO) observables, or after closure-phase offsets are added.

Examples:

Keep the K-band continuum of a GRAVITY file but not the Brγ window: data.select(2.05e-6, 2.16e-6), or the continuum on both sides of the line: data.select(exclude=[(2.162e-6, 2.170e-6)]). Closure phases alone, between 2.05 and 2.18 µm: data.select(2.05e-6, 2.18e-6, observables="phi").

uv grids

AMIGO DISCO data are sampled on a regular uv lattice; OIData.uv_grid records it, so that a matching Image 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 that lie on a regular lattice, possibly rotated on the sky.

AMI data, for example, are sampled on the detector's Fourier grid, which is rotated on the sky by the parallactic angle. The sample k is at grid-frame coordinates (u_axis[index[k] % nu], v_axis[index[k] // nu]) (metres), and on the sky at rotate(u_g, v_g, rotation_deg). Found from the samples by :func:find_uv_grid.

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.

The lattice axes are found from the shortest separation between samples, and every sample must lie within tolerance of a cell of a lattice point (so that a Fourier transform onto the lattice is exact).

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

rotation_deg is in (-45, 45].

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 (a, b).

required
index_cps2 array - like

For each closure phase, the sample of baseline (b, c).

required
index_cps3 array - like

For each closure phase, the sample of baseline (a, c).

required

Returns:

Type Description
array - like

Closure phases φ[i1] + φ[i2] − φ[i3] in radians, wrapped into [-π, π).

Notes

This helper returns radians for internal modeling consistency. Convert to degrees before writing OIFITS phase columns (e.g., T3PHI).

cp_indices(vis_sta_index, cp_sta_index)

Map closure-triangle station indices to baseline indices.

Parameters:

Name Type Description Default
vis_sta_index array - like

Station index pairs (a, b), one per baseline.

required
cp_sta_index array - like

Station index triplets (a, b, c), one per closure triangle.

required

Returns:

Type Description
tuple[ndarray, ndarray, ndarray]

Arrays (i_cps1, i_cps2, i_cps3) giving, for each triangle, the baselines (a, b), (b, c) and (a, c), so that the closure phase is φ[i_cps1] + φ[i_cps2] − φ[i_cps3].

Raises:

Type Description
ValueError

If a triangle needs a baseline that is missing, or stored only in the reversed orientation.

Notes

Baselines are matched on station indices alone. For data with several epochs or wavelength channels use virgil.oifits.read_oifits, which also matches on instrument and MJD.