Esempio n. 1
0
def _get_spins(sample, scell, momtype='e'):

    cell = sample.cell.get_cell()
    fxyz, xyz = supcell_gridgen(cell, scell)

    fpos = sample.cell.get_scaled_positions()
    pos = sample.cell.get_positions()

    sfpos = fpos[None, :, :] + fxyz[:, None, :]
    spos = pos[None, :, :] + xyz[:, None, :]

    spins = (spos * 0.0j)

    k = sample.mm.k
    fc = sample.mm.fc

    ker = np.exp(-2.0j * np.pi * np.dot(k, sfpos.T))
    spins = np.real(fc[None, :, :] * ker[:, None, None])

    # Now turn those into actual dipole moments...
    if momtype == 'e':
        g_e = (cnst.physical_constants['Bohr magneton'][0] *
               cnst.physical_constants['electron g factor'][0]) / cnst.hbar
        gammas = np.repeat([g_e], len(pos))
    elif momtype == 'n':
        gammas = _get_isotope_data(sample.cell.get_chemical_symbols(), 'gamma')
    else:
        raise ValueError('Invalid momtype argument passed to _get_spins')
    gammas = np.repeat(gammas[None, :], len(fxyz), axis=0)

    return spos, spins, gammas
Esempio n. 2
0
    def __init__(self,
                 atoms,
                 mu_pos,
                 isotopes={},
                 isotope_list=None,
                 cutoff=10,
                 overlap_eps=1e-3):

        # Get positions, cell, and species, only things we care about
        self.cell = np.array(atoms.get_cell())
        pos = atoms.get_positions()
        el = np.array(atoms.get_chemical_symbols())

        self.mu_pos = np.array(mu_pos)

        # Is it periodic?
        if np.any(atoms.get_pbc()):
            scell = minimum_supcell(cutoff, self.cell)
        else:
            scell = [1, 1, 1]

        grid_f, grid = supcell_gridgen(self.cell, scell)

        self.grid_f = grid_f

        r = (pos[:, None, :] + grid[None, :, :] -
             self.mu_pos[None, None, :]).reshape((-1, 3))
        rnorm = np.linalg.norm(r, axis=1)
        sphere = np.where(rnorm <= cutoff)[0]
        sphere = sphere[np.argsort(rnorm[sphere])[::-1]]  # Sort by length
        r = r[sphere]
        rnorm = rnorm[sphere]

        self._r = r
        self._rn = rnorm
        self._rn = np.where(self._rn > overlap_eps, self._rn, np.inf)
        self._ri = sphere
        self._dT = (3 * r[:, :, None] * r[:, None, :] /
                    self._rn[:, None, None]**2 - np.eye(3)[None, :, :]) / 2

        self._an = len(pos)
        self._gn = self.grid_f.shape[0]
        self._a_i = self._ri // self._gn
        self._ijk = self.grid_f[self._ri % self._gn]

        # Get gammas
        self.gammas = _get_isotope_data(el, 'gamma', isotopes, isotope_list)
        self.gammas = self.gammas[self._a_i]
        Dn = _dip_constant(self._rn * 1e-10, m_gamma, self.gammas)
        De = _dip_constant(
            self._rn * 1e-10, m_gamma,
            cnst.physical_constants['electron gyromag. ratio'][0])

        self._D = {'n': Dn, 'e': De}

        # Start with all zeros
        self.spins = rnorm * 0
Esempio n. 3
0
    def extract(s, sel_i, sel_j, isotopes, isotope_list, self_coupling,
                block_size):

        # Selections
        if sel_i is None:
            sel_i = AtomSelection.all(s)
        elif not isinstance(sel_i, AtomSelection):
            sel_i = AtomSelection(s, sel_i)

        if sel_j is None:
            sel_j = sel_i
        elif not isinstance(sel_j, AtomSelection):
            sel_j = AtomSelection(s, sel_j)

        # Find gammas
        elems = s.get_chemical_symbols()

        gammas = _get_isotope_data(elems, 'gamma', isotopes, isotope_list)

        # Viable pairs
        pairs = [(i, j) for i in sel_i.indices for j in sel_j.indices]
        if not self_coupling:
            pairs = [p for p in pairs if p[0] != p[1]]

        pairs = np.array(pairs).T
        # Need to sort them and remove any duplicates, also take i < j as
        # convention
        pairs = np.array(
            list(zip(*set([tuple(x) for x in np.sort(pairs, axis=0).T]))))

        pos = s.get_positions()

        # Split this in blocks to make sure we don't clog the memory

        d_ij = np.zeros((0, ))
        v_ij = np.zeros((0, 3))

        npairs = pairs.shape[1]

        for b_i in range(0, npairs, block_size):
            block = pairs.T[b_i:b_i + block_size]
            r_ij = pos[block[:, 1]] - pos[block[:, 0]]
            # Reduce to NN
            r_ij, _ = minimum_periodic(r_ij, s.get_cell(), exclude_self=True)
            # Distance
            R_ij = np.linalg.norm(r_ij, axis=1)
            # Versors
            v_ij = np.concatenate([v_ij, r_ij / R_ij[:, None]], axis=0)
            # Couplings
            d_ij = np.concatenate([
                d_ij,
                _dip_constant(R_ij * 1e-10, gammas[block[:, 0]],
                              gammas[block[:, 1]])
            ])

        return {tuple(ij): [d_ij[l], v_ij[l]] for l, ij in enumerate(pairs.T)}
