Skip to content

virgil.ensemble

Ensembles of randomized image reconstructions in the manner of PYRA and MYTHRA (Drevon et al. 2026): draw regularizer families, weights, pixel sizes, fields and starting images; fit each group as an L-curve; select the members that fit the data and average them into a mean image with a per-pixel spread. A group can instead use a Gaussian-process prior whose hyperparameters the evidence chooses (evidence_grid, gp_members); combine averages both kinds of member. See the tutorial Imaging, part 7.

Ensembles of randomized image reconstructions, averaged into one image.

A single regularized reconstruction depends on choices the data do not fix: the regularizer and its weight, the pixel size, the field and the starting image. Following Drevon et al. (2026, https://arxiv.org/abs/2609.15365), this module runs many reconstructions with these choices drawn at random, keeps those that fit the data, and averages them while the average still fits. The mean is less sensitive to any one choice than a single reconstruction, and the spread of the members maps how much the image depends on those choices. It is written from the paper's description, not from their code; see Imaging, part 7.

The work is split so that a cluster can run it in parallel:

  1. draw_groups draws the settings of each group: one geometry, regularizer family and starting image, and several weights.
  2. run_group fits one group as an l_curve; groups that share a geometry and a family share a compilation. A group of family "gp" is instead an evidence_grid over a Gaussian-process prior (gp_members).
  3. combine selects the members and averages them into an Ensemble.

ensemble runs all three. The ensemble's standard deviation is not a posterior uncertainty: it measures how much the image changes between reasonable reconstruction choices, not the noise in the data.

__provisional__ = ('evidence_grid', 'gp_members', 'GPFit', 'EvidenceGrid') module-attribute

FAMILIES = {'tv': TV, 'tsv': TSV, 'maxent': MaxEntropy, 'starlet': StarletL1} module-attribute

EnsembleSpec dataclass

What an ensemble draws at random, and how it selects members.

See Imaging, part 7.

Attributes:

Name Type Description
families tuple of str

Families drawn uniformly: "tv", "tsv", "maxent", "starlet" or "gp" (gp_members).

weight_ranges dict

Per family, the (low, high) weight per data point; n_weights (at least 3) log-uniform weights are drawn, one per bin of log w.

oversample tuple of float

Pixels per Nyquist pixel.

field_factors tuple of float

The field is field_mas times one of these, at most field_of_view(data) (and 1 on a uv lattice).

field_mas, aperture_mas float or None

Base field (default image_field) and a field-of-view ceiling.

starts tuple of str

Starts drawn uniformly: "moments", "dirty" (needs phases), "flat".

max_npix int

Most pixels on a side; larger fields get coarser pixels, then a crop.

window_dex float

A group keeps weights from corner / 10**window_dex up to the corner.

chi2_ratio, mad_cut float

Drop members whose raw χ² per point on any dataset exceeds chi2_ratio times the best's, then those more than mad_cut robust deviations above the median total. Both are relative to the members, so if all fit badly all pass: summary counts the drops per reason, and the pipeline's χ² checks judge the fit itself.

max_chi2_red float or None

Deprecated absolute cut; use chi2_ratio.

max_shift_mas float or None

Largest recentring shift without a star (default: beam major axis).

mean_rtol float or None

Tolerance on the mean's χ² excess over the best member's (default sqrt(2/N); 0 often keeps one member).

min_kept int

combine warns below this many members.

gp_sigmas, gp_lengths, gp_sigma_limits, gp_length_limits, gp_growths

GP evidence grid (evidence_grid): starting amplitudes and lengths (beam minor axes), limits, growth steps.

gp_window_nats float

GP members are fits within this log evidence of the best.

gp_axis_ratio float or None

Add anisotropic fields if a Gaussian fits below this axis ratio.

families = ('tv', 'tsv', 'maxent', 'starlet') class-attribute instance-attribute

weight_ranges = dataclasses.field(default_factory=lambda: {'tv': (0.0001, 10.0), 'tsv': (0.1, 10000.0), 'maxent': (0.0001, 10.0), 'starlet': (0.0001, 10.0)}) class-attribute instance-attribute

n_weights = 8 class-attribute instance-attribute

oversample = (2.0, 3.0, 4.0) class-attribute instance-attribute

field_factors = (0.75, 1.0, 1.5) class-attribute instance-attribute

starts = ('moments', 'flat') class-attribute instance-attribute

max_npix = 128 class-attribute instance-attribute

window_dex = 1.0 class-attribute instance-attribute

max_chi2_red = None class-attribute instance-attribute

chi2_ratio = 2.0 class-attribute instance-attribute

mad_cut = 5.0 class-attribute instance-attribute

max_shift_mas = None class-attribute instance-attribute

mean_rtol = None class-attribute instance-attribute

min_kept = 3 class-attribute instance-attribute

field_mas = None class-attribute instance-attribute

aperture_mas = None class-attribute instance-attribute

gp_sigmas = (1.0, 2.0, 4.0) class-attribute instance-attribute

gp_lengths = (0.5, 1.0, 2.0) class-attribute instance-attribute

gp_sigma_limits = (0.25, 16.0) class-attribute instance-attribute

gp_length_limits = (0.125, 8.0) class-attribute instance-attribute

gp_growths = 3 class-attribute instance-attribute

gp_window_nats = 2.0 class-attribute instance-attribute

gp_axis_ratio = 0.9 class-attribute instance-attribute

__init__(families=('tv', 'tsv', 'maxent', 'starlet'), weight_ranges=(lambda: {'tv': (0.0001, 10.0), 'tsv': (0.1, 10000.0), 'maxent': (0.0001, 10.0), 'starlet': (0.0001, 10.0)})(), n_weights=8, oversample=(2.0, 3.0, 4.0), field_factors=(0.75, 1.0, 1.5), starts=('moments', 'flat'), max_npix=128, window_dex=1.0, max_chi2_red=None, chi2_ratio=2.0, mad_cut=5.0, max_shift_mas=None, mean_rtol=None, min_kept=3, field_mas=None, aperture_mas=None, gp_sigmas=(1.0, 2.0, 4.0), gp_lengths=(0.5, 1.0, 2.0), gp_sigma_limits=(0.25, 16.0), gp_length_limits=(0.125, 8.0), gp_growths=3, gp_window_nats=2.0, gp_axis_ratio=0.9)

__post_init__()

Draw dataclass

The settings of one group of reconstructions.

Attributes:

Name Type Description
index int

Position of the group in the ensemble.

family str

The regularizer family, or "gp".

npix int

Pixels on a side.

pixel_scale_mas float

Pixel size in mas.

start str

The starting image.

weights tuple of float

The regularizer weights, largest first (empty for "gp").

index instance-attribute

family instance-attribute

npix instance-attribute

pixel_scale_mas instance-attribute

start instance-attribute

weights instance-attribute

geometry property

(family, npix, pixel_scale_mas): groups sharing it share a compilation.

__init__(index, family, npix, pixel_scale_mas, start, weights)

Group dataclass

The result of run_group.

Attributes:

Name Type Description
draw Draw

The group's settings.

curve LCurve or None

Its fits, one per weight, largest weight first; None for a Gaussian-process group.

gp tuple of GPFit

For a Gaussian-process group (gp_members), the fits it offers as members.

grid EvidenceGrid or None

For a Gaussian-process group, the evidence grid they come from.

draw instance-attribute

curve instance-attribute

gp = () class-attribute instance-attribute

grid = None class-attribute instance-attribute

__init__(draw, curve, gp=(), grid=None)

Member dataclass

One reconstruction in an ensemble.

Attributes:

Name Type Description
draw Draw

The settings of its group.

weight float

Its regularizer weight (NaN for a Gaussian-process member).

result FitResult

The fit.

chi2_red tuple of float

Raw χ² per data point of each dataset (with the quoted errors).

kept bool

Whether it is in the mean image.

gp GPFit or None

For a Gaussian-process member, its hyperparameters and evidence.

reason str or None

Why it was left out: "window" (outside its group's L-curve window), "diverged" (non-finite χ²), "chi2" (a dataset fitted too badly), "outlier" (total χ² far above the others) or "mean" (adding it made the mean fit a dataset worse). None if kept.

draw instance-attribute

weight instance-attribute

result instance-attribute

chi2_red instance-attribute

kept = False class-attribute instance-attribute

reason = None class-attribute instance-attribute

gp = None class-attribute instance-attribute

total_chi2_red property

χ² per data point over all datasets.

__init__(draw, weight, result, chi2_red, kept=False, reason=None, gp=None)

Ensemble dataclass

The result of combine.

Attributes:

Name Type Description
model System

The mean scene, exactly: the kept members' images on their own grids (member0, member1, ...), each carrying its share of the flux, and with a star a star component. chi2_red and trace are of this model.

mean Image

The mean extended emission on a common grid (the finest pixels and the largest field of the kept members), summing to one; with a star its flux is the mean flux relative to the star. For display and scoring: resampling smooths it, so it fits the data less well than model.

std (array, shape(npix, npix))

Standard deviation across the kept members of mean.brightness, pixel by pixel.

chi2_red tuple of float

Raw χ² per data point of the mean scene on each dataset.

trace list of tuple

chi2_red of the running mean after each accepted member; on every dataset it stays within spec.mean_rtol of the first entry.

members list of Member

Every reconstruction, kept or not.

groups list of Group

The groups, with their L-curves.

model instance-attribute

mean instance-attribute

std instance-attribute

chi2_red instance-attribute

trace instance-attribute

members instance-attribute

groups instance-attribute

kept property

The members in the mean.

__init__(model, mean, std, chi2_red, trace, members, groups)

summary()

A short table: the groups, what was kept, and the mean's fit.

GPFit dataclass

One Gaussian-process fit of an evidence grid.

Provisional

May change in any 0.4.x release without a deprecation cycle.

Attributes:

Name Type Description
variant str

"iso" or "aniso".

length_mas float

Correlation length in mas (for "aniso" the geometric mean of the two lengths).

sigma float

Standard deviation of the log-brightness.

log_evidence float

log_evidence of the fit.

result FitResult

The fit.

variant instance-attribute

length_mas instance-attribute

sigma instance-attribute

log_evidence instance-attribute

result instance-attribute

__init__(variant, length_mas, sigma, log_evidence, result)

EvidenceGrid dataclass

The result of evidence_grid.

Provisional

May change in any 0.4.x release without a deprecation cycle.

Attributes:

Name Type Description
lengths_mas, sigmas dict of tuple

For each variant ("iso", and "aniso" if the data are elongated), the grid's axes: correlation lengths in mas and amplitudes, ascending.

log_evidence dict of array

Per variant, the log evidence, with rows over lengths_mas and columns over sigmas.

fits dict

(variant, length_mas, sigma) to the GPFit.

best GPFit

The fit of largest evidence.

interior bool

Whether the best point is inside its grid's edges: on an edge the optimum may lie beyond it, because the growth stopped at a limit.

axis_ratio, pa_deg float or None

The elliptical-Gaussian fit's minor-to-major ratio and position angle (None if it was not made).

lengths_mas instance-attribute

sigmas instance-attribute

log_evidence instance-attribute

fits instance-attribute

best instance-attribute

interior instance-attribute

axis_ratio = None class-attribute instance-attribute

pa_deg = None class-attribute instance-attribute

__init__(lengths_mas, sigmas, log_evidence, fits, best, interior, axis_ratio=None, pa_deg=None)

draw_groups(data, n_groups, key, spec=None, star=True)

Draw the settings of n_groups groups of reconstructions.

Each group draws a regularizer family, pixel size, field, starting image and spec.n_weights log-uniform weights, as set by the EnsembleSpec. The draws depend only on key and spec, so every task of a cluster array can draw them all and run its own.

Parameters:

Name Type Description Default
data OIData or sequence of OIData

The data.

required
n_groups int

Number of groups.

required
key PRNGKey

The random key.

required
spec EnsembleSpec

What to draw (default EnsembleSpec()).

None
star bool

Whether the scene has an unresolved star (default), which the field's envelope fit then allows for (envelope_fwhm).

True

Returns:

Type Description
list of Draw

Sorted by geometry, so that groups sharing a compilation run one after the other.

reference_starts(data, star=True, starts=('moments', 'flat'), aperture_mas=None)

The starting images that each group resamples onto its own grid.

Each is made once by starting_image, which fits a star plus a Gaussian envelope: "moments" is that Gaussian, "dirty" the positive part of the dirty image, and "flat" a uniform image with the fitted flux.

aperture_mas : float, optional The aperture, as for EnsembleSpec.

Returns:

Type Description
dict

Start name to an Image.

run_group(data, draw, *, star=True, starts=None, spec=None, **fit_options)

Fit one group of an ensemble: an L-curve over its weights.

A group of family "gp" is instead an evidence grid (gp_members).

Parameters:

Name Type Description Default
data OIData or sequence of OIData

The data.

required
draw Draw

The group, from draw_groups.

required
star bool

Whether the scene is an unresolved star at the origin plus the image (default), or the image alone, centred by a Centroid prior one pixel wide.

True
starts dict

From reference_starts; computed here if not given (pass it to avoid refitting the envelope for every group).

None
spec EnsembleSpec

Used only by a "gp" group, for its grid.

None
**fit_options

Passed to l_curve and so to fit, e.g. method and max_steps.

{}

Returns:

Type Description
Group

evidence_grid(data, draw, *, star=True, starts=None, spec=None, **fit_options)

Fit Gaussian-process images over a grid of amplitude and length.

Provisional

May change in any 0.4.x release without a deprecation cycle.

Each point is a MAP fit of an image whose log-brightness is a GaussianField of amplitude σ and correlation length ℓ about the group's starting image, scored by log_evidence. The grid starts at spec.gp_sigmas by spec.gp_lengths and grows while its best point is on an edge; for elongated data a second grid is anisotropic. See Imaging, part 7. The evidence assumes correct error bars; check them with error_scale.

Parameters:

Name Type Description Default
data OIData or sequence of OIData

The data.

required
draw Draw

Geometry and starting image, as from draw_groups.

required
star bool

As for run_group.

True
starts dict None
spec EnsembleSpec

The grid's settings (default EnsembleSpec()).

None
**fit_options

Passed to fit.

{}

Returns:

Type Description
EvidenceGrid

gp_members(data, draw, *, star=True, starts=None, spec=None, **fit_options)

Fit one Gaussian-process group of an ensemble.

Provisional

May change in any 0.4.x release without a deprecation cycle.

The group's members are the fits of its evidence_grid whose log evidence is within spec.gp_window_nats of the best, so that combine averages over the amplitude and length the data leave open. There is no L-curve window.

Parameters:

Name Type Description Default
data

As for evidence_grid.

required
draw

As for evidence_grid.

required
star

As for evidence_grid.

required
starts

As for evidence_grid.

required
spec

As for evidence_grid.

required
**fit_options

As for evidence_grid.

required

Returns:

Type Description
Group

With curve=None, gp the members and grid the grid.

combine(data, groups, *, spec=None, star=True)

Select the members of an ensemble and average them.

The selection follows MYTHRA (Drevon et al. 2026): keep the weights just before each L-curve's corner (Gaussian-process members skip this), drop diverged, badly fitting and outlying members, resample the survivors to a common grid, and add each to a running mean, in order of χ², only if the mean still fits every dataset as well as the best member, up to spec.mean_rtol. See Imaging, part 7. With a star, the mean is of the whole normalized sky, star included.

Parameters:

Name Type Description Default
data OIData or sequence of OIData

The data the groups were fitted to. Give visibilities and closure phases as separate datasets to judge each separately.

required
groups sequence of Group

From run_group.

required
spec EnsembleSpec

The selection settings (default EnsembleSpec()).

None
star bool

As for run_group.

True

Returns:

Type Description
Ensemble

ensemble(data, n_groups, key, *, spec=None, star=True, **fit_options)

Run, select and average an ensemble of randomized reconstructions.

draw_groups, then run_group for each group in turn, then combine. Each group is one L-curve, so the ensemble has n_groups * spec.n_weights members, and compiles once per distinct (family, npix, pixel_scale_mas): a handful, since each is drawn from a short list.

Parameters:

Name Type Description Default
data OIData or sequence of OIData

The data. Give visibilities and closure phases as separate datasets for the mean to be judged on each.

required
n_groups int

Number of groups.

required
key PRNGKey

The random key.

required
spec EnsembleSpec

What to draw and how to select (default EnsembleSpec()).

None
star bool

Whether the scene has an unresolved star at the origin (default).

True
**fit_options

Passed to fit.

{}

Returns:

Type Description
Ensemble