Exemplo n.º 1
0
 def test_first_neighbours(self):
     i = [1, 1, 1, 1, 3, 3, 3]
     self.assertArrayAlmostEqual(first_neighbours(5, i),
                                 [-1, 0, -1, 4, -1, 7])
     i = [0, 1, 2, 3, 4, 5]
     self.assertArrayAlmostEqual(first_neighbours(6, i),
                                 [0, 1, 2, 3, 4, 5, 6])
Exemplo n.º 2
0
    def calculate(self, atoms, properties, system_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        # construct neighbor list
        i_p, j_p, r_p, r_pc = neighbour_list('ijdD',
                                             atoms=atoms,
                                             cutoff=self.cutoff)

        nb_atoms = len(self.atoms)
        nb_pairs = len(i_p)

        # normal vectors
        n_pc = (r_pc.T / r_p).T
        nx_p, ny_p, nz_p = n_pc.T

        # construct triplet list
        first_i = first_neighbours(nb_atoms, i_p)
        ij_t, ik_t = triplet_list(first_i)

        # calculate energy
        G_t = self.G(r_pc[ij_t], r_pc[ik_t])
        xi_p = np.bincount(ij_t, weights=G_t, minlength=nb_pairs)
        F_p = self.F(r_p, xi_p)
        epot = 0.5 * np.sum(F_p)

        d1G_t = self.d1G(r_pc[ij_t], r_pc[ik_t])
        d2F_d2G_t = (self.d2F(r_p[ij_t], xi_p[ij_t]) *
                     self.d2G(r_pc[ij_t], r_pc[ik_t]).T).T
        # calculate forces (per pair)
        fx_p = \
            self.d1F(r_p, xi_p) * n_pc[:, 0] + \
            self.d2F(r_p, xi_p) * np.bincount(ij_t, d1G_t[:, 0], minlength=nb_pairs) + \
            np.bincount(ik_t, d2F_d2G_t[:, 0], minlength=nb_pairs)
        fy_p = \
            self.d1F(r_p, xi_p) * n_pc[:, 1] + \
            self.d2F(r_p, xi_p) * np.bincount(ij_t, d1G_t[:, 1], minlength=nb_pairs) + \
            np.bincount(ik_t, d2F_d2G_t[:, 1], minlength=nb_pairs)
        fz_p = \
            self.d1F(r_p, xi_p) * n_pc[:, 2] + \
            self.d2F(r_p, xi_p) * np.bincount(ij_t, d1G_t[:, 2], minlength=nb_pairs) + \
            np.bincount(ik_t, d2F_d2G_t[:, 2], minlength=nb_pairs)

        # collect atomic forces
        fx_n = 0.5 * (np.bincount(i_p, weights=fx_p) -
                      np.bincount(j_p, weights=fx_p))
        fy_n = 0.5 * (np.bincount(i_p, weights=fy_p) -
                      np.bincount(j_p, weights=fy_p))
        fz_n = 0.5 * (np.bincount(i_p, weights=fz_p) -
                      np.bincount(j_p, weights=fz_p))

        f_n = np.transpose([fx_n, fy_n, fz_n])

        self.results = {'energy': epot, 'forces': f_n}
Exemplo n.º 3
0
def hydrogenate(a,
                cutoff,
                bond_length,
                b=None,
                mask=[True, True, True],
                exclude=None,
                vacuum=None):
    """
    Hydrogenate a slab of material at its periodic boundary conditions.
    Boundary conditions are turned into nonperiodic.

    Parameters
    ----------
    a : ase.Atoms
        Atomic configuration.
    cutoff : float
        Cutoff for neighbor counting.
    bond_length : float
        X-H bond length for hydrogenation.
    b : ase.Atoms, optional
        If present, this is the configuration to hydrogenate. Number of atoms
        must be identical to a object. All bonds present in a but not present
        in b will be hydrogenated in b.
    mask : list of bool
        Cartesian directions which to hydrogenate, only if b argument is not
        given.
    exclude : array_like
        Boolean array masking atoms to be excluded from hydrogenation.
    vacuum : float, optional
        Add this much vacuum after hydrogenation.

    Returns
    -------
    a : ase.Atoms
        Atomic configuration of the hydrogenated slab.
    """
    if b is None:
        b = a.copy()
        b.set_pbc(np.logical_not(mask))

    if exclude is None:
        exclude = np.zeros(len(a), dtype=bool)

    i_a, j_a, D_a, d_a = neighbour_list('ijDd', a, cutoff)
    i_b, j_b = neighbour_list('ij', b, cutoff)

    firstneigh_a = first_neighbours(len(a), i_a)
    firstneigh_b = first_neighbours(len(b), i_b)

    coord_a = np.bincount(i_a, minlength=len(a))
    coord_b = np.bincount(i_b, minlength=len(b))

    hydrogens = []
    # Surface atoms have coord_a != coord_b. Those need hydrogenation
    for k in np.arange(len(a))[np.logical_and(coord_a != coord_b,
                                              np.logical_not(exclude))]:
        l1_a = firstneigh_a[k]
        l2_a = firstneigh_a[k + 1]
        l1_b = firstneigh_b[k]
        l2_b = firstneigh_b[k + 1]
        n_H = 0
        for l_a in range(l1_a, l2_a):
            assert i_a[l_a] == k
            bond_exists = False
            for l_b in range(l1_b, l2_b):
                assert i_b[l_b] == k
                if j_a[l_a] == j_b[l_b]:
                    bond_exists = True
            if not bond_exists:
                # Bond existed before cut
                hydrogens += [
                    b[k].position + bond_length * D_a[l_a] / d_a[l_a]
                ]
                n_H += 1
        assert n_H == coord_a[k] - coord_b[k]

    if hydrogens == []:
        raise RuntimeError('No Hydrogen created.')

    b += ase.Atoms(['H'] * len(hydrogens), hydrogens)

    if vacuum is not None:
        axis = []
        for i in range(3):
            if mask[i]:
                axis += [i]
        b.center(vacuum, axis=axis)

    return b
Exemplo n.º 4
0
    def hessian_matrix(self, atoms, divide_by_masses=False):
        """
        Calculate the Hessian matrix for a polydisperse systems where atoms interact via a pair potential.
        For an atomic configuration with N atoms in d dimensions the hessian matrix is a symmetric, hermitian matrix
        with a shape of (d*N,d*N). The matrix is due to the cutoff function a sparse matrix, which consists of dense blocks of shape (d,d), which
        are the mixed second derivatives. The result of the derivation for a pair potential can be found in:
        L. Pastewka et. al. "Seamless elastic boundaries for atomistic calculations", Phys. Ev. B 86, 075459 (2012).

        Parameters
        ----------
        atoms: ase.Atoms
            Atomic configuration in a local or global minima.

        divide_by_masses: bool
            Divide the block "l,m" by the corresponding atomic masses "sqrt(m_l, m_m)" to obtain dynamical matrix.

        Restrictions
        ----------
        This method is currently only implemented for three dimensional systems

        """

        if self.atoms is None:
            self.atoms = atoms

        f = self.f
        nat = len(self.atoms)
        if atoms.has("size"):
            size = self.atoms.get_array("size")
        else:
            raise AttributeError(
                "Attribute error: Unable to load atom sizes from atoms object! Probably missing size array."
            )

        i_n, j_n, dr_nc, abs_dr_n = neighbour_list(
            "ijDd", self.atoms,
            f.get_maxSize() * f.get_cutoff())
        ijsize = f.mix_sizes(size[i_n], size[j_n])

        # Mask neighbour list to consider only true neighbors
        mask = abs_dr_n <= f.get_cutoff() * ijsize
        i_n = i_n[mask]
        j_n = j_n[mask]
        dr_nc = dr_nc[mask]
        abs_dr_n = abs_dr_n[mask]
        ijsize = ijsize[mask]
        first_i = first_neighbours(nat, i_n)

        if divide_by_masses:
            mass_nat = self.atoms.get_masses()
            geom_mean_mass_n = np.sqrt(mass_nat[i_n] * mass_nat[j_n])

        # Hessian
        de_n = f.first_derivative(abs_dr_n, ijsize)
        dde_n = f.second_derivative(abs_dr_n, ijsize)
        e_nc = (dr_nc.T / abs_dr_n).T
        H_ncc = -(dde_n *
                  (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3)).T).T
        H_ncc += -(de_n / abs_dr_n *
                   (np.eye(3, dtype=e_nc.dtype) -
                    (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3))).T).T

        if divide_by_masses:
            H = bsr_matrix(((H_ncc.T / geom_mean_mass_n).T, j_n, first_i),
                           shape=(3 * nat, 3 * nat))

        else:
            H = bsr_matrix((H_ncc, j_n, first_i), shape=(3 * nat, 3 * nat))

        Hdiag_icc = np.empty((nat, 3, 3))
        for x in range(3):
            for y in range(3):
                Hdiag_icc[:, x, y] = - \
                    np.bincount(i_n, weights=H_ncc[:, x, y])

        if divide_by_masses:
            H += bsr_matrix(((Hdiag_icc.T / mass_nat).T, np.arange(nat),
                             np.arange(nat + 1)),
                            shape=(3 * nat, 3 * nat))

        else:
            H += bsr_matrix((Hdiag_icc, np.arange(nat), np.arange(nat + 1)),
                            shape=(3 * nat, 3 * nat))

        return H
