Exemple #1
0
 def calculate(self, indices=None, fupdate=0.05):
     """ Charge density on a Cartesian grid is a common routine required for Stockholder-type
         and related methods. This abstract class prepares the grid if input Volume object
         is empty.
     """
     # Obtain charge densities on the grid if it does not contain one.
     if not numpy.any(self.volume.data):
         self.logger.info(
             "Calculating charge densities on the provided empty grid.")
         if len(self.data.mocoeffs) == 1:
             self.charge_density = electrondensity_spin(
                 self.data, self.volume,
                 [self.data.mocoeffs[0][:self.data.homos[0] + 1]])
             self.charge_density.data *= 2
         else:
             self.charge_density = electrondensity_spin(
                 self.data,
                 self.volume,
                 [
                     self.data.mocoeffs[0][:self.data.homos[0] + 1],
                     self.data.mocoeffs[1][:self.data.homos[1] + 1],
                 ],
             )
     # If charge densities are provided beforehand, log this information
     # `Volume` object does not contain (nor rely on) information about the constituent atoms.
     else:
         self.logger.info(
             "Using charge densities from the provided Volume object.")
         self.charge_density = self.volume
Exemple #2
0
    def calculate(self, indices=None, fupdate=0.05):
        """
        Calculate DDEC6 charges based on doi: 10.1039/c6ra04656h paper.
        Cartesian, uniformly spaced grids are assumed for this function.
        """

        # Obtain charge densities on the grid if it does not contain one.
        if not numpy.any(self.volume.data):
            self.logger.info(
                "Calculating charge densities on the provided empty grid.")
            if len(self.data.mocoeffs) == 1:
                self.chgdensity = electrondensity_spin(
                    self.data, self.volume,
                    [self.data.mocoeffs[0][:self.data.homos[0]]])
                self.chgdensity.data *= 2
            else:
                self.chgdensity = electrondensity_spin(
                    self.data,
                    self.volume,
                    [
                        self.data.mocoeffs[0][:self.data.homos[0]],
                        self.data.mocoeffs[1][:self.data.homos[1]],
                    ],
                )
        # If charge densities are provided beforehand, log this information
        # `Volume` object does not contain (nor rely on) information about the constituent atoms.
        else:
            self.logger.info(
                "Using charge densities from the provided Volume object.")
            self.chgdensity = self.volume

        # STEP 1
        # Carry out step 1 of DDEC6 algorithm [Determining ion charge value]
        # Refer to equations 49-57 in doi: 10.1039/c6ra04656h
        self.logger.info("Creating first reference charges.")
        ref, loc, stock = self.calculate_refcharges()
        self.refcharges = [ref]
        self._localizedcharges = [loc]
        self._stockholdercharges = [stock]

        # STEP 2
        # Load new proatom densities.
        self.logger.info("Creating second reference charges.")
        self.proatom_density = []
        self.radial_grid_r = []
        for i, atom_number in enumerate(self.data.atomnos):
            density, r = self._read_proatom(self.proatom_path, atom_number,
                                            float(self.refcharges[0][i]))
            self.proatom_density.append(density)
            self.radial_grid_r.append(r)

        # Carry out step 2 of DDEC6 algorithm [Determining ion charge value again]
        ref, loc, stock = self.calculate_refcharges()
        self.refcharges.append(ref)
        self._localizedcharges.append(loc)
        self._stockholdercharges.append(stock)

        # STEP 3
        # Load new proatom densities.
        self.proatom_density = []
        self.radial_grid_r = []
        for i, atom_number in enumerate(self.data.atomnos):
            density, r = self._read_proatom(self.proatom_path, atom_number,
                                            float(self.refcharges[1][i]))
            self.proatom_density.append(density)
            self.radial_grid_r.append(r)

        # Carry out step 3 of DDEC6 algorithm [Determine conditioned charge density and tau]
        self.logger.info("Conditioning charge densities.")
        self.condition_densities()
