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
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
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)}
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')
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)
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)}
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)
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)
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
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