Exemplo n.º 5
0
    def calculate_hessian_matrix(self, atoms, divide_by_masses=False):
        r"""Compute the Hessian matrix

        The Hessian matrix is the matrix of second derivatives 
        of the potential energy :math:`\mathcal{V}_\mathrm{int}` 
        with respect to coordinates, i.e.\

        .. math:: 
        
            \frac{\partial^2 \mathcal{V}_\mathrm{int}}
                 {\partial r_{\nu{}i}\partial r_{\mu{}j}},

        where the indices :math:`\mu` and :math:`\nu` refer to atoms and
        the indices :math:`i` and :math:`j` refer to the components of the
        position vector :math:`r_\nu` along the three spatial directions.

        The Hessian matrix has contributions from the pair potential
        and the embedding energy, 

        .. math::

            \frac{\partial^2 \mathcal{V}_\mathrm{int}}{\partial r_{\nu{}i}\partial r_{\mu{}j}} = 
            \frac{\partial^2 \mathcal{V}_\mathrm{pair}}{ \partial r_{\nu i} \partial r_{\mu j}} +
            \frac{\partial^2 \mathcal{V}_\mathrm{embed}}{\partial r_{\nu i} \partial r_{\mu j}}. 	


        The contribution from the pair potential is

        .. math::

            \frac{\partial^2 \mathcal{V}_\mathrm{pair}}{ \partial r_{\nu i} \partial r_{\mu j}} &= 
            -\phi_{\nu\mu}'' \left(
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}} 
            \right)
            -\frac{\phi_{\nu\mu}'}{r_{\nu\mu}}\left(
            \delta_{ij}-
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}} 
            \right) \\ 
            &+\delta_{\nu\mu}\sum_{\gamma\neq\nu}^{N}
            \phi_{\nu\gamma}'' \left(
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}} 
            \right)
            +\delta_{\nu\mu}\sum_{\gamma\neq\nu}^{N}\frac{\phi_{\nu\gamma}'}{r_{\nu\gamma}}\left(
            \delta_{ij}-
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}} 
            \right).

        The contribution of the embedding energy to the Hessian matrix is a sum of eight terms,
        
        .. math::

            \frac{\mathcal{V}_\mathrm{embed}}{\partial r_{\mu j} \partial r_{\nu i}} 
            	&= T_1 + T_2 + T_3 + T_4 + T_5 + T_6 + T_7 + T_8 \\ 
            T_1 &= 
            \delta_{\nu\mu}U_\nu''
            \sum_{\gamma\neq\nu}^{N}g_{\nu\gamma}'\frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
            \sum_{\gamma\neq\nu}^{N}g_{\nu\gamma}'\frac{r_{\nu\gamma j}}{r_{\nu\gamma}} \\
            T_2 &= 
            -u_\nu''g_{\nu\mu}' \frac{r_{\nu\mu j}}{r_{\nu\mu}} \sum_{\gamma\neq\nu}^{N} 
            G_{\nu\gamma}' \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} \\
            T_3 &=
            +u_\mu''g_{\mu\nu}' \frac{r_{\nu\mu i}}{r_{\nu\mu}} \sum_{\gamma\neq\mu}^{N} 
            G_{\mu\gamma}' \frac{r_{\mu\gamma j}}{r_{\mu\gamma}} \\
            T_4 &= -\left(u_\mu'g_{\mu\nu}'' + u_\nu'g_{\nu\mu}''\right)
            \left(
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}}
            \right)\\
            T_5 &= \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{N}
            \left(U_\gamma'g_{\gamma\nu}'' + U_\nu'g_{\nu\gamma}''\right)
            \left(
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
            \right) \\
            T_6 &= -\left(U_\mu'g_{\mu\nu}' + U_\nu'g_{\nu\mu}'\right) \frac{1}{r_{\nu\mu}}
            \left(
            \delta_{ij}- 
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}}
            \right) \\
            T_7 &= \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{N}
            \left(U_\gamma'g_{\gamma\nu}' + U_\nu'g_{\nu\gamma}'\right) \frac{1}{r_{\nu\gamma}}
            \left(\delta_{ij}-
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
            \right) \\
            T_8 &= \sum_{\substack{\gamma\neq\nu \\ \gamma \neq \mu}}^{N}
            U_\gamma'' g_{\gamma\nu}'g_{\gamma\mu}' 
            \frac{r_{\gamma\nu i}}{r_{\gamma\nu}}
            \frac{r_{\gamma\mu j}}{r_{\gamma\mu}} 


        Parameters
        ----------
        atoms : ase.Atoms
        divide_by_masses : bool
            Divide block :math:`\nu\mu` by :math:`m_\num_\mu` to obtain the dynamical matrix

        Returns
        -------
        D : numpy.matrix
            Block Sparse Row matrix with the nonzero blocks

        Notes
        -----
        Notation:
         * :math:`N` Number of atoms 
         * :math:`\mathcal{V}_\mathrm{int}`  Total potential energy 
         * :math:`\mathcal{V}_\mathrm{pair}` Pair potential 
         * :math:`\mathcal{V}_\mathrm{embed}` Embedding energy 
         * :math:`r_{\nu{}i}`  Component :math:`i` of the position vector of atom :math:`\nu` 
         * :math:`r_{\nu\mu{}i} = r_{\mu{}i}-r_{\nu{}i}` 
         * :math:`r_{\nu\mu{}}` Norm of :math:`r_{\nu\mu{}i}`, i.e.\ :math:`\left(r_{\nu\mu{}1}^2+r_{\nu\mu{}2}^2+r_{\nu\mu{}3}^2\right)^{1/2}`
         * :math:`\phi_{\nu\mu}(r_{\nu\mu{}})` Pair potential energy of atoms :math:`\nu` and :math:`\mu` 
         * :math:`\rho_nu` Total electron density of atom :math:`\nu`  
         * :math:`U_\nu(\rho_nu)` Embedding energy of atom :math:`\nu` 
         * :math:`g_{\delta}\left(r_{\gamma\delta}\right) \equiv g_{\gamma\delta}` Contribution from atom :math:`\delta` to :math:`\rho_\gamma`
         * :math:`m_\nu` mass of atom :math:`\nu`
        """

        nat = len(atoms)
        atnums = atoms.numbers

        atnums_in_system = set(atnums)
        for atnum in atnums_in_system:
            if atnum not in atnums:
                raise RuntimeError('Element with atomic number {} found, but '
                                   'this atomic number has no EAM '
                                   'parametrization'.format(atnum))

        # i_n: index of the central atom
        # j_n: index of the neighbor atom
        # dr_nc: distance vector between the two
        # abs_dr_n: norm of distance vector
        # Variable name ending with _n indicate arrays that contain
        # one element for each pair in the neighbor list. Names ending
        # with _i indicate arrays containing one element for each atom.
        i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms,
                                                   self._db_cutoff)
        first_i = first_neighbours(nat, i_n)
        # Make sure that the neighborlist does not contain the same pairs twice.
        # Reoccuring entries may be due to small system size. In this case, the
        # Hessian matrix will not be symmetric.
        unique_pairs = set((i, j) for (i, j) in zip(i_n, j_n))
        if len(unique_pairs) != len(i_n):
            raise ValueError("neighborlist contains some pairs more than once")
        assert (np.all(i_n != j_n))

        if divide_by_masses:
            masses_i = atoms.get_masses().reshape(-1, 1, 1)
            geom_mean_mass_n = np.sqrt(
                np.take(masses_i, i_n) * np.take(masses_i, j_n)).reshape(
                    -1, 1, 1)

        # Calculate the derivatives of the pair energy
        drep_n = np.zeros_like(abs_dr_n)  # first derivative
        ddrep_n = np.zeros_like(abs_dr_n)  # second derivative
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            rep1 = self.rep[atidx1]
            drep1 = self.drep[atidx1]
            ddrep1 = self.ddrep[atidx1]
            mask1 = atnums[i_n] == atnum1
            if mask1.sum() > 0:
                for atidx2, atnum2 in enumerate(self._db_atomic_numbers):
                    rep = rep1[atidx2]
                    drep = drep1[atidx2]
                    ddrep = ddrep1[atidx2]
                    mask = np.logical_and(mask1, atnums[j_n] == atnum2)
                    if mask.sum() > 0:
                        r = rep(abs_dr_n[mask]) / abs_dr_n[mask]
                        drep_n[mask] = (drep(abs_dr_n[mask]) -
                                        r) / abs_dr_n[mask]
                        ddrep_n[mask] = (ddrep(abs_dr_n[mask]) -
                                         2.0 * drep_n[mask]) / abs_dr_n[mask]

        # Calculate the total electron density at each atom, and the
        # derivatives of pairwise contributions
        f_n = np.zeros_like(abs_dr_n)
        df_n = np.zeros_like(abs_dr_n)  # first derivative
        ddf_n = np.zeros_like(abs_dr_n)  # second derivative
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            f1 = self.f[atidx1]
            df1 = self.df[atidx1]
            ddf1 = self.ddf[atidx1]
            mask1 = atnums[j_n] == atnum1
            if mask1.sum() > 0:
                if type(f1) == list:
                    for atidx2, atnum2 in enumerate(self._db_atomic_numbers):
                        f = f1[atidx2]
                        df = df1[atidx2]
                        ddf = ddf1[atidx2]
                        mask = np.logical_and(mask1, atnums[i_n] == atnum2)
                        if mask.sum() > 0:
                            f_n[mask] = f(abs_dr_n[mask])
                            df_n[mask] = df(abs_dr_n[mask])
                            ddf_n[mask] = ddf(abs_dr_n[mask])
                else:
                    f_n[mask1] = f1(abs_dr_n[mask1])
                    df_n[mask1] = df1(abs_dr_n[mask1])
                    ddf_n[mask1] = ddf1(abs_dr_n[mask1])
        # Accumulate density contributions
        density_i = np.bincount(i_n, weights=f_n, minlength=nat)

        # Calculate the derivatives of the embedding energy
        demb_i = np.zeros(nat)  # first derivative
        ddemb_i = np.zeros(nat)  # second derivative
        for atidx, atnum in enumerate(self._db_atomic_numbers):
            F = self.F[atidx]
            dF = self.dF[atidx]
            ddF = self.ddF[atidx]
            mask = atnums == atnum
            if mask.sum() > 0:
                demb_i[mask] += dF(density_i[mask])
                ddemb_i[mask] += ddF(density_i[mask])

        symmetry_check = False  # check symmetry of individual terms?
        #------------------------------------------------------------------------
        # Calculate pair contribution to the Hessian matrix
        #------------------------------------------------------------------------
        e_nc = (dr_nc.T /
                abs_dr_n).T  # normalized distance vectors r_i^{\mu\nu}
        outer_1 = e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3)
        outer_2 = np.eye(3, dtype=e_nc.dtype) - outer_1
        D_ncc = -(ddrep_n * outer_1.T).T
        D_ncc += -(drep_n / abs_dr_n * outer_2.T).T
        if divide_by_masses:
            D = bsr_matrix((D_ncc / geom_mean_mass_n, j_n, first_i),
                           shape=(3 * nat, 3 * nat))
        else:
            D = bsr_matrix((D_ncc, j_n, first_i), shape=(3 * nat, 3 * nat))
        Ddiag = np.empty((nat, 3, 3))
        for x in range(3):
            for y in range(3):
                Ddiag[:, x, y] = -np.bincount(
                    i_n, weights=D_ncc[:, x, y])  # summation
        if divide_by_masses:
            Ddiag /= masses_i
        # put 3x3 blocks on diagonal (Kronecker Delta delta_{\mu\nu})
        D += bsr_matrix((Ddiag, np.arange(nat), np.arange(nat + 1)),
                        shape=(3 * nat, 3 * nat))

        #------------------------------------------------------------------------
        # Calculate contribution of embedding term
        #------------------------------------------------------------------------
        # For each pair in the neighborlist, create arrays which store the
        # derivatives of the embedding energy of the corresponding atoms.
        demb_i_n = np.take(demb_i, i_n)
        demb_j_n = np.take(demb_i, j_n)
        ddemb_i_n = np.take(ddemb_i, i_n)
        ddemb_j_n = np.take(ddemb_i, j_n)

        # Let r be an index into the neighbor list. df_n[r] contains the the
        # contribution from atom j_n[r] to the derivative of the electron
        # density of atom i_n[r]. We additionally need the contribution of
        # i_n[r] to the derivative of j_n[r]. This value is also in df_n,
        # but at a different position. reverse[r] gives the new index s
        # where we find this value. The same indexing applies to ddf_n.
        reverse = find_indices_of_reversed_pairs(i_n, j_n, abs_dr_n)
        df_i_n = np.take(df_n, reverse)
        ddf_i_n = np.take(ddf_n, reverse)
        #we already have ddf_j_n = ddf_n

        # Term 1:
        # \delta_{\nu\mu}U_\nu''
        # \sum_{\gamma\neq\nu}^{\natoms}g_{\nu\gamma}'\frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
        # \sum_{\gamma\neq\nu}^{\natoms}g_{\nu\gamma}'\frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
        # Likely zero in equilibrium because the sum is zero (appears in the force vector)
        df_n_e_nc_outer_product = (df_n * e_nc.T).T
        df_n_e_nc_i = np.empty((nat, 3), dtype=df_n.dtype)
        for x in range(3):
            df_n_e_nc_i[:, x] = np.bincount(i_n,
                                            weights=df_n_e_nc_outer_product[:,
                                                                            x],
                                            minlength=nat)
        term_1_ncc = ((ddemb_i * df_n_e_nc_i.T).T).reshape(
            -1, 3, 1) * df_n_e_nc_i.reshape(-1, 1, 3)
        if divide_by_masses:
            term_1_ncc /= masses_i
        term_1 = bsr_matrix((term_1_ncc, np.arange(nat), np.arange(nat + 1)),
                            shape=(3 * nat, 3 * nat))
        D += term_1
        if symmetry_check:
            print("check term 1",
                  np.linalg.norm(term_1.todense() - term_1.todense().T))

        # Term 2:
        # -u_\nu''g_{\nu\mu}' \frac{r_{\nu\mu j}}{r_{\nu\mu}} \sum_{\gamma\neq\nu}^{\natoms}
        #      g_{\nu\gamma}' \frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
        # Likely zero in equilibrium because the sum is zero (appears in the force vector)
        df_n_e_nc_j_n = np.take(df_n_e_nc_i, j_n, axis=0)
        term_2_ncc = ((ddemb_j_n * df_i_n * e_nc.T).T).reshape(
            -1, 3, 1) * df_n_e_nc_j_n.reshape(-1, 1, 3)
        if divide_by_masses:
            term_2_ncc /= geom_mean_mass_n
        term_2 = bsr_matrix((term_2_ncc, j_n, first_i),
                            shape=(3 * nat, 3 * nat))
        D += term_2
        if symmetry_check:
            print("check term 2",
                  np.linalg.norm(term_2.todense() - term_2.todense().T))

        # Term 3:
        # +u_\mu''g_{\mu\nu}' \frac{r_{\nu\mu i}}{r_{\nu\mu}} \sum_{\gamma\neq\mu}^{\natoms}
        #      g_{\mu\gamma}' \frac{r_{\mu\gamma j}}{r_{\mu\gamma}}
        # Likely zero in equilibrium because the sum is zero (appears in the force vector)
        df_n_e_nc_i_n = np.take(df_n_e_nc_i, i_n, axis=0)
        term_3_ncc = -((ddemb_i_n * df_n * df_n_e_nc_i_n.T).T).reshape(
            -1, 3, 1) * e_nc.reshape(-1, 1, 3)
        if divide_by_masses:
            term_3_ncc /= geom_mean_mass_n
        term_3 = bsr_matrix((term_3_ncc, j_n, first_i),
                            shape=(3 * nat, 3 * nat))
        D += term_3
        if symmetry_check:
            print("check term 3",
                  np.linalg.norm(term_3.todense() - term_3.todense().T))

        # Term 4:
        # -\left(u_\mu'g_{\mu\nu}'' + u_\nu'g_{\nu\mu}''\right)
        # \left(
        # \frac{r_{\nu\mu i}}{r_{\nu\mu}}
        # \frac{r_{\nu\mu j}}{r_{\nu\mu}}
        # \right)
        tmp_1 = -((demb_j_n * ddf_i_n + demb_i_n * ddf_n) * outer_1.T).T
        # We don't immediately add term 4 to the matrix, because it would have
        # to be normalized by the masses if divide_by_masses is true. However,
        # for construction of term 5, we need term 4 without normalization

        # Term 5:
        # \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{\natoms}
        #\left(U_\gamma'g_{\gamma\nu}'' + U_\nu'g_{\nu\gamma}''\right)
        #\left(
        #\frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
        #\frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
        #\right)
        tmp_1_summed = np.empty((nat, 3, 3), dtype=tmp_1.dtype)
        for x in range(3):
            for y in range(3):
                tmp_1_summed[:, x,
                             y] = -np.bincount(i_n, weights=tmp_1[:, x, y])
        if divide_by_masses:
            tmp_1_summed /= masses_i
        term_5 = bsr_matrix((tmp_1_summed, np.arange(nat), np.arange(nat + 1)),
                            shape=(3 * nat, 3 * nat))
        D += term_5
        if symmetry_check:
            print("check term 5",
                  np.linalg.norm(term_5.todense() - term_5.todense().T))
        if divide_by_masses:
            tmp_1 /= geom_mean_mass_n
        term_4 = bsr_matrix((tmp_1, j_n, first_i), shape=(3 * nat, 3 * nat))
        D += term_4
        if symmetry_check:
            print("check term 4",
                  np.linalg.norm(term_4.todense() - term_4.todense().T))

        # Term 6:
        # -\left(U_\mu'g_{\mu\nu}' + U_\nu'g_{\nu\mu}'\right) \frac{1}{r_{\nu\mu}}
        #\left(
        #\delta_{ij}-
        #\frac{r_{\nu\mu i}}{r_{\nu\mu}}
        #\frac{r_{\nu\mu j}}{r_{\nu\mu}}
        #\right)
        # Like term 4, which was needed to construct term 5, we don't add
        # term 6 immediately, because it is needed for construction of term 7
        tmp_2 = -(
            (demb_j_n * df_i_n + demb_i_n * df_n) / abs_dr_n * outer_2.T).T

        # Term 7:
        # \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{\natoms}
        #\left(U_\gamma'g_{\gamma\nu}' + U_\nu'g_{\nu\gamma}'\right) \frac{1}{r_{\nu\gamma}}
        #\left(\delta_{ij}-
        #\frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
        #\frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
        #\right)
        tmp_2_summed = np.empty((nat, 3, 3), dtype=tmp_2.dtype)
        for x in range(3):
            for y in range(3):
                tmp_2_summed[:, x,
                             y] = -np.bincount(i_n, weights=tmp_2[:, x, y])
        if divide_by_masses:
            tmp_2_summed /= masses_i
        term_7 = bsr_matrix((tmp_2_summed, np.arange(nat), np.arange(nat + 1)),
                            shape=(3 * nat, 3 * nat))
        D += term_7
        if symmetry_check:
            print("check term 7",
                  np.linalg.norm(term_7.todense() - term_7.todense().T))
        if divide_by_masses:
            tmp_2 /= geom_mean_mass_n
        term_6 = bsr_matrix((tmp_2, j_n, first_i), shape=(3 * nat, 3 * nat))
        D += term_6
        if symmetry_check:
            print("check term 6",
                  np.linalg.norm(term_6.todense() - term_6.todense().T))

        #Term 8:
        #  \sum_{\substack{\gamma\neq\nu \\ \gamma \neq \mu}}^{\natoms}
        #U_\gamma'' g_{\gamma\nu}'g_{\gamma\mu}'
        #\frac{r_{\gamma\nu i}}{r_{\gamma\nu}}
        #\frac{r_{\gamma\mu j}}{r_{\gamma\mu}}
        #-----------------------------------------------------------------------
        # This term requires knowledge of common neighbors of pairs of atoms.
        # Construction of a common neighbor list in Python is likely a
        # performance bottleneck; it should be implemented in C++ instead.
        #-----------------------------------------------------------------------

        # For each (i1, j2)-pair in the neighbor list, find all other
        # pairs (i2, j) which share the same j. This includes (i1, j)
        # itself. In this way, create a list with n blocks of rows, where
        # n is the length of the neighbor list. All rows in a block have
        # the same j. Each row corresponds to one triplet i1, j, i2.
        # The number of rows in the block is equal to the total number
        # of neighbors of j. This is the common neighbor list (cnl).
        # Besides the block number and i1, j, i2, the cnl stores the
        # index into the neighbor list where the pair i2-j can be found.
        j_order = np.argsort(j_n)
        i_n_2 = i_n[j_order]
        j_n_2 = j_n[j_order]
        first_j = first_neighbours(nat, j_n_2)
        num_rows_per_j = first_j[j_n + 1] - first_j[j_n]
        total_num_rows = np.sum(num_rows_per_j)
        # The common neighbor information could be stored as
        # a 2D array. However, multiple 1D arrays are likely
        # better for performance (fewer cache misses later).
        cnl_block_number = np.empty(total_num_rows, dtype=i_n.dtype)
        cnl_j1 = np.empty(total_num_rows, dtype=i_n.dtype)
        cnl_j_order = np.empty(total_num_rows, dtype=i_n.dtype)
        cnl_i1_i2 = np.empty((total_num_rows, 2), dtype=i_n.dtype)
        block_start = np.r_[0, np.cumsum(num_rows_per_j)]
        slice_for_j1 = {
            j1: slice(first_j[j1], first_j[j1 + 1])
            for j1 in np.arange(nat)
        }
        for block_number, (i1, j1) in enumerate(zip(i_n, j_n)):
            slice1 = slice(block_start[block_number],
                           block_start[block_number + 1])
            slice2 = slice_for_j1[j1]
            cnl_block_number[slice1] = block_number
            cnl_j1[slice1] = j1
            cnl_j_order[slice1] = j_order[slice2]
            cnl_i1_i2[slice1, 0] = i1
            cnl_i1_i2[slice1, 1] = i_n_2[slice2]
        # Determine bins for accumulation by bincount
        unique_pairs_i1_i2, bincount_bins = np.unique(cnl_i1_i2,
                                                      axis=0,
                                                      return_inverse=True)
        e_nu_cnl = np.take(e_nc, cnl_block_number, axis=0)
        e_mu_cnl = np.take(e_nc, cnl_j_order, axis=0)
        ddemb_cnl = np.take(ddemb_i, cnl_j1)
        df_nu_cnl = np.take(df_i_n, cnl_block_number)
        df_mu_cnl = np.take(df_i_n, cnl_j_order)
        tmp_3 = ((ddemb_cnl * df_nu_cnl * df_mu_cnl * e_nu_cnl.T).T).reshape(
            -1, 3, 1) * e_mu_cnl.reshape(-1, 1, 3)
        tmp_3_summed = np.empty((unique_pairs_i1_i2.shape[0], 3, 3),
                                dtype=e_nc.dtype)
        for x in range(3):
            for y in range(3):
                tmp_3_summed[:, x, y] = np.bincount(
                    bincount_bins,
                    weights=tmp_3[:, x, y],
                    minlength=unique_pairs_i1_i2.shape[0])
        if divide_by_masses:
            geom_mean_mass_i1_i2 = np.sqrt(
                np.take(masses_i, unique_pairs_i1_i2[:, 0]) *
                np.take(masses_i, unique_pairs_i1_i2[:, 1]))
            tmp_3_summed /= geom_mean_mass_i1_i2[:, np.newaxis, np.newaxis]
        index_ptr = first_neighbours(nat, unique_pairs_i1_i2[:, 0])
        term_8 = bsr_matrix(
            (tmp_3_summed, unique_pairs_i1_i2[:, 1], index_ptr),
            shape=(3 * nat, 3 * nat))
        if symmetry_check:
            print("check term 8",
                  np.linalg.norm(term_8.todense() - term_8.todense().T))
        D += term_8
        return D
