Skip to content

Composition: System and building blocks

See the Composing Models tutorial for usage.

virgil.models.System

Bases: SourceModel

Flux-weighted mixture of named source models.

A System is how you describe a scene with more than one part: a star with a disk, a binary inside a ring, a companion with its own circumstellar material. Each component is given a name, and the visibility is the flux-weighted mean \(V = \sum_i f_i V_i \,/\, \sum_i f_i\), where f_i is each component's flux. Because interferometric visibilities are normalized to 1 at zero baseline, only the ratios of the fluxes can be measured: keep one reference component (usually the star) at flux=1 and fit the others relative to it.

Components are reached by name, both as attributes (system.comp.flux) and as zodiax paths (system.get("comp.flux"), system.set("comp.flux", 0.02)). These paths are how the fitting tools in virgil.grid_fit and numpyro_model address parameters. Components keep the order in which they were given.

A System can itself be a component. Its own flux is then the total flux of the group relative to its siblings, and dra/ddec move the whole group together.

Parameters:

Name Type Description Default
components dict[str, SourceModel]

Components as a mapping from name to model. Usually it is clearer to pass them as keyword arguments instead.

None
flux float or array - like

Weight of the whole system when nested inside another System (default 1). It has no effect at the top level.

1.0
dra float or array - like

Offset of the whole system in milliarcseconds (positive to the East and North).

0.0
ddec float or array - like

Offset of the whole system in milliarcseconds (positive to the East and North).

0.0
**named SourceModel

Components as keyword arguments, e.g. star=PointSource(). Names must be valid Python identifiers that do not start with _ and do not clash with a System attribute (model, render, set, flux, ...).

{}
Notes

Fluxes are physical brightnesses, so they must be non-negative, and they must not all be zero. Both are checked when a model is built from concrete values; changing values afterwards with set is not checked. Inside a traced computation (a fit or grid search) the values cannot be checked, so positivity is the job of the priors and grid axes: numpyro_model rejects flux priors that allow negative values, and the grid tools reject negative flux axes.

Examples:

A star with a faint companion:

>>> binary = System(
...     star=PointSource(),
...     comp=PointSource(dra=45.0, ddec=30.0, flux=0.01),
... )
>>> binary.comp
PointSource(flux=0.01, dra=45, ddec=30)

A star with a rim, and a companion that has its own disk:

>>> scene = System(
...     star=PointSource(),
...     rim=ModulatedGaussianRim(diam=40.0, fwhm=4.0, inc=50.0, pa=30.0, flux=0.5),
...     comp=System(
...         core=PointSource(),
...         disk=GaussianDisk(sigma=4.0, flux=0.5),
...         dra=-30.0,
...         ddec=25.0,
...         flux=0.3,
...     ),
... )
>>> list(scene.components)
['star', 'rim', 'comp']
>>> moved = scene.set(["comp.dra", "comp.ddec"], [30.0, -25.0])

model(u, v, wavel)

virgil.models.Component

Bases: SourceModel

Base class for single shapes with flux, dra and ddec.

A component on its own is normalized to unit flux. Inside a System, flux is its weight relative to the other components, and dra/ddec place its centre (milliarcseconds, positive dra to the East, positive ddec to the North).

New shapes subclass Component and implement _centred_cvis (the unit-flux visibility of the shape at the origin) and _centred_image (an un-normalized image of the shape at the origin); offsets and mixing are handled here.

virgil.models.PointSource

Bases: Component

Unresolved point source.

Parameters:

Name Type Description Default
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1). Keep the reference star at flux=1 and a companion's flux is then its companion/star flux ratio.

1.0
dra float or array - like

Right-ascension offset in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset in milliarcseconds, positive to the North.

0.0

Examples:

>>> star = PointSource()
>>> companion = PointSource(flux=0.01, dra=45.0, ddec=30.0)

virgil.models.GaussianDisk

Bases: Component

Circular Gaussian brightness distribution.

Parameters:

Name Type Description Default
sigma float or array - like

Standard deviation of the Gaussian in milliarcseconds (FWHM = 2.3548 sigma, FWHM_PER_SIGMA).

required
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the centre in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the centre in milliarcseconds, positive to the North.

0.0

Examples:

>>> halo = GaussianDisk(sigma=8.0, flux=0.2)

virgil.models.EllipticalGaussian

Bases: Component

Elliptical Gaussian brightness distribution.

Parameters:

Name Type Description Default
fwhm float or array - like

Full width at half maximum along the major axis, in milliarcseconds.

required
ratio float or array - like

Minor-to-major axis ratio, in (0, 1]; 1 is a circular Gaussian.

1.0
pa float or array - like

Position angle of the major axis in degrees, North to East.

0.0
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0
ddec float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0

Examples:

>>> plume = EllipticalGaussian(fwhm=30.0, ratio=0.5, pa=20.0, flux=2.0)

virgil.models.GaussianArc

Bases: Component

A Gaussian ridge bent along a circular arc, e.g. a curved shock front.

