Esempio n. 1
0
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")
Esempio n. 2
0
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")
Esempio n. 3
0
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')
Esempio n. 4
0
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")
Esempio n. 5
0
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')
Esempio n. 6
0
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")