Exemplo n.º 6
0
    def _calculate_hessian_embedding_term_8(self,
                                            nat,
                                            i_n,
                                            j_n,
                                            e_nc,
                                            ddemb_i,
                                            df_i_n,
                                            divide_by_masses=False,
                                            masses_i=None,
                                            geom_mean_mass_n=None,
                                            symmetry_check=False):
        """Calculate term 8 in the embedding part of the Hessian matrix.

        .. math::

            T_{\nu\mu}^{(8)} = 
            \sum_{\substack{\gamma\neq\nu \\ \gamma \neq \mu}}^{N}
            U_\gamma'' g_{\gamma\nu}'g_{\gamma\mu}' 
            \frac{r_{\gamma\nu i}}{r_{\gamma\nu}}
            \frac{r_{\gamma\mu j}}{r_{\gamma\mu}} 

        This term requires knowledge of common neighbors of pairs of atoms.

        Parameters
        ----------
        nat : int
            Number of atoms
        i_n, j_n : array_like
            Neighbor pairs
        first_i : array_like
            Indices in :code:`i_n` where contiguous 
            blocks with the same value start
        ddemb_i : array_like
            Second derivative of the embedding energy
        e_nc : array_like
            Normalized distance vectors between neighbors
        df_i_n : array_like
            Derivative of the electron density of atom :code:`j`
            with respect to the distance to atom :code:`i`
        divide_by_masses : bool
            Divide term by geometric mean of mass of pairs of atoms
            to obtain the contribution to the dynamical matrix
        masses_i : array_like
            masses of atoms :code:`i`
        geom_mean_mass_n : array_like
            geometric mean of masses of pairs of atoms
        symmetry_check : bool
            Check if the terms are symmetric

        Returns
        -------
        D : scipy.sparse.bsr_matrix
        """
        cnl_i1_i2, cnl_j1, nl_index_i1_j1, nl_index_i2_j1 = find_common_neighbours(
            i_n, j_n, nat)
        unique_pairs_i1_i2, bincount_bins = np.unique(cnl_i1_i2,
                                                      axis=0,
                                                      return_inverse=True)
        cnl_i1_i2 = None
        tmp_3 = np.take(df_i_n, nl_index_i1_j1) * np.take(
            ddemb_i, cnl_j1) * np.take(df_i_n, nl_index_i2_j1)
        cnl_j1 = None
        tmp_3_summed = np.empty((unique_pairs_i1_i2.shape[0], 3, 3),
                                dtype=e_nc.dtype)
        for x, y in np.ndindex(3, 3):
            weights = (tmp_3 * np.take(e_nc[:, x], nl_index_i1_j1) *
                       np.take(e_nc[:, y], nl_index_i2_j1))
            tmp_3_summed[:, x, y] = np.bincount(
                bincount_bins,
                weights=weights,
                minlength=unique_pairs_i1_i2.shape[0])
        nl_index_i1_j1 = None
        nl_index_i2_j1 = None
        weights = None
        tmp_3 = None
        bincount_bins = None
        if divide_by_masses:
            geom_mean_mass_i1_i2 = np.sqrt(
                np.take(masses_i, unique_pairs_i1_i2[:, 0]) *
                np.take(masses_i, unique_pairs_i1_i2[:, 1]))
            tmp_3_summed /= geom_mean_mass_i1_i2[:, np.newaxis, np.newaxis]
        index_ptr = first_neighbours(nat, unique_pairs_i1_i2[:, 0])
        term_8 = bsr_matrix(
            (tmp_3_summed, unique_pairs_i1_i2[:, 1], index_ptr),
            shape=(3 * nat, 3 * nat))
        if symmetry_check:
            print("check term 8",
                  np.linalg.norm(term_8.todense() - term_8.todense().T))
        return term_8