The brightness is a curve convolved with a circular Gaussian of FWHM width. The curve is a circle of radius about the centre of curvature (dra, ddec), weighted along its length by a Gaussian of FWHM length (an arc length, in milliarcseconds) peaking at position angle pa as seen from the centre. For a large radius it tends to an elliptical Gaussian with FWHMs hypot(length, width) along the arc and width across it.

The weight is a Gaussian in the arc length s from pa, taken once round the circle, -π radius <= s <= π radius: it is cut off at the antipode of pa, where the two tails meet, and never wraps round the circle more than once. The quadrature covers ±6σ (the part beyond, a fraction 2e-9 of the flux, is dropped), or the whole circle once 6σ >= π radius, i.e. length >= 0.39 π radius. The cut at the antipode matters only for longer arcs: at length = π radius it removes 2 % of the Gaussian, and as length grows the brightness tends to a uniform ring.

The visibility is the Fourier transform of the curve, a line integral evaluated by the trapezoidal rule on nodes equally spaced points spanning ±min(6σ, π radius), times the Gaussian's. It is accurate while the spacing of the points, 2 min(6σ, π radius) / (nodes - 1), is below half the shortest fringe spacing, λ / 2 B_max.

Parameters:

Name Type Description Default
radius float or array - like

Radius of curvature in milliarcseconds.

required
width float or array - like

FWHM of the ridge across the arc, in milliarcseconds.

required
length float or array - like

FWHM of the brightness along the arc, as an arc length in milliarcseconds.

required
pa float or array - like

Position angle of the brightest point of the arc, seen from the centre of curvature, in degrees North to East.

0.0
flux (float, array - like or Spectrum)

Weight relative to the other components of a System (default 1).

1.0
dra float or array - like

Offset of the centre of curvature in milliarcseconds, positive to the East and North. The arc itself lies radius away from it.

0.0
ddec float or array - like

Offset of the centre of curvature in milliarcseconds, positive to the East and North. The arc itself lies radius away from it.

0.0
nodes int

Quadrature points along the arc, at least 2 (default 128).

128

Examples:

>>> shock = GaussianArc(radius=30.0, width=2.0, length=40.0, pa=270.0)

virgil.models.TruncatedCone

Bases: Component

A thin conical shell, truncated near its apex, e.g. a dust cone.

The cone's axis points towards position angle pa and is tilted tilt degrees out of the sky plane; its half-opening angle is alpha. Its apex lies tip milliarcseconds (along the axis, in 3-D) behind (dra, ddec), opposite to pa. The emission starts a slant distance s0 from the apex along the walls and falls off as exp(-(s - s0) / length), and the shell has a Gaussian thickness of FWHM width. It is optically thin, so the sign of the tilt does not change the image of an unmodulated cone.

The cone is a stack of rings about its axis. A ring of radius ρ in the plane perpendicular to an axis tilted β out of the sky projects to an ellipse, whose visibility is J0(2π ρ q) with q² = q_perp² + (ratio · q_par sin β)², where q_par and q_perp are the spatial frequencies along and across the projected axis, times the phase of the ring's centre, which lies (s cos α - tip) cos β along the projected axis. The rings are weighted by the area element (∝ ρ) and the emissivity, and integrated over s from s0 to s0 + 5 length (the last 0.7 % of the flux is dropped) by the midpoint rule on n_rings rings.

Azimuthal modulation. az_amps and az_pas brighten one side of the walls, e.g. the leading edge of a colliding-wind shock that the orbit sweeps round. Every ring's brightness is multiplied by 1 + Σ_m A_m cos(m (φ - φ_m)) in its own azimuth φ, with A_m in az_amps and φ_m in az_pas, the same modulation along the whole length of the cone. Like those of ModulatedGaussianRim, the azimuths are measured in the plane of the ring, in the same sense as position angle. φ = pa + 90 and pa - 90 are the two walls seen across the projected axis, at those position angles on the sky. φ = pa is the side of each ring that projects towards pa on the sky when tilt > 0 (towards pa + 180 when tilt < 0). The modulation stays analytic: by the Jacobi-Anger expansion a ring adds A_m (-i)^m J_m(2π ρ q) cos(m (θ - φ_m)) to J0, with θ the direction of the spatial frequency in the ring's plane. A modulated cone no longer looks the same at tilt and -tilt: the image at -tilt is the one at tilt with az_pas mirrored to 2 pa + 180 - az_pas. That leaves the walls (pa ± 90) unchanged. Bound to an orbit's frame by Attached (bind={"az_pas": ...}), a sky position angle is deprojected into the rings' azimuth, so the bright side points at it on the sky.

Choosing n_rings. The quadrature is second order: once the rings are fine enough to resolve the fringes, the error in the visibility falls as 1 / n_rings**2, so each doubling of n_rings cuts it by about 4. It is fine enough when both the spacing of the rings' centres on the sky, 5 length cos α cos β / n_rings, and the step between their radii, 5 length sin α max(1, ratio) / n_rings, are below half the shortest fringe spacing (a cone seen down its axis, tilt 90°, has all its centres together, and only the radius step matters). That criterion only says the error is small, not that it is below your noise. For a cone with length 13.8 mas and alpha 62.5°, over baselines out to 0.3 cycles/mas, max |V(n) - V(2n)| is about 6e-4 for n = 32, 1.5e-4 for 64, 4e-5 for 128 and 1e-5 for 256, so the error of n_rings = 32 itself is about 8e-4 in visibility amplitude. That is negligible for noisy data but not for well-measured data: a high-S/N GRAVITY dataset gained about 4 in log-likelihood per epoch going from 32 to 64 rings at fixed parameters, and nearly 29 over three epochs from 24 to 64.

