Skip to content

virgil.likelihood

Likelihoods of source models given data, and numpyro models.

Likelihoods of source models given interferometric data.

A model is given either as a template SourceModel, whose parameters at dot-separated zodiax paths (e.g. "comp.flux") are replaced, or as a class/callable called with the parameters as keyword arguments (see build_model).

Every likelihood, grid, limit and fit goes through one residual vector, whitened_residuals: the residuals divided by their uncertainties, with unprojected phases measured as a chord, 2 sin(Δ/2), so that the likelihood is smooth where phases wrap at ±π. Closure phases from four or more telescopes are correlated: their independent combinations are whitened together, using the sine sin Δ (not the chord) of each residual, with a periodic penalty 2 sin²(Δ/2) / σ added per closure phase. Both terms repeat every 2π and are smooth, so the likelihood is continuous everywhere.

whitened_residuals(model, data, **noise)

Residuals of a model divided by the data uncertainties.

This is the one residual vector behind every likelihood in virgil: model_loglike is -0.5 * sum(whitened_residuals**2) plus the Gaussian normalization, and least-squares fits minimize its sum of squares.

Visibility and projected-phase (kernel or DISCO) residuals are (model - data) / σ. Unprojected phase residuals Δ are 2 sin(Δ/2) / σ: equal to Δ/σ for small Δ, but smooth where Δ wraps at ±π, so that a χ² surface has no kinks there. The resulting likelihood is a von Mises distribution with concentration 1/σ², normalized exactly on the circle.

Closure phases from four or more telescopes are the exception: they are correlated, so the sines sin Δ of the residuals are whitened together and replaced by their independent combinations, and a periodic penalty 2 sin²(Δ/2)/σ per closure phase is appended (chords would flip sign at Δ → Δ + 2π, making correlated combinations discontinuous). The χ² is continuous and smooth everywhere, equals the correlated Gaussian ΔᵀC⁻¹Δ to O(Δ³) for small residuals, and has no false minimum at Δ = π. There are then n_independent + (number of closure phases) residuals, namely OIData.n_residuals.

Parameters:

Name Type Description Default
model SourceModel

Model to evaluate.

required
data OIData

Data to compare with.

required
**noise

Error-inflation terms, vis_scale, phi_scale, vis_error_rel and phi_error (see inflated_errors), and the widths of the data's gains, vis_gain_<group> (see OIData.with_gains), closure-phase offsets, phi_offset_<group> (see OIData.with_closure_offsets), the wavelength scale, wavel_scale and wavel_offset (see OIData.with_wavelength_scale), and the North angle, north_angle (degrees; see OIData.with_north_angle).

{}

Returns:

Type Description
array - like

One dimensionless residual per independent observable (n_independent of them), in the order of flatten_data; correlated closure phases are replaced by their whitened independent combinations, followed by one periodic penalty residual per closure phase, so the length is n_residuals.

build_model(model, params, values)

Build a model from parameter names and values.

model is either a class/callable, called as model(**dict(zip(params, values))), or a SourceModel instance used as a template whose leaves at the (dot-separated) paths params are replaced by values.

model_loglike(model, data, *, reject_unphysical=False, **noise)

Evaluate the log likelihood for an instantiated model object.

This is -0.5 * sum(r**2) for the residuals r of whitened_residuals, plus each density's normalization: Gaussian in visibilities and projected phases, -log σ - ½ log 2π; von Mises with concentration κ = 1/σ² in uncorrelated unprojected phases, -log 2π - log i0e(κ), which is the Gaussian one for σ ≪ 1 but stays a normalized density on the circle when σ is large (e.g. a fitted phi_error). Correlated closure phases (OIData.cp_noise) keep the Gaussian normalization, their small-σ limit, since a correlated circular density has no closed-form normalizer; their periodic penalty residuals add nothing to it.

Parameters:

Name Type Description Default
model SourceModel

Model to evaluate.

required
data OIData

