Пример #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
Пример #2
0
    def __init__(self, seedname, gw_fac=3, path=''):

        super(PEPChargeDistribution, self).__init__(seedname, gw_fac, path)

        # Partition the charges using the Becke method
        # Becke, A. D. ‘A multicenter numerical integration scheme for polyatomic molecules’
        # J. Chem. Phys. 1988, 88, p 2547-2553. http://dx.doi.org/10.1063/1.454033
        xyz = self._elec_den.xyz
        maxR = max_distance_in_cell(self.cell)
        scell = minimum_supcell(maxR, self.cell)
        scgfrac, scgxyz = supcell_gridgen(self.cell, scell)

        # For each point, find their minimum periodic distance from each ion
        pos = self.positions.T
        scgion = pos[:, :, None] + (scgxyz.T)[:, None, :]

        rA = np.zeros(
            (xyz.shape[1], xyz.shape[2], xyz.shape[3], len(self.positions)))

        for i in range(xyz.shape[1]):
            for j in range(xyz.shape[2]):
                rA[i, j, :, :] = np.amin(np.linalg.norm(
                    xyz[:, i, j, :, None, None] - scgion[:, None, :, :],
                    axis=0),
                                         axis=-1)

        rAB = np.linalg.norm(pos[:, :, None] - pos[:, None, :], axis=0)
        rAB += np.diag([np.inf] * len(rAB))
        muAB = ((rA[:, :, :, :, None] - rA[:, :, :, None, :]) /
                rAB[None, None, None, :, :])

        sk = 0.5 * (1.0 - _fk(muAB, 3))
        # Set the diagonal to 1 so that it doesn't affect the products
        ii = range(len(self.positions))
        sk[:, :, :, ii, ii] = 1

        # Now weight functions
        wA = np.prod(sk, axis=-1)
        self._wA = wA / np.sum(wA, axis=-1)[:, :, :, None]

        # Finally, partition the density
        self._rhopart = self._rho[:, :, :, None] * self._wA
        self._rhopart_G = np.fft.fftn(self._rhopart, axes=(0, 1, 2))

        Gnorm = np.linalg.norm(self._g_grid, axis=0)
        Gnorm_fixed = np.where(Gnorm > 0, Gnorm, np.inf)

        vol = self.volume

        self._Vpart_G = 4 * np.pi / Gnorm_fixed[:, :, :, None]**2 * (
            self._rhopart_G / vol)

        # Now compute interaction energy of ions
        self._rhoion_G = self._rhopart_G + self._rhoi_G
        self._Vion_G = self._Vpart_G + self._Vi_G

        self._ionE = np.real(
            np.sum(self._rhoion_G * np.conj(np.sum(
                self._Vion_G, axis=-1))[:, :, :, None])) * _cK * cnst.e * 1e10
Пример #3
0
 def __init__(self, a, sigma=2.0, rcut=10):
     shape = minimum_supcell(rcut, a.get_cell())
     igrid, grid = supcell_gridgen(a.get_cell(), shape)
     self.p = a.get_positions()[None, :, :] + grid[:, None, :]
     self.p = self.p.reshape((-1, 3))
     sph_i = np.where(np.linalg.norm(self.p, axis=1) <= rcut)
     self.p = self.p[sph_i]
     self.s = sigma
Пример #4
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
Пример #5
0
    def from_box(atoms, abc0, abc1, periodic=False, scaled=False):
        """Generate a selection for the given Atoms object of all atoms within
        a given box volume.

        | Args:
        |   atoms (ase.Atoms): Atoms object on which to perform selection
        |   abc0 ([float, float, float]): bottom corner of box
        |   abc1 ([float, float, float]): top corner of box
        |   periodic (Optional[bool]): if True, include periodic copies of the
        |                              atoms
        |   scaled (Optional[bool]): if True, consider scaled (fractional)
        |                            coordinates instead of absolute ones

        | Returns:
        |   selection (AtomSelection)

        """

        if scaled:
            pos = atoms.get_scaled_positions()
        else:
            pos = atoms.get_positions()
        # Do we need periodic copies?
        if periodic and any(atoms.get_pbc()):
            # Get the range
            max_r = np.linalg.norm(np.array(abc1) - abc0)
            scell_shape = minimum_supcell(max_r,
                                          latt_cart=atoms.get_cell(),
                                          pbc=atoms.get_pbc())
            grid_frac, grid = supcell_gridgen(atoms.get_cell(), scell_shape)
            if scaled:
                pos = (pos[:, None, :] + grid_frac[None, :, :])
            else:
                pos = (pos[:, None, :] + grid[None, :, :])

        where_i = np.where(
            np.all(pos > abc0, axis=-1) & np.all(pos < abc1, axis=-1))[:2]

        sel_i = where_i[0]

        sel = AtomSelection(atoms, sel_i)
        if periodic:
            sel.set_array('cell_indices', grid_frac[where_i[1]])

        return sel
