Skip to content

Commit 20adb16

Browse files
authored
Add parametric doppler broadening to Sed objects (#1058)
2 parents d369bf0 + 910282c commit 20adb16

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

docs/source/emissions/emission_objects/sed_example.ipynb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,82 @@
981981
"# Get attenuation at 5500 angstrom\n",
982982
"att_at_5500 = get_attenuation_at_5500(int_sed, att_sed)"
983983
]
984+
},
985+
{
986+
"cell_type": "markdown",
987+
"id": "9d38967c",
988+
"metadata": {},
989+
"source": [
990+
"### Doppler broadening\n",
991+
"\n",
992+
"`Sed` objects also provide tools for producing Doppler-broadened versions of an SED. The method `doppler_broaden` returns a new SED broadened using a specified velocity dispersion. This can either operate in-place or return a new `Sed` object.\n",
993+
"\n"
994+
]
995+
},
996+
{
997+
"cell_type": "code",
998+
"execution_count": null,
999+
"id": "0942c4b6",
1000+
"metadata": {},
1001+
"outputs": [],
1002+
"source": [
1003+
"from unyt import km, s\n",
1004+
"\n",
1005+
"log10age = 6.0 # log10(age/yr)\n",
1006+
"metallicity = 0.01\n",
1007+
"spectra_type = \"nebular\"\n",
1008+
"grid_point = grid.get_grid_point(log10ages=log10age, metallicity=metallicity)\n",
1009+
"\n",
1010+
"# Extract SED\n",
1011+
"sed = grid.get_sed_at_grid_point(grid_point, spectra_type=spectra_type)\n",
1012+
"\n",
1013+
"# Apply Doppler broadening specifying a velocity\n",
1014+
"sed_broadened1 = sed.doppler_broaden(1000.0 * km / s)\n",
1015+
"\n",
1016+
"# Plot to compare\n",
1017+
"fig, ax = sed.plot_spectra(label=\"original\")\n",
1018+
"fig, ax = sed_broadened1.plot_spectra(\n",
1019+
" label=\"broadened\", fig=fig, ax=ax, xlimits=(1000, 1400)\n",
1020+
")\n",
1021+
"ax.set_xscale(\"linear\")"
1022+
]
1023+
},
1024+
{
1025+
"cell_type": "markdown",
1026+
"id": "ea1d0b35",
1027+
"metadata": {},
1028+
"source": [
1029+
"### Thermal Broadening\n",
1030+
"\n",
1031+
"There is also a variant of this method for thermal broadening, called `thermally_broaden`, which computes the broadening from a given temperature and mean molecular mass (which defaults to `1*amu`). This can either operate in-place or return a new `Sed` object."
1032+
]
1033+
},
1034+
{
1035+
"cell_type": "code",
1036+
"execution_count": null,
1037+
"id": "236895aa",
1038+
"metadata": {},
1039+
"outputs": [],
1040+
"source": [
1041+
"from unyt import K\n",
1042+
"\n",
1043+
"sed_thermal = sed.thermally_broaden(1e4 * K)\n",
1044+
"fig, ax = sed.plot_spectra(label=\"original\")\n",
1045+
"fig, ax = sed_thermal.plot_spectra(\n",
1046+
" label=\"thermally broadened\", fig=fig, ax=ax, xlimits=(1000, 1400)\n",
1047+
")\n",
1048+
"ax.set_xscale(\"linear\")"
1049+
]
1050+
},
1051+
{
1052+
"cell_type": "markdown",
1053+
"id": "82167512",
1054+
"metadata": {},
1055+
"source": [
1056+
"These methods are particularly useful when modelling environments where thermal motions contribute to line broadening, such as H II regions.\n",
1057+
"\n",
1058+
"Note that, if you have a particle based component, self-consistent Doppler broadening based on particle velocities can be applied during emission generation by enabling `vel_shift` on an `EmissionModel`."
1059+
]
9841060
}
9851061
],
9861062
"metadata": {},

