Beispiel #1
0
def compile_kieft_elastic(outfile, material_params, K, P):
    print("# Computing Mott cross-sections using ELSEPA.")
    mott_fn = mott_dimfp(material_params, K[K > 10 * units.eV], threads=4)

    def elastic_cs_fn(E, costheta):
        return interpolate_f(
            lambda E: ac_phonon_dimfp(material_params)(E, costheta),
            lambda E: mott_fn(E, costheta), 100 * units.eV, 200 * units.eV)(E)

    print("# Computing elastic total cross-sections and iCDFs.")
    imfp = np.zeros(K.shape) * units('nm^-1')
    icdf = np.zeros((K.shape[0], P.shape[0])) * units.dimensionless
    tl = np.zeros(K.shape) * units.nm
    for i, E in enumerate(K):
        _imfp, _icdf = compute_tcs_icdf(
            lambda costheta: elastic_cs_fn(E, costheta), P,
            np.linspace(-1, 1, 100000))
        imfp[i] = 2 * np.pi * _imfp
        icdf[i, :] = _icdf

        _itl = 2 * np.pi * compute_tcs(
            lambda costheta: elastic_cs_fn(E, costheta) *
            (1 - costheta), np.linspace(-1, 1, 100000))
        tl[i] = 1 / _itl

        print('.', end='', flush=True)
    print()

    group = outfile.create_group("/kieft/elastic")
    group.add_scale("energy", K, 'eV')
    group.add_dataset("imfp", imfp, ("energy", ), 'nm^-1')
    group.add_dataset("costheta_icdf", icdf, ("energy", None), '')
    group.add_dataset("tl", tl, ("energy", ), 'nm')
Beispiel #2
0
 def get_property(self, key):
     value = self.file.attrs[key]
     if isinstance(value, (float, int)):
         return value
     elif isinstance(value, bytes):
         return value.decode('ascii')
     else:
         return value[0] * units(value[1].decode('ascii'))
Beispiel #3
0
def load_quantity(yaml_data, name, target_units, optional=False):
    if optional and name not in yaml_data:
        return None

    value = units(yaml_data[name])
    if not value.is_compatible_with(target_units):
        raise RuntimeError('Inconsistent units for {}'.format(name))

    return value
Beispiel #4
0
    def load(self, yaml_data, basedir):
        try:
            self.name = yaml_data.get('name')
            self.density = units(yaml_data['density'])
            self.elements = [
                element(*el) for el in yaml_data['elements'].items()
            ]
            self.band_structure = band_structure(yaml_data['band_structure'])
            self.optical = optical(yaml_data['optical'], basedir)
            self.phonon = phonon(yaml_data['phonon'], self)
        except KeyError as ke:
            raise RuntimeError(
                "Expected key {} was not found in parameters file".format(ke))

        if not self.density.is_compatible_with(
                units.gram / units.cubic_centimeter):
            raise RuntimeError("Provided density has inconsistent units")
Beispiel #5
0
	def IMFP_ICDF(self, E, P):
		"""Get the integrated inverse mean free path (IMFP) and inverse
		cumulative distribution function (ICDF).

		Parameters:
		 - E: Energies to evaluate the imfp and icdf at
		 - P: The cumulative probabilities for which the ICDF should be found.

		Returns:
		 - IMFP: numpy array of len(E)
		 - ICDF: numpy array of (len(E) × len(P))
		"""
		imfp = np.zeros(len(E)) * units('nm^-1')
		icdf = np.zeros((len(E), len(P_omega))) * self.q.units

		for i, K in enumerate(E):
			timfp, ticdf = compute_tcs_icdf(self, P, self.q)
			imfp[i] = timfp
			icdf[i] = ticdf

		return imfp, icdf
Beispiel #6
0
def compile_ashley_imfp_icdf(dimfp, K, P_omega):
    """Compute inverse mean free path, and ω' ICDF for a dielectric function
	given in the format of Ashley (doi:10.1016/0368-2048(88)80019-7).

	Ashley transforms the dielectric function ε(ω, q) to ε(ω, ω'); with an
	analytical relationship between ω and ω'. Therefore, we only need to store
	the probability distribution for ω'.

	This function computes the total mean free path and ICDF for ω', given a
	function dimfp(K, ω').

	This function calls dimfp() for ω' between 0 and K, despite conservation of
	momentum dictating that ω' < K/2. This is because some models (e.g. Kieft,
	doi:10.1088/0022-3727/41/21/215310) ignore this restriction.

	Parameters:
	 - dimfp:   function, taking 2 parameters (K, ω') and returning differential
				inverse mean free paths.
	 - K:       Array of interesting electron kinetic energies
	 - P_omega: The probabilities for which the ICDF needs to be evaluated

	Returns:
	 - Inverse mean free path array, same length as K
	 - Inverse cumulative distribution function, a 2D array of shape (len(K) × len(P_omega))
	"""

    imfp = np.zeros(K.shape) * units('nm^-1')
    icdf = np.zeros((K.shape[0], P_omega.shape[0])) * units.eV

    for i, E in enumerate(K):
        tcs, ticdf = compute_tcs_icdf(
            lambda w: dimfp(E, w), P_omega,
            np.linspace(0, E.magnitude, 100000) * E.units)
        imfp[i] = tcs.to('nm^-1')
        icdf[i] = ticdf.to('eV')
        print('.', end='', flush=True)
    print()

    return imfp, icdf