Пример #6
0
    def from_sphere(atoms, center, r, periodic=False, scaled=False):
        """Generate a selection for the given Atoms object of all atoms within
        a given spherical volume.

        | Args:
        |   atoms (ase.Atoms): Atoms object on which to perform selection
        |   center ([float, float, float]): center of the sphere
        |   r (float): radius of the sphere
        |   periodic (Optional[bool]): if True, include periodic copies of the
        |                              atoms
        |   scaled (Optional[bool]): if True, consider scaled (fractional)
        |                            coordinates instead of absolute ones

        | Returns:
        |   selection (AtomSelection)

        """

        if scaled:
            pos = atoms.get_scaled_positions()
        else:
            pos = atoms.get_positions()
        # Do we need periodic copies?
        if periodic and any(atoms.get_pbc()):
            # Get the range
            r_bounds = minimum_supcell(r,
                                       latt_cart=atoms.get_cell(),
                                       pbc=atoms.get_pbc())
            grid_frac, grid = supcell_gridgen(atoms.get_cell(), r_bounds)
            if scaled:
                pos = (pos[:, None, :] + grid_frac[None, :, :])
            else:
                pos = (pos[:, None, :] + grid[None, :, :])

        where_i = np.where(np.linalg.norm(pos - center, axis=-1) <= r)

        sel_i = where_i[0]

        sel = AtomSelection(atoms, sel_i)
        if periodic:
            sel.set_array('cell_indices', grid_frac[where_i[1]])

        return sel
