Skip to content

virgil.detection

Detection statistics of a companion search over a grid: the profile likelihood ratio Δχ², the grid-marginalized log Bayes factor and the best flux SNR. They are traceable in the data, so they can be computed for many simulated observations under jax.lax.map with one compilation, which is what empirical false-alarm probabilities and ROC curves need.

The Monte Carlo: gaussian_null and bootstrap_null simulate the null hypothesis (Gaussian noise from the errors, or a residual bootstrap of the data), rescale_errors first brings the null to χ²_r = 1, injection_grid lays out companions to inject, and injection_recovery runs the search on null and injected simulations with one compiled kernel. Its DetectionMC result gives empirical false-alarm probabilities, thresholds, ROC curves, completeness maps and contrast curves, and saves, loads and concatenates for array jobs.

Detection statistics, null simulators and injection-recovery for ROC curves.

detection_statistics reduces a companion search over a grid to delta_chi2, log_bayes_factor and max_snr; it is traceable in the data, so it compiles once under jax.lax.map. gaussian_null and bootstrap_null simulate the null, injection_recovery runs the statistics on null and injected draws, and its DetectionMC gives false-alarm probabilities, thresholds, ROC curves and contrast curves.

See Detection statistics and the Detection ROC curves tutorial.

DetectionMC dataclass

Statistics of simulated null and injected companion searches.

Made by injection_recovery; plain NumPy. Conventions are in Detection statistics.

Source: Fawcett (2006), references.

Attributes:

Name Type Description
null dict[str, ndarray]

Per null draw: the three statistics, flux_peak_steps, converged_fraction and the best position and flux as best_<name> (name is the last dotted part of each grid key).

injected dict[str, ndarray]

The same per injected draw, plus the injected values by name.

meta dict

JSON-serializable: grid, fingerprints, noise model, match_radius, draw counts, seeds and virgil version.

null instance-attribute

injected instance-attribute

meta instance-attribute

n_null property

Number of null draws.

n_injected property

Number of injected draws.

__init__(null, injected, meta)

__repr__()

separations()

Separations of the injections in mas.

hypot(dra, ddec) for Cartesian grids, or the injected sep for angular ones (sep, pa in degrees).

matched()

Whether each injection's best position is within match_radius.

All True when meta["match_radius"] is None. Angular positions (sep, pa in degrees) are compared on the sky.

false_alarm_probability(stat, value, *, confidence=0.95)

Empirical false-alarm probability of value, with an interval.

Parameters:

Name Type Description Default
stat str

"delta_chi2", "log_bayes_factor" or "max_snr".

required
value float or array - like

Observed statistic(s).

required
confidence float

Confidence of the binomial interval.

0.95

Returns:

Type Description
fap, lower, upper : numpy.ndarray

fap = (k + 1) / (n + 1) for k of n null draws at or above value (never 0); lower and upper are the exact Clopper–Pearson bounds on P(null >= value).

threshold(stat, fap, *, n_boot=200, seed=0)

Threshold of stat at false-alarm probability fap.

Parameters:

Name Type Description Default
stat str

The statistic.

required
fap float

False-alarm probability (e.g. 1.35e-3 for one-sided 3σ).

required
n_boot int

Bootstrap resamples of the null draws, for the error.

200
seed int

Seed of the bootstrap.

0

Returns:

Type Description
threshold, error : float

The 1 - fap quantile of the null draws and its bootstrap standard deviation.

Warns:

Type Description
RuntimeWarning

If fap * n < 1: the threshold is then about the largest null value and underestimates the true one.

detected(stat, fap)

Whether each injection is detected at false-alarm probability fap.

Its statistic exceeds :meth:threshold and, with match_radius, its best position matches the injection.

roc(stat, flux=None, sep_bin=None)

ROC curve: true- against false-positive rate over thresholds.

Parameters:

Name Type Description Default
stat str

The statistic.

required
flux float or (float, float)

Only injections of this flux (to 1e-6), or with lo <= flux < hi.

None
sep_bin (float, float)

Only injections with lo <= separation < hi (mas).

None

Returns:

Type Description
fpr, tpr, thresholds : numpy.ndarray

At each threshold (+inf, then every distinct statistic value, decreasing): the fraction of null draws and of selected injections (matched, with match_radius) at or above it.

auc(stat, flux=None, sep_bin=None)

Area under the :meth:roc curve (0.5 for no discrimination).

completeness(stat, fap, sep_bins=None, flux_bins=None)

Detection fraction of the injections by separation and flux.

Parameters:

Name Type Description Default
stat str

The statistic.

required
fap float

False-alarm probability of the detection threshold.

required
sep_bins array - like