Esempio n. 4
0
def magnetic_constant(elem, value='gamma', iso=None):

    if elem in ('e', 'mu') or (elem == 'H' and iso in (None, 1)):
        return _nmr_basic[elem][value]
    elif _get_isotope_data is not None:
        val = _get_isotope_data([elem], value, isotope_list=[iso])[0]
        return val / (2 * np.pi * 1e6 if value == 'gamma' else 1.0)
    else:
        raise RuntimeError('A valid installation of Soprano is necessary '
                           'for magnetic data of elements other than 1H')
Esempio n. 5
0
    def extract(s, force_recalc, use_q_isotopes, isotopes, isotope_list):

        if (not s.has(EFGDiagonal.default_name + '_evals_hsort')
                or force_recalc):
            EFGDiagonal.get(s)

        # First thing, build the isotope dictionary
        elems = s.get_chemical_symbols()

        # Is isotope list valid?
        if isotope_list is not None and len(isotope_list) != len(elems):
            print('WARNING - invalid isotope_list, ignoring')
            isotope_list = None

        q_list = _get_isotope_data(elems, 'Q', isotopes, isotope_list,
                                   use_q_isotopes)

        return EFG_TO_CHI * q_list * EFGVzz.get(s)
Esempio n. 6
0
    def extract(s, sel_i, sel_j, isotopes, isotope_list, self_coupling):

        # Selections
        if sel_i is None:
            sel_i = AtomSelection.all(s)
        elif not isinstance(sel_i, AtomSelection):
            sel_i = AtomSelection(s, sel_i)

        if sel_j is None:
            sel_j = sel_i
        elif not isinstance(sel_j, AtomSelection):
            sel_j = AtomSelection(s, sel_j)

        # Find gammas
        elems = s.get_chemical_symbols()
        _nmr_data = _get_nmr_data()

        gammas = _get_isotope_data(elems, 'gamma', isotopes, isotope_list)

        # Viable pairs
        pairs = [(i, j) for i in sel_i.indices
                 for j in sel_j.indices]
        if not self_coupling:
            pairs = [p for p in pairs if p[0] != p[1]]

        pairs = np.array(pairs).T
        # Need to sort them and remove any duplicates, also take i < j as
        # convention
        pairs = np.array(zip(*set([tuple(x)
                                   for x in np.sort(pairs, axis=0).T])))

        pos = s.get_positions()

        r_ij = pos[pairs[1]] - pos[pairs[0]]
        # Reduce to NN
        r_ij, _ = minimum_periodic(r_ij, s.get_cell(), exclude_self=True)
        # Distance
        R_ij = np.linalg.norm(r_ij, axis=1)
        # Versors
        v_ij = r_ij/R_ij[:, None]
        # Couplings
        d_ij = _dip_constant(R_ij*1e-10, gammas[pairs[0]], gammas[pairs[1]])

        return {tuple(ij): [d_ij[l], v_ij[l]] for l, ij in enumerate(pairs.T)}
Esempio n. 7
0
    def extract(s, force_recalc, use_q_isotopes, isotopes, isotope_list):

        if (not s.has(EFGDiagonal.default_name + '_evals_hsort')
                or force_recalc):
            EFGDiagonal.get(s)

        # First thing, build the isotope dictionary
        elems = s.get_chemical_symbols()

        # Is isotope list valid?
        if isotope_list is not None and len(isotope_list) != len(elems):
            print('WARNING - invalid isotope_list, ignoring')
            isotope_list = None

        q_list = _get_isotope_data(elems, 'Q', isotopes, isotope_list,
                                   use_q_isotopes)

        # Conversion constant
        k = cnst.physical_constants['atomic unit of electric field '
                                    'gradient'][0] * cnst.e * 1e-31 / cnst.h

        return k * q_list * EFGVzz.get(s)
