コード例 #1
0
ファイル: calculator.py プロジェクト: arn-all/matscipy
    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
コード例 #2
0
ファイル: calculator.py プロジェクト: seatonullberg/matscipy
    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
コード例 #3
0
ファイル: calculator.py プロジェクト: arn-all/matscipy
    def energy_virial_and_forces(self, atomic_numbers_i, i_n, j_n, dr_nc,
                                 abs_dr_n):
        """
        Compute the potential energy, the virial and the forces.

        Parameters
        ----------
        atomic_numbers_i : array_like
            Atomic number for each atom in the system
        i_n, j_n : array_like
            Neighbor pairs
        dr_nc : array_like
            Distance vectors between neighbors
        abd_dr_n : array_like
            Length of distance vectors between neighbors

        Returns
        -------
        epot : float
            Potential energy
        virial_v : array
            Virial
        forces_ic : array
            Forces acting on each atom
        """
        nat = len(atomic_numbers_i)
        atnums_in_system = set(atomic_numbers_i)
        for atnum in atnums_in_system:
            if atnum not in self._db_atomic_numbers:
                raise RuntimeError('Element with atomic number {} found, but '
                                   'this atomic number has no EAM '
                                   'parametrization'.format(atnum))

        # Density
        f_n = np.zeros_like(abs_dr_n)
        df_n = np.zeros_like(abs_dr_n)
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            f1 = self.f[atidx1]
            df1 = self.df[atidx1]
            mask1 = atomic_numbers_i[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]
                        mask = np.logical_and(mask1,
                                              atomic_numbers_i[i_n] == atnum2)
                        if mask.sum() > 0:
                            f_n[mask] = f(abs_dr_n[mask])
                            df_n[mask] = df(abs_dr_n[mask])
                else:
                    f_n[mask1] = f1(abs_dr_n[mask1])
                    df_n[mask1] = df1(abs_dr_n[mask1])

        density_i = np.bincount(i_n, weights=f_n, minlength=nat)

        # Repulsion
        rep_n = np.zeros_like(abs_dr_n)
        drep_n = np.zeros_like(abs_dr_n)
        for atidx1, atnum1 in enumerate(self._db_atomic_numbers):
            rep1 = self.rep[atidx1]
            drep1 = self.drep[atidx1]
            mask1 = atomic_numbers_i[i_n] == atnum1
            if mask1.sum() > 0:
                for atidx2, atnum2 in enumerate(self._db_atomic_numbers):
                    rep = rep1[atidx2]
                    drep = drep1[atidx2]
                    mask = np.logical_and(mask1,
                                          atomic_numbers_i[j_n] == atnum2)
                    if mask.sum() > 0:
                        r = rep(abs_dr_n[mask]) / abs_dr_n[mask]
                        rep_n[mask] = r
                        drep_n[mask] = (drep(abs_dr_n[mask]) -
                                        r) / abs_dr_n[mask]

        # Energy
        epot = 0.5 * np.sum(rep_n)
        demb_i = np.zeros(nat)
        for atidx, atnum in enumerate(self._db_atomic_numbers):
            F = self.F[atidx]
            dF = self.dF[atidx]
            mask = atomic_numbers_i == atnum
            if mask.sum() > 0:
                epot += np.sum(F(density_i[mask]))
                demb_i[mask] += dF(density_i[mask])

        # Forces
        reverse = find_indices_of_reversed_pairs(i_n, j_n, abs_dr_n)
        df_i_n = np.take(df_n, reverse)
        df_nc = -0.5 * (
            (demb_i[i_n] * df_n + demb_i[j_n] * df_i_n) + drep_n).reshape(
                -1, 1) * dr_nc / abs_dr_n.reshape(-1, 1)

        # Sum for each atom
        fx_i = np.bincount(j_n, weights=df_nc[:,0], minlength=nat) - \
            np.bincount(i_n, weights=df_nc[:,0], minlength=nat)
        fy_i = np.bincount(j_n, weights=df_nc[:,1], minlength=nat) - \
            np.bincount(i_n, weights=df_nc[:,1], minlength=nat)
        fz_i = np.bincount(j_n, weights=df_nc[:,2], minlength=nat) - \
            np.bincount(i_n, weights=df_nc[:,2], minlength=nat)

        # Virial
        virial_v = -np.array([
            dr_nc[:, 0] * df_nc[:, 0],  # xx
            dr_nc[:, 1] * df_nc[:, 1],  # yy
            dr_nc[:, 2] * df_nc[:, 2],  # zz
            dr_nc[:, 1] * df_nc[:, 2],  # yz
            dr_nc[:, 0] * df_nc[:, 2],  # xz
            dr_nc[:, 0] * df_nc[:, 1]
        ]).sum(axis=1)  # xy

        return epot, virial_v, np.transpose([fx_i, fy_i, fz_i])
コード例 #4
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))