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 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, 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 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