To check, refit or evaluate at the best fit with n_rings doubled and compare χ² (or the log-likelihood): if |Δχ²| ≳ 1 per dataset (equivalently |Δ log L| ≳ 0.5), use more rings, and double again until it is below that. Well-measured data (e.g. GRAVITY) may need 64 or more. The cost is linear in n_rings.

Parameters:

Name Type Description Default
tip float or array - like

Distance from (dra, ddec) back to the apex along the axis, in milliarcseconds.

required
alpha float or array - like

Half-opening angle of the cone, in degrees (0 < alpha < 90).

required
s0 float or array - like

Slant distance from the apex where the emission starts, in milliarcseconds.

required
length float or array - like

e-folding length of the emission along the walls, in milliarcseconds.

required
width float or array - like

FWHM thickness of the shell, in milliarcseconds.

required
tilt float or array - like

Angle of the axis out of the sky plane, in degrees (-90 to 90).

0.0
pa float or array - like

Position angle the cone opens towards, in degrees North to East.

0.0
ratio float or array - like

Axis ratio of the cross-section (default 1, circular): its axis in the plane of the cone's axis and the line of sight is ratio times the one across.

1.0
flux (float, array - like or Spectrum)

Weight relative to the other components of a System (default 1).

1.0
dra float or array - like

Offset of the reference point in milliarcseconds, positive to the East and North.

0.0
ddec float or array - like

Offset of the reference point in milliarcseconds, positive to the East and North.

0.0
n_rings int

Quadrature rings along the walls, at least 2 (default 32). The visibility error falls as 1 / n_rings**2; check convergence by doubling it (see above).

32
az_amps float or array - like

Amplitudes of the cosine azimuthal modulations of the walls, from the first order up (see above). A scalar gives a single first-order modulation, and the default (empty) an unmodulated cone. The brightness must stay non-negative, which sum(abs(az_amps)) <= 1 guarantees. Concrete values are checked when the model is built.

()
az_pas float or array - like

Azimuths of the modulations in degrees, one per entry of az_amps, in the plane of the rings (see above): pa + 90 and pa - 90 are the walls at those position angles on the sky.

()

Examples:

>>> cone = TruncatedCone(tip=5.0, alpha=30.0, s0=4.0, length=10.0,
...                      width=1.0, tilt=20.0, pa=90.0)

A cone brighter on its southern wall (PA 180), half as bright on the northern one:

>>> lopsided = TruncatedCone(tip=5.0, alpha=30.0, s0=4.0, length=10.0,
...                          width=1.0, tilt=20.0, pa=90.0,
...                          az_amps=1 / 3, az_pas=180.0)

virgil.models.UniformDisk

Bases: Component

Uniformly bright (tophat) circular disk, e.g. a resolved stellar photosphere.

Parameters:

Name Type Description Default
diam float or array - like

Angular diameter in milliarcseconds.

required
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the centre in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the centre in milliarcseconds, positive to the North.

0.0

Examples:

>>> photosphere = UniformDisk(diam=3.0)

virgil.models.LimbDarkenedDisk

Bases: _LimbDarkenedDisk

Circular disk with polynomial limb darkening of order up to 22.

The brightness is \(I(\mu) / I(1) = 1 - \sum_{n=1}^{N} u_n (1 - \mu)^n\), with \(\mu = \sqrt{1 - (r / R)^2}\) the cosine of the angle between the line of sight and the surface normal. This is the convention of jaxoplanet, starry and harmonix, so u is the same as a jaxoplanet Surface's u: u=(u1,) is the linear law, u=(u1, u2) the quadratic law, and the default u=() a uniform disk. The order is at most 22, as the Bessel functions needed are of order at most 12. To fit the quadratic law with priors that cover exactly the physical profiles, use QuadraticLimbDarkenedDisk.

The visibility is analytic (Quirrenbach et al. 1996, eq. 3; see cvis_limb_darkened_disk). harmonix (Dholakia & Pope 2025) generalizes the same result to limb-darkened spherical-harmonic maps.

Parameters:

Name Type Description Default
diam float or array - like

Limb-darkened angular diameter (of the stellar limb) in milliarcseconds.

required
u sequence of float

Limb-darkening coefficients \(u_1, \ldots, u_N\) (default: none, a uniform disk). A 1D array; its length, the order of the law (at most 22), is fixed. is_physical is false where the profile goes negative.

()
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the centre in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the centre in milliarcseconds, positive to the North.

0.0

Examples:

>>> linear = LimbDarkenedDisk(3.0, u=[0.6])
>>> quadratic = LimbDarkenedDisk(3.0, u=[0.4, 0.25])

virgil.models.QuadraticLimbDarkenedDisk

Bases: _LimbDarkenedDisk

