Beispiel #1
0
 def is_valid(self, indices, tag):
     """Checks that the given atom specifiers are correctly given. Does not
     yet check that they exist or don't overlap with other subssystems.
     """
     # Determine how the atoms are specified: indices, tag or special set
     if isinstance(indices, str):
         if indices != "remaining":
             error("Use \"remaining\" if you want to assign the yet unassigned atoms to a subsystem")
     elif (indices is None) and (tag is None):
         error("Provide system as indices or tag",)
Beispiel #2
0
 def is_valid(self, indices, tag):
     """Checks that the given atom specifiers are correctly given. Does not
     yet check that they exist or don't overlap with other subssystems.
     """
     # Determine how the atoms are specified: indices, tag or special set
     if isinstance(indices, str):
         if indices != "remaining":
             error(
                 "Use \"remaining\" if you want to assign the yet unassigned atoms to a subsystem"
             )
     elif (indices is None) and (tag is None):
         error("Provide system as indices or tag", )
Beispiel #3
0
    def enable_charge_calculation(self,
                                  division="Bader",
                                  source="all-electron",
                                  gridrefinement=4):
        """Enable the dynamic calculation of atom-centered charges with the
        specified algorithm and from the specified electron density. These
        charges are only used for the interaction between other subsystems.

        Parameters:
            division: string
                Indicates the division algorithm that is used. Available options are:
                    
                    - "Bader": Bader algorithm
                    - "van Der Waals": Spheres with van Der Waals radius

            source: string
                Indicates what type of electron density is used. Available
                options are:
                
                    - "pseudo": Use the pseudo electron density provided by all ASE DFT calculators
                    - "all-electron": Use the all-electron density provided by at least GPAW

            gridrefinement: int
                Indicates the subdivision that is used for the all-electron
                density.  Can be other than unity only for Bader algorithm with
                all-electron density.
        """
        divisions = ["Bader", "van Der Waals"]
        charge_sources = ["pseudo", "all-electron"]

        if division not in divisions:
            error("Invalid division algorithm: " + division)

        if source not in charge_sources:
            error("Invalid source for electron density: " + source)

        if gridrefinement != 1:
            if (division != "Bader") or (division == "Bader"
                                         and source != "all-electron"):
                warn(
                    "The gridrefinement is available only for the Bader algorithm with all-electron density, it is ignored.",
                    3)

        self.charge_calculation_enabled = True
        self.division = division
        self.charge_source = source
        self.gridrefinement = gridrefinement
Beispiel #4
0
    def enable_charge_calculation(self, division="Bader", source="all-electron", gridrefinement=4):
        """Enable the dynamic calculation of atom-centered charges with the
        specified algorithm and from the specified electron density. These
        charges are only used for the interaction between other subsystems.

        Parameters:
            division: string
                Indicates the division algorithm that is used. Available options are:
                    
                    - "Bader": Bader algorithm
                    - "van Der Waals": Spheres with van Der Waals radius

            source: string
                Indicates what type of electron density is used. Available
                options are:
                
                    - "pseudo": Use the pseudo electron density provided by all ASE DFT calculators
                    - "all-electron": Use the all-electron density provided by at least GPAW

            gridrefinement: int
                Indicates the subdivision that is used for the all-electron
                density.  Can be other than unity only for Bader algorithm with
                all-electron density.
        """
        divisions = ["Bader", "van Der Waals"]
        charge_sources = ["pseudo", "all-electron"]

        if division not in divisions:
            error("Invalid division algorithm: " + division)

        if source not in charge_sources:
            error("Invalid source for electron density: " + source)

        if gridrefinement != 1:
            if (division != "Bader") or (division == "Bader" and source != "all-electron"):
                warn("The gridrefinement is available only for the Bader algorithm with all-electron density, it is ignored.", 3)

        self.charge_calculation_enabled = True
        self.division = division
        self.charge_source = source
        self.gridrefinement = gridrefinement
