def add_atom_on_surfaces(surface,element="H", zmax=None, zmin= None, minimum_distance=0.7, maximum_distance=1.4,max_trials=200): """ This function will add an atom (specified by element) on a surface. Z axis must be perpendicular to x,y plane. :param surface: ase.atoms.Atoms object :param element: element to be added :param zmin and zmax: z_range, cartesian coordinates :param minimum_distance: to ensure a good structure returned :param maximum_distance: to ensure a good structure returned :param max_trials: maximum nuber of trials :return: atoms objects """ cell=surface.get_cell() if np.power(cell[0:2, 2],2).sum() > 1.0e-6: raise RuntimeError("Currently, we only support orthorhombic cells") inspector=CheckAtoms(min_bond=minimum_distance,max_bond=maximum_distance) for _ in range(max_trials): x=np.random.uniform(low=0.0,high=cell[0][0]) y=np.random.uniform(low=0.0,high=cell[1][1]) z=np.random.uniform(low=zmin,high=zmax) t=surface.copy() t.append(Atom(symbol=element,position=[x,y,z],tag=1)) if inspector.is_good(t, quickanswer=True): return t raise NoReasonableStructureFound("No good structure found at function add_atom_on_surfaces")
def mutation_atoms(atoms=None, dr_percent=0.3, minimum_displacement=0.5, max_trial=500, verbosity=False, elements=None): """ :param atoms: atoms to be mutated :param dr_percent: maximum distance in the pertubation :param minimum_displacement: :param max_trial: number of trials :param verbosity: output verbosity :param elements: which elements to be displaced :return: """ if atoms is None: raise RuntimeError("You are mutating a None type") frozed_indexes=[] for c in atoms.constraints: if isinstance(c, FixBondLengths): for indi in c.get_indices(): frozed_indexes.append(indi) elif isinstance(c, FixedLine): for indi in c.get_indices(): frozed_indexes.append(indi) symbols = atoms.get_chemical_symbols() if elements is None: move_elements = list(set(symbols)) else: move_elements = elements[:] atoms_checker = CheckAtoms(min_bond=0.5, max_bond=2.0, verbosity=verbosity) for _i_trial in range(max_trial): # copy method would copy the constraints too a = atoms.copy() p0 = a.get_positions() dx = np.zeros(shape=p0.shape) for j, sj in enumerate(symbols): if sj not in move_elements: continue elif j in frozed_indexes: continue else: rmax = max([BOND_LENGTHS[sj] * dr_percent, minimum_displacement]) dx[j] = rmax*np.random.uniform(low=-1.0, high=1.0, size=3) a.set_positions(p0+dx) # set_positions method would not change the positions of fixed atoms if atoms_checker.is_good(a, quickanswer=True): return a else: continue raise NoReasonableStructureFound("No reasonable structure found when mutate atoms")
nCO = 22 bond_range = { ('C', 'Pt'): [1.2, 10], ('Pt', 'Pt'): [1, 10.0], ('C', 'C'): [2.3, 10], ('C', 'O'): [0.6, 10], ('Pt', 'O'): [1.5, 10], ('O', 'O'): [2.3, 10] } for _ in range(nCO): for _ in range(max_trial): s = np.random.choice(len(pos_all), 1)[0] position = pos_all[s, :] normal = normal_all[s, :] height = 1.8 ads_copy = ads.copy() ads_copy.rotate([0, 0, 1], normal, center=[0, 0, 0]) ads_copy.translate(position + (normal * height)) t_copy = slab.copy() slab.extend(ads_copy) inspector = CheckAtoms(bond_range=bond_range) if inspector.is_good(slab, quickanswer=True): break else: slab = t_copy.copy() slab.write('start.traj')
def rand_clustering(surface, dr, bond_range, n=4, max_trial=10, boundary=None): cell = surface.get_cell() a = cell[0, 0] b = cell[1, 0] c = cell[1, 1] tol = 1.5 org_boundary = np.array([[0, 0], [a, 0], [a + b, c], [b, c]]) if boundary is None: boundary = np.array([[-tol, -tol], [a + tol, -tol], [a + b + tol, c + tol], [b - tol, c + tol]]) boundary_polygon = Polygon(boundary) points = random_point_within(boundary_polygon, n=n) temp_var = random_point_within(boundary_polygon, n=n) points = [] for elem in zip(temp_var[0], temp_var[1]): points.extend([elem]) points = [list(ele) for ele in points] points = np.array(points) org_boundary_polygon = Polygon(org_boundary) pos = surface.get_positions() C_ndx = [atom.index for atom in surface if atom.symbol == 'C'] O_ndx = [atom.index for atom in surface if atom.symbol == 'O'] posC = np.zeros((len(C_ndx), 2)) posO = np.zeros((len(C_ndx), 2)) i = 0 for c, o in zip(C_ndx, O_ndx): pc = pos[c, 0:2] po = pos[o, 0:2] posC[i] = pc posO[i] = po i = i + 1 # points = [[points[0][0],points[1][0]],[points[0][1],points[1][1]],[points[0][2],points[1][2]],[points[0][3],points[1][3]]] diameter = np.linalg.norm(boundary.ptp(axis=0)) boundary_polygon = Polygon(boundary) X = [] Y = [] for p in voronoi_polygons(Voronoi(points), diameter): try: x, y = zip(*p.intersection(boundary_polygon).exterior.coords) except (ValueError): raise NoReasonableStructureFound( "No good structure found at function add_atom_on_surfaces") X.append(np.array(x)) Y.append(np.array(y)) ro = surface.get_positions() rn = ro.copy() t = surface.copy() count = 0 for x, y in zip(X, Y): disp = np.random.uniform(-1., 1., [ 1, 3 ]) #difine displacement from particular tesslation region/ cluster #find and move atoms in the cluster comb = np.vstack((x, y)) comb = comb.T bbPath = mplPath.Path(comb) for i, n in enumerate(C_ndx): pc = pos[n, 0:2] if bbPath.contains_point((pc[0], pc[1])): rn[n, :] = ro[n, :] + dr * disp rn[O_ndx[i], :] = ro[O_ndx[i], :] + dr * disp count = count + 1 t.set_positions(rn) inspector = CheckAtoms(bond_range=bond_range) if inspector.is_good(t, quickanswer=True): return t raise NoReasonableStructureFound( "No good structure found at function add_atom_on_surfaces")
def initial_structure_gen(surface, cov, nsurf, coordinates, connectivities, normals, bond_range, max_trial=10, dr=0.05): ''' surface = Clean Pt surface to generate a CO adsorbed stuctures nsurf = no. of surface Pt atoms (from graph) cov = coverage of CO to needed coordinates, connectivities, normals = obtained from catkit grapths bond_range = distance criteria for atoms ''' t = surface.copy() ads_ind = [] # for xyz in range(max_trial): while True: nC = round(cov * nsurf) # number of CO to be adsorbed for _ in range(nC): for _ in range(max_trial): s = np.random.choice(len(coordinates), 1)[0] coordinate = coordinates[s, :] connectivity = connectivities[s] r = random.uniform(-dr, dr) coordinate[0] = coordinate[0] + r coordinate[1] = coordinate[1] + r normal = normals[s, :] #need to find a better way to do this: will change with the changing terrace facet if connectivity == 1: d_CO = 1.158 h = 1.848 if connectivity == 2: d_CO = 1.182 h = 1.46 if connectivity == 3: d_CO = 1.193 h = 1.336 if connectivity == 4: d_CO = 1.204 h = 1.13 ads_copy = Atoms('CO', positions=[(0, 0, 0), (0, 0, d_CO)], cell=[[4, 0, 0], [4, 0, 0], [0, 0, 4]]) ads_copy.rotate([0, 0, 1], normal, center=[0, 0, 0]) c_ads = coordinate + (normal * h) ads_copy.translate(c_ads) t_copy = t.copy() t.extend(ads_copy) inspector = CheckAtoms(bond_range=bond_range) if inspector.is_good(t, quickanswer=True): break else: t = t_copy.copy() return t print('Could not find any structures')
def add_molecule_on_cluster(cluster, molecule='H', anchor_atom=None, metal="Pt", verbosity=False, maxcoordination=12, weights=None, max_attempts_1=50, max_attempts_2=200, minimum_bond_distance_ratio=0.7, maximum_bond_distance_ratio=1.4, outer_sphere=True, distribution=0): """ :param cluster: ase.atoms.Atoms object :param molecule: string. I should be able get the molecule from ase.build.molecule :param anchor_atom: string if there are more than 2 atoms in the molecule, which atom will form bond with metal, This parameter currently is not used. :param metal: string; metal element; currently on support one element cluster (no alloy allowed) :param maxcoordination: the maximum coordination for 'metal',the anchoring is attempted only if CN of meta is smaller than maxcoordition number :param weights: list of floats. If the weights are specified, I will not check the coordination numbers, the anchoring sites will be determined by the weights. The length of weights must be same as the number of atoms in the 'cluster' object :param verbosity: print details to standard out :param max_attempts_1: how many attempts made to find a suitable anchor site on cluster :param max_attempts_2: how many attempts made to anchor the molecule on the cluster :param minimum_bond_distance_ratio: The minimum bond distance will calculated by this ratio multiplied by the sum of atomic radius (got from ase.data) :param maximum_bond_distance_ratio: bla,bla :param outer_sphere: if True, the molecule will be added in the outer sphere region of the cluster, this is good option for large and spherical clusters (center of the mass is used). Other wise, use False, for small cluster :param distribution: 0 or 1: if distribution=0, the maximum coordination number of metal is maxcoordination; if distribution=1, the probability is uniformly distributed for different metal atoms. :return: ase.atoms.Atoms object This function will add one molecule on the cluster and return a new cluster. ++ using of weights ++ If weights are not specified, the anchoring sites will be determined by the coordination number of the metal atoms, otherwise, the anchoring sites will be chosen by the weights. For example, the weights could be determined by atomic energies from high-dimensional neural network. ++ adding molecule rather than single atom++ Currently this function does not support add a molecule (more than 2 atoms), so the anchor_atom is ignored. ++ the total attempts will be max_attempts_1 x max_attempts_2 ++ minimum_bond_distance """ if cluster is None: raise RuntimeError("adding molecule on the None object") try: _mole = ase_create_molecule(molecule) if _mole.get_number_of_atoms() == 1: anchor_atom = molecule.strip() elif anchor_atom is None: raise RuntimeError("You must set anchor_atom when a molecule (natoms > 1) is adsorbed") else: assert anchor_atom in _mole.get_chemical_symbols() except KeyError as e: raise RuntimeError("I can not find the molecule {}".format(e)) cluster_topology = Topology(cluster, ratio=1.3) symbols = cluster.get_chemical_symbols() props = [] # The probabilities of attaching _mole to sites adsorption_sites=[] # adsorption_sites is a list of elements which can absorb molecule if isinstance(metal,str): adsorption_sites.append(metal) elif isinstance(metal, list): for m in metal: adsorption_sites.append(m) else: raise RuntimeError("metal=str or list, for the adsorption sites") # setup the probabilities for adsorbing _mole for index, symbol in enumerate(symbols): if weights is not None: props.append(weights[index]) elif symbol not in adsorption_sites: props.append(0.0) else: if distribution == 0: _cn = cluster_topology.get_coordination_number(index) props.append(max([maxcoordination-_cn, 0.0])) else: props.append(1.0) atoms_checker=CheckAtoms(max_bond=maximum_bond_distance_ratio, min_bond=minimum_bond_distance_ratio) for n_trial in range(max_attempts_1): if verbosity: sys.stdout.write("Try to find a metal atoms, trial number %3d\n" % n_trial) attaching_index = selecting_index(props) site_symbol = symbols[attaching_index] if site_symbol not in adsorption_sites: continue else: for n_trial_2 in range(max_attempts_2): p0 = cluster.get_positions()[attaching_index] if outer_sphere: p1 = generate_H_site(p0, distance=BOND_LENGTHS[site_symbol]+BOND_LENGTHS[anchor_atom], width=0.05, center_of_mass=cluster.get_center_of_mass()) else: p1 = generate_H_site(p0, distance=BOND_LENGTHS[site_symbol]+BOND_LENGTHS[anchor_atom], width=0.05, center_of_mass=None) if p1 is None: # I did not find a good H position continue if _mole.get_number_of_atoms() == 1: _mole.set_positions(_mole.get_positions() - _mole.get_center_of_mass() + p1) new_atoms = cluster.copy() for _a in _mole: _a.tag = 1 new_atoms.append(_a) if atoms_checker.is_good(new_atoms, quickanswer=True): return new_atoms else: # if _mole has more than 1 atoms. I will try to rotate the _mole several times to give it a chance # to fit other atoms for n_rotation in range(20): new_atoms = cluster.copy() # firstly, randomly rotate the _mole phi = np.rad2deg(np.random.uniform(low=0.0, high=np.pi * 2.0)) theta = np.rad2deg(np.arccos(1.0 - 2.0 * np.random.uniform(low=0.0, high=1.0))) _mole.euler_rotate(phi=phi, theta=theta, psi=0.0, center="COP") # get the positions of anchored atoms possible_anchored_sites = [i for i, e in enumerate(_mole.get_chemical_symbols()) if e == anchor_atom] decided_anchored_site = np.random.choice(possible_anchored_sites) p_anchored_position =_mole.get_positions()[decided_anchored_site] # make sure the anchored atom will locate at p1, which is determined by the function _mole.set_positions(_mole.get_positions()+(p1-p_anchored_position)) for _a in _mole: _a.tag = 1 new_atoms.append(_a) if atoms_checker.is_good(new_atoms, quickanswer=True): return new_atoms else: continue raise NoReasonableStructureFound("No reasonable structure found when adding an addsorbate")