예제 #1
0
파일: test_fiasco.py 프로젝트: vlslv/fiasco
def test_proton_electron_ratio(hdf5_dbase_root):
    t = np.logspace(4, 9, 100) * u.K
    # NOTE: this number will not be accurate as we are using only a subset of
    # the database
    pe_ratio = fiasco.proton_electron_ratio(t, hdf5_dbase_root=hdf5_dbase_root)
    assert type(pe_ratio) is u.Quantity
    assert pe_ratio.shape == t.shape
예제 #2
0
파일: ion.py 프로젝트: eblur/fiasco
    def contribution_function(self, density: u.cm**(-3), **kwargs):
        """
        Contribution function :math:`G(n,T)` for all transitions

        The contribution function for ion :math:`k` of element :math:`X` for a
        particular transition :math:`ij` is given by,

        .. math::

           G_{ij} = \\frac{n_H}{n_e}\mathrm{Ab}(X)f_{X,k}N_jA_{ij}\Delta E_{ij}\\frac{1}{n_e},

        and has units erg :math:`\mathrm{cm}^{3}` :math:`\mathrm{s}^{-1}`. Note that the
        contribution function is often defined in differing ways by different authors. The
        contribution function is defined as above in [1]_.

        The corresponding wavelengths can be retrieved with,

        .. code-block:: python

           ion.transitions.wavelength[~ion.transitions.is_twophoton]

        Parameters
        ----------
        density : `~astropy.units.Quantity`
            Electron number density

        References
        ----------
        .. [1] Young, P. et al., 2016, J. Phys. B: At. Mol. Opt. Phys., `49, 7 <http://iopscience.iop.org/article/10.1088/0953-4075/49/7/074009/meta>`_
        """
        populations = self.level_populations(density, **kwargs)
        p2e = proton_electron_ratio(self.temperature, **self._dset_names)
        term = np.outer(p2e * self.ioneq,
                        1. / density.value) * self.abundance / density.unit
        # Exclude two-photon transitions
        upper_level = self.transitions.upper_level[~self.transitions.
                                                   is_twophoton]
        # CHIANTI records theoretical transitions with negative wavelengths
        wavelength = np.fabs(
            self.transitions.wavelength[~self.transitions.is_twophoton])
        A = self.transitions.A[~self.transitions.is_twophoton]
        energy = ((const.h * const.c) / wavelength).to(u.erg)
        i_upper = vectorize_where(self._elvlc['level'], upper_level)
        g = term[:, :, np.newaxis] * populations[:, :, i_upper] * (A * energy)
        return g
예제 #3
0
파일: ion.py 프로젝트: eblur/fiasco
    def level_populations(self, density: u.cm**(-3), include_protons=True):
        """
        Compute energy level populations as a function of temperature and density

        Parameters
        ----------
        density : `~astropy.units.Quantity`
        include_protons : `bool`, optional
            If True (default), include proton excitation and de-excitation rates
        """
        level = self._elvlc['level']
        lower_level = self._scups['lower_level']
        upper_level = self._scups['upper_level']
        coeff_matrix = np.zeros(self.temperature.shape + (
            level.max(),
            level.max(),
        )) / u.s

        # Radiative decay out of current level
        coeff_matrix[:, level - 1, level - 1] -= vectorize_where_sum(
            self.transitions.upper_level, level,
            self.transitions.A.value) * self.transitions.A.unit
        # Radiative decay into current level from upper levels
        coeff_matrix[:, self.transitions.lower_level - 1,
                     self.transitions.upper_level - 1] += (self.transitions.A)

        # Collisional--electrons
        ex_rate_e = self.electron_collision_excitation_rate()
        dex_rate_e = self.electron_collision_deexcitation_rate()
        ex_diagonal_e = vectorize_where_sum(
            lower_level, level, ex_rate_e.value.T, 0).T * ex_rate_e.unit
        dex_diagonal_e = vectorize_where_sum(
            upper_level, level, dex_rate_e.value.T, 0).T * dex_rate_e.unit
        # Collisional--protons
        if include_protons and self._psplups is not None:
            pe_ratio = proton_electron_ratio(self.temperature,
                                             **self._dset_names)
            proton_density = np.outer(pe_ratio, density)[:, :, np.newaxis]
            ex_rate_p = self.proton_collision_excitation_rate()
            dex_rate_p = self.proton_collision_deexcitation_rate()
            ex_diagonal_p = vectorize_where_sum(self._psplups['lower_level'],
                                                level, ex_rate_p.value.T,
                                                0).T * ex_rate_p.unit
            dex_diagonal_p = vectorize_where_sum(self._psplups['upper_level'],
                                                 level, dex_rate_p.value.T,
                                                 0).T * dex_rate_p.unit

        # Solve matrix equation for each density value
        populations = np.zeros(self.temperature.shape + density.shape +
                               (level.max(), ))
        b = np.zeros(self.temperature.shape + (level.max(), ))
        b[:, -1] = 1.0
        for i, d in enumerate(density):
            c_matrix = coeff_matrix.copy()
            # Collisional excitation and de-excitation out of current state
            c_matrix[:, level - 1,
                     level - 1] -= d * (ex_diagonal_e + dex_diagonal_e)
            # De-excitation from upper states
            c_matrix[:, lower_level - 1, upper_level - 1] += d * dex_rate_e
            # Excitation from lower states
            c_matrix[:, upper_level - 1, lower_level - 1] += d * ex_rate_e
            # Same processes as above, but for protons
            if include_protons and self._psplups is not None:
                d_p = proton_density[:, i, :]
                c_matrix[:, level - 1,
                         level - 1] -= d_p * (ex_diagonal_p + dex_diagonal_p)
                c_matrix[:, self._psplups['lower_level'] - 1,
                         self._psplups['upper_level'] - 1] += (d_p *
                                                               dex_rate_p)
                c_matrix[:, self._psplups['upper_level'] - 1,
                         self._psplups['lower_level'] - 1] += (d_p * ex_rate_p)
            # Invert matrix
            c_matrix[:, -1, :] = 1. * c_matrix.unit
            pop = np.linalg.solve(c_matrix.value, b)
            pop = np.where(pop < 0., 0., pop)
            pop /= pop.sum(axis=1)[:, np.newaxis]
            populations[:, i, :] = pop

        return u.Quantity(populations)
