def _refresh_hemisphere(self):
        if self.hemisphere is not None:
            self.removeItem(self.hemisphere)
            self.hemisphere = None

        if self.orbital is None or not self.options.is_show_hemisphere():
            return

        k = energy_to_k(self.E_kin)
        kx, ky, kz = [self.orbital.psik[key] for key in ['kx', 'ky', 'kz']]
        self.dx, self.dy, self.dz = [
            kx[1] - kx[0], ky[1] - ky[0], kz[1] - kz[0]]

        # this produces a hemisphere using the GLSurfacePlotItem, however,
        # the resulting hemisphere appears a little ragged at the circular
        # boundary
        # x = np.linspace(-k / self.dx, k / self.dx, 200)
        # y = np.linspace(-k / self.dy, k / self.dy, 200)
        # X, Y = np.meshgrid(x, y)
        # Z = np.sqrt(k**2 / (self.dx * self.dy) - X**2 - Y**2)

        # self.hemisphere = GLSurfacePlotItem(x=x, y=y, z=Z, color=(
        #    0.5, 0.5, 0.5, 0.7), shader='edgeHilight')
        # self.hemisphere.setGLOptions('translucent')

        # NEW method:
        nz = len(kz)
        X, Y, Z = np.meshgrid(kx / self.dx, ky / self.dy,
                              kz[nz // 2:] / self.dz)
        data = X**2 + Y**2 + Z**2
        iso = k**2 / (self.dx * self.dy)
        color = (0.5, 0.5, 0.5, 0.8)
        self.hemisphere = self._get_iso_mesh(data, iso, color, z_shift=False)

        self.addItem(self.hemisphere)
Exemplo n.º 2
0
    def set_kinetic_energy(self, E_kin, dk):

        kmax = energy_to_k(E_kin)
        if type(dk) == tuple:
            kxi = dk[0]
            kyi = dk[1]
            num_kx = len(kxi)
            num_ky = len(kyi)
        else:
            num_kx = int(2 * kmax / dk)
            num_ky = int(2 * kmax / dk)
            kxi = np.linspace(-kmax, +kmax, num_kx)
            kyi = np.linspace(-kmax, +kmax, num_ky)

        krange = ((kxi[0], kxi[-1]), (kyi[0], kyi[-1]))
        KX, KY = np.meshgrid(kxi, kyi, indexing='xy')
        KZ = np.sqrt(kmax**2 - KX**2 - KY**2)
        kxkykz = list(
            map(lambda a, b, c: (a, b, c), KX.flatten(), KY.flatten(),
                KZ.flatten()))
        data = np.reshape(self.psik['data_interp'](kxkykz), (num_kx, num_ky))

        # Set kmap attributes
        self.kmap = {
            'E_kin': E_kin,
            'dk': dk,
            'krange': krange,
            'KX': KX,
            'KY': KY,
            'KZ': KZ,
            'phi': 0,
            'theta': 0,
            'psi': 0,
            'data': data
        }
Exemplo n.º 3
0
    def set_polarization(self, Ak_type, polarization, alpha, beta, gamma):

        if Ak_type == 'no':  # Set |A.k|^2 to 1
            self.Ak = {
                'Ak_type': Ak_type,
                'polarization': polarization,
                'alpha': alpha,
                'beta': beta,
                'gamma': gamma,
                'data': np.ones_like(self.kmap['data'])
            }

            return

        # Convert angles to rad and compute sin and cos for later use
        a = np.radians(alpha)
        b = np.radians(beta)
        sin_a = np.sin(a)
        cos_a = np.cos(a)
        sin_b = np.sin(b)
        cos_b = np.cos(b)

        # Compute gamma according to inelastic free mean path
        if gamma == 'auto':
            # lambda is calculated from the "universal curve" empirical
            # relation
            E_kin = self.kmap['E_kin']
            c1 = 143
            c2 = 0.054
            lam = c1 * E_kin**(-2) + c2 * np.sqrt(E_kin)
            lam *= 10
            gamma_calc = 1 / lam
        else:
            gamma_calc = gamma

        # Retrieve k-grid from kmap
        kx, ky, kz = self.kmap['KX'], self.kmap['KY'], self.kmap['KZ']
        # Magnitude of k-vector
        k2 = kx**2 + ky**2 + kz**2
        kmax = energy_to_k(self.kmap['E_kin'])

        # At the toroid, the emitted electron is always in the plane of
        # incidence and the sample is rotated
        if Ak_type == 'toroid' or Ak_type == 'only-toroid':
            # Parallel component of k-vector
            kpar = np.sqrt(k2 - kz**2)
            # |A.k|^2 factor
            Ak = (kpar * cos_a + kz * sin_a)**2

        # At the NanoESCA, either p-polarization ,s-polarization, or
        # circularly polarized light can be simulated
        elif Ak_type == 'NanoESCA' or Ak_type == 'only-NanoESCA':
            # In-plane = p-polarization
            if polarization == 'p':
                Ak = kx * cos_a * cos_b + ky * cos_a * sin_b + kz * sin_a
                Ak = Ak**2 + gamma_calc**2 * sin_a**2

            # Out-of-plane = s-polarization
            elif polarization == 's':
                Ak = -kx * sin_b + ky * cos_b
                Ak = Ak**2
                Ak[kx**2 + ky**2 > kmax**2] = np.nan

            # unpolarized light, e.g. He-lamp
            # Compare Equation (37) in S. Moser, J. Electr. Spectr.
            # Rel. Phen. 214, 29-52 (2017).
            elif polarization == 'unpolarized':
                Ak = (k2 + gamma_calc**2 + 2 * kx * kz * cos_b +
                      2 * ky * kz * sin_b) * np.sin(2 * a)
                Ak += (kx**2 * sin_b + ky**2 * cos_b - kz**2 -
                       gamma_calc**2) * np.cos(2 * a)
                Ak *= (2 / 3)

            # Circularly polarized light (right-handed)
            elif polarization == 'C+':
                polp = kx * cos_a * cos_b + ky * cos_a * sin_b + kz * sin_a
                pols = -kx * sin_b + ky * cos_b
                Ak = 0.5 * (polp**2 + gamma_calc**2 * sin_a**2) + 0.5 * \
                    pols**2 + (sin_b * kx - cos_b * ky) * gamma_calc * sin_a

            # Circularly polarized light (left-handed)
            elif polarization == 'C-':
                polp = kx * cos_a * cos_b + ky * cos_a * sin_b + kz * sin_a
                pols = -kx * sin_b + ky * cos_b
                Ak = 0.5 * (polp**2 + gamma_calc**2 * sin_a**2) + 0.5 * \
                    pols**2 - (sin_b * kx - cos_b * ky) * gamma_calc * sin_a

            # CDAD-signal (right-handed - left-handed) using empirically
            # damped plane wave
            elif polarization == 'CDAD':
                # Compare Equation (31) in S. Moser, J. Electr. Spectr.
                # Rel. Phen. 214, 29-52 (2017).
                Ak = +2 * (sin_b * kx - cos_b * ky) * gamma_calc * sin_a
                Ak[kx**2 + ky**2 > kmax**2] = np.nan

        # Set attributes
        self.Ak = {
            'Ak_type': Ak_type,
            'polarization': polarization,
            'alpha': alpha,
            'beta': beta,
            'gamma': gamma,
            'data': Ak
        }
Exemplo n.º 4
0
    def compute_3DFT(self, dk3D, E_kin_max, value):
        """Compute 3D-FT."""

        # Determine required size (nkx,nky,nkz) of 3D-FT array to reach
        # desired resolution dk3D
        pad_x = max(
            int(2 * np.pi / (dk3D * self.psi['dx'])) - self.psi['nx'], 0)
        pad_y = max(
            int(2 * np.pi / (dk3D * self.psi['dy'])) - self.psi['ny'], 0)
        pad_z = max(
            int(2 * np.pi / (dk3D * self.psi['dz'])) - self.psi['nz'], 0)

        if pad_x % 2 == 1: pad_x += 1  # make sure it's an even number
        if pad_y % 2 == 1: pad_y += 1  # make sure it's an even number
        if pad_z % 2 == 1: pad_z += 1  # make sure it's an even number

        nkx = self.psi['nx'] + pad_x
        nky = self.psi['ny'] + pad_y
        nkz = self.psi['nz'] + pad_z

        # Set up k-grids for 3D-FT
        kx = self.set_3Dkgrid(nkx, self.psi['dx'])
        ky = self.set_3Dkgrid(nky, self.psi['dy'])
        kz = self.set_3Dkgrid(nkz, self.psi['dz'])

        # Compute 3D FFT
        # !! TESTING !!! This is supposed to yield also proper Real- and Imaginary parts!!
        #        print(nkx, nky, nkz)
        #        print(pad_x, pad_y, pad_z)
        psi_padded = np.pad(self.psi['data'],
                            pad_width=((pad_x // 2, pad_x // 2),
                                       (pad_y // 2, pad_y // 2), (pad_z // 2,
                                                                  pad_z // 2)),
                            mode='constant',
                            constant_values=(0, 0))
        psi_padded = np.fft.ifftshift(psi_padded)
        psik = np.fft.fftshift(np.fft.fftn(psi_padded))

        # THIS IS THE OLD AND WELL TESTED WAY TO Compute 3D FFT
        #        psik = np.fft.fftshift(np.fft.fftn(self.psi['data'],
        #                                           s=[nkx, nky, nkz]))

        # properly normalize wave function in momentum space
        dkx, dky, dkz = kx[1] - kx[0], ky[1] - ky[0], kz[1] - kz[0]
        factor = dkx * dky * dkz * np.sum(np.abs(psik)**2)
        psik /= np.sqrt(factor)

        # Reduce size of array to value given by E_kin_max to save memory
        k_max = energy_to_k(E_kin_max)
        kx_indices = np.where((kx <= k_max) & (kx >= -k_max))[0]
        ky_indices = np.where((ky <= k_max) & (ky >= -k_max))[0]
        kz_indices = np.where((kz <= k_max) & (kz >= -k_max))[0]
        kx = kx[kx_indices]
        ky = ky[ky_indices]
        kz = kz[kz_indices]
        psik = np.take(psik, kx_indices, axis=0)
        psik = np.take(psik, ky_indices, axis=1)
        psik = np.take(psik, kz_indices, axis=2)

        # decide whether real, imaginry part, absolute value, or squared absolute value is used
        if value == 'real':
            psik = np.asarray(np.real(psik), order='C')

        elif value == 'imag':
            psik = np.asarray(np.imag(psik), order='C')

        elif value == 'abs':
            psik = np.abs(psik)

        else:
            psik = np.abs(psik)**2

        # Define interpolating function to be used later for kmap
        # computation
        psik_interp = interp.RegularGridInterpolator((kx, ky, kz),
                                                     psik,
                                                     bounds_error=False,
                                                     fill_value=np.nan)

        # Set attributes
        self.psik = {
            'kx': kx,
            'ky': ky,
            'kz': kz,
            'E_kin_max': E_kin_max,
            'value': value,
            'data': psik,
            'data_interp': psik_interp
        }

        # Why? Local variables should only live until end of function
        # anyways...
        # Free memory for psik-array
        del psik
Exemplo n.º 5
0
#declare l = 4.5;
union{
 cylinder {  <0,0,0>,<0,0,l>,0.075 }
 cone     {  <0,0,l>, 0.15, <0,0,l+0.5>,0.0 }
 pigment { rgb<0,0,0>}
}
// hemisphere
#declare k = %g;
difference{
    sphere{0,k}
    sphere{0,k-0.01}// adjust for the thickness you want
    box{<-k-0.1,-k-0.1,-k-0.1>,<k+0.1,k+0.1,0.0>}
    pigment{rgbt<1,0,0,0.5>}
    scale 1
    }
'''%(energy_to_k(E_kin))

real_stuff = '''
// x-axis
#declare l = 6;
union{
 cylinder {  <0,0,0>,<l,0,0>,0.075 }
 cone     {  <l,0,0>, 0.15, <l+0.5,0,0>,0.0 }
 pigment { rgb<0,0,0>}
}
// y-axis
#declare l = 7;
union{
 cylinder {  <0,0,0>,<0,-l,0>,0.075 }
 cone     {  <0,-l,0>, 0.15, <0,-l-0.5,0>,0.0 }
 pigment { rgb<0,0,0>}
Exemplo n.º 6
0
    def init_from_orbital_photonenergy(cls, name, orbital, parameters):
        """Returns a SlicedData object with the data[photonenergy,kx,ky] 
           computed from the kmaps of one orbital for a series of
           photon energies.

        Args:
            name (str): name for SlicedData object
            orbital (list): [ 'URL',dict ]
                dict needs keys: 'energy' and 'name' 
            parameters (list): list of parameters
                hnu_min (float): minimal photon energy
                hnu_max (float): maximal photon energy
                hnu_step (float): stepsize for photon energy
                fermi_energy (float): Fermi energy in eV
                dk (float): Desired k-resolution in kmap in Angstroem^-1.
                phi (float): Euler orientation angle phi in degree.
                theta (float): Euler orientation angle theta in degree.
                psi (float): Euler orientation angle psi in degree.
                Ak_type (string): Treatment of |A.k|^2: either 'no',
                        'toroid' or 'NanoESCA'.
                polarization (string): Either 'p', 's', 'unpolarized', C+', 'C-' or
                        'CDAD'.   
                alpha (float): Angle of incidence plane in degree.
                beta (float): Azimuth of incidence plane in degree.
                gamma (float/str): Damping factor for final state in
                        Angstroem^-1. str = 'auto' sets gamma automatically
                symmetrization (str): either 'no', '2-fold', '2-fold+mirror',
                        '3-fold', '3-fold+mirror','4-fold', '4-fold+mirror'
        Returns:
            (SlicedData): SlicedData containing kmaps for various photon energies
        """

        log = logging.getLogger('kmap')
        orbital = orbital[0]  # only consider first orbital in list!

        # extract parameters
        hnu_min = parameters[0]
        hnu_max = parameters[1]
        hnu_step = parameters[2]
        fermi_energy = parameters[3]
        dk = parameters[4]
        phi, theta, psi = parameters[5], parameters[6], parameters[7]
        Ak_type = parameters[8]
        polarization = parameters[9]
        alpha, beta, gamma = parameters[10], parameters[11], parameters[12]
        symmetrization = parameters[13]

        # binding energy of orbital and work function
        BE = orbital[1]['energy'] - fermi_energy
        Phi = -fermi_energy  # work function

        # determine axis_1 = photon energy
        hnu = np.arange(hnu_min, hnu_max, hnu_step)
        n_hnu = len(hnu)

        axis_1 = ['photonenergy', 'eV', [hnu_min, hnu_max]]

        # determine axis_2 and axis_3 kmax at BE_max
        E_kin_max = hnu[-1] - Phi + BE
        k_max = energy_to_k(E_kin_max)
        nk = int((2 * k_max) / dk) + 1
        k_grid = np.linspace(-k_max, +k_max, nk)
        nk = len(k_grid)
        axis_2 = ['kx', '1/Å', [-k_max, +k_max]]
        axis_3 = ['ky', '1/Å', [-k_max, +k_max]]

        # initialize 3D-numpy array with zeros
        data = np.zeros((n_hnu, nk, nk))
        orbital_names = []

        log.info('Adding orbital to SlicedData Object, please wait!')
        # read orbital from cube-file database
        url = orbital[0]
        log.info('Loading from database: %s' % url)
        with urllib.request.urlopen(url) as f:
            file = f.read().decode('utf-8')
            orbital_data = Orbital(file)

        # loop over photon energies
        for i in range(len(hnu)):

            # kinetic energy of emitted electron
            E_kin = hnu[i] - Phi + BE
            kmap = orbital_data.get_kmap(E_kin, dk, phi, theta, psi, Ak_type,
                                         polarization, alpha, beta, gamma,
                                         symmetrization)
            kmap.interpolate(k_grid, k_grid, update=True)
            data[i, :, :] = kmap.data

        # define meta-data for tool-tip display
        orbital_info = orbital[1]

        meta_data = {
            'Fermi energy (eV)': fermi_energy,
            'Molecular orientation': (phi, theta, psi),
            '|A.k|^2 factor': Ak_type,
            'Polarization': polarization,
            'Incidence direction': (alpha, beta),
            'Symmetrization': symmetrization,
            'Orbital Info': orbital_info
        }

        return cls(name, axis_1, axis_2, axis_3, data, meta_data)
Exemplo n.º 7
0
    def init_from_orbitals(cls, name, orbitals, parameters):
        """Returns a SlicedData object with the data[BE,kx,ky] 
           computed from the kmaps of several orbitals and
           broadened in energy.

        Args:
            name (str): name for SlicedData object
            orbitals (list): [[ 'URL1',dict1], ['URL2',dict2], ...]
                dict needs keys: 'energy' and 'name' 
            parameters (list): list of parameters
                photon_energy (float): Photon energy in eV.
                fermi_energy (float): Fermi energy in eV
                energy_broadening (float): FWHM of Gaussian energy broadenening in eV
                dk (float): Desired k-resolution in kmap in Angstroem^-1.
                phi (float): Euler orientation angle phi in degree.
                theta (float): Euler orientation angle theta in degree.
                psi (float): Euler orientation angle psi in degree.
                Ak_type (string): Treatment of |A.k|^2: either 'no',
                        'toroid' or 'NanoESCA'.
                polarization (string): Either 'p', 's', 'unpolarized', C+', 'C-' or
                        'CDAD'.   
                alpha (float): Angle of incidence plane in degree.
                beta (float): Azimuth of incidence plane in degree.
                gamma (float/str): Damping factor for final state in
                        Angstroem^-1. str = 'auto' sets gamma automatically
                symmetrization (str): either 'no', '2-fold', '2-fold+mirror',
                        '3-fold', '3-fold+mirror','4-fold', '4-fold+mirror'
        Returns:
            (SlicedData): SlicedData containing kmaps of all orbitals
        """

        log = logging.getLogger('kmap')
        # extract parameters
        photon_energy = parameters[0]
        fermi_energy = parameters[1]
        energy_broadening = parameters[2]
        dk = parameters[3]
        phi, theta, psi = parameters[4], parameters[5], parameters[6]
        Ak_type = parameters[7]
        polarization = parameters[8]
        alpha, beta, gamma = parameters[9], parameters[10], parameters[11]
        symmetrization = parameters[12]

        # determine axis_1 from minimal and maximal binding energy
        extend_range = 3 * energy_broadening
        energies = []

        for orbital in orbitals:
            energies.append(orbital[1]['energy'])

        BE_min = min(energies) - fermi_energy - extend_range
        BE_max = max(energies) - fermi_energy + extend_range
        # set energy grid spacing 6 times smaller than broadening
        dBE = energy_broadening / 6
        nBE = int((BE_max - BE_min) / dBE) + 1
        BE = np.linspace(BE_min, BE_max, nBE)
        nBE = len(BE)
        axis_1 = ['-BE', 'eV', [BE_min, BE_max]]

        # determine axis_2 and axis_3 kmax at BE_max
        Phi = -fermi_energy  # work function
        E_kin_max = photon_energy - Phi + BE_max
        k_max = energy_to_k(E_kin_max)
        nk = int((2 * k_max) / dk) + 1
        k_grid = np.linspace(-k_max, +k_max, nk)
        nk = len(k_grid)
        axis_2 = ['kx', '1/Å', [-k_max, +k_max]]
        axis_3 = ['ky', '1/Å', [-k_max, +k_max]]

        # initialize 3D-numpy array with zeros
        data = np.zeros((nBE, nk, nk))
        orbital_names = []

        # add kmaps of orbitals to
        log.info('Adding orbitals to SlicedData Object, please wait!')
        for orbital in orbitals:
            # binding energy of orbital
            BE0 = orbital[1]['energy'] - fermi_energy
            # kinetic energy of emitted electron
            E_kin = photon_energy - Phi + BE0

            # Gaussian weight function
            norm = (1 / np.sqrt(2 * np.pi * energy_broadening**2))
            weight = norm * \
                np.exp(-((BE - BE0)**2 / (2 * energy_broadening**2)))

            url = orbital[0]
            log.info('Loading from database: %s' % url)
            with urllib.request.urlopen(url) as f:
                file = f.read().decode('utf-8')
                orbital_data = Orbital(file)

            orbital_names.append(orbital[1]['name'])
            log.info('Computing k-map for %s' % orbital[1]['name'])

            kmap = orbital_data.get_kmap(E_kin, dk, phi, theta, psi, Ak_type,
                                         polarization, alpha, beta, gamma,
                                         symmetrization)
            kmap.interpolate(k_grid, k_grid, update=True)
            log.info('Adding to 3D-array: %s' % orbital[1]['name'])
            for i in range(len(BE)):
                data[i, :, :] += weight[i] * np.nan_to_num(kmap.data)

        # set NaNs outside photoemission horizon
        KX, KY = np.meshgrid(k_grid, k_grid)
        for i in range(len(BE)):
            E_kin = photon_energy - Phi + BE[i]
            k_max = energy_to_k(E_kin)
            out = np.sqrt(KX**2 + KY**2) > k_max
            tmp = data[i, :, :]
            tmp[out] = np.NaN
            data[i, :, :] = tmp

        # define meta-data for tool-tip display
        orbital_info = {}
        for orbital_name, energy in zip(orbital_names, energies):
            orbital_info[orbital_name] = energy

        meta_data = {
            'Photon energy (eV)': photon_energy,
            'Fermi energy (eV)': fermi_energy,
            'Energy broadening (eV)': energy_broadening,
            'Molecular orientation': (phi, theta, psi),
            '|A.k|^2 factor': Ak_type,
            'Polarization': polarization,
            'Incidence direction': (alpha, beta),
            'Symmetrization': symmetrization,
            'Orbital Info': orbital_info
        }

        return cls(name, axis_1, axis_2, axis_3, data, meta_data)
Exemplo n.º 8
0
    rgbt = np.zeros((isosurface.faceCount(), 4), dtype=float)
    for c in range(3):
        rgbt[:, c] = color[c]
    rgbt[:, 3] = 1  # transparency (I guess)
    isosurface.setFaceColors(rgbt)

    p = gl.GLMeshItem(
        meshdata=isosurface, smooth=True, shader='edgeHilight'
    )  # shader options: 'balloon', 'shaded', 'normalColor', 'edgeHilight'
    p.setGLOptions(
        'translucent')  # choose between 'opaque', 'translucent' or 'additive'
    w.addItem(p)

# add hemisphere for a given kinetic energy
E_kin = 35.0
k = energy_to_k(E_kin)
x = np.linspace(kx[0] / dx, kx[-1] / dx, nx)
y = np.linspace(ky[0] / dy, ky[-1] / dy, ny)
z = np.linspace(0, kz[-1] / dz, nz // 2)
X, Y, Z = np.meshgrid(x, y, z)
scalar_field = X**2 + Y**2 + Z**2
isoval = k**2 / (dx * dy)
color = (0.5, 0.5, 0.5, 0.8)
verts, faces = pg.isosurface(scalar_field, isoval)
verts[:, 0] = verts[:, 0] - nx / 2
verts[:, 1] = verts[:, 1] - ny / 2
verts[:, 2] = verts[:, 2]

isosurface = gl.MeshData(vertexes=verts, faces=faces)
rgbt = np.zeros((isosurface.faceCount(), 4), dtype=float)
for c in range(3):