Source code for ebisim.xs

"""
This module contains functions to compute the cross sections for various ionisation and
recombination processes.
"""

import logging
import math
import numpy as np
import numba

from .physconst import RY_EV, ALPHA, PI, COMPT_E_RED, M_E_EV

logger = logging.getLogger(__name__)

logger.debug("Defining _normpdf.")


@numba.njit(cache=True)
def _normpdf(x, mu, sigma):
    """
    The pdf of the normal distribution f_mu,sigma(x).


    Parameters
    ----------
    x : numpy.ndarray
        Function argument
    mu : numpy.ndarray
        Mean of the distribution
    sigma : numpy.ndarray
        Standard deviation of the distribution

    Returns
    -------
    numpy.ndarray
        Value of the normal PDF evaluated elementwise on the input arrays
    """
    return np.exp(-(x - mu)**2 / (2 * sigma**2)) / (2 * PI * sigma**2)**0.5


logger.debug("Defining cxxs.")


[docs]@numba.njit(cache=True) def cxxs(q, ip): """ Single charge exchange cross section according to the Mueller Salzborn formula Parameters ---------- q : int Charge state of the colliding ion ip : float <eV> Ionisation potential of the collision partner (neutral gas) Returns ------- float <m^2> Charge exchange cross section """ return 1.43e-16 * q**1.17 * ip**-2.76
logger.debug("Defining eixs_vec.")
[docs]@numba.njit(cache=True) def eixs_vec(element, e_kin): """ Electron ionisation cross section according to a simplified version of the models given in [Lotz1967]_. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. Returns ------- numpy.ndarray <m^2> The cross sections for each individual charge state, where the array-index corresponds to the charge state, i.e. out[q] ~ cross section of q+ ion. References ---------- .. [Lotz1967] "An empirical formula for the electron-impact ionization cross-section", W. Lotz, Zeitschrift Für Physik, 206(2), 205–211 (1967), https://doi.org/10.1007/BF01325928 See Also -------- ebisim.xs.eixs_mat : Similar method with different output format. """ css = element.e_bind.shape[0] shells = element.e_bind.shape[1] avail_factors = element.ei_lotz_a.shape[0] xs_vec = np.zeros(css + 1) t = e_kin / M_E_EV for cs in range(css): xs = 0 for shell in range(shells): e = element.e_bind[cs, shell] n = element.e_cfg[cs, shell] if n > 0 and e_kin > e: i = e / M_E_EV grys_fact = ((2+i)/(2+t) * ((1+t) / (1+i))**2 * (((i+t) * (2+t) * (1+i)**2) / (t * (2+t) * (1+i)**2 + i * (2+i)))**1.5) if cs < avail_factors: a = element.ei_lotz_a[cs, shell] b = element.ei_lotz_b[cs, shell] c = element.ei_lotz_c[cs, shell] xs += (grys_fact * a * n * math.log(e_kin / e) / (e_kin * e) * (1 - b*np.exp(-c*(e_kin/e - 1)))) else: xs += grys_fact * 4.5e-18 * n * math.log(e_kin / e) / (e_kin * e) xs_vec[cs] = xs return xs_vec
logger.debug("Defining eixs_mat.")
[docs]@numba.njit(cache=True) def eixs_mat(element, e_kin): """ Electron ionisation cross section. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. Returns ------- numpy.array <m^2> The cross sections for each individual charge state, arranged in a matrix suitable for implementation of a rate equation like dN/dt = j * xs_matrix dot N. out[q, q] = - cross section of q+ ion out[q+1, q] = + cross section of (q+1)+ ion See Also -------- ebisim.xs.eixs_vec : Similar method with different output format. """ xs = eixs_vec(element, e_kin) return np.diag(xs[:-1], -1) - np.diag(xs)
logger.debug("Defining rrxs_vec.")
[docs]@numba.njit(cache=True) def rrxs_vec(element, e_kin): """ Radiative recombination cross section according to [Kim1983]_. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. Returns ------- numpy.ndarray <m^2> The cross sections for each individual charge state, where the array-index corresponds to the charge state, i.e. out[q] ~ cross section of q+ ion. References ---------- .. [Kim1983] "Direct radiative recombination of electrons with atomic ions: Cross sections and rate coefficients", Young Soon Kim and R. H. Pratt, Phys. Rev. A 27, 2913 (1983), https://journals.aps.org/pra/abstract/10.1103/PhysRevA.27.2913 See Also -------- ebisim.xs.rrxs_mat : Similar method with different output format. """ chi = 2 * element.rr_z_eff**2 * RY_EV / e_kin xs = (8 * PI * ALPHA / (3 * np.sqrt(3)) * COMPT_E_RED**2 * chi * np.log(1 + chi/(2 * element.rr_n_0_eff**2))) xs[0] = 0 return xs
logger.debug("Defining rrxs_mat.")
[docs]@numba.njit(cache=True) def rrxs_mat(element, e_kin): """ Radiative recombination cross section. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. Returns ------- numpy.array <m^2> The cross sections for each individual charge state, arranged in a matrix suitable for implementation of a rate equation like dN/dt = j * xs_matrix dot N. out[q, q] = - cross section of q+ ion out[q, q+1] = + cross section of (q+1)+ ion See Also -------- ebisim.xs.rrxs_vec : Similar method with different output format. """ xs = rrxs_vec(element, e_kin) return np.diag(xs[1:], 1) - np.diag(xs)
logger.debug("Defining drxs_vec.")
[docs]@numba.njit(cache=True) def drxs_vec(element, e_kin, fwhm): """ Dielectronic recombination cross section. The cross sections are estimated by weighing the strength of each transition with the profile of a normal Gaussian distribution. This simulates the effective spreading of the resonance peaks due to the energy spread of the electron beam Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. fwhm : float <eV> Energy spread to apply for the resonance smearing, expressed in terms of full width at half maximum. Returns ------- numpy.ndarray <m^2> The cross sections for each individual charge state, where the array-index corresponds to the charge state, i.e. out[q] ~ cross section of q+ ion. See Also -------- ebisim.xs.drxs_mat : Similar method with different output format. """ xs_vec = np.zeros(element.z + 1) if element.dr_cs.size > 0: sig = fwhm/2.35482 # 2.35482approx.(2*np.sqrt(2*np.log(2))) tmp = element.dr_strength * _normpdf(e_kin, element.dr_e_res, sig)*1e-24 for k in range(element.dr_cs.size): # cs = int(element.dr_cs[k]) xs_vec[element.dr_cs[k]] = xs_vec[element.dr_cs[k]] + tmp[k] return xs_vec
logger.debug("Defining drxs_mat.")
[docs]@numba.njit(cache=True) def drxs_mat(element, e_kin, fwhm): """ Dielectronic recombination cross section. The cross sections are estimated by weighing the strength of each transition with the profile of a normal Gaussian distribution. This simulates the effective spreading of the resonance peaks due to the energy spread of the electron beam Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : float <eV> Kinetic energy of the impacting electron. fwhm : float <eV> Energy spread to apply for the resonance smearing, expressed in terms of full width at half maximum. Returns ------- numpy.array <m^2> The cross sections for each individual charge state, arranged in a matrix suitable for implementation of a rate equation like dN/dt = j * xs_matrix dot N. out[q, q] = - cross section of q+ ion out[q, q+1] = + cross section of (q+1)+ ion See Also -------- ebisim.xs.drxs_vec : Similar method with different output format. """ xs = drxs_vec(element, e_kin, fwhm) return np.diag(xs[1:], 1) - np.diag(xs)
logger.debug("Defining precompute_rr_quantities.")
[docs]@numba.njit(cache=True) def precompute_rr_quantities(e_cfg, shell_n): """ Precomputes the effective valence shell and nuclear charge for all charge states, as required for the computation of radiative recombinations cross sections. According to the procedure described in [Kim1983a]_. This function is primarily meant for internal use inside the ebisim.get_element() function. Parameters ---------- e_cfg : numpy.ndarray Matrix holding the number of electrons in each shell. The row index corresponds to the charge state, the columns to different subshells shell_n : numpy.ndarray Array holding the main quantum number n corresponding to each shell listed in e_cfg Returns ------- rr_z_eff : numpy.ndarray Array holding the effective nuclear charge for each charge state, where the array-index corresponds to the charge state. rr_n_0_eff : numpy.ndarray Array holding the effective valence shell number for each charge state, where the array-index corresponds to the charge state. References ---------- .. [Kim1983a] "Direct radiative recombination of electrons with atomic ions: Cross sections and rate coefficients", Young Soon Kim and R. H. Pratt, Phys. Rev. A 27, 2913 (1983), https://journals.aps.org/pra/abstract/10.1103/PhysRevA.27.2913 See Also -------- ebisim.xs.rrxs_vec ebisim.xs.rrxs_mat """ z = e_cfg.shape[0] shell_n = shell_n[:e_cfg.shape[1]] # Crop shell_n to the shells described in e_cfg n_0 = np.zeros(z + 1) occup = np.zeros(z + 1) # Determine, for each charge state, the valence shell (n_0), # and the number of electrons in it (occup) # Fully ionised n_0[z] = 1 occup[z] = 0 # All other charge states for cs in range(z): conf = e_cfg[cs, :] n_0[cs] = np.max(shell_n[np.nonzero(conf)]) occup[cs] = np.sum(np.extract((shell_n == n_0[cs]), conf)) w_n0 = (2 * n_0**2 - occup) / (2 * n_0**2) rr_n_0_eff = n_0 + (1 - w_n0) - 0.3 rr_z_eff = (z + np.arange(z + 1)) / 2 return rr_z_eff, rr_n_0_eff
logger.debug("Defining lookup_lotz_factors.")
[docs]def lookup_lotz_factors(e_cfg, shellorder): """ Analyses the shell structure of each charge state and looks up the correct factors for the Lotz formula. This function is primarily meant for internal use inside the ebisim.get_element() function and the results are consumed during the Electron Ionisation (EI) cross section computations. Parameters ---------- e_cfg : numpy.ndarray Matrix holding the number of electrons in each shell. The row index corresponds to the charge state, the columns to different subshells shellorder : numpy.ndarray Tuple containing the names of all shells in the same order as they appear in 'e_cfg' Returns ------- ei_lotz_a : numpy.ndarray Array holding 'Lotz' factor 'a' for each occupied shell in 'e_cfg' up to a certain charge state. ei_lotz_b : numpy.ndarray Array holding 'Lotz' factor 'b' for each occupied shell in 'e_cfg' up to a certain charge state. ei_lotz_b : numpy.ndarray Array holding 'Lotz' factor 'c' for each occupied shell in 'e_cfg' up to a certain charge state. See Also -------- ebisim.xs.eixs_vec ebisim.xs.eixs_mat """ z = e_cfg.shape[0] cols = e_cfg.shape[1] if z > 20: # No specific data available, use factors for neutral to 1+ ionisation ei_lotz_a = np.zeros((1, cols)) ei_lotz_b = np.zeros((1, cols)) ei_lotz_c = np.zeros((1, cols)) for i in range(cols): shell = shellorder[i] n = int(shell[0]) # main quantum number lc = shell[1] # angular momentum character s = shell[2] if len(shell) > 2 else None shell_stub = shell[:2] # We need to determine, for each column, whether the electrons in another column # also need to be counted because they have the same n and l if lc == "s": i2 = None elif s == "-": if shell_stub == "7p": # 7p+ does not exist in currently used data i2 = None else: i2 = shellorder.index(shell_stub + "+") elif s == "+": i2 = shellorder.index(shell_stub + "-") n_e = e_cfg[0, i] if i2 and i2 < cols: n_e += e_cfg[0, i2] if n_e == 0: a = b = c = 0 else: if (lc in ["s", "p"] and n > 3) or (lc == "d" and n > 4) or (lc == "f"): nstr = "n" else: nstr = "" + str(int(n)) a, b, c = _LOTZ_NEUTRAL_TABLE[nstr + lc + str(int(n_e))] ei_lotz_a[0, i] = a * 1.0e-18 ei_lotz_b[0, i] = b ei_lotz_c[0, i] = c else: table = _LOTZ_ADVANCED_TABLE[z] ncs = len(table.keys()) ei_lotz_a = np.ones((ncs, cols)) * 4.5e-18 ei_lotz_b = np.zeros((ncs, cols)) ei_lotz_c = np.zeros((ncs, cols)) for cs in range(ncs): for i in range(cols): shell_stub = shellorder[i][:2] if shell_stub in table[cs]: a, b, c = table[cs][shell_stub] ei_lotz_a[cs, i] = a * 1.0e-18 ei_lotz_b[cs, i] = b ei_lotz_c[cs, i] = c ei_lotz_a.setflags(write=False) ei_lotz_b.setflags(write=False) ei_lotz_c.setflags(write=False) return ei_lotz_a, ei_lotz_b, ei_lotz_c
logger.debug("Defining eixs_energyscan.")
[docs]@numba.njit(cache=True) def eixs_energyscan(element, e_kin=None, n=1000): """ Creates an array of EI cross sections for varying electron energies. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : None or numpy.ndarray, optional <eV> If e_kin is None, the range of sampling energies is chosen based on the binding enrgies of the element and energies are sampled on a logscale. If e_kin is an array with 2 elements, they are interpreted as the minimum and maximum sampling energy. If e_kin is an array with more than two values, the energies are taken as the sampling energies directly, by default None. n : int, optional The number of energy sampling points, if the sampling locations are not supplied by the user, by default 1000. Returns ------- e_samp : numpy.ndarray <eV> Array holding the sampling energies xs_scan : numpy.ndarray <m^2> Array holding the cross sections, where the row index corresponds to the charge state and the columns correspond to the different sampling energies See Also -------- ebisim.xs.rrxs_energyscan ebisim.xs.drxs_energyscan """ e_samp = _eirr_e_samp(element, e_kin, n) xs_scan = np.zeros((element.z + 1, len(e_samp))) for ind, ek in enumerate(e_samp): xs_scan[:, ind] = eixs_vec(element, ek) return e_samp, xs_scan
logger.debug("Defining rrxs_energyscan.")
[docs]@numba.njit(cache=True) def rrxs_energyscan(element, e_kin=None, n=1000): """ Creates an array of RR cross sections for varying electron energies. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : None or numpy.ndarray, optional <eV> If e_kin is None, the range of sampling energies is chosen based on the binding enrgies of the element and energies are sampled on a logscale. If e_kin is an array with 2 elements, they are interpreted as the minimum and maximum sampling energy. If e_kin is an array with more than two values, the energies are taken as the sampling energies directly, by default None. n : int, optional The number of energy sampling points, if the sampling locations are not supplied by the user, by default 1000. Returns ------- e_samp : numpy.ndarray <eV> Array holding the sampling energies xs_scan : numpy.ndarray <m^2> Array holding the cross sections, where the row index corresponds to the charge state and the columns correspond to the different sampling energies See Also -------- ebisim.xs.eixs_energyscan ebisim.xs.drxs_energyscan """ e_samp = _eirr_e_samp(element, e_kin, n) xs_scan = np.zeros((element.z + 1, len(e_samp))) for ind, ek in enumerate(e_samp): xs_scan[:, ind] = rrxs_vec(element, ek) return e_samp, xs_scan
logger.debug("Defining _eirr_e_samp.") @numba.njit(cache=True) def _eirr_e_samp(element, e_kin, n): """ Generates a resonable energy interval for EI and RR cross section scans based on user input and element binding energies Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. e_kin : None or numpy.ndarray <eV> If e_kin is None, the range of sampling energies is chosen based on the binding enrgies of the element and energies are sampled on a logscale. If e_kin is an array with 2 elements, they are interpreted as the minimum and maximum sampling energy. If e_kin is an array with more than two values, the energies are taken as the sampling energies directly. n : int The number of energy sampling points, if the sampling locations are not supplied by the user. Returns ------- numpy.ndarray <eV> Array of sampling energies """ if e_kin is None: e_min = 100.0 for eb in element.e_bind.flatten(): if eb > 0 and eb < e_min: e_min = eb e_min = 1.0 if (e_min < 10.0) else 10.0 # Go to next smaller magnitude e_max = 10 * element.e_bind.max() e_max = 10**np.ceil(np.log10(e_max)) e_samp = 10**np.linspace(np.log10(e_min), np.log10(e_max), n) elif len(e_kin) == 2: e_min = e_kin[0] e_max = e_kin[1] e_samp = 10**np.linspace(np.log10(e_min), np.log10(e_max), n) else: e_samp = e_kin return e_samp logger.debug("Defining drxs_energyscan.")
[docs]@numba.njit(cache=True) def drxs_energyscan(element, fwhm, e_kin=None, n=1000): """ Creates an array of DR cross sections for varying electron energies. Parameters ---------- element : ebisim.Element An ebisim.Element object that holds the required physical information for cross section calculations. fwhm : float <eV> Energy spread to apply for the resonance smearing, expressed in terms of full width at half maximum. e_kin : None or numpy.ndarray, optional <eV> If e_kin is None, the range of sampling energies is chosen based on the binding enrgies of the element and energies are sampled on a logscale. If e_kin is an array with 2 elements, they are interpreted as the minimum and maximum sampling energy. If e_kin is an array with more than two values, the energies are taken as the sampling energies directly, by default None. n : int, optional The number of energy sampling points, if the sampling locations are not supplied by the user, by default 1000. Returns ------- e_samp : numpy.ndarray <eV> Array holding the sampling energies xs_scan : numpy.ndarray <m^2> Array holding the cross sections, where the row index corresponds to the charge state and the columns correspond to the different sampling energies See Also -------- ebisim.xs.eixs_energyscan ebisim.xs.rrxs_energyscan """ if e_kin is None: e_min = element.dr_e_res.min() - 3 * fwhm e_max = element.dr_e_res.max() + 3 * fwhm e_samp = 10**np.linspace(np.log10(e_min), np.log10(e_max), n) elif len(e_kin) == 2: e_min = e_kin[0] e_max = e_kin[1] e_samp = 10**np.linspace(np.log10(e_min), np.log10(e_max), n) else: e_samp = e_kin xs_scan = np.zeros((element.z + 1, len(e_samp))) for ind, ek in enumerate(e_samp): xs_scan[:, ind] = drxs_vec(element, ek, fwhm) return e_samp, xs_scan
# Tables with factors for LOTZ formula #: Dictionary with the Lotz formula factors for different shells, a in units of <1.0e-14cm**2/eV> #: These values are relevant for computing the ionisation of neutral atoms only _LOTZ_NEUTRAL_TABLE = { "1s1": (4.00, 0.60, 0.56), "1s2": (4.00, 0.75, 0.50), "2p1": (3.80, 0.60, 0.40), "2p2": (3.50, 0.70, 0.30), "2p3": (3.20, 0.80, 0.25), "2p4": (3.00, 0.85, 0.22), "2p5": (2.80, 0.90, 0.20), "2p6": (2.60, 0.92, 0.19), "3d1": (3.70, 0.60, 0.40), "3d2": (3.40, 0.70, 0.30), "3d3": (3.10, 0.80, 0.25), "3d4": (2.80, 0.85, 0.20), "3d5": (2.50, 0.90, 0.18), "3d6": (2.20, 0.92, 0.17), "3d7": (2.00, 0.93, 0.16), "3d8": (1.80, 0.94, 0.15), "3d9": (1.60, 0.95, 0.14), "3d10": (1.40, 0.96, 0.13), "2s1": (4.00, 0.30, 0.60), "2s2": (4.00, 0.50, 0.60), "3p1": (4.00, 0.35, 0.60), "3p2": (4.00, 0.40, 0.60), "3p3": (4.00, 0.45, 0.60), "3p4": (4.00, 0.50, 0.50), "3p5": (4.00, 0.55, 0.45), "3p6": (4.00, 0.60, 0.40), "4d1": (4.00, 0.30, 0.60), "4d2": (3.80, 0.45, 0.50), "4d3": (3.50, 0.60, 0.40), "4d4": (3.20, 0.70, 0.30), "4d5": (3.00, 0.80, 0.25), "4d6": (2.80, 0.85, 0.20), "4d7": (2.60, 0.90, 0.18), "4d8": (2.40, 0.92, 0.17), "4d9": (2.20, 0.93, 0.16), "4d10": (2.00, 0.94, 0.15), "3s1": (4.00, 0.00, 0.00), "3s2": (4.00, 0.30, 0.60), "np1": (4.00, 0.00, 0.00), "np2": (4.00, 0.00, 0.00), "np3": (4.00, 0.20, 0.60), "np4": (4.00, 0.30, 0.60), "np5": (4.00, 0.40, 0.60), "np6": (4.00, 0.50, 0.50), "nd1": (4.00, 0.00, 0.00), "nd2": (4.00, 0.20, 0.60), "nd3": (3.80, 0.30, 0.60), "nd4": (3.60, 0.45, 0.50), "nd5": (3.40, 0.60, 0.40), "nd6": (3.20, 0.70, 0.30), "nd7": (3.00, 0.80, 0.25), "nd8": (2.80, 0.85, 0.20), "nd9": (2.60, 0.90, 0.18), "nd10": (2.40, 0.92, 0.17), "ns1": (4.00, 0.00, 0.00), "ns2": (4.00, 0.00, 0.00), "nf1": (3.70, 0.60, 0.40), "nf2": (3.40, 0.70, 0.30), "nf3": (3.10, 0.80, 0.25), "nf4": (2.80, 0.85, 0.20), "nf5": (2.50, 0.90, 0.18), "nf6": (2.20, 0.92, 0.17), "nf7": (2.00, 0.93, 0.16), "nf8": (1.80, 0.94, 0.15), "nf9": (1.60, 0.95, 0.14), "nf10": (1.40, 0.96, 0.13), "nf11": (1.30, 0.96, 0.12), "nf12": (1.20, 0.97, 0.12), "nf13": (1.10, 0.97, 0.11), "nf14": (1.00, 0.97, 0.11) } #: Dictionary with the Lotz formula factors for different shells, a in units of <1.0e-14cm**2/eV> #: These values are relevant for computing the ionisation elements up to Z=20 and rank over #: the _LOTZ_NEUTRAL_TABLE #: The nested dictionary is arranged as [Z][cs][shell] _LOTZ_ADVANCED_TABLE = { 1: { 0: {"1s": (4.00, 0.60, 0.56)} }, 2: { 0: {"1s": (4.00, 0.75, 0.46)}, 1: {"1s": (4.40, 0.38, 0.60)} }, 3: { 0: {"2s": (4.00, 0.70, 2.4), "1s": (4.20, 0.60, 0.60)}, 1: {"1s": (4.00, 0.48, 0.60)}, 2: {"1s": (4.50, 0.20, 0.60)} }, 4: { 0: {"2s": (4.00, 0.70, 0.50), "1s": (4.20, 0.60, 0.60)}, 1: {"2s": (4.40, 0.00, 0.00), "1s": (4.40, 0.40, 0.60)}, 2: {"1s": (4.50, 0.30, 0.60)}, 3: {"1s": (4.50, 0.00, 0.00)} }, 5: { 0: {"2p": (3.80, 0.70, 0.40), "2s": (4.00, 0.70, 0.50)}, 1: {"2s": (4.40, 0.40, 0.60), "1s": (4.40, 0.40, 0.60)}, 2: {"2s": (4.50, 0.00, 0.00), "1s": (4.50, 0.20, 0.60)}, 3: {"1s": (4.50, 0.00, 0.00)} }, 6: { 0: {"2p": (3.50, 0.70, 0.40), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (4.20, 0.40, 0.60), "2s": (4.40, 0.40, 0.60)}, 2: {"2s": (4.50, 0.20, 0.60), "1s": (4.50, 0.20, 0.60)}, 3: {"2s": (4.50, 0.00, 0.00), "1s": (4.50, 0.00, 0.00)} }, 7: { 0: {"2p": (3.20, 0.83, 0.22), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (3.90, 0.46, 0.62), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.50, 0.20, 0.60), "2s": (4.50, 0.20, 0.60)}, 3: {"2s": (4.50, 0.00, 0.00), "1s": (4.50, 0.00, 0.00)} }, 8: { 0: {"2p": (2.80, 0.74, 0.24), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (3.70, 0.60, 0.60), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.50, 0.30, 0.60), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.00, 0.00), "2s": (4.50, 0.00, 0.00)} }, 9: { 0: {"2p": (2.70, 0.90, 0.20), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (3.50, 0.70, 0.50), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.50, 0.40, 0.60), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.00, 0.00), "2s": (4.50, 0.00, 0.00)} }, 10: { 0: {"2p": (2.60, 0.92, 0.19), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (3.20, 0.83, 0.48), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.20, 0.50, 0.60), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.20, 0.60), "2s": (4.50, 0.00, 0.00)} }, 11: { 0: {"3s": (4.00, 0.00, 0.00), "2p": (3.00, 0.90, 0.20), "2s": (4.00, 0.70, 0.50)}, 1: {"2p": (3.40, 0.84, 0.32), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.00, 0.60, 0.50), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.20, 0.60), "2s": (4.50, 0.00, 0.00)} }, 12: { 0: {"3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20), "2s": (4.00, 0.70, 0.50)}, 1: {"3s": (4.40, 0.00, 0.00), "2p": (3.70, 0.80, 0.40), "2s": (4.40, 0.40, 0.60)}, 2: {"2p": (4.00, 0.60, 0.50), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.30, 0.60), "2s": (4.50, 0.00, 0.00)} }, 13: { 0: {"3p": (4.00, 0.30, 0.60), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40), "2s": (4.40, 0.40, 0.60)}, 2: {"3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50), "2s": (4.50, 0.20, 0.60)}, 3: {"2p": (4.50, 0.30, 0.60), "2s": (4.50, 0.00, 0.00)} }, 14: { 0: {"3p": (4.00, 0.30, 0.60), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3p": (4.40, 0.20, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50), "2s": (4.50, 0.20, 0.60)}, 3: {"3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60), "2s": (4.50, 0.00, 0.00)} }, 15: { 0: {"3p": (4.00, 0.40, 0.60), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3p": (4.40, 0.20, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60), "2s": (4.50, 0.00, 0.00)} }, 16: { 0: {"3p": (4.00, 0.40, 0.60), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3p": (4.40, 0.30, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60)} }, 17: { 0: {"3p": (4.00, 0.50, 0.50), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3p": (4.40, 0.30, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3p": (4.50, 0.20, 0.60), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60)} }, 18: { 0: {"3p": (4.00, 0.62, 0.40), "3s": (4.00, 0.40, 0.60), "2p": (3.00, 0.90, 0.20)}, 1: {"3p": (4.20, 0.30, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3p": (4.50, 0.20, 0.60), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60)} }, 19: { 0: {"4s": (4.00, 0.00, 0.00), "3p": (4.00, 0.60, 0.40), "3s": (4.00, 0.40, 0.60)}, 1: {"3p": (4.00, 0.30, 0.60), "3s": (4.40, 0.20, 0.60), "2p": (3.70, 0.80, 0.40)}, 2: {"3p": (4.50, 0.20, 0.60), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60)} }, 20: { 0: {"4s": (4.00, 0.40, 0.60), "3p": (4.00, 0.60, 0.40), "3s": (4.00, 0.40, 0.60)}, 1: {"4s": (4.40, 0.00, 0.00), "3p": (4.40, 0.30, 0.60), "3s": (4.40, 0.20, 0.60)}, 2: {"3p": (4.50, 0.20, 0.60), "3s": (4.50, 0.00, 0.00), "2p": (4.20, 0.60, 0.50)}, 3: {"3p": (4.50, 0.00, 0.00), "3s": (4.50, 0.00, 0.00), "2p": (4.50, 0.30, 0.60)} } }