Circular disk with quadratic limb darkening in Kipping's (2013) \(q_1, q_2\).

The brightness is \(I(\mu) / I(1) = 1 - u_1 (1 - \mu) - u_2 (1 - \mu)^2\), with \(u_1 = 2 \sqrt{q_1}\, q_2\) and \(u_2 = \sqrt{q_1}\,(1 - 2 q_2)\) (Kipping 2013, eqs. 15-16). Every \((q_1, q_2)\) in the unit square gives a profile that is positive and decreases from the centre to the limb, and every such profile has one, so uniform priors on \([0, 1]\) for both are uninformative over exactly the physical laws. The visibility is analytic (Quirrenbach et al. 1996, eq. 3; see cvis_limb_darkened_disk).

Parameters:

Name Type Description Default
diam float or array - like

Limb-darkened angular diameter in milliarcseconds.

required
q1 float or array - like

Kipping's coefficients, each in \([0, 1]\); \(q_1 = 0\) is a uniform disk. Start a fit inside the square, not on its edges: fit maps Uniform priors onto unbounded variables, and the edges map to infinity (and \(\sqrt{q_1}\) has an infinite derivative at 0).

required
q2 float or array - like

Kipping's coefficients, each in \([0, 1]\); \(q_1 = 0\) is a uniform disk. Start a fit inside the square, not on its edges: fit maps Uniform priors onto unbounded variables, and the edges map to infinity (and \(\sqrt{q_1}\) has an infinite derivative at 0).

required
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the centre in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the centre in milliarcseconds, positive to the North.

0.0

Examples:

>>> import numpyro.distributions as dist
>>> star = QuadraticLimbDarkenedDisk(3.0, q1=0.4, q2=0.3)
>>> priors = {
...     "diam": dist.LogUniform(2.0, 4.0),  # a scale: log-uniform
...     "q1": dist.Uniform(0.0, 1.0),  # Kipping's uniform prior
...     "q2": dist.Uniform(0.0, 1.0),
... }