Beispiel #5
0
def get_bader_charges(atoms, calc, charge_source="all-electron", gridrefinement=4):
        """This function uses an external Bader charge calculator from
        http://theory.cm.utexas.edu/henkelman/code/bader/. This tool is
        provided also in pysic/tools. Before using this function the bader
        executable directory has to be added to PATH.

        Parameters:
            atoms: ASE Atoms
                The structure from which we want to calculate the charges from.
            calc: ASE calculator
            charge_source: string
                Indicates the electron density that is used in charge calculation.
                Can be "pseudo" or "all-electron".
            gridrefinement: int
                The factor by which the calculation grid is densified in charge
                calculation.

        Returns: numpy array of the atomic charges
        """
        # First check that the bader executable is in PATH
        if spawn.find_executable("bader") is None:
            error((
                "Cannot find the \"bader\" executable in PATH. The bader "
                "executable is provided in the pysic/tools folder, or it can be "
                "downloaded from http://theory.cm.utexas.edu/henkelman/code/bader/. "
                "Ensure that the executable is named \"bader\", place it in any "
                "directory you want and then add that directory to your system"
                "PATH."))

        atoms_copy = atoms.copy()
        calc.set_atoms(atoms_copy)

        if charge_source == "pseudo":
            try:
                density = np.array(calc.get_pseudo_density())
            except AttributeError:
                error("The calculator doesn't provide pseudo density.")

        if charge_source == "all-electron":
            try:
                density = np.array(calc.get_all_electron_density(gridrefinement=gridrefinement))
            except AttributeError:
                error("The calculator doesn't provide all electron density.")

        wrk_dir = os.getcwd()+"/.BADERTEMP"
        dir_created = False

        # Write the density in bader supported units and format
        if rank == 0:

            # Create temporary folder for calculations
            if not os.path.exists(wrk_dir):
                os.makedirs(wrk_dir)
                dir_created = True
            else:
                error("Tried to create a temporary folder in " + wrk_dir + ", but the folder already existed. Please remove it manually first.")

            rho = density * Bohr**3
            write(wrk_dir + '/electron_density.cube', atoms, data=rho)

            # Run the bader executable in terminal. The bader executable included
            # int pysic/tools has to be in the PATH/PYTHONPATH
            command = "cd " + wrk_dir + "; bader electron_density.cube"
            subprocess.check_output(command, shell=True)
            #os.system("gnome-terminal --disable-factory -e '"+command+"'")

        # Wait for the main process to write the file
        barrier()

        # ASE provides an existing function for attaching the charges to the
        # atoms (safe because using a copy). Although we don't want to actually
        # attach the charges to anything, we use this function and extract the
        # charges later.
        bader.attach_charges(atoms_copy, wrk_dir + "/ACF.dat")

        # The call for charges was changed between
        # ASE 3.6 and 3.7
        try:
            bader_charges = np.array(atoms_copy.get_initial_charges())
        except:
            bader_charges = np.array(atoms_copy.get_charges())

        # Remove the temporary files
        if rank == 0:
            if dir_created:
                shutil.rmtree(wrk_dir)

        return bader_charges
Beispiel #6
0
def get_bader_charges(atoms,
                      calc,
                      charge_source="all-electron",
                      gridrefinement=4):
    """This function uses an external Bader charge calculator from
        http://theory.cm.utexas.edu/henkelman/code/bader/. This tool is
        provided also in pysic/tools. Before using this function the bader
        executable directory has to be added to PATH.

        Parameters:
            atoms: ASE Atoms
                The structure from which we want to calculate the charges from.
            calc: ASE calculator
            charge_source: string
                Indicates the electron density that is used in charge calculation.
                Can be "pseudo" or "all-electron".
            gridrefinement: int
                The factor by which the calculation grid is densified in charge
                calculation.

        Returns: numpy array of the atomic charges
        """
    # First check that the bader executable is in PATH
    if spawn.find_executable("bader") is None:
        error((
            "Cannot find the \"bader\" executable in PATH. The bader "
            "executable is provided in the pysic/tools folder, or it can be "
            "downloaded from http://theory.cm.utexas.edu/henkelman/code/bader/. "
            "Ensure that the executable is named \"bader\", place it in any "
            "directory you want and then add that directory to your system"
            "PATH."))

    atoms_copy = atoms.copy()
    calc.set_atoms(atoms_copy)

    if charge_source == "pseudo":
        try:
            density = np.array(calc.get_pseudo_density())
        except AttributeError:
            error("The calculator doesn't provide pseudo density.")

    if charge_source == "all-electron":
        try:
            density = np.array(
                calc.get_all_electron_density(gridrefinement=gridrefinement))
        except AttributeError:
            error("The calculator doesn't provide all electron density.")

    wrk_dir = os.getcwd() + "/.BADERTEMP"
    dir_created = False

    # Write the density in bader supported units and format
    if rank == 0:

        # Create temporary folder for calculations
        if not os.path.exists(wrk_dir):
            os.makedirs(wrk_dir)
            dir_created = True
        else:
            error(
                "Tried to create a temporary folder in " + wrk_dir +
                ", but the folder already existed. Please remove it manually first."
            )

        rho = density * Bohr**3
        write(wrk_dir + '/electron_density.cube', atoms, data=rho)

        # Run the bader executable in terminal. The bader executable included
        # int pysic/tools has to be in the PATH/PYTHONPATH
        command = "cd " + wrk_dir + "; bader electron_density.cube"
        subprocess.check_output(command, shell=True)
        #os.system("gnome-terminal --disable-factory -e '"+command+"'")

    # Wait for the main process to write the file
    barrier()

    # ASE provides an existing function for attaching the charges to the
    # atoms (safe because using a copy). Although we don't want to actually
    # attach the charges to anything, we use this function and extract the
    # charges later.
    bader.attach_charges(atoms_copy, wrk_dir + "/ACF.dat")

    # The call for charges was changed between
    # ASE 3.6 and 3.7
    try:
        bader_charges = np.array(atoms_copy.get_initial_charges())
    except:
        bader_charges = np.array(atoms_copy.get_charges())

    # Remove the temporary files
    if rank == 0:
        if dir_created:
            shutil.rmtree(wrk_dir)

    return bader_charges