Data to compare with.

required
reject_unphysical bool

If True, return -inf when is_physical is false, e.g. for a negative flux or a rim whose brightness goes negative. This works inside jax.jit, so samplers can use it as a hard prior boundary.

False
**noise

Error-inflation terms, e.g. fitted as nuisance parameters: vis_scale and phi_scale multiply the uncertainties, and vis_error_rel (relative to the model visibility) and phi_error (radians) are added in quadrature (see inflated_errors). The Gaussian normalization uses the inflated errors. For data with gains (OIData.with_gains), vis_gain_<group> sets the width of a group of gains, which are marginalized: the normalization then includes the log-determinant of the visibility covariance. phi_offset_<group> does the same for closure-phase offsets (OIData.with_closure_offsets). wavel_scale and wavel_offset evaluate the model at corrected wavelengths (see OIData.with_wavelength_scale), and north_angle on a rotated sky (see OIData.with_north_angle).

{}

inflated_errors(data, prediction, vis_error_rel=None, phi_error=None, vis_scale=None, phi_scale=None, vis_error=None, *, where='model', combine='quadrature')

The data uncertainties, scaled and with extra terms in quadrature.

The visibility errors become hypot(vis_scale σ, vis_error, vis_error_rel V), for the model visibility observable V, and the phase errors hypot(phi_scale σ, phi_error). Terms left as None are not applied. Extra observables (OIData.extras) are left unchanged; give them floors with OIData.with_error_floor.

Parameters:

Name Type Description Default
data OIData

Data whose uncertainties are inflated.

required
prediction array - like

Model vector, e.g. from OIData.model.

required
vis_error_rel float

Extra visibility error, as a fraction of the model visibility observable (e.g. of the model V² for squared visibilities): a calibration error.

None
phi_error float

Extra phase error in radians.

None
vis_scale float

Factors multiplying the visibility and phase uncertainties.

None
phi_scale float

Factors multiplying the visibility and phase uncertainties.

None
vis_error float

Extra absolute visibility error, in the units of the observable.

None
where (model, data)

What vis_error_rel is relative to: the model (default, the right choice for a fitted term, as it does not reward the model for low data points) or the data (as error floors are).

"model"
combine (quadrature, max)

Add the terms in quadrature (default), or take the largest (a floor, as with_error_floor does; both use _utils.inflate_errors).

"quadrature"

Returns:

Type Description
array - like

Uncertainties matching flatten_data.

flux_scale_posterior(model, data)

The grey scales of the extra spectra, given a model.

The scales (and polynomial coefficients) of OI_FLUX spectra and correlated fluxes are marginalized in the likelihood (see FluxSpectrum); this is their Gaussian posterior conditional on model, for reporting.

Parameters:

Name Type Description Default
model SourceModel

E.g. the best fit.

required
data OIData

Data with extra spectra (extras=("flux",) and the like).

required

Returns:

Type Description
dict

Per kind ("flux", "nflux", "corrflux"): mean (n_group, p) and cov (n_group, p, p) of the weights, whose first is the scale k multiplying the model's template (normalized to a mean of 1 per group); and scale, the scale of total_spectrum itself (k over the template's normalization), for "flux" and "corrflux": data ≈ scale × model. groups gives each sample's group.

noise_sites(noise, n_datasets)

Expand a noise specification into named sites.

noise maps error-inflation terms (NOISE_TERMS) to priors and applies to every dataset, giving sites "noise.<term>"; a list of such dicts, one per dataset, gives sites "noise[i].<term>". An entry may also be tied: a function of the dict of sampled parameter values (keyed like priors) that returns the term's value, which then has no prior of its own (see is_tied), unless it has a log_prior(values) method (see tied_log_prior).

Returns:

Type Description
dict

{site: (prior, datasets, term)}, datasets being the indices of the datasets the term applies to; prior is the function for a tied term.

is_tied(spec)

