Skip to content

virgil.angles

Angles sampled as 2-D vectors, so that no prior has a wrap boundary. Put an AngleVector in a priors dict under an angle's path (in degrees, such as a binary's "pa" or an orbit's node), and fit, gauss_newton_mass and numpyro_model sample a vector v = r(cos θ, sin θ) at the site "<path>_vec" instead of the angle. The model receives θ in degrees, in [0°, 360°): numpyro_model records it as the deterministic site "<path>", and fit reports both.

from virgil.angles import AngleVector

priors = {"pa": AngleVector()}            # uniform, no wall at 0°/360°
priors = {"pa": AngleVector(350.0, 20.0)} # von Mises, mean 350°, κ = 20
priors = {"node": AngleVector(100.0, 4.0, axial=True)}  # known modulo 180°

The radius has a ring prior, ∝ exp(−(r − 1)²/2s²), whose mode is the unit circle, so that maximum a posteriori fits stay away from the origin, where θ is undefined. A von Mises prior enters as the chord √κ (v̂ − m̂), the same form as virgil's unprojected phase residuals (2 sin(Δ/2)/σ, with κ = 1/σ²), so every term has a least-squares form and Levenberg–Marquardt takes it. The density is normalized in the plane (with the von Mises normalizer on the circle, through i0e), so evidences stay normalized. The default, a uniform angle, is the invariant prior; a von Mises prior is strong information from an external measurement.

For orbits, orientation_priors samples 2Ω and ϖ = Ω + ω for position-only fits (so the node ambiguity is one point, not two modes), or Ω and ϖ when RVs fix the node.

Credit. The construction follows Octofitter's UniformCircular (Thompson et al. 2023, AJ 166, 164) and exoplanet's Angle (Foreman-Mackey et al. 2021, JOSS 6, 3285), which sample v ~ N(0, I). The ring and the von Mises chords are virgil's. No code is taken from either.

Angles sampled as 2-D vectors, so that no prior has a wrap boundary.

AngleVector in a priors dict, under an angle's path, samples a vector v = r(cos θ, sin θ) at the site "<path>_vec" instead of the angle; the model receives θ in degrees in [0°, 360°), recorded (by numpyro_model) and reported (by fit) under "<path>". See angle priors without a wrap.

AngleVector

Bases: Distribution

A prior on an angle (degrees), sampled as a 2-D vector.

Put it in a priors dict, keyed by the angle's path, for fit, gauss_newton_mass or numpyro_model. The vector is sampled at "<path>_vec", giving θ = atan2(v₂, v₁) in [0°, 360°); a fit starts from the starting angle or init["<path>_vec"]. The prior on θ is uniform unless mean and kappa give a von Mises prior; see angle priors.

Parameters:

Name Type Description Default
mean float

Mean angle (degrees) and concentration of a von Mises prior (of 2θ when axial), of width about 57.3 / sqrt(kappa) degrees (half if axial). Give both, or neither. It is strong information from an external measurement; with axial, mean + 180 is equivalent.

None
kappa float

Mean angle (degrees) and concentration of a von Mises prior (of 2θ when axial), of width about 57.3 / sqrt(kappa) degrees (half if axial). Give both, or neither. It is strong information from an external measurement; with axial, mean + 180 is equivalent.

None
axial bool

Whether the von Mises prior is on 2θ (an axis known modulo 180°).

False
ring_width float

The ring's radial width (default 0.25); it does not change the prior on θ.

0.25

arg_constraints = {'ring_width': constraints.positive} class-attribute instance-attribute

pytree_data_fields = ('mean_deg', 'kappa', 'ring_width') class-attribute instance-attribute

pytree_aux_fields = ('axial',) class-attribute instance-attribute

support = constraints.real_vector class-attribute instance-attribute

mean_deg = None if mean is None else mean if isinstance(mean, jax.Array) else float(mean) instance-attribute

kappa = None if kappa is None else kappa if isinstance(kappa, jax.Array) else float(kappa) instance-attribute

ring_width = ring_width if isinstance(ring_width, jax.Array) else float(ring_width) instance-attribute

axial = bool(axial) instance-attribute

uniform property

Whether the prior on the angle is uniform.

__init__(mean=None, kappa=None, *, axial=False, ring_width=0.25, validate_args=None)

residuals(vector)

Residuals whose half sum of squares is -log p(vector) + const.

The ring, (r - 1)/s, then, for a von Mises prior, the chord √κ (v̂ - m̂) (two more rows).

log_prob(value)

sample(key, sample_shape=())

vector_angle(vector)

The direction of vector (last axis of length 2), in degrees in [0°, 360°), measured from its first component towards its second.

vector_site(path)

The numpyro site of the vector that carries the angle at path.

is_angle_vector(prior)

Whether prior is an AngleVector.