Beispiel #7
0
    def update_charges_van_der_waals(self):
        """Updates the atomic charges by using the electron density within a
        sphere of van Der Waals radius.

        The charge for each atom in the system is integrated from the electron
        density inside the van Der Waals radius of the atom in hand. The link
        atoms will affect the distribution of the electron density.
        """
        self.timer.start("van Der Waals charge calculation")

        # Turn debugging on or off here
        debugging = False

        atoms_with_links = self.atoms_for_subsystem
        calc = self.calculator

        # The electron density is calculated from the system with link atoms.
        # This way the link atoms can modify the charge distribution
        calc.set_atoms(atoms_with_links)

        if self.charge_source == "pseudo":
            try:
                density = np.array(calc.get_pseudo_density())
            except AttributeError:
                error("The DFT calculator on subsystem \"" + self.name +
                      "\" doesn't provide pseudo density.")

        if self.charge_source == "all-electron":
            try:
                density = np.array(
                    calc.get_all_electron_density(gridrefinement=1))
            except AttributeError:
                error("The DFT calculator on subsystem \"" + self.name +
                      "\" doesn't provide all electron density.")

        # Write the charge density as .cube file for VMD
        if debugging:
            write('nacl.cube', atoms_with_links, data=density)

        grid = self.density_grid

        if debugging:
            debug_list = []

        # The link atoms are at the end of the list
        n_atoms = len(atoms_with_links)
        projected_charges = np.zeros((1, n_atoms))

        for i_atom, atom in enumerate(atoms_with_links):
            r_atom = atom.position
            z = atom.number

            # Get the van Der Waals radius
            R = ase.data.vdw.vdw_radii[z]

            # Create a 3 x 3 x 3 x 3 array that can be used for vectorized
            # operations with the density grid
            r_atom_array = np.tile(
                r_atom, (grid.shape[0], grid.shape[1], grid.shape[2], 1))

            diff = grid - r_atom_array

            # Numpy < 1.8 doesn't recoxnize axis argument on norm. This is a
            # workaround for diff = np.linalg.norm(diff, axis=3)
            diff = np.apply_along_axis(np.linalg.norm, 3, diff)
            indices = np.where(diff <= R)
            densities = density[indices]
            atom_charge = np.sum(densities)
            projected_charges[0, i_atom] = atom_charge

            if debugging:
                debug_list.append((atom, indices, densities))

        #DEBUG: Visualize the grid and contributing grid points as atoms
        if debugging:
            d = Atoms()
            d.set_cell(atoms_with_links.get_cell())

            # Visualize the integration spheres with atoms
            for point in debug_list:
                atom = point[0]
                indices = point[1]
                densities = point[2]
                d.append(atom)
                print "Atom: " + str(atom.symbol) + ", Density sum: " + str(
                    np.sum(densities))
                print "Density points included: " + str(len(densities))
                for i in range(len(indices[0])):
                    x = indices[0][i]
                    y = indices[1][i]
                    z = indices[2][i]
                    a = Atom('H')
                    a.position = grid[x, y, z, :]
                    d.append(a)
            view(d)

        # Normalize the projected charges according to the electronic charge in
        # the whole system excluding the link atom to preserve charge neutrality
        atomic_numbers = np.array(atoms_with_links.get_atomic_numbers())
        total_electron_charge = -np.sum(atomic_numbers)
        total_charge = np.sum(np.array(projected_charges))
        projected_charges *= total_electron_charge / total_charge

        # Add the nuclear charges and initial charges
        projected_charges += atomic_numbers

        # Set the calculated charges to the atoms.  The call for charges was
        # changed between ASE 3.6 and 3.7
        try:
            self.atoms_for_interaction.set_initial_charges(
                projected_charges[0, :].tolist())
        except:
            self.atoms_for_interaction.set_charges(
                projected_charges[0, :].tolist())

        self.pseudo_density = density
        self.timer.stop()