src/synthesizer/emissions/sed.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@
1919
import numpy as np
2020
from matplotlib.colors import LogNorm
2121
from scipy.interpolate import interp1d
22+
from scipy.signal import fftconvolve
2223
from scipy.stats import linregress
2324
from spectres import spectres
2425
from unyt import (
2526
Hz,
27+
K,
28+
amu,
2629
angstrom,
2730
c,
2831
erg,
2932
eV,
3033
h,
34+
kb,
35+
km,
3136
pc,
3237
s,
3338
unyt_array,
@@ -1598,6 +1603,88 @@ def calculate_ionising_photon_production_rate(
15981603

15991604
return ion_photon_prod_rate
16001605

1606+
@accepts(sigma_v=km / s)
1607+
def doppler_broaden(self, sigma_v, inplace=False):
1608+
"""Doppler broaden the spectra.
1609+
1610+
Doppler broadens the spectra either in place or returning a new Sed.
1611+
1612+
Args:
1613+
sigma_v (unyt_array):
1614+
The velocity dispersion to broaden the spectra by.
1615+
inplace (bool):
1616+
Flag for whether to modify self or return a new Sed.
1617+
1618+
Returns:
1619+
Sed:
1620+
A new Sed containing the rest frame spectra of self broadened
1621+
by the provided velocity dispersion. Only returned if
1622+
in_place is False.
1623+
"""
1624+
# Convert wavelength grid to log-lambda space
1625+
x = np.log(self.lam)
1626+
1627+
# Regrid to uniform log-lambda spacing, using a very fine grid
1628+
x_uniform = np.linspace(x.min(), x.max(), 100000)
1629+
lnu_uniform = spectres(x_uniform, x, self.lnu, fill=0.0, verbose=False)
1630+
1631+
# Convert velocity sigma to log-lambda sigma
1632+
# Δx = ln(λ) gives Δv = c*Δx
1633+
sigma_x = sigma_v / c
1634+
1635+
# Gaussian kernel in log-lambda
1636+
dx = x_uniform[1] - x_uniform[0]
1637+
N = len(x_uniform)
1638+
half = N // 2
1639+
1640+
# Define and normalise the broadening kernel
1641+
kernel_x = (np.arange(N) - half) * dx
1642+
kernel = np.exp(-(kernel_x**2) / (2 * sigma_x**2))
1643+
kernel /= kernel.sum()
1644+
1645+
# Convolution in velocity/log-lambda space
1646+
lnu_broad = fftconvolve(lnu_uniform, kernel, mode="same")
1647+
1648+
# Re-interpolate back onto original wavelength grid and update the sed
1649+
new_lnu = (
1650+
spectres(x, x_uniform, lnu_broad, fill=0.0, verbose=False)
1651+
* self.lnu.units
1652+
)
1653+
1654+
# Return new Sed or modify in place
1655+
if inplace:
1656+
self.lnu = new_lnu
1657+
return self
1658+
else:
1659+
return Sed(self.lam, new_lnu)
1660+
1661+
@accepts(temperature=K, mu=amu)
1662+
def thermally_broaden(self, temperature, mu=1.0 * amu, inplace=False):
1663+
"""Create a spectra including the thermal broadening.
1664+
1665+
This simply calculates the velocity dispersion from the temperature
1666+
and mean molecular weight.
1667+
1668+
Args:
1669+
temperature (unyt_array):
1670+
The temperature to broaden the spectra by.
1671+
mu (unyt_array):
1672+
The mean molecular weight of the gas. By default assumed to be
1673+
1 amu.
1674+
inplace (bool):
1675+
Flag for whether to modify self or return a new Sed.
1676+
1677+
Returns:
1678+
Sed:
1679+
A new Sed containing the rest frame spectra of self
1680+
broadened by the provided velocity dispersion. Only returned if
1681+
inplace is False.
1682+
"""
1683+
# Calculate the velocity dispersion
1684+
sigma_v = np.sqrt(kb * temperature / mu)
1685+
1686+
return self.doppler_broaden(sigma_v, inplace=inplace)
1687+
16011688
def plot_spectra(self, **kwargs):
16021689
"""Plot the spectra.
16031690

0 commit comments

Comments
 (0)