def place_neighbour(self, atomic_number, cluster, neighbour_index, max_tries=10, min_distance_ratio=1.0): """ Tries to add the new atom to the cluster by placing it at bond_length's distance from the atom at neighbour_index and at min_distance_ratio x bond_length distance from all other atoms. If it succeeds a Atom object is return, else None. Parameters: atomic_number : {int, str} Atomic number of the new atom cluster : Atoms object to add an atom to. max_tries : {positive int} The number of random directions to try and add the new atom at every neighbour before switching to the next one. min_distance_ratio : {positive float} Ratio of bond_length allowed as minimum distance to other atoms (not the one it is placed by). Only use value less than 2. Returns: Atom object if successful, else None. """ new_atom = Atom(symbol=atomic_number) counter = 0 found_position = False while not found_position and counter < max_tries: counter += 1 # Start at neighbour position new_atom.position = cluster.positions[neighbour_index].copy() # Add random vector with norm of the provided bond_length bond_length = self.blmin[(atomic_number, cluster.numbers[neighbour_index])] new_atom.position += get_random_direction() * bond_length # Check if other distances are large enough atoms_to_check = np.array([True] * cluster.get_number_of_atoms()) # neighbour distance may be smaller than other distances atoms_to_check[neighbour_index] = False found_position = min_distance_fulfilled( new_atom, cluster.numbers[atoms_to_check], cluster.positions[atoms_to_check], self.blmin, min_distance_ratio) if counter == max_tries: # Could not place atom return None return new_atom
def random_structure_on_substrate(symbols, amin, amax, dmin, model_file, Natt=RANDOM_ATTEMPTS): # returns random structure (ase Atoms) on substrate with lowest e_tot according to Megnet model substrate = read_vasp("POSCAR.substrate") adapt = AseAtomsAdaptor() model = MEGNetModel.from_file(model_file) e_tot_min = 1000. for i in range(Natt): s = surface(substrate, (0, 0, 1), 1, vacuum=0., tol=1e-10) cell = s.get_cell() cell[2][2] = CELL_Z s.set_cell(cell) amin = cell[0][0] amax = cell[0][0] struct = random_structure(symbols, amin, amax, dmin, iwrite=0) j = 0 atoms = struct.get_chemical_symbols() positions = struct.get_positions() for atom in atoms: at = Atom(atom) positions[j][2] = positions[j][2] + SURF_DIST pos = positions[j] at.position = pos s.append(at) j = j + 1 struct_pymatgen = adapt.get_structure(s) try: e_tot = model.predict_structure(struct_pymatgen) # print(e_tot) except: e_tot = 0. print("isolated molecule exception handled") if e_tot < e_tot_min: struct_out = s e_tot_min = e_tot print("e_tot min: ", e_tot_min) write(filename='best.in', images=struct_out, format="espresso-in") del model return struct_out
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()
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()