Whether a noise entry is tied: a function of the sampled parameters (rather than a prior), e.g. one epoch's error scale drawn from a population (see hierarchical_scales).

tied_log_prior(sites, values)

The summed log_prior(values) of the distinct tied noise terms that have one, such as the population density of a centred hierarchical_scales member (a term used twice counts once).

noise_for(sites, values, index)

The error-inflation terms of dataset index, from site values.

loglike(values, params, model, data, **options)

Gaussian log-likelihood of a model with the given parameter values, assuming Gaussian errors.

Parameters:

Name Type Description Default
values array - like

Values of the model parameters.

required
params list

List of parameter names.

required
model SourceModel or callable

Template model whose parameters at the dot-separated paths params are replaced by values, or a class/callable called as model(**dict(zip(params, values))) (see build_model).

required
data OIData

Object containing the data to be fitted.

required
**options

Error terms and reject_unphysical, passed to model_loglike.

{}

Returns:

Type Description
float

Log-likelihood value.

joint_prediction(params, model, data)

Concatenate predictions for a parameter pytree and multiple data.

model(params, index) defines which parameters are shared and which are specific to each observation.

joint_data(data)

Concatenate observed vectors in the same order as joint_prediction.

joint_errors(data)

Concatenate uncertainty vectors in the same order as joint_prediction.

joint_loglike(params, model, data, **options)

Sum independent Gaussian log likelihoods over multiple data.

options (error terms and reject_unphysical) are passed to model_loglike.

numpyro_model(model, priors, data, regularizers=(), noise=None, likelihoods=(), **options)

Return a numpyro model sampling the parameters in priors.

Parameters:

Name Type Description Default
model SourceModel or callable

Either a template model whose leaves at the paths in priors are sampled, or a function called with the sampled values as keyword arguments that returns a SourceModel. A function lets you sample parameters that are not leaves of the model, such as a separation and position angle, or one inclination shared by two components (see build_model). As for fit, the function may return a list of models, one per dataset in data, sharing parameters: for example binaries at two epochs with one flux ratio and a position each. Model i is compared with dataset i, and regularizers act on the first model only.

required
priors dict[str, Distribution]

Mapping from parameter path (e.g. "comp.flux") or function argument name to prior; each key is also used as the numpyro sample-site name. Priors on fluxes (keys named flux or ending in .flux) must have non-negative support. An angle (degrees) with an AngleVector prior is sampled as a 2-D vector at the site "<path>_vec", with the angle recorded as the deterministic site "<path>": there is no wrap boundary at 0°/360°.

required
data OIData or sequence of OIData

Data whose Gaussian log likelihood is added with numpyro.factor. May be () when likelihoods holds all the data.

required
regularizers sequence

Log-prior terms on the model, e.g. a Centroid prior, added with numpyro.factor. Only genuine prior densities (probabilistic) are allowed: penalties such as maximum entropy are for fit.

()
noise dict or list of dict

Priors on error-inflation terms (vis_scale, phi_scale, vis_error_rel, phi_error; see inflated_errors) and on gain widths (vis_gain_<group>; see OIData.with_gains), on closure-offset widths (phi_offset_<group>; see OIData.with_closure_offsets), on the wavelength scale (wavel_scale, wavel_offset; see OIData.with_wavelength_scale) and on the North angle (north_angle, degrees; see OIData.with_north_angle), sampled as sites "noise.<term>". A list gives each dataset its own terms, as sites "noise[i].<term>". An entry may instead be a function of the dict of sampled values (keyed like priors), recorded as a deterministic site: a term tied to parameters in priors, as in a hierarchical model where each epoch's error scale is drawn from a population with fitted hyperparameters (see hierarchical_scales). A tied term's log_prior(values), if it has one, is added once (site "noise_prior"). Priors. As for fit: the default (Jeffreys) prior of these scale parameters is log-uniform on stated bounds that must contain the plausible values.

None
likelihoods sequence