예제 #4
0
파일: ion.py 프로젝트: vlslv/fiasco
    def level_populations(self,
                          density: u.cm**(-3),
                          include_protons=True) -> u.dimensionless_unscaled:
        """
        Energy level populations as a function of temperature and density.

        Parameters
        ----------
        density : `~astropy.units.Quantity`
        include_protons : `bool`, optional
            If True (default), include proton excitation and de-excitation rates.

        Returns
        -------
        `~astropy.units.Quantity`
            A ``(l, m, n)`` shaped quantity, where ``l`` is the number of
            temperatures, ``m`` is the number of densities, and ``n``
            is the number of energy levels.
        """
        # NOTE: Cannot include protons if psplups data not available
        try:
            _ = self._psplups
        except KeyError:
            # TODO: log this
            include_protons = False

        level = self._elvlc['level']
        lower_level = self._scups['lower_level']
        upper_level = self._scups['upper_level']
        coeff_matrix = np.zeros(self.temperature.shape + (level.max(), level.max(),))/u.s

        # Radiative decay out of current level
        coeff_matrix[:, level-1, level-1] -= vectorize_where_sum(
            self.transitions.upper_level, level, self.transitions.A.value) * self.transitions.A.unit
        # Radiative decay into current level from upper levels
        coeff_matrix[:, self.transitions.lower_level-1, self.transitions.upper_level-1] += (
            self.transitions.A)

        # Collisional--electrons
        dex_rate_e = self.electron_collision_deexcitation_rate()
        ex_rate_e = self.electron_collision_excitation_rate(deexcitation_rate=dex_rate_e)
        ex_diagonal_e = vectorize_where_sum(
            lower_level, level, ex_rate_e.value.T, 0).T * ex_rate_e.unit
        dex_diagonal_e = vectorize_where_sum(
            upper_level, level, dex_rate_e.value.T, 0).T * dex_rate_e.unit
        # Collisional--protons
        if include_protons:
            lower_level_p = self._psplups['lower_level']
            upper_level_p = self._psplups['upper_level']
            pe_ratio = proton_electron_ratio(self.temperature,
                                             **self._dset_names,
                                             hdf5_dbase_root=self.hdf5_dbase_root)
            proton_density = np.outer(pe_ratio, density)[:, :, np.newaxis]
            ex_rate_p = self.proton_collision_excitation_rate()
            dex_rate_p = self.proton_collision_deexcitation_rate(excitation_rate=ex_rate_p)
            ex_diagonal_p = vectorize_where_sum(
                lower_level_p, level, ex_rate_p.value.T, 0).T * ex_rate_p.unit
            dex_diagonal_p = vectorize_where_sum(
                upper_level_p, level, dex_rate_p.value.T, 0).T * dex_rate_p.unit

        # Populate density dependent terms and solve matrix equation for each density value
        density = np.atleast_1d(density)
        populations = np.zeros(self.temperature.shape + density.shape + (level.max(),))
        for i, d in enumerate(density):
            c_matrix = coeff_matrix.copy()
            # Collisional excitation and de-excitation out of current state
            c_matrix[:, level-1, level-1] -= d*(ex_diagonal_e + dex_diagonal_e)
            # De-excitation from upper states
            c_matrix[:, lower_level-1, upper_level-1] += d*dex_rate_e
            # Excitation from lower states
            c_matrix[:, upper_level-1, lower_level-1] += d*ex_rate_e
            # Same processes as above, but for protons
            if include_protons and self._psplups is not None:
                d_p = proton_density[:, i, :]
                c_matrix[:, level-1, level-1] -= d_p*(ex_diagonal_p + dex_diagonal_p)
                c_matrix[:, lower_level_p-1, upper_level_p-1] += d_p * dex_rate_p
                c_matrix[:, upper_level_p-1, lower_level_p-1] += d_p * ex_rate_p
            # Invert matrix
            val, vec = np.linalg.eig(c_matrix.value)
            # Eigenvectors with eigenvalues closest to zero are the solutions to the homogeneous
            # system of linear equations
            # NOTE: Sometimes eigenvalues may have complex component due to numerical stability.
            # We will take only the real component as our rate matrix is purely real
            i_min = np.argmin(np.fabs(np.real(val)), axis=1)
            pop = np.take(np.real(vec), i_min, axis=2)[range(vec.shape[0]), :, range(vec.shape[0])]
            # NOTE: The eigenvectors can only be determined up to a sign so we must enforce
            # positivity
            np.fabs(pop, out=pop)
            np.divide(pop, pop.sum(axis=1)[:, np.newaxis], out=pop)
            populations[:, i, :] = pop

        return u.Quantity(populations)