Exemplo n.º 7
0
    def calculate_hessian_matrix(self, atoms, divide_by_masses=False):
        r"""Compute the Hessian matrix

        The Hessian matrix is the matrix of second derivatives 
        of the potential energy :math:`\mathcal{V}_\mathrm{int}` 
        with respect to coordinates, i.e.\

        .. math:: 
        
            \frac{\partial^2 \mathcal{V}_\mathrm{int}}
                 {\partial r_{\nu{}i}\partial r_{\mu{}j}},

        where the indices :math:`\mu` and :math:`\nu` refer to atoms and
        the indices :math:`i` and :math:`j` refer to the components of the
        position vector :math:`r_\nu` along the three spatial directions.

        The Hessian matrix has contributions from the pair potential
        and the embedding energy, 

        .. math::

            \frac{\partial^2 \mathcal{V}_\mathrm{int}}{\partial r_{\nu{}i}\partial r_{\mu{}j}} = 
            \frac{\partial^2 \mathcal{V}_\mathrm{pair}}{ \partial r_{\nu i} \partial r_{\mu j}} +
            \frac{\partial^2 \mathcal{V}_\mathrm{embed}}{\partial r_{\nu i} \partial r_{\mu j}}. 	


        The contribution from the pair potential is

        .. math::

            \frac{\partial^2 \mathcal{V}_\mathrm{pair}}{ \partial r_{\nu i} \partial r_{\mu j}} &= 
            -\phi_{\nu\mu}'' \left(
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}} 
            \right)
            -\frac{\phi_{\nu\mu}'}{r_{\nu\mu}}\left(
            \delta_{ij}-
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}} 
            \right) \\ 
            &+\delta_{\nu\mu}\sum_{\gamma\neq\nu}^{N}
            \phi_{\nu\gamma}'' \left(
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}} 
            \right)
            +\delta_{\nu\mu}\sum_{\gamma\neq\nu}^{N}\frac{\phi_{\nu\gamma}'}{r_{\nu\gamma}}\left(
            \delta_{ij}-
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}} 
            \right).

        The contribution of the embedding energy to the Hessian matrix is a sum of eight terms,
        
        .. math::

            \frac{\mathcal{V}_\mathrm{embed}}{\partial r_{\mu j} \partial r_{\nu i}} 
            	&= T_1 + T_2 + T_3 + T_4 + T_5 + T_6 + T_7 + T_8 \\ 
            T_1 &= 
            \delta_{\nu\mu}U_\nu''
            \sum_{\gamma\neq\nu}^{N}g_{\nu\gamma}'\frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
            \sum_{\gamma\neq\nu}^{N}g_{\nu\gamma}'\frac{r_{\nu\gamma j}}{r_{\nu\gamma}} \\
            T_2 &= 
            -u_\nu''g_{\nu\mu}' \frac{r_{\nu\mu j}}{r_{\nu\mu}} \sum_{\gamma\neq\nu}^{N} 
            G_{\nu\gamma}' \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} \\
            T_3 &=
            +u_\mu''g_{\mu\nu}' \frac{r_{\nu\mu i}}{r_{\nu\mu}} \sum_{\gamma\neq\mu}^{N} 
            G_{\mu\gamma}' \frac{r_{\mu\gamma j}}{r_{\mu\gamma}} \\
            T_4 &= -\left(u_\mu'g_{\mu\nu}'' + u_\nu'g_{\nu\mu}''\right)
            \left(
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}}
            \right)\\
            T_5 &= \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{N}
            \left(U_\gamma'g_{\gamma\nu}'' + U_\nu'g_{\nu\gamma}''\right)
            \left(
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}}
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
            \right) \\
            T_6 &= -\left(U_\mu'g_{\mu\nu}' + U_\nu'g_{\nu\mu}'\right) \frac{1}{r_{\nu\mu}}
            \left(
            \delta_{ij}- 
            \frac{r_{\nu\mu i}}{r_{\nu\mu}} 
            \frac{r_{\nu\mu j}}{r_{\nu\mu}}
            \right) \\
            T_7 &= \delta_{\nu\mu} \sum_{\gamma\neq\nu}^{N}
            \left(U_\gamma'g_{\gamma\nu}' + U_\nu'g_{\nu\gamma}'\right) \frac{1}{r_{\nu\gamma}}
            \left(\delta_{ij}-
            \frac{r_{\nu\gamma i}}{r_{\nu\gamma}} 
            \frac{r_{\nu\gamma j}}{r_{\nu\gamma}}
            \right) \\
            T_8 &= \sum_{\substack{\gamma\neq\nu \\ \gamma \neq \mu}}^{N}
            U_\gamma'' g_{\gamma\nu}'g_{\gamma\mu}' 
            \frac{r_{\gamma\nu i}}{r_{\gamma\nu}}
            \frac{r_{\gamma\mu j}}{r_{\gamma\mu}} 


        Parameters
        ----------
        atoms : ase.Atoms
        divide_by_masses : bool
            Divide block :math:`\nu\mu` by :math:`m_\nu{}m_\mu{}` to obtain the dynamical matrix

        Returns
        -------
        D : numpy.matrix
            Block Sparse Row matrix with the nonzero blocks

        Notes
        -----
        Notation:
         * :math:`N` Number of atoms 
         * :math:`\mathcal{V}_\mathrm{int}`  Total potential energy 
         * :math:`\mathcal{V}_\mathrm{pair}` Pair potential 
         * :math:`\mathcal{V}_\mathrm{embed}` Embedding energy 
         * :math:`r_{\nu{}i}`  Component :math:`i` of the position vector of atom :math:`\nu` 
         * :math:`r_{\nu\mu{}i} = r_{\mu{}i}-r_{\nu{}i}` 
         * :math:`r_{\nu\mu{}}` Norm of :math:`r_{\nu\mu{}i}`, i.e.\ :math:`\left(r_{\nu\mu{}1}^2+r_{\nu\mu{}2}^2+r_{\nu\mu{}3}^2\right)^{1/2}`
         * :math:`\phi_{\nu\mu}(r_{\nu\mu{}})` Pair potential energy of atoms :math:`\nu` and :math:`\mu` 
         * :math:`\rho_{\nu}` Total electron density of atom :math:`\nu`  
         * :math:`U_\nu(\rho_nu)` Embedding energy of atom :math:`\nu` 
         * :math:`g_{\delta}\left(r_{\gamma\delta}\right) \equiv g_{\gamma\delta}` Contribution from atom :math:`\delta` to :math:`\rho_\gamma`
         * :math:`m_\nu` mass of atom :math:`\nu`
        """

        nat = len(atoms)
        atnums = atoms.numbers

        atnums_in_system = set(atnums)
        for atnum in atnums_in_system:
            if atnum not in atnums:
                raise RuntimeError('Element with atomic number {} found, but '
                                   'this atomic number has no EAM '
                                   'parametrization'.format(atnum))

        # i_n: index of the central atom
        # j_n: index of the neighbor atom
        # dr_nc: distance vector between the two
        # abs_dr_n: norm of distance vector
        # Variable name ending with _n indicate arrays that contain
        # one element for each pair in the neighbor list. Names ending
        # with _i indicate arrays containing one element for each atom.
        i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms,
                                                   self._db_cutoff)

        # Calculate derivatives of the pair energy
        drep_n = np.zeros_like(abs_dr_n)  # first derivative
        ddrep_n = np.zeros_like(abs_dr_n)  # second derivative
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            rep1 = self.rep[atidx1]
            drep1 = self.drep[atidx1]
            ddrep1 = self.ddrep[atidx1]
            mask1 = atnums[i_n] == atnum1
            if mask1.sum() > 0:
                for atidx2, atnum2 in enumerate(self._db_atomic_numbers):
                    rep = rep1[atidx2]
                    drep = drep1[atidx2]
                    ddrep = ddrep1[atidx2]
                    mask = np.logical_and(mask1, atnums[j_n] == atnum2)
                    if mask.sum() > 0:
                        r = rep(abs_dr_n[mask]) / abs_dr_n[mask]
                        drep_n[mask] = (drep(abs_dr_n[mask]) -
                                        r) / abs_dr_n[mask]
                        ddrep_n[mask] = (ddrep(abs_dr_n[mask]) -
                                         2.0 * drep_n[mask]) / abs_dr_n[mask]
        # Calculate electron density and its derivatives
        f_n = np.zeros_like(abs_dr_n)
        df_n = np.zeros_like(abs_dr_n)  # first derivative
        ddf_n = np.zeros_like(abs_dr_n)  # second derivative
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            f1 = self.f[atidx1]
            df1 = self.df[atidx1]
            ddf1 = self.ddf[atidx1]
            mask1 = atnums[j_n] == atnum1
            if mask1.sum() > 0:
                if type(f1) == list:
                    for atidx2, atnum2 in enumerate(self._db_atomic_numbers):
                        f = f1[atidx2]
                        df = df1[atidx2]
                        ddf = ddf1[atidx2]
                        mask = np.logical_and(mask1, atnums[i_n] == atnum2)
                        if mask.sum() > 0:
                            f_n[mask] = f(abs_dr_n[mask])
                            df_n[mask] = df(abs_dr_n[mask])
                            ddf_n[mask] = ddf(abs_dr_n[mask])
                else:
                    f_n[mask1] = f1(abs_dr_n[mask1])
                    df_n[mask1] = df1(abs_dr_n[mask1])
                    ddf_n[mask1] = ddf1(abs_dr_n[mask1])
        # Accumulate density contributions
        density_i = np.bincount(i_n, weights=f_n, minlength=nat)
        # Calculate the derivatives of the embedding energy
        demb_i = np.zeros(nat)  # first derivative
        ddemb_i = np.zeros(nat)  # second derivative
        for atidx, atnum in enumerate(self._db_atomic_numbers):
            F = self.F[atidx]
            dF = self.dF[atidx]
            ddF = self.ddF[atidx]
            mask = atnums == atnum
            if mask.sum() > 0:
                demb_i[mask] += dF(density_i[mask])
                ddemb_i[mask] += ddF(density_i[mask])

        # There are two ways to divide the Hessian by atomic masses, either
        # during or after construction. The former is preferable with regard
        # to memory consumption. If we would divide by masses afterwards,
        # we would have to create a sparse matrix with the same size as the
        # Hessian matrix, i.e. we would momentarily need twice the given memory.
        if divide_by_masses:
            masses_i = atoms.get_masses().reshape(-1, 1, 1)
            geom_mean_mass_n = np.sqrt(
                np.take(masses_i, i_n) * np.take(masses_i, j_n)).reshape(
                    -1, 1, 1)
        else:
            masses_i = None
            geom_mean_mass_n = None

        #------------------------------------------------------------------------
        # Calculate pair contribution to the Hessian matrix
        #------------------------------------------------------------------------
        first_i = first_neighbours(nat, i_n)
        e_nc = (dr_nc.T /
                abs_dr_n).T  # normalized distance vectors r_i^{\mu\nu}
        outer_e_ncc = e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3)
        eye_minus_outer_e_ncc = np.eye(3, dtype=e_nc.dtype) - outer_e_ncc
        D = self._calculate_hessian_pair_term(nat, i_n, j_n, abs_dr_n, first_i,
                                              drep_n, ddrep_n, outer_e_ncc,
                                              eye_minus_outer_e_ncc,
                                              divide_by_masses,
                                              geom_mean_mass_n, masses_i)
        drep_n = None
        ddrep_n = None

        #------------------------------------------------------------------------
        # Calculate contribution of embedding term
        #------------------------------------------------------------------------
        # For each pair in the neighborlist, create arrays which store the
        # derivatives of the embedding energy of the corresponding atoms.
        demb_i_n = np.take(demb_i, i_n)
        demb_j_n = np.take(demb_i, j_n)

        # Let r be an index into the neighbor list. df_n[r] contains the the
        # contribution from atom j_n[r] to the derivative of the electron
        # density of atom i_n[r]. We additionally need the contribution of
        # i_n[r] to the derivative of j_n[r]. This value is also in df_n,
        # but at a different position. reverse[r] gives the new index s
        # where we find this value. The same indexing applies to ddf_n.
        reverse = find_indices_of_reversed_pairs(i_n, j_n, abs_dr_n)
        df_i_n = np.take(df_n, reverse)
        ddf_i_n = np.take(ddf_n, reverse)
        #we already have ddf_j_n = ddf_n
        reverse = None

        df_n_e_nc_outer_product = (df_n * e_nc.T).T
        df_e_ni = np.empty((nat, 3), dtype=df_n.dtype)
        for x in range(3):
            df_e_ni[:, x] = np.bincount(i_n,
                                        weights=df_n_e_nc_outer_product[:, x],
                                        minlength=nat)
        df_n_e_nc_outer_product = None

        D += self._calculate_hessian_embedding_term_1(nat, ddemb_i, df_e_ni,
                                                      divide_by_masses,
                                                      masses_i)
        D += self._calculate_hessian_embedding_term_2(nat, j_n, first_i,
                                                      ddemb_i, df_i_n, e_nc,
                                                      df_e_ni,
                                                      divide_by_masses,
                                                      geom_mean_mass_n)
        D += self._calculate_hessian_embedding_term_3(nat, i_n, j_n, first_i,
                                                      ddemb_i, df_n, e_nc,
                                                      df_e_ni,
                                                      divide_by_masses,
                                                      geom_mean_mass_n)
        df_e_ni = None
        D += self._calculate_hessian_embedding_terms_4_and_5(
            nat, first_i, i_n, j_n, outer_e_ncc, demb_i_n, demb_j_n, ddf_i_n,
            ddf_n, divide_by_masses, masses_i, geom_mean_mass_n)
        outer_e_ncc = None
        ddf_i_n = None
        ddf_n = None
        D += self._calculate_hessian_embedding_terms_6_and_7(
            nat, i_n, j_n, first_i, abs_dr_n, eye_minus_outer_e_ncc, demb_i_n,
            demb_j_n, df_n, df_i_n, divide_by_masses, masses_i,
            geom_mean_mass_n)
        eye_minus_outer_e_ncc = None
        df_n = None
        demb_i_n = None
        demb_j_n = None
        abs_dr_n = None
        D += self._calculate_hessian_embedding_term_8(nat, i_n, j_n, e_nc,
                                                      ddemb_i, df_i_n,
                                                      divide_by_masses,
                                                      masses_i,
                                                      geom_mean_mass_n)
        return D