Esempio n. 8
0
    def extract(s, cutoff, isonuclear, isotopes, isotope_list):

        # Supercell size
        scell_shape = minimum_supcell(cutoff, s.get_cell())
        _, scell = supcell_gridgen(s.get_cell(), scell_shape)

        pos = s.get_positions()
        elems = np.array(s.get_chemical_symbols())

        gammas = _get_isotope_data(elems, 'gamma', isotopes, isotope_list)

        dip_rss = []

        for i, el in enumerate(elems):

            # Distances?
            if not isonuclear:
                rij = pos.copy()
                gj = np.tile(gammas, len(scell))
            else:
                rij = pos[np.where(elems == el)]
                gj = gammas[i]
            rij = rij[None, :, :]+scell[:, None, :]-pos[i, None, None]
            Rij = np.linalg.norm(rij.reshape((-1, 3)), axis=-1)
            # Valid indices?
            ij = np.where((Rij > 0) & (Rij <= cutoff))
            Rij = Rij[ij]*1e-10
            try:
                gj = gj[ij]
            except IndexError:
                pass

            dip = _dip_constant(Rij, gammas[i], gj)
            dip_rss.append(np.sqrt(np.sum(dip**2)))

        return np.array(dip_rss)
Esempio n. 9
0
def compute_hfine_tensor(points,
                         spins,
                         cell=None,
                         self_i=0,
                         species='e',
                         cut_r=10,
                         lorentz=True,
                         fermi_mm=0):
    """Compute the hyperfine tensor experienced at point of index i generated
    by a number of localised spins at points, for a given periodic unit cell
    and species.

    | Args:
    |   points (np.ndarray): coordinates of points at which the spins are
    |                        localised
    |   spins (np.ndarray):  magnetic moments (as spin quantum number, e.g.
    |                        0.5 for an electron or 1H nucleus with spin up)
    |   cell (np.ndarray):   unit cell (if None, considered non-periodic)
    |   self_i (int):        index of point at which to compute the tensor.
    |                        Local spin density will give rise to a Fermi
    |                        contact term
    |   species (str or [str]): symbol or list of symbols identifying the
    |                           species generating the magnetic field.
    |                           Determines the magnetic moments
    |   cut_r (float):       cutoff radius for dipolar component calculation
    |   lorentz (bool):      if True, include a Lorentz term (average bulk
    |                        magnetization). Default is True
    |   fermi_mm (float):    Magnetic moment density at site i to use for 
    |                        computation of the Fermi contact term. Units
    |                        are Bohr magnetons/Ang^3

    | Returns:
    |   HT (np.ndarray):    hyperfine tensor at point i
    """

    N = len(points)

    magmoms = np.array(spins).astype(float)
    species = np.array(species).astype('S2')
    if species.shape == ():
        species = np.repeat(species[None], N)
    for i, s in enumerate(species):
        if s == b'e':
            mm = 2 * _bohrmag
        elif s == b'mu':
            mm = mu_cnst.m_gamma * cnst.hbar
        else:
            mm = _get_isotope_data(s, 'gamma')[0] * cnst.hbar
        magmoms[i] *= mm * abs(cnst.physical_constants['electron g factor'][0])

    # Do we need a supercell?
    r = np.array(points) - points[self_i]
    if cell is not None:
        scell = minimum_supcell(cut_r, latt_cart=cell)
        fxyz, xyz = supcell_gridgen(cell, scell)
        r = r[:, None, :] + xyz[None, :, :]
    else:
        r = r[:, None, :]

    rnorm = np.linalg.norm(r, axis=-1)
    # Expunge the ones that are outside of the sphere
    sphere = np.where(rnorm <= cut_r)
    r = r[sphere]
    rnorm = rnorm[sphere]
    magmoms = magmoms[sphere[0]]

    # Find the contact point
    self_i = np.argmin(rnorm)
    magmoms[self_i] = 0

    rnorm_inv = 1.0 / np.where(rnorm > 0, rnorm, np.inf)
    rdyad = r[:, None, :] * r[:, :, None]
    rdip = 3 * rdyad * rnorm_inv[:, None, None]**2 - np.eye(3)[None, :, :]

    HT = np.sum(magmoms[:, None, None] * rdip * rnorm_inv[:, None, None]**3,
                axis=0)
    HT *= cnst.mu_0 / (4 * np.pi) * 1e30

    # Add Lorentz term
    if cell is not None and lorentz:
        avgM = np.sum(magmoms) * 3.0 / (4.0 * np.pi * cut_r**3)
        HT += np.eye(3) * avgM * cnst.mu_0 / 3.0 * 1e30
    # Add contact term
    if fermi_mm:
        fermi_mm *= (_bohrmag *
                     abs(cnst.physical_constants['electron g factor'][0]))
        HT += np.eye(3) * fermi_mm * 2.0 / 3.0 * cnst.mu_0 * 1e30

    return HT