From tabulated \(u_1, u_2\) (e.g. Claret's tables):

>>> star = QuadraticLimbDarkenedDisk.from_u(3.0, u1=0.4, u2=0.25)

from_u(diam, u1, u2, **kwargs) classmethod

Build from the usual \(u_1, u_2\) (Kipping 2013, eqs. 17-18).

virgil.models.SquareRootLimbDarkenedDisk

Bases: _LimbDarkenedDisk

Circular disk with square-root limb darkening in Kipping's (2013) \(q_1, q_2\).

The brightness is \(I(\mu) / I(1) = 1 - c (1 - \mu) - d (1 - \sqrt{\mu})\) (Díaz-Cordovés & Giménez 1992), which suits late-type stars in the near-infrared better than the quadratic law (van Hamme 1993), with \(c = \sqrt{q_1}\,(1 - 2 q_2)\) and \(d = 2 \sqrt{q_1}\, q_2\), inverting Kipping (2013), eqs. 23-24. As for QuadraticLimbDarkenedDisk, the unit square in \((q_1, q_2)\) is exactly the set of positive profiles that decrease towards the limb, so uniform priors on \([0, 1]\) are uninformative over the physical laws. The \(\sqrt{\mu}\) term needs a Bessel function of order \(5/4\) in the analytic visibility (Quirrenbach et al. 1996, eq. 3; see cvis_limb_darkened_disk).

Parameters:

Name Type Description Default
diam float or array - like

Limb-darkened angular diameter in milliarcseconds.

required
q1 float or array - like

Kipping's coefficients, each in \([0, 1]\); \(q_1 = 0\) is a uniform disk. Start a fit inside the square, not on its edges: fit maps Uniform priors onto unbounded variables, and the edges map to infinity (and \(\sqrt{q_1}\) has an infinite derivative at 0).

required
q2 float or array - like

Kipping's coefficients, each in \([0, 1]\); \(q_1 = 0\) is a uniform disk. Start a fit inside the square, not on its edges: fit maps Uniform priors onto unbounded variables, and the edges map to infinity (and \(\sqrt{q_1}\) has an infinite derivative at 0).

required
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the centre in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the centre in milliarcseconds, positive to the North.

0.0

Examples:

>>> star = SquareRootLimbDarkenedDisk(3.0, q1=0.5, q2=0.4)
>>> same = SquareRootLimbDarkenedDisk.from_cd(3.0, c=star.c, d=star.d)

from_cd(diam, c, d, **kwargs) classmethod

Build from the usual \(c, d\) (Kipping 2013, eqs. 23-24).

virgil.models.EllipticalLimbDarkenedDisk

Bases: _LimbDarkenedDisk

Limb-darkened disk with an elliptical outline, e.g. an oblate star.

A LimbDarkenedDisk compressed along its minor axis: the brightness is \(I(\mu) / I(1) = 1 - \sum_{n=1}^{N} u_n (1 - \mu)^n\), with \(\mu = \sqrt{1 - \rho^2}\) and \(\rho\) the elliptical radius (1 on the limb), so the profile follows the outline. The major axis is oriented exactly as an EllipticalGaussian's. Like every component it has unit flux on its own: compressing the disk does not change its total flux, so the visibility at zero baseline is 1 and flux is its weight in a System.

The visibility is that of the circular disk at the spatial frequencies stretched by ratio along the minor axis (the Fourier similarity theorem), so it is analytic like cvis_limb_darkened_disk. This is a geometric ellipse with a radial profile, not a model of a rapid rotator's gravity darkening; for that use GravityDarkenedStar.

Parameters:

Name Type Description Default
diam float or array - like

Angular diameter of the limb along the major axis, in milliarcseconds.

required
ratio float or array - like

Minor-to-major axis ratio, in (0, 1] (default 1, a circular LimbDarkenedDisk).

1.0
pa float or array - like

Position angle of the major axis in degrees, North to East (default 0).

0.0
u sequence of float

Limb-darkening coefficients \(u_1, \ldots, u_N\) as in LimbDarkenedDisk (default: none, a uniform elliptical disk). is_physical is false where the profile goes negative.

()
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0
ddec float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0

Examples:

>>> star = EllipticalLimbDarkenedDisk(5.5, ratio=0.8, pa=30.0, u=[0.5])

virgil.models.GravityDarkenedStar

Bases: Component

Rapidly rotating star: Roche shape and gravity darkening (ELR11).

The model of Espinosa Lara & Rieutord (2011, A&A 533, A43): a rigidly rotating star has the oblate Roche shape, and its local bolometric flux follows the effective gravity without a free gravity-darkening exponent \(\beta\). The brightness is proportional to that local flux (no limb darkening yet), summed over the visible triangles of a surface mesh. Ported from Shashank Dholakia's jax-interferometry (ELR_Model, commit 70689ed); the physics lives in virgil._elr.

Parameters:

Name Type Description Default
diam_eq float or array - like

Equatorial angular diameter in milliarcseconds.

required
omega float or array - like

Angular velocity as a fraction of the Keplerian (critical) rate at the equator, \(\Omega/\Omega_K\), in [0, 1) (default 0, a sphere).

0.0
inc float or array - like

Inclination in degrees, from 0 (pole-on) to 90 (equator-on, the default). The star is symmetric about its equator, so inc and 180 - inc give the same image with the pole flipped; the range [0, 90] keeps pa unambiguous.

90.0
pa float or array - like

Position angle in degrees, North through East, of the visible rotation pole on the sky (default 0).

0.0
flux (float, array - like or Spectrum)

Weight relative to the other components of a System (default 1).

1.0
dra float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0
ddec float or array - like

Offset of the centre in milliarcseconds, positive to the East and North.

0.0
n_lat int

Number of latitude rings of the surface mesh (default 32, giving 2520 triangles). Visibilities cost O(n_lat\(^2\)) per baseline.

32
t_pole float or array - like

Effective temperature of the pole in kelvin. The default None is the grey model; a value switches on the chromatic model (see Notes).

None
wavel0 float or array - like

Reference wavelength in metres (default 1.65e-6, H band), at which the star's spectrum is normalized to flux and which render shows. Only used when t_pole is set.

1.65e-06
Notes

Grey mode (t_pole=None, Dholakia's model): each triangle is weighted by its bolometric flux times its projected area, the same at every wavelength, so wavelength enters only through u / wavel.

Chromatic mode (t_pole set): each triangle has temperature \(T = T_\mathrm{pole}\,T_\mathrm{eff}/T_\mathrm{eff,pole}\) from the ELR11 gravity darkening, radiates the Planck function \(B_\lambda(T)\), and is weighted by its projected area times that, at each sample's own wavelength. The hot pole and cool equator then have a contrast that rises towards short wavelengths. Limb darkening and bandwidth smearing are not modelled. The weights have shape (n_samples, n_triangles), so memory is about 200 MB of complex64 for 10^4 samples at the default n_lat.

In chromatic mode the star supplies its own spectrum to a System: its weight is flux times the summed Planck flux of its visible surface, relative to that at wavel0. A companion then gets a physically consistent flux ratio at every wavelength with no separate stellar spectrum, and flux must be a number, not a Spectrum, which would count the spectrum twice:

star = GravityDarkenedStar(
    1.0, omega=0.9, inc=45.0, t_pole=9000.0
)
companion = PointSource(flux=BlackBody(0.01, 3000.0))
system = System(star=star, companion=companion)

Dholakia's ELR_Model parameters map onto these as follows (his angles are in radians):

his here
diam diam_eq (his r_eq is diam_eq / 2)
omega omega
inc (0 = equator-on) 90 - inc degrees
obl pa degrees

Choosing n_lat. The mesh is a second-order quadrature: the error in the visibility falls as 1 / n_lat**2, so each doubling of n_lat cuts it by about 4. An independent ELR11 reference (virgil-validation) measured it. In grey mode the error is 1.4-3.4e-4 in visibility at n_lat = 128, falling 4x per doubling. At the default n_lat = 32 it is 2-6e-3 for fast rotators. In chromatic mode (t_pole 9000 K) it is 1.3-1.4e-4 at 0.7, 1.65 and 2.2 µm, also at n_lat = 128. The default suits most data, but well-measured data (e.g. GRAVITY) can be sensitive to errors of this size.

To check, evaluate or refit at the best fit with n_lat doubled and compare χ²: if |Δχ²| ≳ 1 per dataset, use more latitude rings, and double again until the change is below that. The cost is O(n_lat\(^2\)) per baseline.

Examples:

>>> star = GravityDarkenedStar(2.0, omega=0.8, inc=60.0, pa=30.0)
>>> v0 = star.model(np.zeros(1), np.zeros(1), 1.65e-6)
>>> round(float(np.abs(v0)[0]), 3)
1.0

The chromatic model's pole-to-equator contrast depends on wavelength:

>>> hot = GravityDarkenedStar(2.0, omega=0.9, inc=45.0, t_pole=9000.0)
>>> w = hot._weight(np.array([1.0e-6, 2.2e-6]))
>>> bool(w[0] > 1.0 > w[1])
True

plot_surface(ax=None, cmap='plasma')

Plot the visible surface, coloured by its local brightness.

That is the bolometric flux in grey mode, and the Planck intensity at wavel0 in chromatic mode. East is to the left and North up, as in plot_model; the offset dra, ddec is not applied. Returns the matplotlib collection.

virgil.models.ModulatedGaussianRim

Bases: Component

Azimuthally modulated, infinitely thin rim convolved with a 2D Gaussian that is isotropic in the plane of the rim, optionally inclined and rotated.

Parameters:

Name Type Description Default
diam float or array - like

Diameter of the rim in milliarcseconds.

required
fwhm float or array - like

Gaussian FWHM of the rim in milliarcseconds, measured in the plane of the rim. On the sky this is the FWHM along the projected major axis; along the minor axis it is fwhm * cos(inc).

required
inc float or array - like

Apparent inclination of the rim in degrees.

required
pa float or array - like

Position angle of the rim's projected major axis in degrees, measured North to East (i.e. counter-clockwise in conventional astronomical image orientation).

required
az_amps float or array - like

Amplitudes of the cosine azimuthal modulations. The first element is the amplitude for the first-order modulation, the second for the second-order modulation, etc. A scalar gives a single first-order modulation, and the default (empty) gives an unmodulated, azimuthally symmetric rim. The brightness must stay non-negative, which sum(abs(az_amps)) <= 1 guarantees; with several orders larger amplitudes can also be valid. Concrete values are checked when the model is built.

()
az_pas float or array - like

Azimuths of the cosine azimuthal modulations in degrees, one per entry of az_amps, measured in the plane of the rim in the same sense as position angle, with pa on the major axis (see Notes). For an inclined rim these are not on-sky position angles.

()
flux (float, array - like or Spectrum)

Weight relative to the other components of a System, or a spectrum from virgil.spectra (default 1).

1.0
dra float or array - like

Right-ascension offset of the rim's center in milliarcseconds, positive to the East.

0.0
ddec float or array - like

Declination offset of the rim's center in milliarcseconds, positive to the North.

0.0
Notes

The rim is defined in its own plane and then inclined. In polar coordinates \((r, \phi)\) in the plane of the rim, the thin ring is \(\delta(r - \mathrm{diam}/2) \left( 1 + \sum_{m=1}^{n} A_m \cos{(m(\phi - \mathrm{pa}_m))} \right)\), convolved with an isotropic Gaussian of FWHM fwhm in that same plane. The in-plane azimuth \(\phi\) is counted in the same sense as position angle, with \(\phi = \mathrm{pa}\) along the major axis. This face-on image is then compressed by \(\cos(\mathrm{inc})\) along the minor axis, so on the sky the blur is an elliptical Gaussian with FWHM fwhm along the major axis and fwhm * cos(inc) along the minor axis. An unmodulated rim therefore has the same peak brightness all the way round, without bright ansae at the ends of the major axis.

So az_pas are in-plane (deprojected) angles, not on-sky position angles. A point at in-plane azimuth \(\phi\) appears at the on-sky position angle \(\theta\) with \(\tan(\theta - \mathrm{pa}) = \cos(\mathrm{inc}) \tan(\phi - \mathrm{pa})\), in the same quadrant. The two agree for a face-on rim (inc = 0) and along the major and minor axes. Otherwise a peak at az_pas appears on the sky at a position angle closer to the major axis.

This model is achromatic: it does not represent any spectral dependence. The rim contains no star; put it in a System with a PointSource for that.

Examples:

>>> rim = ModulatedGaussianRim(
...     diam=40.0, fwhm=4.0, inc=50.0, pa=30.0, az_amps=0.7, az_pas=120.0
... )

virgil.models.FlaredDisk

Bases: Component

Flared, inclined scattered-light disk (Blakely et al. 2024, §III).

The geometrical disk model that Blakely et al. (2024, arXiv:2404.13032) fitted to JWST AMI data of PDS 70: a skewed Gaussian ring on a flared surface, with a forward-scattering peak on its near side. It is an abstract base: use FlaredDiskHG, FlaredDiskGaussian or FlaredDiskPowerLaw, which differ only in the azimuthal phase function. The disk contains no star or planets; compose them in a System, and any over-resolved flux with a Resolved component (the paper's \(I_o\)).

The brightness has no analytic Fourier transform, so the visibilities are the exact Fourier transform of the brightness sampled on a fixed, centred grid of npix x npix pixels of pixel_scale_mas. The grid must cover the whole disk, and its pixels must be small enough to resolve the ring and its sharp inner edge.

Parameters:

Name Type Description Default
radius float or array - like

Radius of peak brightness \(r_0\) in milliarcseconds (before the skew, which moves the peak outwards).

required
fwhm float or array - like

Radial FWHM of the Gaussian ring, \(2\sqrt{2\ln 2}\,\sigma_r\), in milliarcseconds.

required
inc float or array - like

Inclination in degrees, from 0 (face-on) up to but not including 90 (edge-on, where the surface cannot be deprojected).

required
pa float or array - like

Position angle of the projected major axis in degrees, North to East. The near side, where forward scattering peaks, is at pa + 90: pa and pa + 180 are mirror images.

required
npix int

Pixels on a side of the grid the visibilities are computed from; must be even, so that no pixel centre falls on the star, where the surface is singular.

required
pixel_scale_mas float

Pixel size of that grid in milliarcseconds.

required
skew float or array - like

Truncation \(\alpha\) of the ring's inner edge (default 0, a symmetric Gaussian ring).

0.0
aspect float or array - like

Aspect ratio \(z/\rho\) of the scattering surface at radius (default 0, a flat disk).

0.0
flaring float or array - like

Flaring index \(\beta\) of the surface (default 1.25).

1.25
symmetric float or array - like

Brightness of the axisymmetric part relative to the phase function, \(A_s/A_a\) in the paper (default 0).

0.0
flux (float, array - like or Spectrum)

Weight relative to the other components of a System (default 1): the disk/star flux ratio when the star has flux=1.

1.0
dra float or array - like

Offset of the disk centre in milliarcseconds, positive to the East and North.

0.0
ddec float or array - like

Offset of the disk centre in milliarcseconds, positive to the East and North.

0.0
Notes

The brightness follows Eqs. 2–9 of the paper. Sky offsets are rotated so that \(x\) runs along the major axis and \(y\) along the minor axis, positive towards the near side, and \(y\) is divided by \(\cos i\) to give mid-plane coordinates. The surface height \(z = h\,r_0\,(\rho/r_0)^\beta\), with \(\rho = \sqrt{x^2 + y^2}\) and \(h\) = aspect, raises the apparent radius to \(r = \sqrt{x^2 + (y + z\sin i)^2 + z^2}\), which shifts the ring towards the far side. The brightness is

\[I(r, \theta) = \left(f(\theta) + A_s/A_a\right) \exp\left(-\frac{(r - r_0)^2}{2\sigma_r^2}\right) \frac{1}{2}\left(1 + \mathrm{erf}\left( \frac{\alpha (r - r_0)}{\sqrt{2}\sigma_r}\right)\right),\]

where \(\theta = \arctan(x / y)\) is the mid-plane azimuth from the near-side minor axis and \(f\) the phase function. The paper gives the height as \(H_{100}\) (au) at 100 au, which is aspect \(= (H_{100} / 100\,\mathrm{au})(r_0 / 100\,\mathrm{au})^{\beta - 1}\) with \(r_0\) in au; its fitted fluxes \(A_a, A_s\) are absolute, and here only their ratio and the disk's total flux enter.

virgil.models.FlaredDiskHG

Bases: FlaredDisk

FlaredDisk with a Henyey–Greenstein phase function.

\(f(\theta) = \dfrac{1 - g^2}{4\pi\,(1 + g^2 - 2g\cos\theta)^{3/2}}\) (Blakely et al. 2024, Eq. 6).

Parameters:

Name Type Description Default
g float or array - like

Asymmetry parameter, with -1 < g < 1: 0 is isotropic, positive values scatter forwards (peaking on the near side) and negative values backwards (peaking on the far side).

required
**geometry

The parameters of FlaredDisk.

{}

Examples:

>>> disk = FlaredDiskHG(
...     g=0.3, radius=440.0, fwhm=290.0, inc=52.0, pa=160.0,
...     npix=96, pixel_scale_mas=20.0, flux=0.05,
... )

virgil.models.FlaredDiskGaussian

Bases: FlaredDisk

FlaredDisk with a Gaussian phase function.

\(f(\theta) = \exp\left(-\theta^2 / 2\sigma_\theta^2\right)\), with \(\theta \in (-180°, 180°]\) (Blakely et al. 2024, Eq. 7).

Parameters:

Name Type Description Default
sigma_theta float or array - like

Azimuthal width in degrees.

required
**geometry

The parameters of FlaredDisk.

{}

virgil.models.FlaredDiskPowerLaw

Bases: FlaredDisk

FlaredDisk with a power-law phase function.

\(f(\theta) = \cos^N(\theta / 2)\), with \(\theta \in (-180°, 180°]\) (Blakely et al. 2024, Eq. 8), the best-fitting form for PDS 70.

Parameters:

Name Type Description Default
n float or array - like

Power \(N\); larger is more concentrated towards the near side.

required
**geometry

The parameters of FlaredDisk.

{}

virgil.models.Resolved

Bases: SourceModel

Fully resolved (over-resolved) flux, e.g. a large, diffuse envelope.

Its visibility is 0 on every non-zero baseline, so inside a System it only adds to the normalization, lowering every other component's visibility by the same factor.

Parameters:

Name Type Description Default
flux (float, array - like or Spectrum)

Weight relative to the other components of a System (default 1), or a spectrum from virgil.spectra.

1.0
Notes

A resolved component spreads its light far beyond any image, so it cannot be rendered on its own, and a rendered System shows only its other components, renormalized to unit sum. The Fourier transform of such an image is therefore the visibility of the unresolved components alone, i.e. model() divided by the unresolved fraction of the flux, not model() itself.

Examples:

>>> scene = System(star=PointSource(), background=Resolved(flux=0.1))

virgil.models.Rotated

Bases: SourceModel

A model rotated on the sky about the phase centre.

The rotation is by rotation_deg from North towards East, and the angle is an ordinary (fittable) parameter, unlike Image's rotation_deg, which only orients its pixel grid. Use it for a scene seen at several epochs, e.g. a spiral rotating between them (see fit with a model per dataset).

Parameters:

Name Type Description Default
source SourceModel

The model to rotate.

required
rotation_deg float

Position angle of the rotation, North towards East, in degrees.

required

Examples:

A companion to the North, rotated by 90°, lands to the East:

>>> import jax.numpy as np
>>> from virgil.models import GaussianDisk, Rotated, System
>>> north = System(a=GaussianDisk(1.0), b=GaussianDisk(1.0, ddec=10.0))
>>> east = System(a=GaussianDisk(1.0), b=GaussianDisk(1.0, dra=10.0))
>>> u, v = np.array([3.0, 5.0]), np.array([1.0, -2.0])
>>> rotated = Rotated(north, 90.0).model(u, v, 1e-6)
>>> bool(np.allclose(rotated, east.model(u, v, 1e-6), atol=1e-6))
True

source = source instance-attribute

rotation_deg = np.asarray(rotation_deg, dtype=float) instance-attribute

time_dependent property

__init__(source, rotation_deg)

model(u, v, wavel)

total_spectrum(wavel)

is_physical()

at(mjd, t_ref=0.0)

virgil.models.Attached

Bases: SourceModel

A component placed and oriented in the frame of a binary's orbit.

At each time the component is moved to its anchor on the orbit, and its angle attributes are set from angles of the binary frame (see KeplerOrbit.frame). A companion on its orbit is Attached(PointSource(flux), orbit); a disc around it in the orbital plane, brighter on the side facing the primary, is

Attached(ModulatedGaussianRim(...), orbit, bind={"pa": "node_pa", "inc": "apparent_inc", "az_pas": "towards_primary"}).

The model changes with time, so it is evaluated through at; inside a System, OIData.model evaluates every sample at its own time. Components that should share one orbit (a companion and its disc) are best built from shared parameters in a model function (see fit).

Parameters:

Name Type Description Default
component Component

The component (its dra, ddec and bound angles are set).

required
orbit KeplerOrbit

The orbit of the secondary about the primary (the scene's origin).

required
anchor (secondary, primary)

Where the component sits: on the secondary (default), on the primary, or at this fraction of the way from the primary to the secondary (e.g. q / (1 + q) for the barycentre).

"secondary"
bind dict

{attribute: frame angle}, e.g. {"pa": "line_pa"}. Frame angles: line_pa, towards_primary, line_tilt, node_pa, inc and apparent_inc. An az_pas binding is a sky position angle, converted to the component's deprojected rim angle after its pa and inc are set (as ModulatedGaussianRim measures it).

None
offsets dict

{attribute: degrees} added to the bound angles (fittable, e.g. a skew); zero by default.

None

at(mjd, t_ref=0.0)

virgil.models.OrbitalBinary

Bases: SourceModel

A primary and a point-source companion moving on a Keplerian orbit.

The time-dependent form of BinaryModelCartesian: at each time, at returns the BinaryModelCartesian with the companion at the orbit's sky position, so each snapshot keeps the fast binary path. It is the same scene as System(primary=PointSource(), companion=Attached( PointSource(flux), orbit)), for the common case of two unresolved stars. The primary is the scene's origin and reference (flux is the companion's flux relative to it).

Evaluate it with one snapshot per dataset or epoch through Epochs, or per sample by OIData.model.

Parameters:

Name Type Description Default
orbit KeplerOrbit

The companion's orbit about the primary (any orbit class with t_ref and _relative).

required
flux float

Companion/primary flux ratio.

required

Examples:

>>> from virgil.models import OrbitalBinary
>>> from virgil.orbits import KeplerOrbit
>>> orbit = KeplerOrbit(700.0, 0.0, 0.3, 50.0, 60.0, 120.0, 20.0,
...                     t_ref=60500.0)
>>> snap = OrbitalBinary(orbit, 0.1).at(60500.0)
>>> type(snap).__name__, round(float(snap.flux), 2)
('BinaryModelCartesian', 0.1)

at(mjd, t_ref=0.0)

Models are fitted through virgil.likelihood (build_model, loglike, numpyro_model).