Extra data terms, as for fit: callables of the sampled values (a dict keyed like priors) returning whitened residuals, such as PositionData.term(orbit_fn) or RVData.term(params_fn). Term i is added with numpyro.factor as site "likelihood_<i>". Those two built-in terms add their full normalized Gaussian log density, like the OIData terms; a plain callable adds -0.5 * sum(r**2) only.

()
**options

Fixed error terms and reject_unphysical, passed to model_loglike.

{}

Returns:

Type Description
callable

Zero-argument numpyro model, e.g. for numpyro.infer.NUTS.

Examples:

Sample an orbit from measured positions alone, with no OIData:

>>> import numpy as onp
>>> import numpyro.distributions as dist
>>> from virgil.likelihood import numpyro_model
>>> from virgil.orbits import KeplerOrbit, PositionData
>>> mjd = 60500.0 + onp.array([0.0, 100.0, 200.0, 300.0])
>>> truth = KeplerOrbit(400.0, 30.0, 0.4, 60.0, 40.0, 110.0, 20.0, t_ref=60500.0)
>>> dra, ddec, _ = (onp.asarray(x) for x in truth.relative(mjd))
>>> cov = onp.broadcast_to(0.05**2 * onp.eye(2), (4, 2, 2))
>>> positions = PositionData(mjd, dra, ddec, cov)
>>> priors = {"a_mas": dist.LogUniform(5.0, 50.0), "ecc": dist.Uniform(0.0, 0.9)}
>>> def orbit_fn(v):
...     return KeplerOrbit(
...         400.0, 30.0, v["ecc"], 60.0, 40.0, 110.0, v["a_mas"], t_ref=60500.0
...     )
>>> model = numpyro_model(
...     lambda **kw: None, priors, (), likelihoods=[positions.term(orbit_fn)]
... )
>>> from numpyro.infer.util import log_density
>>> values = {"a_mas": 20.0, "ecc": 0.4}
>>> bool(onp.isfinite(float(log_density(model, (), {}, values)[0])))
True

chain_init_params(model, starts, key=None)

Initial parameters for numpyro's MCMC, one chain per start.

init_to_value starts every chain at one point. To start each chain in its own mode (e.g. the distinct fits of OrbitStart.chain_values), numpyro instead takes init_params, in its unconstrained coordinates and with a leading axis over chains; this converts one dict of values per chain into them.

Parameters:

Name Type Description Default
model callable

The numpyro model, e.g. from numpyro_model.

required
starts sequence of dict

One dict of values per chain, keyed by sample site as init_to_value takes them (e.g. FitResult.values, which holds the "<path>_vec" sites of angle vectors). Sites missing from a dict start uniformly at random, as init_to_value does.

required
key Array

Random key for those missing sites (default PRNGKey(0)).

None

Returns:

Type Description
dict

Unconstrained parameters by site, with a leading axis of len(starts) (none for a single start). Pass them as mcmc.run(key, init_params=...) with num_chains=len(starts).

Examples:

>>> posterior = numpyro_model(model, priors, data)
>>> init = chain_init_params(posterior, start.chain_values(4))
>>> mcmc = MCMC(NUTS(posterior), num_warmup=500, num_samples=500,
...             num_chains=4, chain_method="vectorized")
>>> mcmc.run(jax.random.PRNGKey(1), init_params=init)

posterior_predictive_summary(samples, model, data, params=None)

Mean and spread of the model observables over posterior samples.

Parameters:

Name Type Description Default
samples dict[str, array - like]

Posterior samples, as equal-length 1D arrays keyed by parameter name or path (e.g. from mcmc.get_samples()).

required
model SourceModel or callable

Template model or class, as for :func:loglike.

required
data OIData

Data defining the observables.

required
params list[str]

Which keys of samples to use (default: all of them).

None

Returns:

Type Description
dict

vis_mean, vis_std, phi_mean and phi_std: the mean and standard deviation over the samples of each visibility and phase observable.