Exemplo n.º 8
0
    def calculate_hessian_matrix(self, atoms, H_format="dense", limits=None):
        """
        Calculate the Hessian matrix for a pair potential.
        For an atomic configuration with N atoms in d dimensions the hessian matrix is a symmetric, hermitian matrix
        with a shape of (d*N,d*N). The matrix is in general a sparse matrix, which consists of dense blocks of shape (d,d), which
        are the mixed second derivatives. The result of the derivation for a pair potential can be found in:
        L. Pastewka et. al. "Seamless elastic boundaries for atomistic calculations", Phys. Ev. B 86, 075459 (2012).

        Parameters
        ----------
        atoms: ase.Atoms
            Atomic configuration in a local or global minima.

        H_format: "dense" or "sparse"
            Output format of the hessian matrix.
            The format "sparse" is only possible if matscipy was build with scipy.

        limits: list [atomID_low, atomID_up]
            Calculate the Hessian matrix only for the given atom IDs. 
            If limits=[5,10] the Hessian matrix is computed for atom IDs 5,6,7,8,9 only.
            The Hessian matrix will have the full shape dim(3*N,3*N) where N is the number of atoms. 
            This ensures correct indexing of the data. 

        Restrictions
        ----------
        This method is currently only implemented for three dimensional systems

        """

        if H_format == "sparse":
            try:
                from scipy.sparse import bsr_matrix, vstack, hstack
            except ImportError:
                raise ImportError(
                    "Import error: Can not output the hessian matrix since scipy.sparse could not be loaded!")

        f = self.f
        dict = self.dict
        df = self.df
        df2 = self.df2

        nat = len(atoms)
        atnums = atoms.numbers

        i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms, dict)
        first_i = first_neighbours(nat, i_n)

        e_n = np.zeros_like(abs_dr_n)
        de_n = np.zeros_like(abs_dr_n)
        dde_n = np.zeros_like(abs_dr_n)
        for params, pair in enumerate(dict):
            if pair[0] == pair[1]:
                mask1 = atnums[i_n] == pair[0]
                mask2 = atnums[j_n] == pair[0]
                mask = np.logical_and(mask1, mask2)

                e_n[mask] = f[pair](abs_dr_n[mask])
                de_n[mask] = df[pair](abs_dr_n[mask])
                dde_n[mask] = df2[pair](abs_dr_n[mask])

            if pair[0] != pair[1]:
                mask1 = np.logical_and(
                    atnums[i_n] == pair[0], atnums[j_n] == pair[1])
                mask2 = np.logical_and(
                    atnums[i_n] == pair[1], atnums[j_n] == pair[0])
                mask = np.logical_or(mask1, mask2)

                e_n[mask] = f[pair](abs_dr_n[mask])
                de_n[mask] = df[pair](abs_dr_n[mask])
                dde_n[mask] = df2[pair](abs_dr_n[mask])

        if limits != None:
            if limits[1] < limits[0]:
                raise ValueError(
                    "Value error: The upper atom id cannot be smaller than the lower atom id.")
            else:
                mask = np.logical_and(i_n >= limits[0], i_n < limits[1])
                i_n = i_n[mask]
                i_n1 = i_n - i_n[0]
                j_n = j_n[mask]
                dr_nc = dr_nc[mask]
                abs_dr_n = abs_dr_n[mask]
                e_n = e_n[mask]
                de_n = de_n[mask]
                dde_n = dde_n[mask]
                nat1 = limits[1] - limits[0]

                first_i = [0] * (nat1 + 1)
                j = 1
                for k in range(1, len(i_n)):
                    if i_n[k] != i_n[k-1]:
                        first_i[j] = k
                        j = j+1
                first_i[-1] = len(i_n)

                if H_format == "sparse":
                    # Off-diagonal elements of the Hessian matrix
                    e_nc = (dr_nc.T / abs_dr_n).T
                    H_ncc = -(dde_n * (e_nc.reshape(-1, 3, 1)
                                       * e_nc.reshape(-1, 1, 3)).T).T
                    H_ncc += -(de_n / abs_dr_n * (np.eye(3, dtype=e_nc.dtype)
                                                  - (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3))).T).T

                    H_nat1nat = bsr_matrix(
                        (H_ncc, j_n, first_i), shape=(3*nat1, 3*nat))

                    # Stack matrices in order to obtain full shape (3*nat, 3*nat)
                    H = vstack([bsr_matrix((limits[0]*3, 3*nat)), H_nat1nat,
                                bsr_matrix((3*nat - limits[1]*3, 3*nat))])

                    # Diagonal elements of the Hessian matrix
                    Hdiag_icc = np.empty((nat1, 3, 3))
                    for x in range(3):
                        for y in range(3):
                            Hdiag_icc[:, x, y] = - \
                                np.bincount(i_n1, weights=H_ncc[:, x, y])

                    Hdiag_nat1nat = bsr_matrix((Hdiag_icc, np.arange(limits[0], limits[1]),
                                                np.arange(nat1+1)), shape=(3*nat1, 3*nat))

                    # Compute full Hessian matrix
                    H += vstack([bsr_matrix((limits[0]*3, 3*nat)), Hdiag_nat1nat,
                                 bsr_matrix((3*nat - limits[1]*3, 3*nat))])

                    return H

                elif H_format == "dense":
                    # Off-diagonal elements of the Hessian matrix
                    e_nc = (dr_nc.T / abs_dr_n).T
                    H_ncc = -(dde_n * (e_nc.reshape(-1, 3, 1) *
                                       e_nc.reshape(-1, 1, 3)).T).T
                    H_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype)
                                                - (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3))).T).T

                    H = np.zeros((3*nat, 3*nat))
                    for atom in range(len(i_n)):
                        H[3*i_n[atom]:3*i_n[atom]+3,
                          3*j_n[atom]:3*j_n[atom]+3] += H_ncc[atom]

                    # Diagonal elements of the Hessian matrix
                    Hdiag_icc = np.empty((nat1, 3, 3))
                    for x in range(3):
                        for y in range(3):
                            Hdiag_icc[:, x, y] = - \
                                np.bincount(i_n1, weights=H_ncc[:, x, y])

                    Hdiag_ncc = np.zeros((3*nat, 3*nat))
                    for atom in range(nat1):
                        Hdiag_ncc[3*(atom+limits[0]):3*(atom+limits[0])+3,
                                  3*(atom+limits[0]):3*(atom+limits[0])+3] += Hdiag_icc[atom]

                    # Compute full Hessian matrix
                    H += Hdiag_ncc

                    return H

        # Sparse BSR-matrix
        elif H_format == "sparse":
            e_nc = (dr_nc.T/abs_dr_n).T
            H_ncc = -(dde_n * (e_nc.reshape(-1, 3, 1)
                               * e_nc.reshape(-1, 1, 3)).T).T
            H_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype)
                                        - (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3))).T).T

            H = bsr_matrix((H_ncc, j_n, first_i), shape=(3*nat, 3*nat))

            Hdiag_icc = np.empty((nat, 3, 3))
            for x in range(3):
                for y in range(3):
                    Hdiag_icc[:, x, y] = - \
                        np.bincount(i_n, weights=H_ncc[:, x, y])

            H += bsr_matrix((Hdiag_icc, np.arange(nat),
                             np.arange(nat+1)), shape=(3*nat, 3*nat))
            return H

        # Dense matrix format
        elif H_format == "dense":
            e_nc = (dr_nc.T/abs_dr_n).T
            H_ncc = -(dde_n * (e_nc.reshape(-1, 3, 1)
                               * e_nc.reshape(-1, 1, 3)).T).T
            H_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype)
                                        - (e_nc.reshape(-1, 3, 1) * e_nc.reshape(-1, 1, 3))).T).T

            H = np.zeros((3*nat, 3*nat))
            for atom in range(len(i_n)):
                H[3*i_n[atom]:3*i_n[atom]+3,
                  3*j_n[atom]:3*j_n[atom]+3] += H_ncc[atom]

            Hdiag_icc = np.empty((nat, 3, 3))
            for x in range(3):
                for y in range(3):
                    Hdiag_icc[:, x, y] = - \
                        np.bincount(i_n, weights=H_ncc[:, x, y])

            Hdiag_ncc = np.zeros((3*nat, 3*nat))
            for atom in range(nat):
                Hdiag_ncc[3*atom:3*atom+3,
                          3*atom:3*atom+3] += Hdiag_icc[atom]

            H += Hdiag_ncc

            return H