Beispiel #8
0
    def __init__(self, atoms, info, index_map, reverse_index_map, n_atoms):
        """
        Parameters:
            atoms: ASE Atoms
                The subsystem atoms.
            info: SubSystem object
                Contains all the information about the subsystem
            index_map: dictionary of int to int
                The keys are the atom indices in the full system, values are
                indices in the subssystem.
            reverse_index_map: dicitonary of int to int
                The keys are the atom indices in the subsystem, values are the
                keys in the full system.
            n_atoms: int
                Number of atoms in the full system.
        """
        # Extract data from info
        self.name = info.name
        self.calculator = copy.copy(info.calculator)
        self.cell_size_optimization_enabled = info.cell_size_optimization_enabled
        self.cell_padding = info.cell_padding
        self.charge_calculation_enabled = info.charge_calculation_enabled
        self.charge_source = info.charge_source
        self.division = info.division
        self.gridrefinement = info.gridrefinement

        self.n_atoms = n_atoms
        self.atoms_for_interaction = atoms.copy()
        self.atoms_for_subsystem = atoms.copy()
        self.index_map = index_map
        self.reverse_index_map = reverse_index_map
        self.potential_energy = None
        self.forces = None
        self.density_grid = None
        self.pseudo_density = None
        self.link_atom_indices = []
        self.timer = Timer([
            "Bader charge calculation", "van Der Waals charge calculation",
            "Energy", "Forces", "Density grid update", "Cell minimization"
        ])

        # The older ASE versions do not support get_initial_charges()
        try:
            charges = np.array(atoms.get_initial_charges())
        except:
            charges = np.array(atoms.get_charges())
        self.initial_charges = charges

        ## Can't enable charge calculation on non-DFT calculator
        self.dft_system = hasattr(self.calculator, "get_pseudo_density")
        if self.charge_calculation_enabled is True and not self.dft_system:
            error("Can't enable charge calculation on non-DFT calculator!")

        # If the cell size minimization flag has been enabled, then try to reduce the
        # cell size
        if self.cell_size_optimization_enabled:
            pbc = atoms.get_pbc()
            if pbc[0] or pbc[1] or pbc[2]:
                warn(("Cannot optimize cell size when periodic boundary"
                      "condition have been enabled, disabling optimization."),
                     2)
                self.cell_size_optimization_enabled = False
            else:
                self.optimize_cell()
