Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two-photon continuum #260

Merged
merged 42 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5e865e0
create two_photon continuum function
jwreep Jan 23, 2024
ae6fe06
fix spline
jwreep Jan 23, 2024
34d08af
add logic to observed vs theoretical wavelength
jwreep Jan 25, 2024
0666c6e
remove units from A_sum
jwreep Jan 27, 2024
548ab2e
add 2p method for IonCollection
Jan 30, 2024
a8e381d
allow wavelength, temperature, density to be arrays
Jan 30, 2024
bed7303
bug fixes for array handling
Jan 30, 2024
8697f4e
error handling for ions that aren't H- or He-like
Jan 30, 2024
5df071c
add needs_dataset decorator
Feb 4, 2024
dfc7d41
fix variable description in IDL tests
Feb 9, 2024
6c58624
fix code to correct units of EM and add missing 1/wvl
Feb 10, 2024
27dc34c
change name of A_sum and add note in fiasco.io about its origin
Feb 15, 2024
66b3022
add include_protons option to two_photon calculation, default to True
Feb 17, 2024
bb5032d
fix He-like level index
Feb 21, 2024
522f3b2
Merge branch 'wtbarnes:main' into main
jwreep Feb 22, 2024
4539f17
Merge branch 'wtbarnes:main' into main
jwreep Mar 11, 2024
0f59faf
change normalization
Mar 12, 2024
8ed52e8
revert to 4539f17
Mar 12, 2024
149d430
fix normalization, now with current main tree
Mar 12, 2024
cfb33c6
add description of the function and references
Mar 12, 2024
b766f90
refactor EM, and move ioneq and abund to ioncollection
Mar 15, 2024
28461c1
Merge branch 'wtbarnes:main' into main
jwreep Mar 19, 2024
4de9505
fix LateX in description of 2p function
Mar 23, 2024
6c51700
add tests for ion and collections
Mar 23, 2024
cd1b3e7
fix ion collection test
Mar 23, 2024
c66eb20
add he_2.wgfa to test file list
Mar 23, 2024
0090b99
add he_2.scups to test file list
Mar 23, 2024
68388e9
typo
Mar 23, 2024
cdacf06
test fix for test_ion.py -- roundoff error?
Apr 5, 2024
886d1d3
text fix for test_collection.py -- roundoff error?
Apr 5, 2024
77406bd
add tests for helium-like ion and an ion missing input dataset
Apr 11, 2024
1b8c393
fix bug in test_ion.test_two_photon
Apr 11, 2024
b0223ae
add elvlc for c4 and c5 to test list
Apr 11, 2024
ba82b19
add wgfa and scups for c4 and c5 to test list
Apr 11, 2024
ddf2cb4
set include_protons to False by default
Apr 11, 2024
a931809
update value for C V test
Apr 12, 2024
352d8a7
add c_5.reclvl to test database
wtbarnes Apr 12, 2024
694141e
require dbase_Version <= 8.0.7 for two_photon test
jwreep Apr 12, 2024
71571c2
minor refactoring of 2p continuum
wtbarnes Apr 17, 2024
2c60f40
change comparison with == to np.allclose
Apr 18, 2024
ca0f0d4
allclose -> isclose, oops
Apr 18, 2024
05b47fe
add comment to docstring about no photons beneath rest wavelength
Apr 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions fiasco/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,13 @@ def two_photon(self, wavelength: u.angstrom, electron_density: u.cm**-3, **kwarg
two_photon = u.Quantity(np.zeros(wavelength.shape + self.temperature.shape + electron_density.shape),
'erg cm^3 s^-1 Angstrom^-1')
for ion in self:
if ion.hydrogenic or ion.helium_like:
try:
tp = ion.two_photon(wavelength, electron_density, **kwargs)
except MissingDatasetException as e:
self.log.warning(f'{ion.ion_name} not included in two-photon emission. {e}')
continue
else:
two_photon += tp * ion.abundance * ion.ioneq[:, np.newaxis]
try:
tp = ion.two_photon(wavelength, electron_density, **kwargs)
except MissingDatasetException as e:
self.log.warning(f'{ion.ion_name} not included in two-photon emission. {e}')
continue
else:
two_photon += tp * ion.abundance * ion.ioneq[:, np.newaxis]
return two_photon

@u.quantity_input
Expand Down
62 changes: 27 additions & 35 deletions fiasco/ions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1530,9 +1530,10 @@ def _karzas_cross_section(self, photon_energy: u.erg, ionization_energy: u.erg,

@needs_dataset('elvlc')
@u.quantity_input
def two_photon(self, wavelength: u.angstrom,
electron_density: u.cm**(-3),
include_protons=False) -> u.erg * u.cm**3 / u.s / u.angstrom:
def two_photon(self,
wavelength: u.angstrom,
electron_density: u.cm**(-3),
include_protons=False) -> u.Unit('erg cm3 s-1 Angstrom-1'):
r"""
Two-photon continuum emission of a hydrogenic or helium-like ion.

Expand All @@ -1541,17 +1542,16 @@ def two_photon(self, wavelength: u.angstrom,
expressed per steradian and as such the factor of
:math:`1/4\pi` is not included.

In hydrogen-like ions, the transition :math: `2S_{1/2} \rightarrow 1S_{1/2} + h\nu` cannot occur
In hydrogen-like ions, the transition :math:`2S_{1/2} \rightarrow 1S_{1/2} + h\nu` cannot occur
as an electric dipole transition, but only as a much slower magnetic dipole transition.
The dominant transition then becomes :math: `2S_{1/2} \rightarrow 1S_{1/2} + h\nu_{1} + h\nu_{2}`.
The dominant transition then becomes :math:`2S_{1/2} \rightarrow 1S_{1/2} + h\nu_{1} + h\nu_{2}`.

In helium-like ions, the transition from :math: `1s2s ^{1}S_{0} \rightarrow 1s^{2}\ ^{1}S_{0} + h\nu`
is forbidden under quantum selection rules since :math: `\Delta J = 0`.
In helium-like ions, the transition from :math:`1s2s ^{1}S_{0} \rightarrow 1s^{2}\ ^{1}S_{0} + h\nu`
is forbidden under quantum selection rules since :math:`\Delta J = 0`.
Similarly, the dominant transition becomes
:math: `1s2s ^{1}S_{0} \rightarrow 1s^{2}\ ^{1}S_{0} + h\nu_{1} + h\nu_{2}`.
:math:`1s2s ^{1}S_{0} \rightarrow 1s^{2}\ ^{1}S_{0} + h\nu_{1} + h\nu_{2}`.

In both cases, the energy of the two photons emitted equals the energy difference of the two levels.

See the introduction of :cite:t:`drake_1986` for a concise description of the process.

The emission is given by
Expand All @@ -1564,8 +1564,8 @@ def two_photon(self, wavelength: u.angstrom,
:math:`A_{ji}` is the Einstein spontaneous emission coefficient,
:math:`\psi` is so-called spectral distribution function, given approximately by
:math:`\psi(y) \approx 2.623 \sqrt{\cos{\Big(\pi(y-\frac{1}{2})\Big)}}` :cite:p:`gronenschild_twophoton_1978`,
:math: `\psi_{\text{norm}}` is a normalization factor for hydrogen-like ions such
that :math: `\frac{1}{\psi_{\text{norm}}} \int_{0}^{1} \psi(y) dy = 2` (and 1 for helium-like ions),
:math:`\psi_{\text{norm}}` is a normalization factor for hydrogen-like ions such
that :math:`\frac{1}{\psi_{\text{norm}}} \int_{0}^{1} \psi(y) dy = 2` (and 1 for helium-like ions),
and :math:`n_{j}(X^{+m})` is the density of ions m of element X in excited state j, given by
:math:`n_{j}(X^{+m}) = \frac{n_{j}(X^{+m})}{n(X^{+m})} \frac{n(X^{+m})}{n(X)} \frac{n(X)}{n_{H}} \frac{n_{H}}{n_{e}} n_{e}`.

Expand All @@ -1576,48 +1576,40 @@ def two_photon(self, wavelength: u.angstrom,
include_protons : `bool`, optional
If True, use proton excitation and de-excitation rates in the level population calculation.
"""

wavelength = np.atleast_1d(wavelength)
temperature = np.atleast_1d(self.temperature)
electron_density = np.atleast_1d(electron_density)

prefactor = (const.h * const.c)

if not self.hydrogenic and not self.helium_like:
return u.Quantity(np.zeros(wavelength.shape + self.temperature.shape + electron_density.shape),
'erg cm^3 s^-1 Angstrom^-1')
final_shape = wavelength.shape + self.temperature.shape + electron_density.shape
if self.hydrogenic:
A_ji = self._hseq['A']
psi_norm = self._hseq['psi_norm']
cubic_spline = CubicSpline(self._hseq['y'], self._hseq['psi'])
# Get the index of the 2S1/2 state for H-like:
level_index = np.where((self._elvlc['config'] == '2s') & (self._elvlc['J'] == 0.5))[0][0]
if self.helium_like:
config = '2s' # Get the index of the 2S1/2 state for H-like
J = 0.5
wtbarnes marked this conversation as resolved.
Show resolved Hide resolved
elif self.helium_like:
A_ji = self._heseq['A']
psi_norm = 1.0 * u.dimensionless_unscaled
cubic_spline = CubicSpline(self._heseq['y'], self._heseq['psi'])
# Get the index of the 1s2s 1S0 state for He-like:
level_index = np.where((self._elvlc['config'] == '1s.2s') & (self._elvlc['J'] == 0))[0][0]
config = '1s.2s' # Get the index of the 1s2s 1S0 state for He-like:
J = 0
else:
return u.Quantity(np.zeros(final_shape), 'erg cm^3 s^-1 Angstrom^-1')
level_index = np.where((self._elvlc['config'] == config) & (self._elvlc['J'] == J))[0][0]

# store the rest wavelength:
E_obs = self._elvlc['E_obs'][level_index]
E_th = self._elvlc['E_th'][level_index]
E_2p = E_obs if E_obs > 0.0 else E_th
rest_wavelength = 1 / (E_2p.to(1/u.angstrom))
rest_wavelength = 1 / E_2p

psi_interp = cubic_spline(rest_wavelength / wavelength)
indices = np.where(wavelength < rest_wavelength)
psi_interp[indices] = 0.0 # intensity below rest_wavelength is 0
psi_interp = cubic_spline((rest_wavelength / wavelength).decompose())
psi_interp = np.where(wavelength < rest_wavelength, 0.0, psi_interp)
jwreep marked this conversation as resolved.
Show resolved Hide resolved

energy_dist = (A_ji * rest_wavelength * psi_interp) / (psi_norm * wavelength**3)

level_population = self.level_populations(electron_density,
include_protons=include_protons)[:,:,level_index]
level_population = self.level_populations(electron_density, include_protons=include_protons)
level_population = level_population[..., level_index]
jwreep marked this conversation as resolved.
Show resolved Hide resolved

# N_j(X+m) = N_j(X+m)/N(X+m) * N(X+m)/N(X) * N(X)/N(H) * N(H)/Ne * Ne
level_density = level_population / electron_density
matrix = np.outer(energy_dist, level_density).reshape(*final_shape)

matrix = np.outer(energy_dist, level_density).reshape(
len(wavelength),len(temperature),len(electron_density))

return (prefactor * matrix)
return const.h * const.c * matrix