Пример #7
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)
Пример #8
0
    def __init__(self, seedname, gw_fac=3, path=''):

        super().__init__(seedname, gw_fac, path)
        """
        Partition the charges using the Becke method
        Becke, A. D. ‘A multicenter numerical integration scheme for polyatomic molecules’
        J. Chem. Phys. 1988, 88, p 2547-2553. http://dx.doi.org/10.1063/1.454033
        """
        xyz = self._elec_den.xyz
        maxR = max_distance_in_cell(self.cell)
        scell = minimum_supcell(maxR, self.cell)
        scgfrac, scgxyz = supcell_gridgen(self.cell, scell)

        # For each point, find their minimum periodic distance from each ion
        pos = self.positions.T
        scgion = pos[:, :, None] + (scgxyz.T)[:, None, :]

        # Grid
        xn, yn, zn = xyz.shape[1:]
        N = len(self.positions)

        rA = np.zeros((xn, yn, zn, N))

        for i in range(xn):
            for j in range(yn):
                rA[i, j, :, :] = np.amin(np.linalg.norm(
                    xyz[:, i, j, :, None, None] - scgion[:, None, :, :],
                    axis=0),
                                         axis=-1)

        rAB = np.linalg.norm(pos[:, :, None] - pos[:, None, :], axis=0)
        rAB += np.diag([np.inf] * len(rAB))
        muAB = ((rA[:, :, :, :, None] - rA[:, :, :, None, :]) /
                rAB[None, None, None, :, :])

        sk = 0.5 * (1.0 - _fk(muAB, 3))
        # Set the diagonal to 1 so that it doesn't affect the products
        ii = range(len(self.positions))
        sk[:, :, :, ii, ii] = 1

        # Now weight functions
        wA = np.prod(sk, axis=-1)
        self._wA = wA / np.sum(wA, axis=-1)[:, :, :, None]

        # Finally, partition the density
        self._rhopart = self._rho[:, :, :, None] * self._wA
        self._rhopart_G = np.fft.fftn(self._rhopart, axes=(0, 1, 2))

        Gnorm = np.linalg.norm(self._g_grid, axis=0)
        Gnorm_fixed = np.where(Gnorm > 0, Gnorm, np.inf)

        vol = self.volume

        self._Vpart_G = 4 * np.pi / Gnorm_fixed[:, :, :, None]**2 * (
            self._rhopart_G / vol)

        # The individual ion density must be redefined too
        self._rhoipart_G = np.zeros((xn, yn, zn, N)) * 0.j
        pos = self.atoms.get_positions()
        for i, p in enumerate(pos):
            self._rhoipart_G[:, :, :, i] = (
                self._q[i] * np.exp(-1.0j * np.sum(self._g_grid[:, :, :, :] *
                                                   p[:, None, None, None],
                                                   axis=0) - 0.5 *
                                    (self._gw[i] * Gnorm)**2))

        # # Now compute interaction energy of ions
        self._rhotot_G = self._rhoe_G + self._rhoi_G
        self._Vtot_G = self._Ve_G + self._Vi_G

        for r in (self._rhoe_G, self._rhoi_G):
            for v in (self._Ve_G, self._Vi_G):
                print(
                    np.real(np.sum(r * np.conj(np.sum(v, axis=-1)))) * _cK *
                    cnst.e * 1e10)

        self._ionE = np.real(
            np.sum(self._rhotot_G * np.conj(np.sum(
                self._Vtot_G, axis=-1)))) * _cK * cnst.e * 1e10
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
Пример #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
Пример #11
0
def molecularNeighbourhoodGen(struct, mols, central_mol=0, max_R=10,
                              method='com', use_supercell=False):
    """Generator function to create a spherical molecular neighbourhood. Given
    a structure and its molecules as returned by the Molecules property, 
    produce supercell structures that contain one molecule each, progressively
    further away from the one indicated as central.

    | Args:
    |   struct (ase.Atoms): original structure
    |   mols ([ase.AtomsSelection]): list of molecules, as returned by the
    |                                soprano.properties.linkage.Molecules 
    |                                class.
    |   central_mol (int): index of the molecule whose centre of mass is 
    |                      considered central. Default is 0.
    |   max_R (float): maximum radius of the neighbourhood sphere. Default is 
    |                  10 Ang.
    |   method (str): method to compute distance between molecules. 'com' 
    |                 means using the center of mass. 'nearest' means using
    |                 the closest atom. Default is 'com'.
    |   use_supercell (bool): if True, all returned structures will have a 
    |                         cell large enough to contain the entire
    |                         neighbourhood. Default is False.

    | Returns:
    |   molecularNeighbourhoodGen (generator): an iterator object that yields
    |                                         structures within the given 
    |                                         spherical neighbourhood.                                         

    """

    # Supercell size?
    scell = minimum_supcell(max_R, struct.get_cell())
    fgrid, grid = supcell_gridgen(struct.get_cell(), scell)

    # Center?
    mol_structs = [m.subset(struct) for m in mols]
    mol_coms = np.array([a.get_center_of_mass() for a in mol_structs])

    # Origin
    p0 = mol_coms[central_mol]

    # Positions?
    if method == 'com':
        positions = mol_coms[None, :, :]+grid[:, None, :]-p0
    elif method == 'nearest':
        positions = np.zeros((len(grid), len(mols), 3))
        for i, a in enumerate(mol_structs):
            dp = a.get_positions() - p0
            p = dp[None, :, :] + grid[:, None, :]
            # Closest one?
            positions[:, i, :] = p[range(len(grid)),
                                   np.argmin(np.linalg.norm(p, axis=-1),
                                             axis=1)]
    else:
        raise RuntimeError('Invalid method passed to '
                           'molecularNeighbourhoodGen')

    # Order of appearance?
    distances = np.linalg.norm(positions, axis=-1)
    sphere = np.where(distances <= max_R)
    distances = distances[sphere[0], sphere[1]]
    fxyz = fgrid[sphere[0]]
    xyz = grid[sphere[0]]
    mol_i = np.arange(len(mols))[sphere[1]]
    positions = positions[sphere[0], sphere[1]]

    # Now the order
    order = np.argsort(distances)

    for i in order:
        a = mol_structs[mol_i[i]].copy()
        # Create the structure
        if use_supercell:
            a.set_cell(np.dot(np.diag(scell), a.get_cell()))
        a.set_positions(a.get_positions() + xyz[i])

        # Add some info
        a.info['neighbourhood_info'] = {
            'molecule_index': mol_i[i],
            'molecule_cell': fxyz[i],
            'molecule_distance': distances[i]
        }

        yield a