Beispiel #9
0
    def update_charges_van_der_waals(self):
        """Updates the atomic charges by using the electron density within a
        sphere of van Der Waals radius.

        The charge for each atom in the system is integrated from the electron
        density inside the van Der Waals radius of the atom in hand. The link
        atoms will affect the distribution of the electron density.
        """
        self.timer.start("van Der Waals charge calculation")

        # Turn debugging on or off here
        debugging = False

        atoms_with_links = self.atoms_for_subsystem
        calc = self.calculator

        # The electron density is calculated from the system with link atoms.
        # This way the link atoms can modify the charge distribution
        calc.set_atoms(atoms_with_links)

        if self.charge_source == "pseudo":
            try:
                density = np.array(calc.get_pseudo_density())
            except AttributeError:
                error("The DFT calculator on subsystem \"" + self.name + "\" doesn't provide pseudo density.")

        if self.charge_source == "all-electron":
            try:
                density = np.array(calc.get_all_electron_density(gridrefinement=1))
            except AttributeError:
                error("The DFT calculator on subsystem \"" + self.name + "\" doesn't provide all electron density.")

        # Write the charge density as .cube file for VMD
        if debugging:
            write('nacl.cube', atoms_with_links, data=density)

        grid = self.density_grid

        if debugging:
            debug_list = []

        # The link atoms are at the end of the list
        n_atoms = len(atoms_with_links)
        projected_charges = np.zeros((1, n_atoms))

        for i_atom, atom in enumerate(atoms_with_links):
            r_atom = atom.position
            z = atom.number

            # Get the van Der Waals radius
            R = ase.data.vdw.vdw_radii[z]

            # Create a 3 x 3 x 3 x 3 array that can be used for vectorized
            # operations with the density grid
            r_atom_array = np.tile(r_atom, (grid.shape[0], grid.shape[1], grid.shape[2], 1))

            diff = grid - r_atom_array

            # Numpy < 1.8 doesn't recoxnize axis argument on norm. This is a
            # workaround for diff = np.linalg.norm(diff, axis=3)
            diff = np.apply_along_axis(np.linalg.norm, 3, diff)
            indices = np.where(diff <= R)
            densities = density[indices]
            atom_charge = np.sum(densities)
            projected_charges[0, i_atom] = atom_charge

            if debugging:
                debug_list.append((atom, indices, densities))

        #DEBUG: Visualize the grid and contributing grid points as atoms
        if debugging:
            d = Atoms()
            d.set_cell(atoms_with_links.get_cell())

            # Visualize the integration spheres with atoms
            for point in debug_list:
                atom = point[0]
                indices = point[1]
                densities = point[2]
                d.append(atom)
                print "Atom: " + str(atom.symbol) + ", Density sum: " + str(np.sum(densities))
                print "Density points included: " + str(len(densities))
                for i in range(len(indices[0])):
                    x = indices[0][i]
                    y = indices[1][i]
                    z = indices[2][i]
                    a = Atom('H')
                    a.position = grid[x, y, z, :]
                    d.append(a)
            view(d)

        # Normalize the projected charges according to the electronic charge in
        # the whole system excluding the link atom to preserve charge neutrality
        atomic_numbers = np.array(atoms_with_links.get_atomic_numbers())
        total_electron_charge = -np.sum(atomic_numbers)
        total_charge = np.sum(np.array(projected_charges))
        projected_charges *= total_electron_charge/total_charge

        # Add the nuclear charges and initial charges
        projected_charges += atomic_numbers

        # Set the calculated charges to the atoms.  The call for charges was
        # changed between ASE 3.6 and 3.7
        try:
            self.atoms_for_interaction.set_initial_charges(projected_charges[0, :].tolist())
        except:
            self.atoms_for_interaction.set_charges(projected_charges[0, :].tolist())

        self.pseudo_density = density
        self.timer.stop()
Beispiel #10
0
    def __init__(self, atoms, info, index_map, reverse_index_map, n_atoms):
        """
        Parameters:
            atoms: ASE Atoms
                The subsystem atoms.
            info: SubSystem object
                Contains all the information about the subsystem
            index_map: dictionary of int to int
                The keys are the atom indices in the full system, values are
                indices in the subssystem.
            reverse_index_map: dicitonary of int to int
                The keys are the atom indices in the subsystem, values are the
                keys in the full system.
            n_atoms: int
                Number of atoms in the full system.
        """
        # Extract data from info
        self.name = info.name
        self.calculator = copy.copy(info.calculator)
        self.cell_size_optimization_enabled = info.cell_size_optimization_enabled
        self.cell_padding = info.cell_padding
        self.charge_calculation_enabled = info.charge_calculation_enabled
        self.charge_source = info.charge_source
        self.division = info.division
        self.gridrefinement = info.gridrefinement

        self.n_atoms = n_atoms
        self.atoms_for_interaction = atoms.copy()
        self.atoms_for_subsystem = atoms.copy()
        self.index_map = index_map
        self.reverse_index_map = reverse_index_map
        self.potential_energy = None
        self.forces = None
        self.density_grid = None
        self.pseudo_density = None
        self.link_atom_indices = []
        self.timer = Timer([
            "Bader charge calculation",
            "van Der Waals charge calculation",
            "Energy",
            "Forces",
            "Density grid update",
            "Cell minimization"])

        # The older ASE versions do not support get_initial_charges()
        try:
            charges = np.array(atoms.get_initial_charges())
        except:
            charges = np.array(atoms.get_charges())
        self.initial_charges = charges

        ## Can't enable charge calculation on non-DFT calculator
        self.dft_system = hasattr(self.calculator, "get_pseudo_density")
        if self.charge_calculation_enabled is True and not self.dft_system:
            error("Can't enable charge calculation on non-DFT calculator!")

        # If the cell size minimization flag has been enabled, then try to reduce the
        # cell size
        if self.cell_size_optimization_enabled:
            pbc = atoms.get_pbc()
            if pbc[0] or pbc[1] or pbc[2]:
                warn(("Cannot optimize cell size when periodic boundary"
                      "condition have been enabled, disabling optimization."), 2)
                self.cell_size_optimization_enabled = False
            else:
                self.optimize_cell()