Source code for m4opt.synphot.extinction._dust
import warnings
from functools import cache
import numpy as np
from astropy import units as u
from astropy.modeling import Model, Parameter
from astropy.utils.data import download_file
from dust_extinction.parameter_averages import G23
with warnings.catch_warnings():
# Suppress configuration file warnings from the dustmaps package.
# We are not using the configuration file.
warnings.simplefilter("ignore", UserWarning)
from dustmaps.planck import PlanckGNILCQuery
from synphot.spectrum import BaseSpectrum, SpectralElement
from .._extrinsic import state
reddening_law = G23()
@cache
def dust_map():
sources = [
"https://irsa.ipac.caltech.edu/data/Planck/release_2/all-sky-maps/maps/component-maps/foregrounds/COM_CompMap_Dust-GNILC-Model-Opacity_2048_R2.01.fits",
"https://pla.esac.esa.int/pla/aio/product-action?MAP.MAP_ID=COM_CompMap_Dust-GNILC-Model-Opacity_2048_R2.01.fits",
]
return PlanckGNILCQuery(download_file(sources[-1], cache=True, sources=sources))
[docs]
def DustExtinction(Ebv: float | None = None):
"""Milky Way dust extinction.
Parameters
----------
Ebv:
Reddening color excess, :math:`E(B-V)`.
Notes
-----
We use :class:`dust_extinction.parameter_averages.G23` because of its broad
wavelength coverage from ultraviolet to infrared.
Examples
--------
You can create an extinction model with an explicitly set value of E(B-V):
>>> from astropy import units as u
>>> from m4opt.synphot.extinction import DustExtinction
>>> extinction = DustExtinction(Ebv=1.0)
>>> extinction(10 * u.micron)
<Quantity 0.78756244>
Or you can leave it unspecified, to evaluate later for a given sky location
using :meth:`m4opt.synphot.observing`:
>>> from astropy.coordinates import EarthLocation, SkyCoord
>>> from astropy.time import Time
>>> from m4opt.synphot import observing
>>> extinction = DustExtinction()
>>> with observing(EarthLocation.of_site("Las Campanas Observatory"), SkyCoord.from_name("NGC 4993"), Time("2017-08-17")):
... extinction(10 * u.micron)
<Quantity 0.9748155>
"""
if Ebv is None:
return SpectralElement(DustExtinctionForSkyCoord())
else:
return SpectralElement(DustExtinctionForEbv(Ebv=Ebv))
class DustExtinctionBase(Model):
n_inputs = 1
n_outputs = 1
input_units = {"x": BaseSpectrum._internal_wave_unit}
return_units = {"y": u.dimensionless_unscaled}
input_units_equivalencies = {"x": u.spectral()}
# argh, why!! synphot.BaseSpectrum passes unitless quantities to the underlying model.
_input_units_allow_dimensionless = True
@classmethod
def evaluate(cls, x, Ebv):
# Handle dimensionless units
if (
not hasattr(x, "unit")
or u.unit is None
or u.unit is u.dimensionless_unscaled
):
x = x * cls.input_units["x"]
# Convert to internal units used by dust_extinction package:
# wavenumber in units of inverse microns.
x = x.to(1 / u.micron, equivalencies=u.spectral())
lo, hi = reddening_law.x_range
good = (x.value >= lo) & (x.value <= hi)
x[~good] = np.nan
return reddening_law.extinguish(x, Ebv=Ebv)
class DustExtinctionForEbv(DustExtinctionBase):
Ebv = Parameter(description="E(B-V)")
class DustExtinctionForSkyCoord(DustExtinctionBase):
@property
def Ebv_max(self):
"""Maximum value of E(B-V) for the assumed dust model."""
return dust_map()._pix_val.max()
@classmethod
def evaluate(cls, x):
return super().evaluate(x, dust_map().query(state.get().target_coord))