Exemplo n.º 9
0
def hydrogenate(a, cutoff, bond_length, b=None, mask=[True, True, True],
                exclude=None, vacuum=None):
    """
    Hydrogenate a slab of material at its periodic boundary conditions.
    Boundary conditions are turned into nonperiodic.

    Parameters
    ----------
    a : ase.Atoms
        Atomic configuration.
    cutoff : float
        Cutoff for neighbor counting.
    bond_length : float
        X-H bond length for hydrogenation.
    b : ase.Atoms, optional
        If present, this is the configuration to hydrogenate. Number of atoms
        must be identical to a object. All bonds present in a but not present
        in b will be hydrogenated in b.
    mask : list of bool
        Cartesian directions which to hydrogenate, only if b argument is not
        given.
    exclude : array_like
        Boolean array masking atoms to be excluded from hydrogenation.
    vacuum : float, optional
        Add this much vacuum after hydrogenation.

    Returns
    -------
    a : ase.Atoms
        Atomic configuration of the hydrogenated slab.
    """
    if b is None:
        b = a.copy()
        b.set_pbc(np.logical_not(mask))

    if exclude is None:
        exclude = np.zeros(len(a), dtype=bool)

    i_a, j_a, D_a, d_a = neighbour_list('ijDd', a, cutoff)
    i_b, j_b = neighbour_list('ij', b, cutoff)

    firstneigh_a = first_neighbours(len(a), i_a)
    firstneigh_b = first_neighbours(len(b), i_b)

    coord_a = np.bincount(i_a, minlength=len(a))
    coord_b = np.bincount(i_b, minlength=len(b))

    hydrogens = []
    # Surface atoms have coord_a != coord_b. Those need hydrogenation
    for k in np.arange(len(a))[np.logical_and(coord_a!=coord_b,
                                              np.logical_not(exclude))]:
        l1_a = firstneigh_a[k]
        l2_a = firstneigh_a[k+1]
        l1_b = firstneigh_b[k]
        l2_b = firstneigh_b[k+1]
        n_H = 0
        for l_a in range(l1_a, l2_a):
            assert i_a[l_a] == k
            bond_exists = False
            for l_b in range(l1_b, l2_b):
                assert i_b[l_b] == k
                if j_a[l_a] == j_b[l_b]:
                    bond_exists = True
            if not bond_exists:
                # Bond existed before cut
                hydrogens += [b[k].position+bond_length*D_a[l_a]/d_a[l_a]]
                n_H += 1
        assert n_H == coord_a[k]-coord_b[k]

    if hydrogens == []:
        raise RuntimeError('No Hydrogen created.')

    b += ase.Atoms(['H']*len(hydrogens), hydrogens)

    if vacuum is not None:
        axis=[]
        for i in range(3):
            if mask[i]:
                axis += [i]
        b.center(vacuum, axis=axis)

    return b