Esempio n. 10
0
    def dq_buildup(self, sel_i, sel_j=None, t_max=1e-3, t_steps=1000,
                   R_cut=3, kdq=0.155, A=1, tau=np.inf):
        """
        Return a dictionary of double quantum buildup curves for given pairs
        of atoms, built according to the theory given in:

        G. Pileio et al., "Analytical theory of gamma-encoded double-quantum
        recoupling sequences in solid-state nuclear magnetic resonance"
        Journal of Magnetic Resonance 186 (2007) 65-74

        | Args:
        |   sel_i (AtomSelection or [int]): Selection or list of indices of 
        |                                   atoms for which to compute the
        |                                   curves. By default is None
        |                                   (= all of them).
        |   sel_i (AtomSelection or [int]): Selection or list of indices of 
        |                                   atoms for which to compute the
        |                                   curves with sel_i. By default is 
        |                                   None (= same as sel_i).
        |   t_max (float): maximum DQ buildup time, in seconds. Default 
        |                  is 1e-3.
        |   t_steps (int): number of DQ buildup time steps. Default is 1000.
        |   R_cut (float): cutoff radius for which periodic copies to consider
        |                  in each pair, in Angstrom. Default is 3.
        |   kdq (float): same as the k constant in eq. 35 of the reference. A
        |                parameter depending on the specific sequence used.
        |                Default is 0.155. 
        |   A (float): overall scaling factor for the curve. Default is 1.
        |   tau (float): exponential decay factor for the curve. Default
        |                is np.inf.

        | Returns:
        |   curves (dict): a dictionary of all buildup curves indexed by pair,
        |                  plus the time axis in seconds as member 't'.
        """

        tdq = np.linspace(0, t_max, t_steps)

       # Selections
        if sel_i is None:
            sel_i = AtomSelection.all(s)
        elif not isinstance(sel_i, AtomSelection):
            sel_i = AtomSelection(self._sample, sel_i)

        if sel_j is None:
            sel_j = sel_i
        elif not isinstance(sel_j, AtomSelection):
            sel_j = AtomSelection(self._sample, sel_j)

        # Find gammas
        elems = self._sample.get_chemical_symbols()
        gammas = _get_isotope_data(elems, 'gamma', {}, self._isos)

        # Need to sort them and remove any duplicates, also take i < j as
        # convention
        pairs = [tuple(sorted((i, j))) for i in sorted(sel_i.indices)
                 for j in sorted(sel_j.indices)]

        scell_shape = minimum_supcell(R_cut, latt_cart=self._sample.get_cell())
        nfg, ng = supcell_gridgen(self._sample.get_cell(), scell_shape)

        pos = self._sample.get_positions()

        curves = {'t': tdq}

        for ij in pairs:

            r = pos[ij[1]]-pos[ij[0]]

            all_r = r[None, :]+ng

            all_R = np.linalg.norm(all_r, axis=1)

            # Apply cutoff
            all_R = all_R[np.where((all_R <= R_cut)*(all_R > 0))]
            n = all_R.shape[0]

            bij = _dip_constant(all_R*1e-10, gammas[ij[0]], gammas[ij[1]])

            th = 1.5*kdq*abs(bij[:, None])*tdq[None, :]*2*np.pi
            x = (2*th/np.pi)**0.5

            Fs, Fc = fresnel(x*2**0.5)

            x[:, 0] = np.inf

            bdup = 0.5-(1.0/(x*8**0.5)) * \
                (Fc*np.cos(2*th) + Fs*np.sin(2*th))
            bdup[:, 0] = 0

            curves[ij] = A*np.sum(bdup, axis=0)*np.exp(-tdq/tau)

        return curves