Bin edges (mas, and flux); by default each distinct injected value (six significant figures) is a bin, which suits injection_grid.

None
flux_bins array - like

Bin edges (mas, and flux); by default each distinct injected value (six significant figures) is a bin, which suits injection_grid.

None

Returns:

Type Description
dict

completeness (n_sep × n_flux, NaN where empty), n (injections per bin), sep and flux (distinct values or bin centres; geometric for positive flux bins) and threshold.

contrast_curve(stat, fap, completeness=0.5, sep_bins=None, flux_bins=None)

Flux detected with a given completeness, against separation.

Per separation bin of :meth:completeness, the detection fraction is made non-decreasing in flux and interpolated in log flux to where it first reaches completeness.

Parameters:

Name Type Description Default
stat str

The statistic.

required
fap float

False-alarm probability of the detection threshold.

required
completeness float

Target detection fraction.

0.5
sep_bins array - like

As for :meth:completeness.

None
flux_bins array - like

As for :meth:completeness.

None

Returns:

Type Description
sep, flux : numpy.ndarray

Separations (mas) and flux relative to the primary (convert with flux_to_contrast); NaN where the positive injected fluxes do not bracket the target.

save(path)

Write to path, an .npz with the metadata as JSON.

meta["format"] is the file-format version (1).

load(path) classmethod

Read a file written by :meth:save.

A file without meta["format"] is read as version 1. Any other version than the one this virgil writes raises a ValueError, rather than being read in a layout it may not have.

concatenate(results) classmethod

Merge results of one experiment run with different seeds.

Parameters:

Name Type Description Default
results sequence of DetectionMC

E.g. one per array job. They must agree on grid, model, null scene, template, noise and match_radius, with distinct seeds.

required

Returns:

Type Description
DetectionMC

All draws, with every seed in meta.

Raises:

Type Description
ValueError

If the metadata are incompatible or a seed repeats.

detection_statistics(model, data, grid, *, flux_param=None, batch_size=None)

Detection statistics of a companion search over a grid.

The null hypothesis is model with the companion flux at zero; see Detection statistics.

Parameters:

Name Type Description Default
model SourceModel or class

Companion model, as for likelihood_grid.

required
data OIData

Data to search; may be traced (a simulation built under jax.lax.map).

required
grid dict[str, array - like]

Coordinate axes (dra/ddec in mas, or sep in mas and pa in degrees) and a non-negative, usually log-spaced flux axis, which must resolve the peak.

required
flux_param optional

Key of grid holding the flux (default: the key ending in flux), and grid points evaluated at once, as for likelihood_grid.

None
batch_size optional

Key of grid holding the flux (default: the key ending in flux), and grid points evaluated at once, as for likelihood_grid.

None

Returns:

Type Description
dict[str, Array]

Scalars delta_chi2 (profile likelihood ratio, flux >= 0), log_bayes_factor (companion somewhere on the grid against none), max_snr (best flux over its Laplace uncertainty), one entry per grid key (where delta_chi2 peaks) and flux_peak_steps (peak FWHM over flux spacing; below about 2 the Bayes factor is inaccurate).

Warns:

Type Description
RuntimeWarning

With concrete data, if the flux axis does not resolve the peak, the best flux lies above it, or the optimizer did not converge. Nothing is checked under jit or lax.map: test flux_peak_steps yourself.

Examples:

>>> stats = jax.lax.map(
...     lambda key: detection_statistics(
...         model, template.with_model(null_scene, key=key), grid
...     ),
...     jax.random.split(jax.random.PRNGKey(0), 100),
... )

local_nsigma(delta_chi2)

Local significance of delta_chi2, in Gaussian sigma.

The significance at one position fixed in advance: nsigma with one degree of freedom. It ignores the look-elsewhere effect, so for a search it overstates; see Detection statistics.

Source: Wilks (1938), Chernoff (1954), references.

Parameters:

Name Type Description Default
delta_chi2 float or array - like

Profile likelihood ratio from detection_statistics.

required

Returns:

Type Description
array - like

sqrt(delta_chi2) up to rounding (saturates near 13σ in float32).

gaussian_null(null_scene, template, *, error_scale=1.0)

A simulator of the null hypothesis with Gaussian noise.

Each draw is template.with_model(scene, key=key): the scene's prediction plus Gaussian noise from the template's errors. See Detection statistics.

Parameters:

Name Type Description Default
null_scene SourceModel

The scene without a companion.

required
template OIData

Sampling and errors to simulate; its values are not used.

required
error_scale float

True noise as a multiple of the template's errors, to test statistics under wrong errors. Static: a new value compiles anew.

1.0

Returns:

Type Description
callable