Beispiel #7
0
def compile_full_imfp_icdf(elf_omega, elf_q, elf_data, K, P_omega, n_omega_q,
                           P_q, F):
    """Compute inverse mean free path, energy transfer ICDF and momentum transfer
	ICDF for an arbitrary dielectric function ε(ω, q).

	ε(ω, q) is given by elf_omega, elf_q and elf_data.

	The data is evaluated for energies given by K. P_omega is the probability
	axis for the energy loss ICDF.
	The momentum transfer ICDF is computed for each (K, omega), with the second
	parameter in n_omega_q evenly-spaced steps between 0 and K. The probability
	axis for the momentum ICDF is given by P_q.

	F is the Fermi energy of the material. This is used for a "Fermi correction"
	to prevent omega > K-F.

	This function is relativistically correct.

	Parameters:
	 - elf_omega: ω for which the ELF 1/ε(ω, q) is known
	 - elf_q:     q for which the ELF is known
	 - elf_data:  The actual data. Shape is (len(ω) × len(q))
	 - K:         Electron kinetic energies to evaluate at.
	 - P_omega:   The probabilities to evaluate the ICDF for energy loss at.
	 - n_omega_q: The "energy loss axis" for the momentum ICDF
	 - P_q:       The probabilities to evaluate the ICDF for momentum transfer at.
	 - F:         The Fermi energy of the material

	Returns:
	 - Inverse mean free path, same length as K
	 - Stopping power, same length as K
	 - ICDF for energy loss, shape (len(K) × len(P_omega))
	 - 2D ICDF for momentum transfer, shape (len(K) × n_omega_q × len(P_q))
	"""

    K_units = units.eV
    q_units = units('nm^-1')

    mc2 = units.m_e * units.c**2

    # Helper function, sqrt(2m/hbar^2 * K(1 + K/mc^2)), appears when getting
    # momentum boundaries from kinetic energy
    q_k = lambda _k: np.sqrt(2 * units.m_e * _k * (1 + _k /
                                                   (2 * mc2))) / units.hbar

    def elf(omega, q):
        # Linear interpolation, with extrapolation if out of bounds
        def find_index(a, v):
            low_i = np.clip(
                np.searchsorted(a, v, side='right') - 1, 0,
                len(a) - 2)
            vl = a[low_i]
            vh = a[low_i + 1]
            return low_i, (v - vl) / (vh - vl)

        low_x, frac_x = find_index(elf_omega.magnitude,
                                   omega.to(elf_omega.units).magnitude)
        low_y, frac_y = find_index(elf_q.magnitude,
                                   q.to(elf_q.units).magnitude)
        elf = (1-frac_x)*(1-frac_y) * elf_data[low_x, low_y] + \
         frac_x*(1-frac_y) * elf_data[low_x+1, low_y] + \
         (1-frac_x)*frac_y * elf_data[low_x, low_y+1] + \
         frac_x*frac_y * elf_data[low_x+1, low_y+1]

        elf[elf <= 0] = 0
        return elf

    def q_part(eval_omega, eval_q):
        # Returns DCS[i,j] = ∫_0^{eval_q[j]} dq/q ELF(eval_omega[i], q)
        dcs_data = np.divide(elf(eval_omega[:, np.newaxis], eval_q),
                             eval_q.magnitude)
        for i in range(len(eval_omega)):
            dcs_data[i, 1:] = cumtrapz(dcs_data[i, :], eval_q.magnitude)
            dcs_data[i, 0] = 0
        return dcs_data

    eval_omega = np.geomspace(elf_omega[0].to(K_units).magnitude,
                              (K[-1] - F).to(K_units).magnitude,
                              10000) * K_units
    eval_q = np.geomspace(
        (q_k(K[-1]) - q_k(K[-1] - elf_omega[0])).to(q_units).magnitude,
        2 * q_k(K[-1]).to(q_units).magnitude, 10000) * q_units
    dcs_data = q_part(eval_omega, eval_q)

    inel_imfp = np.zeros(K.shape) * units('nm^-1')
    inel_sp = np.zeros(K.shape) * units('eV/nm')
    inel_omega_icdf = np.zeros((K.shape[0], P_omega.shape[0])) * K_units
    inel_q_2dicdf = np.zeros((K.shape[0], n_omega_q, P_q.shape[0])) * q_units

    for i, E in enumerate(K):
        tcs, sp, omega_icdf, q_2dicdf = tcs_2dicdf(
            dcs_data[eval_omega < E - F, :], eval_omega[eval_omega < E - F],
            eval_q,
            lambda omega: q_k(E) - q_k(np.maximum(0 * units.eV, E - omega)),
            lambda omega: q_k(E) + q_k(np.maximum(0 * units.eV, E - omega)),
            P_omega,
            np.linspace(0, (E - F).to(K_units).magnitude, n_omega_q) * K_units,
            P_q)
        tcs /= np.pi * units.a_0 * .5 * (1 - 1 / (E / mc2 + 1)**2) * mc2
        sp /= np.pi * units.a_0 * .5 * (1 - 1 / (E / mc2 + 1)**2) * mc2

        inel_imfp[i] = tcs.to('nm^-1')
        inel_sp[i] = sp.to('eV/nm')
        inel_omega_icdf[i] = omega_icdf.to('eV')
        inel_q_2dicdf[i] = q_2dicdf.to('nm^-1')
        print('.', end='', flush=True)
    print()

    return inel_imfp, inel_sp, inel_omega_icdf, inel_q_2dicdf
Beispiel #8
0
 def get_dataset(self, name):
     h5_dset = self.group[name]
     data = np.copy(h5_dset[()]) * units(h5_dset.attrs['units'].decode('ascii'))
     return data