Exemplo n.º 10
0
    def calculate_hessian_matrix(self, atoms, divide_by_masses=False):
        """
        Calculate the Hessian matrix for a bond order potential.
        For an atomic configuration with N atoms in d dimensions the hessian matrix is a symmetric, hermitian matrix
        with a shape of (d*N,d*N). The matrix is in general a sparse matrix, which consists of dense blocks of shape (d,d), which
        are the mixed second derivatives.

        Parameters
        ----------
        atoms: ase.Atoms
            Atomic configuration in a local or global minima.

        divide_by_masses: bool
        	if true return the dynamic matrix else hessian matrix 

		Returns
		-------
		bsr_matrix
			either hessian or dynamic matrix

        Restrictions
        ----------
        This method is currently only implemented for three dimensional systems
        """

        # construct neighbor list
        i_p, j_p, r_p, r_pc = neighbour_list('ijdD',
                                             atoms=atoms,
                                             cutoff=2 * self.cutoff)
        mask_p = r_p > self.cutoff

        nb_atoms = len(self.atoms)
        nb_pairs = len(i_p)

        # reverse pairs
        tr_p = find_indices_of_reversed_pairs(i_p, j_p, r_p)

        # normal vectors
        n_pc = (r_pc.T / r_p).T
        nx_p, ny_p, nz_p = n_pc.T

        # construct triplet list
        first_i = first_neighbours(nb_atoms, i_p)
        ij_t, ik_t, jk_t = triplet_list(first_i, r_p, self.cutoff, i_p, j_p)
        first_ij = first_neighbours(len(i_p), ij_t)

        nb_triplets = len(ij_t)

        # basic triplet and pair terms
        G_t = self.G(r_pc[ij_t], r_pc[ik_t])
        xi_p = np.bincount(ij_t, weights=G_t, minlength=nb_pairs)
        F_p = self.F(r_p, xi_p)

        # Hessian term #4
        d1F_p = self.d1F(r_p, xi_p)
        d1F_p[
            mask_p] = 0.0  # we need to explicitly exclude everything with r > cutoff
        H_temp_pcc = (d1F_p *
                      (np.eye(3) -
                       (n_pc.reshape(-1, 3, 1) * n_pc.reshape(-1, 1, 3))).T /
                      r_p).T
        H_pcc = -H_temp_pcc

        # Hessian term #1
        d11F_p = self.d11F(r_p, xi_p)
        d11F_p[mask_p] = 0.0
        H_temp_pcc = (d11F_p *
                      (n_pc.reshape(-1, 3, 1) * n_pc.reshape(-1, 1, 3)).T).T
        H_pcc -= H_temp_pcc

        # Hessian term #2
        d12F_p = self.d12F(r_p, xi_p)
        d12F_p[mask_p] = 0.0

        d2G_tc = self.d2G(r_pc[ij_t], r_pc[ik_t])
        H_temp_t = (
            d12F_p[ij_t] *
            (d2G_tc.reshape(-1, 3, 1) * n_pc[ij_t].reshape(-1, 1, 3)).T).T

        H_temp_pcc = np.empty_like(H_temp_pcc)
        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = np.bincount(
                    tr_p[jk_t], weights=H_temp_t[:, x, y],
                    minlength=nb_pairs) - np.bincount(
                        ij_t, weights=H_temp_t[:, x, y],
                        minlength=nb_pairs) - np.bincount(
                            tr_p[ik_t],
                            weights=H_temp_t[:, x, y],
                            minlength=nb_pairs)
        H_pcc += H_temp_pcc

        d1G_tc = self.d1G(r_pc[ij_t], r_pc[ik_t])

        H_temp_t = (
            d12F_p[ij_t] *
            (d1G_tc.reshape(-1, 3, 1) * n_pc[ij_t].reshape(-1, 1, 3)).T).T

        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = -np.bincount(
                    ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs
                ) - np.bincount(
                    tr_p[ij_t], weights=H_temp_t[:, x, y], minlength=nb_pairs)
        H_pcc += H_temp_pcc

        # Hessian term #5
        d2F_p = self.d2F(r_p, xi_p)
        d2F_p[mask_p] = 0.0

        d22G_tcc = self.d22G(r_pc[ij_t], r_pc[ik_t])

        H_temp_t = (d2F_p[ij_t] * d22G_tcc.T).T

        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = -np.bincount(
                    ik_t, weights=H_temp_t[:, x, y], minlength=nb_pairs)
                # H_temp_pcc[:, x, y] = np.bincount(tr_p[jk_t], weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(tr_p[ik_t], weights=H_temp_t[:, x, y], minlength=nb_pairs)
        H_pcc += H_temp_pcc

        d11G_tcc = self.d11G(r_pc[ij_t], r_pc[ik_t])

        H_temp_t = (d2F_p[ij_t] * d11G_tcc.T).T

        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = -np.bincount(
                    ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs)
                # H_temp_pcc[:, x, y] = np.bincount(tr_p[jk_t], weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(tr_p[ik_t], weights=H_temp_t[:, x, y], minlength=nb_pairs)
        H_pcc += H_temp_pcc

        Hxx_p = +H_pcc[:, 0, 0]
        Hyy_p = +H_pcc[:, 1, 1]
        Hzz_p = +H_pcc[:, 2, 2]
        Hyz_p = +H_pcc[:, 1, 2]
        Hxz_p = +H_pcc[:, 0, 2]
        Hxy_p = +H_pcc[:, 0, 1]
        Hzy_p = +H_pcc[:, 2, 1]
        Hzx_p = +H_pcc[:, 2, 0]
        Hyx_p = +H_pcc[:, 1, 0]

        d1x2xG_t = self.d1x2xG(r_pc[ij_t], r_pc[ik_t])
        d1y2yG_t = self.d1y2yG(r_pc[ij_t], r_pc[ik_t])
        d1z2zG_t = self.d1z2zG(r_pc[ij_t], r_pc[ik_t])
        d1y2zG_t = self.d1y2zG(r_pc[ij_t], r_pc[ik_t])
        d1x2zG_t = self.d1x2zG(r_pc[ij_t], r_pc[ik_t])
        d1x2yG_t = self.d1x2yG(r_pc[ij_t], r_pc[ik_t])
        d1z2yG_t = self.d1z2yG(r_pc[ij_t], r_pc[ik_t])
        d1z2xG_t = self.d1z2xG(r_pc[ij_t], r_pc[ik_t])
        d1y2xG_t = self.d1y2xG(r_pc[ij_t], r_pc[ik_t])
        Hxx_t = d2F_p[ij_t] * d1x2xG_t
        Hyy_t = d2F_p[ij_t] * d1y2yG_t
        Hzz_t = d2F_p[ij_t] * d1z2zG_t
        Hyz_t = d2F_p[ij_t] * d1y2zG_t
        Hxz_t = d2F_p[ij_t] * d1x2zG_t
        Hxy_t = d2F_p[ij_t] * d1x2yG_t
        Hzy_t = d2F_p[ij_t] * d1z2yG_t
        Hzx_t = d2F_p[ij_t] * d1z2xG_t
        Hyx_t = d2F_p[ij_t] * d1y2xG_t
        Hxx_p += np.bincount(
            jk_t, weights=Hxx_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hxx_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hxx_t, minlength=nb_pairs)
        Hyy_p += np.bincount(
            jk_t, weights=Hyy_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hyy_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hyy_t, minlength=nb_pairs)
        Hzz_p += np.bincount(
            jk_t, weights=Hzz_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hzz_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hzz_t, minlength=nb_pairs)
        Hyz_p += np.bincount(
            jk_t, weights=Hyz_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hyz_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hyz_t, minlength=nb_pairs)
        Hxz_p += np.bincount(
            jk_t, weights=Hxz_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hxz_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hxz_t, minlength=nb_pairs)
        Hxy_p += np.bincount(
            jk_t, weights=Hxy_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hxy_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hxy_t, minlength=nb_pairs)
        Hzy_p += np.bincount(
            jk_t, weights=Hzy_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hzy_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hzy_t, minlength=nb_pairs)
        Hzx_p += np.bincount(
            jk_t, weights=Hzx_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hzx_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hzx_t, minlength=nb_pairs)
        Hyx_p += np.bincount(
            jk_t, weights=Hyx_t, minlength=nb_pairs) - np.bincount(
                tr_p[ij_t], weights=Hyx_t, minlength=nb_pairs) - np.bincount(
                    ik_t, weights=Hyx_t, minlength=nb_pairs)

        # Hessian term #3

        ## Terms involving D_1 * D_1
        d1G_t = self.d1G(r_pc[ij_t], r_pc[ik_t])

        d22F_p = self.d22F(r_p, xi_p)
        d22F_p[mask_p] = 0.0

        d1xG_p = np.bincount(ij_t, weights=d1G_t[:, 0], minlength=nb_pairs)
        d1yG_p = np.bincount(ij_t, weights=d1G_t[:, 1], minlength=nb_pairs)
        d1zG_p = np.bincount(ij_t, weights=d1G_t[:, 2], minlength=nb_pairs)

        d1G_p = np.transpose([d1xG_p, d1yG_p, d1zG_p])
        H_pcc = -(d22F_p *
                  (d1G_p.reshape(-1, 3, 1) * d1G_p.reshape(-1, 1, 3)).T).T

        ## Terms involving D_2 * D_2
        d2G_t = self.d2G(r_pc[ij_t], r_pc[ik_t])

        d2xG_p = np.bincount(ij_t, weights=d2G_t[:, 0], minlength=nb_pairs)
        d2yG_p = np.bincount(ij_t, weights=d2G_t[:, 1], minlength=nb_pairs)
        d2zG_p = np.bincount(ij_t, weights=d2G_t[:, 2], minlength=nb_pairs)

        d2G_p = np.transpose([d2xG_p, d2yG_p, d2zG_p])

        Q = ((d22F_p * d2G_p.T).T[ij_t].reshape(-1, 3, 1) *
             d2G_t.reshape(-1, 1, 3))
        H_temp_pcc = np.zeros_like(H_temp_pcc)
        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = -np.bincount(
                    ik_t, weights=Q[:, x, y], minlength=nb_pairs)
                # H_temp_pcc[:, x, y] = np.bincount(tr_p[jk_t], weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(tr_p[ik_t], weights=H_temp_t[:, x, y], minlength=nb_pairs)
        H_pcc += H_temp_pcc

        Hxx_p += H_pcc[:, 0, 0]
        Hyy_p += H_pcc[:, 1, 1]
        Hzz_p += H_pcc[:, 2, 2]
        Hyz_p += H_pcc[:, 1, 2]
        Hxz_p += H_pcc[:, 0, 2]
        Hxy_p += H_pcc[:, 0, 1]
        Hzy_p += H_pcc[:, 2, 1]
        Hzx_p += H_pcc[:, 2, 0]
        Hyx_p += H_pcc[:, 1, 0]

        for il_im in range(nb_triplets):
            il = ij_t[il_im]
            im = ik_t[il_im]
            lm = jk_t[il_im]
            for t in range(first_ij[il], first_ij[il + 1]):
                ij = ik_t[t]
                if ij != il and ij != im:
                    r_p_ij = np.array([r_pc[ij]])
                    r_p_il = np.array([r_pc[il]])
                    r_p_im = np.array([r_pc[im]])
                    H_pcc = (0.5 * d22F_p[ij] *
                             (self.d2G(r_p_ij, r_p_il).reshape(-1, 3, 1) *
                              self.d2G(r_p_ij, r_p_im).reshape(-1, 1, 3)).T).T
                    Hxx_p[lm] += H_pcc[:, 0, 0]
                    Hyy_p[lm] += H_pcc[:, 1, 1]
                    Hzz_p[lm] += H_pcc[:, 2, 2]
                    Hyz_p[lm] += H_pcc[:, 1, 2]
                    Hxz_p[lm] += H_pcc[:, 0, 2]
                    Hxy_p[lm] += H_pcc[:, 0, 1]
                    Hzy_p[lm] += H_pcc[:, 2, 1]
                    Hzx_p[lm] += H_pcc[:, 2, 0]
                    Hyx_p[lm] += H_pcc[:, 1, 0]

        ## Terms involving D_1 * D_2
        # Was d2*d1
        Q = ((d22F_p * d1G_p.T).T[ij_t].reshape(-1, 3, 1) *
             d2G_t.reshape(-1, 1, 3))

        H_temp_pcc = np.zeros_like(H_temp_pcc)
        for x in range(3):
            for y in range(3):
                H_temp_pcc[:, x, y] = np.bincount(
                    jk_t, weights=Q[:, x, y],
                    minlength=nb_pairs) - np.bincount(
                        ik_t, weights=Q[:, x, y], minlength=nb_pairs)
                # H_temp_pcc[:, x, y] = np.bincount(tr_p[jk_t], weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(ij_t, weights=H_temp_t[:, x, y], minlength=nb_pairs) - np.bincount(tr_p[ik_t], weights=H_temp_t[:, x, y], minlength=nb_pairs)
        H_pcc = H_temp_pcc

        H_pcc -= (d22F_p *
                  (d2G_p.reshape(-1, 3, 1) * d1G_p.reshape(-1, 1, 3)).T).T

        Hxx_p += H_pcc[:, 0, 0]
        Hyy_p += H_pcc[:, 1, 1]
        Hzz_p += H_pcc[:, 2, 2]
        Hyz_p += H_pcc[:, 1, 2]
        Hxz_p += H_pcc[:, 0, 2]
        Hxy_p += H_pcc[:, 0, 1]
        Hzy_p += H_pcc[:, 2, 1]
        Hzx_p += H_pcc[:, 2, 0]
        Hyx_p += H_pcc[:, 1, 0]

        # Add the conjugate terms (symmetrize Hessian)
        Hxx_p += Hxx_p[tr_p]
        Hyy_p += Hyy_p[tr_p]
        Hzz_p += Hzz_p[tr_p]
        tmp = Hyz_p.copy()
        Hyz_p += Hzy_p[tr_p]
        Hzy_p += tmp[tr_p]
        tmp = Hxz_p.copy()
        Hxz_p += Hzx_p[tr_p]
        Hzx_p += tmp[tr_p]
        tmp = Hxy_p.copy()
        Hxy_p += Hyx_p[tr_p]
        Hyx_p += tmp[tr_p]

        # Construct diagonal terms from off-diagonal terms
        Hxx_a = -np.bincount(i_p, weights=Hxx_p)
        Hyy_a = -np.bincount(i_p, weights=Hyy_p)
        Hzz_a = -np.bincount(i_p, weights=Hzz_p)
        Hyz_a = -np.bincount(i_p, weights=Hyz_p)
        Hxz_a = -np.bincount(i_p, weights=Hxz_p)
        Hxy_a = -np.bincount(i_p, weights=Hxy_p)
        Hzy_a = -np.bincount(i_p, weights=Hzy_p)
        Hzx_a = -np.bincount(i_p, weights=Hzx_p)
        Hyx_a = -np.bincount(i_p, weights=Hyx_p)

        # Construct full off-diagonal term
        H_pcc = np.transpose([[Hxx_p, Hyx_p, Hzx_p], [Hxy_p, Hyy_p, Hzy_p],
                              [Hxz_p, Hyz_p, Hzz_p]])

        # Construct full diagonal term
        H_acc = np.transpose([[Hxx_a, Hxy_a, Hxz_a], [Hyx_a, Hyy_a, Hyz_a],
                              [Hzx_a, Hzy_a, Hzz_a]])

        if divide_by_masses:
            mass_nat = atoms.get_masses()
            geom_mean_mass_n = np.sqrt(mass_nat[i_p] * mass_nat[j_p])
            return \
                bsr_matrix(((H_pcc.T/(2 * geom_mean_mass_n)).T, j_p, first_i), shape=(3*nb_atoms, 3*nb_atoms)) \
                + bsr_matrix(((H_acc.T/(2 * mass_nat)).T, np.arange(nb_atoms), np.arange(nb_atoms+1)),
                     shape=(3*nb_atoms, 3*nb_atoms))
        else:
            return \
                bsr_matrix((H_pcc/2, j_p, first_i), shape=(3*nb_atoms, 3*nb_atoms)) \
                + bsr_matrix((H_acc/2, np.arange(nb_atoms), np.arange(nb_atoms+1)),
                     shape=(3*nb_atoms, 3*nb_atoms))
Exemplo n.º 11
0
 def test_first_neighbours(self):
     i = [1,1,1,1,3,3,3]
     self.assertArrayAlmostEqual(first_neighbours(5, i), [-1,0,-1,4,-1,7])
     i = [0,1,2,3,4,5]
     self.assertArrayAlmostEqual(first_neighbours(6, i), [0,1,2,3,4,5,6])