simulate(key, scene=None) -> OIData, observing scene (default null_scene) with fresh noise per key; an equinox Module, traceable in both.

Examples:

>>> simulate = gaussian_null(BinaryModelCartesian(0, 0, 0), template)
>>> data = simulate(jax.random.PRNGKey(0))

bootstrap_null(null_scene, data, *, method='sign_flip')

A simulator of the null hypothesis by residual bootstrap.

The residuals of data about null_scene are whitened, sign-flipped or resampled, re-coloured (keeping closure-phase correlations) and added to the simulated scene's prediction. The null must fit, and a companion in the data biases the null distribution upward; see Detection statistics.

Source: Efron (1979), references.

Parameters:

Name Type Description Default
null_scene SourceModel

The scene without a companion, fitted to data.

required
data OIData

The real data, concrete, without gains, closure-phase offsets or extras.

required
method (sign_flip, resample)

A wild bootstrap keeping each residual's magnitude, or resampling with replacement.

"sign_flip"

Returns:

Type Description
callable

simulate(key, scene=None) -> OIData, as for gaussian_null, with data's errors.

rescale_errors(null_scene, data)

Scale the errors so that the null scene has χ²_r = 1.

Visibility and phase errors are scaled separately, each by sqrt(χ² / n) of the whitened residuals of null_scene; see Detection statistics.

Parameters:

Name Type Description Default
null_scene SourceModel

The scene without a companion, fitted to data.

required
data OIData

Data to rescale; concrete.

required

Returns:

Name Type Description
data OIData

A copy with d_vis and d_phi scaled (the factors are already applied); data.extras unchanged.

factors dict[str, float]

{"vis": s_vis, "phi": s_phi} (s_phi is 1 without phases).

Raises:

Type Description
ValueError

If a block fits perfectly or not at all.

injection_grid(separations, fluxes, n_pa, key)

Companions to inject: every separation and flux at random PAs.

Parameters:

Name Type Description Default
separations array - like

Separations in mas (non-negative).

required
fluxes array - like

Fluxes relative to the primary (non-negative; 0 gives null draws).

required
n_pa int

Position angles per (separation, flux), uniform in [0°, 360°).

required
key Array or int

Random key or integer seed.

required

Returns:

Type Description
dict[str, ndarray]

dra, ddec (mas, East and North positive) and flux, each of length len(separations) * len(fluxes) * n_pa, ordered by separation, flux, then draw.

injection_recovery(model, null_scene, template, grid, key, *, n_null, injections=None, noise='gaussian', match_radius=None, flux_param=None, draw_batch=1, chunk_size=None, batch_size=None, progress=True)

Detection statistics of simulated null and injected observations.

Each draw simulates an observation (null, or with a companion injected) and runs detection_statistics on it in one compiled kernel; see Detection statistics.

Compare: Mawet et al. (2014), whose small-sample false-alarm correction is not applied here, references.

Parameters:

Name Type Description Default
model SourceModel or class

Companion model: injected at the injected position and flux, null draws at zero flux.

required
null_scene (SourceModel, OIData)

The scene without a companion (it must equal model at zero flux), and the sampling and errors to simulate (the data for "bootstrap").

required
template (SourceModel, OIData)

The scene without a companion (it must equal model at zero flux), and the sampling and errors to simulate (the data for "bootstrap").

required
grid

The search grid and its flux key, as for detection_statistics.

required
flux_param

The search grid and its flux key, as for detection_statistics.

required
key (Array or int, int)

Random key or integer seed, and the number of null draws (may be 0).

required
n_null (Array or int, int)

Random key or integer seed, and the number of null draws (may be 0).

required
injections dict[str, array - like]

Companions to inject, e.g. from injection_grid: one equal-length array per grid key, by full name or last part ("dra").

None
noise (gaussian, bootstrap)

gaussian_null, bootstrap_null, or a simulator built by either from template.

"gaussian"
match_radius float

In mas: an injection counts as detected only if its best position is within this of the injected one (needs position keys).

None
draw_batch int

Draws evaluated together in jax.lax.map; draws per kernel call (default min(64, draws)); grid points per search.

1
chunk_size int

Draws evaluated together in jax.lax.map; draws per kernel call (default min(64, draws)); grid points per search.

1
batch_size int

Draws evaluated together in jax.lax.map; draws per kernel call (default min(64, draws)); grid points per search.

1
progress bool

Show a tqdm bar.

True

Returns:

Type Description
DetectionMC

The statistics of every draw, the injections, and metadata.

Examples:

>>> mc = injection_recovery(
...     BinaryModelCartesian, BinaryModelCartesian(0, 0, 0), template,
...     grid, 0, n_null=1000,
... )