Source code for brutus.priors.extinction

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Extinction priors for Bayesian stellar parameter estimation.

This module provides log-prior functions for dust extinction modeling
using 3D dust maps. These priors incorporate spatial dust distribution
information to constrain extinction in stellar fitting.

Functions
---------
logp_extinction : Dust map extinction prior
    Gaussian prior from 3D dust maps (e.g., Bayestar)

See Also
--------
brutus.dust.maps : 3D dust map utilities
brutus.priors.galactic : Galactic structure priors
brutus.analysis.individual.BruteForce : Uses extinction priors for fitting

Notes
-----
The extinction prior uses 3D dust maps (e.g., Bayestar from Green et al.
2015, 2018) which provide distance-dependent extinction estimates across
the sky.

The prior is Gaussian when dust map data is available, and uniform when
coverage is unavailable. This gracefully handles regions outside the
mapped volume.

Examples
--------
>>> from brutus.priors.extinction import logp_extinction
>>> from brutus.dust import Bayestar
>>> import numpy as np
>>>
>>> # Load 3D dust map and evaluate extinction prior at a distance
>>> # dustmap = Bayestar('bayestar2019_v1.h5')
>>> # logp = logp_extinction([0.1, 0.5], dustmap, [180.0, 30.0], distance=1.0)
"""

import numpy as np

__all__ = ["logp_extinction"]


[docs] def logp_extinction(avs, dustmap, coord, distance=None, return_components=False): r""" Log-prior for dust extinction using 3D dust maps. Implements Gaussian extinction priors based on dust maps with systematic uncertainty treatment. Supports both 3D dust maps (e.g., Bayestar) that return distance-resolved profiles and simpler maps that return a single mean and standard deviation. Parameters ---------- avs : array_like Extinction values (A_V) in magnitudes to evaluate prior for. dustmap : object Dust map object with a ``query(coord)`` method. For 3D dust maps (e.g., ``Bayestar``), ``query`` returns ``(distances, av_mean, av_std)`` with distance-resolved profiles. For simpler maps, ``query`` returns ``(av_mean, av_std)`` scalars. coord : SkyCoord or array_like Sky coordinates for dust map query. Accepts ``astropy.coordinates.SkyCoord`` or ``[l, b]`` in degrees. distance : float or array_like, optional Distance(s) in kpc at which to evaluate the extinction prior. Required for 3D dust maps to interpolate the extinction profile. If ``avs`` and ``distance`` are both arrays, they must have the same shape and the prior is evaluated element-wise. return_components : bool, optional If True, returns tuple ``(logp, (av_mean, av_err))`` including the dust map statistics used. Default is False. Returns ------- logp : ndarray Log-prior probability density for the input extinction values. Returns 0 (uniform prior) when no dust map coverage is available. components : tuple, optional If ``return_components=True``, returns ``(av_mean, av_err)`` containing the dust map mean and standard deviation used. Notes ----- The log-prior follows a Gaussian distribution when dust map data is available: .. math:: \\log p(A_V | A_{V,\\text{map}}, \\sigma_{A_V}) = -\\frac{1}{2} \\left[ \\frac{(A_V - A_{V,\\text{map}})^2}{\\sigma_{A_V}^2} + \\log(2\\pi\\sigma_{A_V}^2) \\right] For 3D dust maps, the expected extinction and uncertainty at the requested distance are obtained by linear interpolation of the map's distance-resolved profiles. Distances outside the map range use the boundary values. For regions without dust map coverage (NaN values), a uniform (uninformative) prior is returned. Examples -------- >>> from astropy.coordinates import SkyCoord >>> coord = SkyCoord(l=90., b=0., unit='deg', frame='galactic') >>> # With a 3D dust map: >>> # logp = logp_extinction([0.1, 0.5], dustmap, coord, distance=1.0) >>> # logp, (mean, err) = logp_extinction([0.1], dustmap, coord, >>> # distance=1.0, >>> # return_components=True) """ avs = np.asarray(avs, dtype=float) # Query the dust map try: result = dustmap.query(coord) except (AttributeError, TypeError): # dustmap doesn't have a query method (e.g., was passed as string) lnprior = np.zeros_like(avs, dtype=float) if return_components: return lnprior, (np.nan, np.nan) return lnprior # Handle 3D dust maps returning (distances, av_profile, std_profile) if isinstance(result, tuple) and len(result) == 3: map_distances, av_profile, std_profile = result # Squeeze single-coordinate queries that return (1, n_dist) arrays av_profile = np.squeeze(av_profile) std_profile = np.squeeze(std_profile) if distance is None: # Cannot evaluate distance-dependent prior without distance lnprior = np.zeros_like(avs, dtype=float) av_mean = np.nan av_err = np.nan else: distance = np.asarray(distance, dtype=float) # Interpolate dust map profiles to requested distance(s) av_mean = np.interp(distance, map_distances, av_profile) av_err = np.interp(distance, map_distances, std_profile) # Compute Gaussian prior where valid valid = np.isfinite(av_mean) & np.isfinite(av_err) & (av_err > 0) lnprior = np.zeros_like(avs, dtype=float) if np.any(valid): # Use safe denominator to avoid division by zero av_err_safe = np.where(valid, av_err, 1.0) chi2 = (avs - av_mean) ** 2 / av_err_safe**2 lnorm = np.log(2.0 * np.pi * av_err_safe**2) lnprior = np.where(valid, -0.5 * (chi2 + lnorm), 0.0) # Handle simple dust maps returning (av_mean, av_std) elif isinstance(result, tuple) and len(result) == 2: av_mean, av_err = result if np.isfinite(av_mean) and np.isfinite(av_err) and av_err > 0: chi2 = (avs - av_mean) ** 2 / av_err**2 lnorm = np.log(2.0 * np.pi * av_err**2) lnprior = -0.5 * (chi2 + lnorm) else: lnprior = np.zeros_like(avs, dtype=float) av_mean, av_err = np.nan, np.nan else: # Unrecognized return format lnprior = np.zeros_like(avs, dtype=float) av_mean = np.nan av_err = np.nan if return_components: return lnprior, (av_mean, av_err) return lnprior