Exemple #3
0
    def calculate(self, indices=None, fupdate=0.05):
        """Calculate Bader's QTAIM charges using on-grid algorithm proposed by Henkelman group
           in doi:10.1016/j.commatsci.2005.04.010
           
           Cartesian, uniformly spaced grids are assumed for this function.
           """

        # First obtain charge densities on the grid
        if len(self.data.mocoeffs) == 1:
            self.chgdensity = electrondensity_spin(
                self.data, self.volume,
                [self.data.mocoeffs[0][:self.data.homos[0]]])
            self.chgdensity.data *= 2
        else:
            self.chgdensity = electrondensity_spin(
                self.data,
                self.volume,
                [
                    self.data.mocoeffs[0][:self.data.homos[0]],
                    self.data.mocoeffs[1][:self.data.homos[1]],
                ],
            )

        # Assign each grid point to Bader areas
        self.fragresults = numpy.zeros(self.chgdensity.data.shape, "d")
        next_index = 1

        self.logger.info("Partitioning space into Bader areas.")

        # Generator to iterate over the elements excluding the outermost positions
        xshape, yshape, zshape = self.chgdensity.data.shape
        indices = ((x, y, z) for x in range(1, xshape - 1)
                   for y in range(1, yshape - 1) for z in range(1, zshape - 1))

        for xindex, yindex, zindex in indices:
            if self.fragresults[xindex, yindex, zindex] != 0:
                # index has already been assigned for this grid point
                continue
            else:
                listcoord = []
                local_max_reached = False

                while not local_max_reached:
                    # Here, `delta_rho` corresponds to equation 2,
                    # and `grad_rho_dot_r` corresponds to equation 1 in the aforementioned
                    # paper (doi:10.1016/j.commatsci.2005.04.010)
                    delta_rho = (
                        self.chgdensity.data[xindex - 1:xindex + 2,
                                             yindex - 1:yindex + 2,
                                             zindex - 1:zindex + 2, ] -
                        self.chgdensity.data[xindex, yindex, zindex])
                    grad_rho_dot_r = delta_rho / _griddist
                    maxat = numpy.where(
                        grad_rho_dot_r == numpy.amax(grad_rho_dot_r))

                    directions = list(zip(maxat[0], maxat[1], maxat[2]))
                    next_direction = [ind - 1 for ind in directions[0]]

                    if len(directions) > 1:
                        # when one or more directions indicate max grad (of 0), prioritize
                        # to include all points in the Bader space
                        if directions[0] == [1, 1, 1]:
                            next_direction = [ind - 1 for ind in direction[1]]

                    listcoord.append((xindex, yindex, zindex))
                    bader_candidate_index = self.fragresults[
                        xindex + next_direction[0], yindex + next_direction[1],
                        zindex + next_direction[2], ]

                    if bader_candidate_index != 0:
                        # Path arrived at a point that has already been assigned with an index
                        bader_index = bader_candidate_index
                        listcoord = tuple(numpy.array(listcoord).T)
                        self.fragresults[listcoord] = bader_index

                        local_max_reached = True

                    elif (next_direction == [0, 0, 0]
                          or xindex + next_direction[0] == 0
                          or xindex + next_direction[0]
                          == (len(self.chgdensity.data) - 1)
                          or yindex + next_direction[1] == 0
                          or yindex + next_direction[1]
                          == (len(self.chgdensity.data[0]) - 1)
                          or zindex + next_direction[2] == 0
                          or zindex + next_direction[2]
                          == (len(self.chgdensity.data[0][0]) - 1)):
                        # When next_direction is [0, 0, 0] -- local maximum
                        # Other conditions indicate that the path is heading out to edge of
                        # the grid. Here, assign new Bader space to avoid exiting the grid.
                        bader_index = next_index
                        next_index += 1

                        listcoord = tuple(numpy.array(listcoord).T)
                        self.fragresults[listcoord] = bader_index

                        local_max_reached = True

                    else:
                        # Advance to the next point according to the direction of
                        # maximum gradient
                        xindex += next_direction[0]
                        yindex += next_direction[1]
                        zindex += next_direction[2]

        # Now try to identify each Bader region to individual atom.
        # Try to find an area that captures enough representation
        self.matches = numpy.zeros_like(self.data.atomnos)
        for pos in range(len(self.data.atomcoords[-1])):
            gridpt = numpy.round(
                (self.data.atomcoords[-1][pos] - self.volume.origin) /
                self.volume.spacing)
            xgrid = int(gridpt[0])
            ygrid = int(gridpt[1])
            zgrid = int(gridpt[2])
            self.matches[pos] = self.fragresults[xgrid, ygrid, zgrid]

        assert (
            0 not in self.matches
        ), "Failed to assign Bader regions to atoms. Try with a finer grid. Content of Bader area matches: {}".format(
            self.matches)
        assert len(
            numpy.unique(self.matches) != len(self.data.atomnos)
        ), "Failed to assign unique Bader regions to each atom. Try with a finer grid."

        # Finally integrate the assigned Bader areas
        self.logger.info("Creating fragcharges: array[1]")
        self.fragcharges = numpy.zeros(len(self.data.atomcoords[-1]), "d")

        for atom_index, baderarea_index in enumerate(self.matches):
            # turn off all other grid points
            chargedensity = copy.deepcopy(self.chgdensity)
            mask = self.fragresults == baderarea_index
            chargedensity.data *= mask
            self.fragcharges[atom_index] = chargedensity.integrate()

        return True