Beispiel #1
0
def rattle(individual, stdev=0.5, x_avg_bond=True):
    """Randomly displace all atoms in a random direction with a magnitude
    drawn from a gaussian distribution.
    
    Parameters
    ----------
    individual : Individual
        An individual
    stdev : float
        The standard deviation of the gaussian distribution to rattle
        all the atoms. If x_avg_bond is set to True, given as the fraction
        of the average bond length of the material.
    x_avg_bond : bool
        If True, the gaussian distributions standard deviation is 
        stdev * avg_bond_length. Note, this only applies to fcc, hcp,
        or bcc materials.
    """

    if x_avg_bond:
        stdev *= get_avg_radii(individual) * 2

    pos = individual.get_positions()
    individual.set_positions(pos + np.random.normal(scale=stdev, size=pos.shape))

    return
def permute_column_surface(individual,
                           STEM_parameters,
                           filter_size=0.5,
                           column_cutoff=0.5):
    """Permutes a column by shifting atoms up and down and filling defects.
    """

    module = STEM(STEM_parameters)
    module.generate_target()

    image = module.get_image(individual)

    # Find the xy coordinates of the columns in the image
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    column_coords = np.argwhere(columns)
    column_xys = column_coords[:, ::-1] / resolution

    if len(column_xys) < 2:
        return False

    ##########################################################
    ### This code is for checking the local maximum finder ###
    ##########################################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(image_max, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')
    # plt.show()
    # import sys; sys.exit()

    # Pick a random column and find atoms near the column
    i = random.randint(0, len(column_xys) - 1)
    column_xy = column_xys[i]

    xys = individual.get_positions()[:, :2]
    dists = np.linalg.norm(column_xy - xys, axis=1)
    indices = np.arange(len(individual))[dists < column_cutoff]

    if len(indices) < 2:
        return False

    z_positions = individual.get_positions()[indices, 2]
    top_atom = indices[np.argmax(z_positions)]
    bot_atom = indices[np.argmin(z_positions)]

    # Move the top atom the bottom atom or vice versa
    if random.random() < 0.5:
        individual[top_atom].z = individual[bot_atom].z - avg_bond_length
    else:
        individual[bot_atom].z = individual[top_atom].z + avg_bond_length

    return
def get_particle_radius(atomlist, fill_factor=0.74):
    """Returns an estimated nanoparticle radius given a
    concentration of atoms and void fraction of the sphere.
    Given an average sphere, this is given by the formula

    R_sphere = (n_tot / f)**(1.0/3.0) * R_atom

    where n_tot is the total number of atoms and f is
    the fill factor of the particle.

    Parameters
    ----------
    atomlist : list
        An N x M list where N is the number of unique atoms and M are
        the attributes of the atom. The first and second index of each
        N list must be the chemical symbol and number, respectively.

    fill_factor : float between 0.0 and 1.0
        Factor that determines the packing of spheres in the particle.

    Output
    ------
    out : float
        Radius of nanoparticle.
    """

    n_tot = sum([atom[1] for atom in atomlist])
    R_atom = get_avg_radii(atomlist)
    R_sphere = (n_tot / fill_factor)**(1.0/3.0) * R_atom

    return R_sphere
Beispiel #4
0
def rattle(individual, stdev=0.5, x_avg_bond=True):
    """Randomly displace all atoms in a random direction with a magnitude
    drawn from a gaussian distribution.
    
    Parameters
    ----------
    individual : Individual
        An individual
    stdev : float
        The standard deviation of the gaussian distribution to rattle
        all the atoms. If x_avg_bond is set to True, given as the fraction
        of the average bond length of the material.
    x_avg_bond : bool
        If True, the gaussian distributions standard deviation is 
        stdev * avg_bond_length. Note, this only applies to fcc, hcp,
        or bcc materials.
    """

    if x_avg_bond:
        stdev *= get_avg_radii(individual) * 2

    pos = individual.get_positions()
    individual.set_positions(pos +
                             np.random.normal(scale=stdev, size=pos.shape))

    return
Beispiel #5
0
def move_column_random(individual, cutoff=0.2):
    """Calculates the error per column of atoms in the z-direction"""

    avg_radii = get_avg_radii(individual)
    cutoff *= avg_radii * 2

    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:,0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:,0] != j]
            NNs = NNs[NNs[:,1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    for indices in column_indices:
        zs = pos[indices][:,2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
        else:
            avg_bond_length = np.average([zs[i+1] - zs[i] for i in range(len(zs)-1)])
            avg_bond_lengths.append(avg_bond_length)

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)), np.expand_dims(avg_bond_lengths, 1),  axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95

    # Choose moves based on difference in coordination numbers
    move_indices = top_indices + bot_indices
    move_new_pos = np.append(bot_new_pos, top_new_pos, axis=0)

    move_indices_i = np.random.choice(range(len(move_indices)))
    move_index = move_indices[move_indices_i]
    move_new_pos = move_new_pos[move_indices_i]
    
    individual.positions[move_index] = move_new_pos

    return
def permute_column_surface(individual, STEM_parameters, filter_size=0.5,
                           column_cutoff=0.5):
    """Permutes a column by shifting atoms up and down and filling defects.
    """

    module = STEM(STEM_parameters)
    module.generate_target()

    image = module.get_image(individual)

    # Find the xy coordinates of the columns in the image
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']        
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    column_coords = np.argwhere(columns)
    column_xys = column_coords[:, ::-1] / resolution

    if len(column_xys) < 2:
        return False

    ##########################################################
    ### This code is for checking the local maximum finder ###
    ##########################################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(image_max, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')
    # plt.show()
    # import sys; sys.exit()

    # Pick a random column and find atoms near the column
    i = random.randint(0, len(column_xys) - 1)
    column_xy = column_xys[i]

    xys = individual.get_positions()[:,:2]
    dists = np.linalg.norm(column_xy - xys, axis=1)
    indices = np.arange(len(individual))[dists < column_cutoff]

    if len(indices) < 2:
        return False

    z_positions = individual.get_positions()[indices, 2]
    top_atom = indices[np.argmax(z_positions)]
    bot_atom = indices[np.argmin(z_positions)]

    # Move the top atom the bottom atom or vice versa
    if random.random() < 0.5:
        individual[top_atom].z = individual[bot_atom].z - avg_bond_length
    else:
        individual[bot_atom].z = individual[top_atom].z + avg_bond_length

    return
Beispiel #7
0
def move_surface_defects(individual, surf_CN=11):
    """Moves atoms around on the surface based on coordination number
    Moves a surface atom with a low CN to an atom with a high CN

    Parameters
    ----------
    individual : Individual
        The individual object to be modified in place
    surf_CN : int
        The maximum coordination number to considered a surface atom
    """

    if len(individual) == 0:
        return False

    # Analyze the individual
    CNs = CoordinationNumbers(individual)
    
    # Get indices, CNs, and positions of all surface sites
    surf_indices_CNs = [[i, CN] for i, CN in enumerate(CNs) if CN <= surf_CN]
    if len(surf_indices_CNs) == 0:
        return False
    surf_indices, surf_CNs = list(zip(*surf_indices_CNs))
    surf_indices = list(surf_indices)
    surf_CNs = list(surf_CNs)
    surf_positions = np.array([individual.positions[i] for i in surf_indices])

    # Get differences of CNs to find potentially good moves
    surf_CN_diffs = np.array([surf_CNs]) - np.array([surf_CNs]).T
    min_CN_diff = np.min(surf_CN_diffs) - 1
    surf_CN_diffs -= min_CN_diff
    unique_CN_diffs = np.array(list(set(surf_CN_diffs.flatten())))
    surf_CN_ps =  2.0 ** unique_CN_diffs
    surf_CN_ps /= np.sum(surf_CN_ps)
    surf_CN = np.random.choice(unique_CN_diffs, p=surf_CN_ps)

    switch_indices = np.argwhere(surf_CN_diffs == surf_CN)

    old_index, new_index = switch_indices[random.randint(0, len(switch_indices) - 1)]
    new_position = individual.positions[surf_indices[new_index]]
    
    # Get the average bond length of the particle
    chemical_symbols = individual.get_chemical_symbols()
    unique_symbols = set(chemical_symbols)
    atomlist = [[symbol, chemical_symbols.count(symbol)] for symbol in unique_symbols]
    avg_bond_length = get_avg_radii(atomlist) * 2

    # Choose sites as projections one bond length away from COP
    COP = surf_positions.mean(axis=0)
    vec = new_position - COP
    vec /= np.linalg.norm(vec)
    add_position = new_position + vec * avg_bond_length * 0.5

    individual[surf_indices[old_index]].position = add_position

    return 
def permute_column_bulk(individual, STEM_parameters, filter_size=0.5,
                        column_cutoff=0.5):
    """This mutation randomly does an atom swap within a column of atoms"""

    module = STEM(STEM_parameters)
    module.generate_target()

    image = module.get_image(individual)

    # Find the xy coordinates of the columns in the image
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']        
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    column_coords = np.argwhere(columns)
    column_xys = column_coords[:, ::-1] / resolution

    if len(column_xys) < 2:
        return False

    ##########################################################
    ### This code is for checking the local maximum finder ###
    ##########################################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots(num=1)
    # fig.colorbar(ax.pcolormesh(image_max, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(columns, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')
    
    # plt.show()
    # import sys; sys.exit()

    # Get the symbols in each column with > 1 type of atom
    xys = individual.get_positions()[:, :2]
    dists_to_columns = np.expand_dims(xys, 0) - np.transpose(np.expand_dims(column_xys, 0), (1, 0, 2))
    dists_to_columns = np.linalg.norm(dists_to_columns, axis=2)
    column_indices = [np.where(dists < column_cutoff)[0] for dists in dists_to_columns]
    syms = np.asarray(individual.get_chemical_symbols())
    column_indices = [indices for indices in column_indices if len(np.unique((syms[indices]))) > 1]

    # Shuffle the indices in the atomic column
    column_indices = column_indices[random.randint(0, len(column_indices) - 1)]
    column_symbols = syms[column_indices]
    np.random.shuffle(column_symbols)
    for index, symbol in zip(column_indices, column_symbols):
        individual[index].symbol = symbol
    
    return
Beispiel #9
0
def rattle(individual, stdev=0.5, x_avg_bond=True):
    """Rattle atoms based on a fraction of the average bond length"""

    if x_avg_bond:
        stdev *= get_avg_radii(individual) * 2

    pos = individual.get_positions()
    individual.set_positions(pos + np.random.normal(scale=stdev, size=pos.shape))

    return
Beispiel #10
0
    def get_STEM_projection(self, individual, test=False):
        """Gets the projection of bonds from the brighest STEM image"""

        if self.target is None:
            self.generate_target()

        parameters = self.parameters
        target = self.target

        # Get a cutoff between maximum points in the STEM image based
        # on nearest neighbor distances
        cutoff = get_avg_radii(individual) * 2 * 1.1
        size = cutoff * parameters.kwargs['resolution'] * parameters.kwargs['filter_size']

        # Get a list of xy positions from analyzing local maxima in STEM image
        # as well as the position of a spot near the center of mass
        data_max = filters.maximum_filter(target, size=size)
        maxima = ((target == data_max) & (target > 0.1)) # Filter out low maxima
        com = np.asarray(center_of_mass(target)[::-1]) / parameters.kwargs['resolution']
        pos = np.argwhere(maxima)[:,::-1] / parameters.kwargs['resolution']
        dists_from_com = np.linalg.norm(pos - com, axis=1)
        prob = dists_from_com / sum(dists_from_com)
        bulk_atom_index = np.random.choice(list(range(len(dists_from_com))), p=prob)
        bulk_atom_pos = pos[bulk_atom_index]
        vecs = pos - bulk_atom_pos
        dists = np.linalg.norm(vecs, axis=1)
        vecs = vecs[((dists < cutoff) & (dists > 0))]

        if test:
            import matplotlib.pyplot as plt
            import matplotlib.cm as cm

            fig, ax = plt.subplots(num=1)
            fig.colorbar(ax.pcolormesh(target, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters.kwargs['dimensions'][0] * parameters.kwargs['resolution']))
            ax.set_ylim((0, parameters.kwargs['dimensions'][1] * parameters.kwargs['resolution']))

            fig, ax = plt.subplots(num=2)
            fig.colorbar(ax.pcolormesh(data_max, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters.kwargs['dimensions'][0] * parameters.kwargs['resolution']))
            ax.set_ylim((0, parameters.kwargs['dimensions'][1] * parameters.kwargs['resolution']))

            fig, ax = plt.subplots(num=3)
            fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters.kwargs['dimensions'][0] * parameters.kwargs['resolution']))
            ax.set_ylim((0, parameters.kwargs['dimensions'][1] * parameters.kwargs['resolution']))

            plt.show()

        return vecs
Beispiel #11
0
    def get_STEM_projection(self, individual, test=False):
        """Gets the projection of bonds from the brighest STEM image"""

        if self.target is None:
            self.generate_target()

        parameters = self.parameters
        target = self.target

        # Get a cutoff between maximum points in the STEM image based
        # on nearest neighbor distances
        cutoff = get_avg_radii(individual) * 2 * 1.1
        size = cutoff * parameters['resolution'] * parameters['filter_size']

        # Get a list of xy positions from analyzing local maxima in STEM image
        # as well as the position of a spot near the center of mass
        data_max = filters.maximum_filter(target, size=size)
        maxima = ((target == data_max) & (target > 0.1)) # Filter out low maxima
        com = np.asarray(center_of_mass(target)[::-1]) / parameters['resolution']
        pos = np.argwhere(maxima)[:,::-1] / parameters['resolution']
        dists_from_com = np.linalg.norm(pos - com, axis=1)
        prob = dists_from_com / sum(dists_from_com)
        bulk_atom_index = np.random.choice(list(range(len(dists_from_com))), p=prob)
        bulk_atom_pos = pos[bulk_atom_index]
        vecs = pos - bulk_atom_pos
        dists = np.linalg.norm(vecs, axis=1)
        vecs = vecs[((dists < cutoff) & (dists > 0))]

        if test:
            import matplotlib.pyplot as plt
            import matplotlib.cm as cm

            fig, ax = plt.subplots(num=1)
            fig.colorbar(ax.pcolormesh(target, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters['dimensions'][0] * parameters['resolution']))
            ax.set_ylim((0, parameters['dimensions'][1] * parameters['resolution']))

            fig, ax = plt.subplots(num=2)
            fig.colorbar(ax.pcolormesh(data_max, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters['dimensions'][0] * parameters['resolution']))
            ax.set_ylim((0, parameters['dimensions'][1] * parameters['resolution']))

            fig, ax = plt.subplots(num=3)
            fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
            ax.set_xlim((0, parameters['dimensions'][0] * parameters['resolution']))
            ax.set_ylim((0, parameters['dimensions'][1] * parameters['resolution']))

            plt.show()

        return vecs
Beispiel #12
0
def NeighborList(atoms, cutoff=None, factor=1.1):
    """Calculates the neighbors of all atoms based on
    cutoff radius "cutoff". Does not obey periodic boundary conditions.

    Parameters
    ----------
    atoms : ase.Atoms or structopt.Individual object
        The atoms object to be analyzed
    cutoff : float
        The radius to search for neighbors. If cutoff is not
        specified, returns average bond length from a weighted
        average of experimental bond lengths.
    factor : float
        If cutoff is None, nearest neighbor distance is taken as
        two times the average atomic radius. factor is used to
        expand the cutoff by cutoff * factor to ensure python
        numerical behavior doesn't "lose" atoms.

    Output
    ------
    out : list
        A list of a neighbors. out[i] returns a list of the neighbors
        of atom i.
    """

    if cutoff is None:
        chemical_symbols = atoms.get_chemical_symbols()
        unique_symbols = set(chemical_symbols)
        atomlist = [[symbol, chemical_symbols.count(symbol)]
                    for symbol in unique_symbols]
        cutoff = get_avg_radii(atomlist) * 2 * factor

    pos = np.array([atoms.get_positions()])
    pos_T = np.transpose(pos, [1, 0, 2])
    vecs = pos - pos_T
    dists = np.linalg.norm(vecs, axis=2)

    bonds = ((dists < cutoff).astype(int) * (dists > 0).astype(int))

    neighbors = np.resize(np.arange(len(atoms)), (len(atoms), len(atoms)))
    neighbors = neighbors / bonds.astype(float)

    neighbors = [
        a[~(np.isnan(a) | np.isinf(a))].astype(int) for a in neighbors
    ]

    return np.asarray(neighbors)
Beispiel #13
0
def NeighborList(atoms, cutoff=None, factor=1.1):
    """Calculates the neighbors of all atoms based on
    cutoff radius "cutoff". Does not obey periodic boundary conditions.

    Parameters
    ----------
    atoms : ase.Atoms or structopt.Individual object
        The atoms object to be analyzed
    cutoff : float
        The radius to search for neighbors. If cutoff is not
        specified, returns average bond length from a weighted
        average of experimental bond lengths.
    factor : float
        If cutoff is None, nearest neighbor distance is taken as
        two times the average atomic radius. factor is used to
        expand the cutoff by cutoff * factor to ensure python
        numerical behavior doesn't "lose" atoms.

    Output
    ------
    out : list
        A list of a neighbors. out[i] returns a list of the neighbors
        of atom i.
    """

    if cutoff is None:
        chemical_symbols = atoms.get_chemical_symbols()
        unique_symbols = set(chemical_symbols)
        atomlist = [[symbol, chemical_symbols.count(symbol)] for symbol in unique_symbols]
        cutoff = get_avg_radii(atomlist) * 2 * factor

    pos = np.array([atoms.get_positions()])
    pos_T = np.transpose(pos, [1, 0, 2])
    vecs = pos - pos_T
    dists = np.linalg.norm(vecs, axis=2)

    bonds = ((dists < cutoff).astype(int) * (dists > 0).astype(int))

    neighbors = np.resize(np.arange(len(atoms)), (len(atoms), len(atoms)))
    neighbors = neighbors / bonds.astype(float)

    neighbors = [a[~(np.isnan(a) | np.isinf(a))].astype(int) for a in neighbors]    

    return np.asarray(neighbors)
Beispiel #14
0
def CoordinationNumbers(atoms, cutoff=None, factor=1.1):
    """Calculates the coordination number of all atoms based on
    cutoff radius "cutoff". Does not obey periodic boundary conditions.

    Parameters
    ----------
    atoms : ase.Atoms or structopt.Individual object
        The atoms object to be analyzed
    cutoff : float
        The radius to search for neighbors. If cutoff is not
        specified, returns average bond length from a weighted
        average of experimental bond lengths.
    factor : float
        If cutoff is None, nearest neighbor distance is taken as
        two times the average atomic radius. factor is used to
        expand the cutoff by cutoff * factor to ensure python
        numerical behavior doesn't "lose" atoms.

    Output
    ------
    out : list
        A list of coordination numbers, where out[i] corresponds
        to the coordination number of atoms[i]
    """

    if cutoff is None:
        chemical_symbols = atoms.get_chemical_symbols()
        unique_symbols = set(chemical_symbols)
        atomlist = [[symbol, chemical_symbols.count(symbol)]
                    for symbol in unique_symbols]
        cutoff = get_avg_radii(atomlist) * 2 * factor

    pos = np.array([atoms.get_positions()])
    pos_T = np.transpose(pos, [1, 0, 2])
    vecs = pos - pos_T
    dists = np.linalg.norm(vecs, axis=2)

    bonds = ((dists < cutoff).astype(int) * (dists > 0).astype(int))
    CNs = np.sum(bonds, axis=1)

    return CNs
Beispiel #15
0
def add_atoms(individual, n_diff, atomlist, target_atomlist, surf_CN):
    '''Function that adds atoms to make the total atoms correct'''

    # Get a list of symbols for adding
    syms = individual.get_chemical_symbols()

    difflist = {sym: atomlist[sym] - target_atomlist[sym] for sym in atomlist}
    add_syms = []
    for sym in difflist:
        if difflist[sym] < 0:
            add_syms += [sym] * -difflist[sym]

    random.shuffle(add_syms)
    add_syms = add_syms[:-n_diff]

    # Get a list of surface atoms to add atoms to
    CNs = CoordinationNumbers(individual)
    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN]
    CNs = np.array(CNs)[surf_indices]
    count_CNs = np.bincount(CNs)
    surf_prob_factors = [1.0 / count_CNs[CN] for CN in CNs]
    surf_probs = 2.0**CNs
    surf_probs /= surf_prob_factors
    surf_probs /= np.sum(surf_probs)
    if len(individual) < -n_diff:
        add_indices = np.random.choice(surf_indices, -n_diff, True, surf_probs)
    else:
        add_indices = np.random.choice(surf_indices, -n_diff, False,
                                       surf_probs)

    # Get positions to add atoms to
    surf_positions = individual.get_positions()[add_indices]
    COP = individual.get_positions().mean(axis=0)
    vec = surf_positions - COP
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    cutoff = get_avg_radii(individual) * 2
    surf_positions = surf_positions + vec * cutoff

    individual.extend(Atoms(symbols=add_syms, positions=surf_positions))

    return
Beispiel #16
0
def CoordinationNumbers(atoms, cutoff=None, factor=1.1):
    """Calculates the coordination number of all atoms based on
    cutoff radius "cutoff". Does not obey periodic boundary conditions.

    Parameters
    ----------
    atoms : ase.Atoms or structopt.Individual object
        The atoms object to be analyzed
    cutoff : float
        The radius to search for neighbors. If cutoff is not
        specified, returns average bond length from a weighted
        average of experimental bond lengths.
    factor : float
        If cutoff is None, nearest neighbor distance is taken as
        two times the average atomic radius. factor is used to
        expand the cutoff by cutoff * factor to ensure python
        numerical behavior doesn't "lose" atoms.

    Output
    ------
    out : list
        A list of coordination numbers, where out[i] corresponds
        to the coordination number of atoms[i]
    """

    if cutoff is None:
        chemical_symbols = atoms.get_chemical_symbols()
        unique_symbols = set(chemical_symbols)
        atomlist = [[symbol, chemical_symbols.count(symbol)] for symbol in unique_symbols]
        cutoff = get_avg_radii(atomlist) * 2 * factor

    pos = np.array([atoms.get_positions()])
    pos_T = np.transpose(pos, [1, 0, 2])
    vecs = pos - pos_T
    dists = np.linalg.norm(vecs, axis=2)

    bonds = ((dists < cutoff).astype(int) * (dists > 0).astype(int))
    CNs = np.sum(bonds, axis=1)

    return CNs
Beispiel #17
0
def add_atoms(individual, n_diff, atomlist, target_atomlist, surf_CN):
    '''Function that adds atoms to make the total atoms correct'''

    # Get a list of symbols for adding
    syms = individual.get_chemical_symbols()
    
    difflist = {sym: atomlist[sym] - target_atomlist[sym] for sym in atomlist}
    add_syms = []
    for sym in difflist:
        if difflist[sym] < 0:
            add_syms += [sym] * -difflist[sym]

    random.shuffle(add_syms)
    add_syms = add_syms[:-n_diff]

    # Get a list of surface atoms to add atoms to 
    CNs = CoordinationNumbers(individual)
    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN]
    CNs = np.array(CNs)[surf_indices]
    count_CNs = np.bincount(CNs)
    surf_prob_factors = [1.0 / count_CNs[CN] for CN in CNs]
    surf_probs = 2.0 ** CNs
    surf_probs /= surf_prob_factors
    surf_probs /= np.sum(surf_probs)
    if len(individual) < -n_diff:
        add_indices = np.random.choice(surf_indices, -n_diff, True, surf_probs)
    else:
        add_indices = np.random.choice(surf_indices, -n_diff, False, surf_probs)

    # Get positions to add atoms to 
    surf_positions = individual.get_positions()[add_indices]
    COP = individual.get_positions().mean(axis=0)
    vec = surf_positions - COP
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    cutoff = get_avg_radii(individual) * 2
    surf_positions = surf_positions + vec * cutoff

    individual.extend(Atoms(symbols=add_syms, positions=surf_positions))

    return
Beispiel #18
0
def move_surface_STEM(individual, STEM_parameters, move_CN=11, surf_CN=11,
                      filter_size=1, move_cutoff=0.5, surf_cutoff=0.5,
                      max_cutoff=0.5, min_cutoff=0.5):
    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    move_CN : int
        The maximum coordination number considered as a surface atom to move.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM({'kwargs': STEM_parameters})
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)
    min_min = np.min(contrast)

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # import sys; sys.exit()

    # Find a list of local maximum and local minimum in the image
    cutoff = get_avg_radii(individual) * 2 * 1.1
    move_cutoff *= cutoff
    surf_cutoff *= cutoff
    resolution = module.parameters['kwargs']['resolution']
    size = cutoff * resolution * filter_size

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > max_max * max_cutoff))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:,::-1] - np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray([data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False
    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:,::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray([data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Get indices of atoms considered to be moved and sites to move too
    CNs = CoordinationNumbers(individual)
    positions = individual.get_positions()

    move_indices = [i for i, CN in enumerate(CNs) if CN <= move_CN]
    move_xys = positions[list(move_indices)][:, :2]

    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN]
    surf_positions = positions[list(surf_indices)]
    COM = surf_positions.mean(axis=0)
    vec = surf_positions - COM
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    epsilon = np.array([np.random.random(len(surf_positions)) * 0.5 + 0.5]).T
    surf_positions = surf_positions + vec * cutoff * epsilon
    surf_xys = surf_positions[:, :2]

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    high_xy_index = np.random.choice(np.arange(len(max_xys)), p=max_intensities)
    low_xy_index = np.random.choice(np.arange(len(min_xys)), p=min_intensities)
    high_xy = max_xys[high_xy_index]
    low_xy = min_xys[low_xy_index]

    # Choose move_atom (surf_atom) from within the move_cutoff (surf_cutoff)
    # of high_xy (low_xy)
    dists_move_xys = np.linalg.norm(move_xys - high_xy, axis=1)
    indices_move_xys = [i for i, d in zip(move_indices, dists_move_xys) if d < move_cutoff]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase.visualize import view
    # for i in indices_move_xys:
    #     individual[i].symbol = 'Mo'
    # view(individual)
    # import sys; sys.exit()

    if len(indices_move_xys) == 0:
        move_index = np.argmin(dists_move_xys)
    else:
        move_index = random.choice(indices_move_xys)

    dists_surf_xys = np.linalg.norm(surf_xys - low_xy, axis=1)
    indices_surf_xys = [i for i, d in enumerate(dists_surf_xys) if d < surf_cutoff]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase import Atom
    # from ase.visualize import view
    # for i in indices_surf_xys:
    #     individual.append(Atom('Mo', surf_positions[i]))
    # view(individual)
    # import sys; sys.exit()

    if len(indices_surf_xys) == 0:
        surf_index = np.argmin(dists_surf_xys)
    else:
        surf_index = random.choice(indices_surf_xys)

    new_position = surf_positions[surf_index]

    positions[move_index] = new_position
    individual.set_positions(positions)

    return
Beispiel #19
0
def permute_column_STEM(individual, STEM_parameters, filter_size=1,
                        column_cutoff=0.5, max_cutoff=0.5):
    """Permutes a column based on the misalignment of columns between
    the individual and target.

    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)
    min_min = np.min(contrast)

    # Find a list of local maximum and local minimum in the image
    cutoff = get_avg_radii(individual) * 2 * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']        
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    columns = np.argwhere(columns)
    columns = (columns[:, ::-1] - x_shift, y_shift)    

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > 0.01))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:,::-1] - np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray([data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < -0.01))
    if len(minima) == 0:
        return False
    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:,::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray([data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Find areas where where max and min are next to each other
    mins = np.array([min_xys])
    maxs = np.transpose(np.array([max_xys]), [1, 0, 2])
    vecs = mins - maxs
    dists = np.linalg.norm(vecs, axis=2)

    bonds = (dists < column_cutoff).astype(int)
    CNs = np.sum(bonds, axis=1)
    max_xys = max_xys[CNs > 0]
    max_intensities = max_intensities[CNs > 0] ** 10
    print(max_intensities / sum(max_intensities))

    columns = np.zeros(image.shape)
    for loc in max_xys:
        x, y = loc * resolution
        columns[y][x] = 1
    
    import matplotlib.pyplot as plt
    import matplotlib.cm as cm

    fig, ax = plt.subplots(num=3)
    fig.colorbar(ax.pcolormesh(columns, cmap=cm.viridis, linewidths=0))
    ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))    
        
    plt.show()
    print(len(min_intensities))
    import sys; sys.exit()
Beispiel #20
0
def add_atom_defects(individual, add_prob=None, cutoff=0.2, CN_factor=1.1):
    """Calculates the error per column of atoms in the z-direction"""

    avg_radii = get_avg_radii(individual)
    avg_bond_length = avg_radii * 2
    cutoff *= avg_radii * 2

    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:, 0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:, 0] != j]
            NNs = NNs[NNs[:, 1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    vac_new_pos = []
    for indices in column_indices:
        zs = pos[indices][:, 2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
            continue
        else:
            avg_length = np.average([
                zs[i + 1] - zs[i] for i in range(len(zs) - 1)
                if zs[i + 1] - zs[i] < avg_bond_length * 1.7
            ])
            avg_bond_lengths.append(avg_length)

        # Check for vacancies in the column
        for i, z in enumerate(zs[:-1]):
            diff = zs[i + 1] - z
            if diff < avg_length * 1.5:
                continue
            z += avg_bond_length
            xy = pos[indices][:, :2].mean(axis=0)
            vac_new_pos.append(np.append(xy, z))

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(
        np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)),
                                 np.expand_dims(avg_bond_lengths, 1),
                                 axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95
    if np.size(vac_new_pos) > 0:
        surf_positions = np.concatenate(
            (bot_new_pos, top_new_pos, vac_new_pos), axis=0)
    else:
        surf_positions = np.concatenate((bot_new_pos, top_new_pos), axis=0)

    # Calculate CNs of old sites and potential new sites
    CN_cutoff = avg_radii * 2 * CN_factor
    add_vecs = np.transpose(np.expand_dims(surf_positions, 0),
                            [1, 0, 2]) - np.expand_dims(pos, 0)
    add_dists = np.linalg.norm(add_vecs, axis=2)
    add_bonds = (add_dists < CN_cutoff).astype(int)
    add_CNs = list(np.sum(add_bonds, axis=1))

    # Choose moves based on difference in coordination numbers
    add_CN_counts = {CN: add_CNs.count(CN) for CN in set(add_CNs)}
    add_probs = [2.0**CN / add_CN_counts[CN] for CN in add_CNs]
    add_probs = np.array(add_probs) / sum(add_probs)

    new_position = surf_positions[np.random.choice(range(len(surf_positions)),
                                                   p=add_probs)]

    # Choose the element to add
    if add_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in add_prob]
        p = [add_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)
    individual.extend(Atoms([Atom(element, new_position)]))

    return
def move_column_defects(individual, cutoff=0.2, CN_factor=1.1):
    """Calculates the error per column of atoms in the z-direction"""

    avg_radii = get_avg_radii(individual)
    cutoff *= avg_radii * 2

    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:, 0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:, 0] != j]
            NNs = NNs[NNs[:, 1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    for indices in column_indices:
        zs = pos[indices][:, 2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
        else:
            avg_bond_length = np.average(
                [zs[i + 1] - zs[i] for i in range(len(zs) - 1)])
            avg_bond_lengths.append(avg_bond_length)

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(
        np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)),
                                 np.expand_dims(avg_bond_lengths, 1),
                                 axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95

    # Calculate CNs of old sites and potential new sites
    CNs = CoordinationNumbers(individual, factor=CN_factor)
    CN_cutoff = avg_radii * 2 * CN_factor
    top_CNs = CNs[np.array(top_indices)]
    bot_CNs = CNs[np.array(bot_indices)]

    bot_new_vecs = np.transpose(np.expand_dims(bot_new_pos, 0),
                                [1, 0, 2]) - np.expand_dims(pos, 0)
    bot_new_dists = np.linalg.norm(bot_new_vecs, axis=2)
    bot_new_bonds = (bot_new_dists < CN_cutoff).astype(int)
    bot_new_CNs = np.sum(bot_new_bonds, axis=1)

    top_new_vecs = np.transpose(np.expand_dims(top_new_pos, 0),
                                [1, 0, 2]) - np.expand_dims(pos, 0)
    top_new_dists = np.linalg.norm(top_new_vecs, axis=2)
    top_new_bonds = (top_new_dists < CN_cutoff).astype(int)
    top_new_CNs = np.sum(top_new_bonds, axis=1)

    # Choose moves based on difference in coordination numbers
    move_indices = top_indices + bot_indices
    move_new_pos = np.append(bot_new_pos, top_new_pos, axis=0)
    top_to_bot_CNs = bot_new_CNs - top_CNs
    bot_to_top_CNs = top_new_CNs - bot_CNs
    move_CNs = list(np.append(top_to_bot_CNs, bot_to_top_CNs))
    move_CN_counts = {CN: move_CNs.count(CN) for CN in set(move_CNs)}
    move_probs = [2.0**CN / move_CN_counts[CN] for CN in move_CNs]
    move_probs = np.array(move_probs) / sum(move_probs)

    move_indices_i = np.random.choice(range(len(move_indices)), p=move_probs)
    move_index = move_indices[move_indices_i]
    move_new_pos = move_new_pos[move_indices_i]

    individual.positions[move_index] = move_new_pos

    return
Beispiel #22
0
def add_atom_defects(individual, add_prob=None, cutoff=0.2, CN_factor=1.1):
    """Calculates the error per column of atoms in the z-direction"""

    avg_radii = get_avg_radii(individual)
    avg_bond_length = avg_radii * 2
    cutoff *= avg_radii * 2

    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:,0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:,0] != j]
            NNs = NNs[NNs[:,1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    vac_new_pos = []
    for indices in column_indices:
        zs = pos[indices][:,2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
            continue
        else:
            avg_length = np.average([zs[i+1] - zs[i] for i in range(len(zs)-1)
                                          if zs[i+1] - zs[i] < avg_bond_length * 1.7])
            avg_bond_lengths.append(avg_length)

        # Check for vacancies in the column
        for i, z in enumerate(zs[:-1]):
            diff = zs[i+1] - z
            if diff < avg_length * 1.5:
                continue
            z += avg_bond_length
            xy = pos[indices][:,:2].mean(axis=0)
            vac_new_pos.append(np.append(xy, z))

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)), np.expand_dims(avg_bond_lengths, 1),  axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95
    if np.size(vac_new_pos) > 0:
        surf_positions = np.concatenate((bot_new_pos, top_new_pos, vac_new_pos), axis=0)
    else:
        surf_positions = np.concatenate((bot_new_pos, top_new_pos), axis=0)

    # Calculate CNs of old sites and potential new sites
    CN_cutoff = avg_radii * 2 * CN_factor
    add_vecs = np.transpose(np.expand_dims(surf_positions, 0), [1, 0, 2]) - np.expand_dims(pos, 0)
    add_dists = np.linalg.norm(add_vecs, axis=2)
    add_bonds = (add_dists < CN_cutoff).astype(int)
    add_CNs = list(np.sum(add_bonds, axis=1))

    # Choose moves based on difference in coordination numbers
    add_CN_counts = {CN: add_CNs.count(CN) for CN in set(add_CNs)}
    add_probs = [2.0 ** CN / add_CN_counts[CN] for CN in add_CNs]
    add_probs = np.array(add_probs) / sum(add_probs)

    new_position = surf_positions[np.random.choice(range(len(surf_positions)), p=add_probs)]

    # Choose the element to add
    if add_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in add_prob]
        p = [add_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)
    individual.extend(Atoms([Atom(element, new_position)]))

    return
def permute_column_bulk(individual,
                        STEM_parameters,
                        filter_size=0.5,
                        column_cutoff=0.5):
    """This mutation randomly does an atom swap within a column of atoms"""

    module = STEM(STEM_parameters)
    module.generate_target()

    image = module.get_image(individual)

    # Find the xy coordinates of the columns in the image
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    column_coords = np.argwhere(columns)
    column_xys = column_coords[:, ::-1] / resolution

    if len(column_xys) < 2:
        return False

    ##########################################################
    ### This code is for checking the local maximum finder ###
    ##########################################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots(num=1)
    # fig.colorbar(ax.pcolormesh(image_max, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(columns, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')

    # plt.show()
    # import sys; sys.exit()

    # Get the symbols in each column with > 1 type of atom
    xys = individual.get_positions()[:, :2]
    dists_to_columns = np.expand_dims(xys, 0) - np.transpose(
        np.expand_dims(column_xys, 0), (1, 0, 2))
    dists_to_columns = np.linalg.norm(dists_to_columns, axis=2)
    column_indices = [
        np.where(dists < column_cutoff)[0] for dists in dists_to_columns
    ]
    syms = np.asarray(individual.get_chemical_symbols())
    column_indices = [
        indices for indices in column_indices
        if len(np.unique((syms[indices]))) > 1
    ]

    # Shuffle the indices in the atomic column
    column_indices = column_indices[random.randint(0, len(column_indices) - 1)]
    column_symbols = syms[column_indices]
    np.random.shuffle(column_symbols)
    for index, symbol in zip(column_indices, column_symbols):
        individual[index].symbol = symbol

    return
Beispiel #24
0
def poor2rich_column(individual, STEM_parameters, filter_size=0.5,
                       column_cutoff=0.5, species=None, surf_CN=11):
    """This mutation randomly does an atom swap within a column of atoms"""

    NN_list = NeighborList(individual)
    
    module = STEM(STEM_parameters)
    module.generate_target()

    image = module.get_image(individual)

    # Find the xy coordinates of the columns in the image
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']        
    size = cutoff * resolution * filter_size

    image_max = filters.maximum_filter(image, size=size)
    columns = ((image == image_max) & (image > 0.01))
    column_coords = np.argwhere(columns)
    column_xys = column_coords[:, ::-1] / resolution

    if len(column_xys) < 2:
        return False

    ##########################################################
    ### This code is for checking the local maximum finder ###
    ##########################################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots(num=1)
    # fig.colorbar(ax.pcolormesh(image_max, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(columns, cmap=cm.viridis))
    # ax.set_aspect('equal', 'box')
    
    # plt.show()
    # import sys; sys.exit()

    # Get the symbols in each column with > 1 type of atom and a non-species site
    # at the surface available to be switched
    xys = individual.get_positions()[:, :2]
    dists_to_columns = np.expand_dims(xys, 0) - np.transpose(np.expand_dims(column_xys, 0), (1, 0, 2))
    dists_to_columns = np.linalg.norm(dists_to_columns, axis=2)
    all_column_indices = [np.where(dists < column_cutoff)[0] for dists in dists_to_columns]
    syms = individual.get_chemical_symbols()
    if species is None:
        unique_syms = np.unique(syms)
        counts = [syms.count(sym) for sym in unique_syms]
        species = unique_syms[np.argmin(counts)]

    syms = np.asarray(syms)
    
    # In this case, CNs refers to coordination to the species, not all atoms
    CNs = np.asarray([list(syms[i]).count(species) for i in NN_list])
    
    column_indices = []
    for indices in all_column_indices:
        column_syms = syms[indices]
        unique_syms = np.unique(column_syms)
        column_CNs = CNs[indices]
        species_CNs = [CN for sym, CN in zip(column_syms, column_CNs) if sym == species]
        other_CNs = [CN for sym, CN in zip(column_syms, column_CNs) if sym != species]
        diff_CNs = np.expand_dims(species_CNs, 0) - np.expand_dims(other_CNs, 0).T
        if len(unique_syms) > 1 and not (diff_CNs >= 0).all():
            column_indices.append(indices)

    if len(column_indices) == 0:
        return False
    
    # Pick a random column and calculate coordination numbers
    column_indices = column_indices[random.randint(0, len(column_indices) - 1)]
    column_CNs = CNs[column_indices]
    species_indices_CNs = [[index, CN] for index, CN in zip(column_indices, column_CNs) if syms[index] == species]
    other_indices_CNs = [[index, CN] for index, CN in zip(column_indices, column_CNs) if syms[index] != species]

    species_indices, species_CNs = zip(*species_indices_CNs)
    other_indices, other_CNs = zip(*other_indices_CNs)

    # Probabilistically select moves that decrease the CN of the enriched species
    diff_CNs = np.expand_dims(species_CNs, 0) - np.expand_dims(other_CNs, 0).T
    moves = np.argwhere(diff_CNs < 0)
    diffs = [diff_CNs[i, j] for i, j in moves]
    diff_counts = {diff: diffs.count(diff) for diff in set(diffs)}
    moves_p = 2.0 ** (np.max(diffs) - np.array(diffs) + 1)
    moves_p /= np.array([diff_counts[diff] for diff in diffs])
    moves_p /= np.sum(moves_p)
    move = moves[np.random.choice(range(len(moves)), p=moves_p)]
    species_index, other_index = species_indices[move[1]], other_indices[move[0]]
    other_symbol = syms[other_index]

    # Apply the mutation
    individual[species_index].symbol = other_symbol
    individual[other_index].symbol = species

    return
Beispiel #25
0
def move_surface_SCSA(individual,
                      STEM_parameters,
                      move_CN=11,
                      surf_CN=11,
                      filter_size=1,
                      move_cutoff=0.5,
                      surf_cutoff=0.5,
                      max_cutoff=0.5,
                      min_cutoff=0.5):
    """Moves surface atoms around based on the difference in the target
    and individual scattering cross-sectional areas.

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    move_CN : int
        The maximum coordination number considered as a surface atom to move.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target
    image, x_shift, y_shift = module.cross_correlate(
        module.get_image(individual))

    # We now need to calculate the scattering cross-sectional areas of all the
    # columns and their locations in x,y space of the image
    contrast = image - target
    max_max = np.max(contrast)
    min_min = np.min(contrast)

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # import sys; sys.exit()

    # Find a list of local maximum and local minimum in the image
    cutoff = get_avg_radii(individual) * 2 * 1.1
    move_cutoff *= cutoff
    surf_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > max_max * max_cutoff))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:, ::-1] -
               np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray(
        [data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False
    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:, ::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray(
        [data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Get indices of atoms considered to be moved and sites to move too
    CNs = CoordinationNumbers(individual)
    positions = individual.get_positions()

    move_indices = [i for i, CN in enumerate(CNs) if CN <= move_CN]
    move_xys = positions[list(move_indices)][:, :2]

    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN]
    surf_positions = positions[list(surf_indices)]
    COM = surf_positions.mean(axis=0)
    vec = surf_positions - COM
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    epsilon = np.array([np.random.random(len(surf_positions)) * 0.5 + 0.5]).T
    surf_positions = surf_positions + vec * cutoff * epsilon
    surf_xys = surf_positions[:, :2]

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    high_xy_index = np.random.choice(np.arange(len(max_xys)),
                                     p=max_intensities)
    low_xy_index = np.random.choice(np.arange(len(min_xys)), p=min_intensities)
    high_xy = max_xys[high_xy_index]
    low_xy = min_xys[low_xy_index]

    # Choose move_atom (surf_atom) from within the move_cutoff (surf_cutoff)
    # of high_xy (low_xy)
    dists_move_xys = np.linalg.norm(move_xys - high_xy, axis=1)
    indices_move_xys = [
        i for i, d in zip(move_indices, dists_move_xys) if d < move_cutoff
    ]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase.visualize import view
    # for i in indices_move_xys:
    #     individual[i].symbol = 'Mo'
    # view(individual)
    # import sys; sys.exit()

    if len(indices_move_xys) == 0:
        move_index = np.argmin(dists_move_xys)
    else:
        move_index = random.choice(indices_move_xys)

    dists_surf_xys = np.linalg.norm(surf_xys - low_xy, axis=1)
    indices_surf_xys = [
        i for i, d in enumerate(dists_surf_xys) if d < surf_cutoff
    ]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase import Atom
    # from ase.visualize import view
    # for i in indices_surf_xys:
    #     individual.append(Atom('Mo', surf_positions[i]))
    # view(individual)
    # import sys; sys.exit()

    if len(indices_surf_xys) == 0:
        surf_index = np.argmin(dists_surf_xys)
    else:
        surf_index = random.choice(indices_surf_xys)

    new_position = surf_positions[surf_index]

    positions[move_index] = new_position
    individual.set_positions(positions)

    return
def remove_atom_STEM(individual,
                     STEM_parameters,
                     permute=True,
                     remove_prob=None,
                     filter_size=1,
                     remove_CN=11,
                     remove_cutoff=0.5,
                     max_cutoff=0.5,
                     column_cutoff=0.2):
    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    remove_CN : int
        The maximum coordination number considered as a surface atom to remove.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(
        module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)

    # Determine filter size for locating local minimum
    cutoff = get_avg_radii(individual) * 2 * 1.1
    remove_cutoff *= cutoff
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # import sys; sys.exit()

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > max_max * max_cutoff))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:, ::-1] -
               np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray(
        [data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    # Get indices of atoms considered to be moved and sites to move too
    CNs = CoordinationNumbers(individual)
    positions = individual.get_positions()

    remove_indices = [i for i, CN in enumerate(CNs) if CN <= remove_CN]
    remove_xys = positions[list(remove_indices)][:, :2]

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    high_xy_index = np.random.choice(np.arange(len(max_xys)),
                                     p=max_intensities)
    high_xy = max_xys[high_xy_index]

    # Choose move_atom (surf_atom) from within the move_cutoff (surf_cutoff)
    # of high_xy (low_xy)
    dists_remove_xys = np.linalg.norm(remove_xys - high_xy, axis=1)
    indices_remove_xys = [
        i for i, d in zip(remove_indices, dists_remove_xys)
        if d < remove_cutoff
    ]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase.visualize import view
    # for i in indices_move_xys:
    #     individual[i].symbol = 'Mo'
    # view(individual)
    # import sys; sys.exit()

    if len(indices_remove_xys) == 0:
        remove_index = np.argmin(dists_remove_xys)
    else:
        remove_index = random.choice(indices_remove_xys)

    remove_xy = positions[remove_index][:2]
    syms = individual.get_chemical_symbols()
    remove_element = syms[remove_index]

    # Sometimes the element at the surface isn't what we want to remove
    # So flip a random element in the same column to the one that
    if permute == False:
        individual.pop(remove_index)
        return

    # Choose an element to remove. If its the same as the one
    # we're already removing, just remove it and return
    if remove_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in remove_prob]
        p = [remove_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)
    if element == remove_element:
        individual.pop(remove_index)
        return

    # If the element is not the same, permute the column so the surface
    # atom to removed is the element chosen to be removed. If the column
    # doesn't contain the element to remove, then just remove it
    xys = positions[:, :2]
    dists_xys = np.linalg.norm(xys - remove_xy, axis=1)
    indices_to_switch = [
        i for i, d in enumerate(dists_xys)
        if d < column_cutoff and syms[i] == element
    ]
    if len(indices_to_switch) == 0:
        individual.pop(remove_index)
        return

    index_to_switch = random.choice(indices_to_switch)
    individual[index_to_switch].symbol = remove_element
    individual.pop(remove_index)

    return
def move_surface_atoms(individual, max_natoms=0.2, move_CN=11, surf_CN=11):
    """Randomly moves atoms at the surface to other surface sites

    Parameters
    ----------
    individual : structopt.Individual object
        The individual object to be modified in place
    max_natoms : float or int
        if float, the maximum number of atoms that will be moved is 
        max_natoms*len(individual)
        if int, the maximum number of atoms that will be moved is max_natoms
        default: 0.20
    max_CN : int
        The coordination number cutoff for determining which atoms are surface atoms
        Any atoms with coordnation number at or above CN will not be considered as 
        surface.

    Output
    ------
    out : None
        Modifies individual in-place
    """

    if len(individual) == 0:
        return False

    # Analyze the individual
    NNs = NeighborList(individual)
    CNs = [len(NN) for NN in NNs]

    # Get indices of atoms considered to be moved
    move_indices_CNs = [[i, CN] for i, CN in enumerate(CNs) if CN <= move_CN]
    if len(move_indices_CNs) == 0:
        return False
    move_indices_CNs.sort(key=lambda i: i[1])
    move_indices = list(zip(*move_indices_CNs))[0]

    # Get surface sites to move atoms to
    # First get all surface atoms
    positions = individual.get_positions()
    surf_indices_CNs = [[i, CN] for i, CN in enumerate(CNs)
                        if CN <= surf_CN and CN > 2]
    surf_indices_CNs.sort(key=lambda i: i[1])
    surf_indices = list(zip(*surf_indices_CNs))[0]
    surf_positions = np.array([positions[i] for i in surf_indices])

    # Get the average bond length of the particle
    chemical_symbols = individual.get_chemical_symbols()
    unique_symbols = set(chemical_symbols)
    atomlist = [[symbol, chemical_symbols.count(symbol)]
                for symbol in unique_symbols]
    avg_bond_length = get_avg_radii(atomlist) * 2

    # Choose sites as projections one bond length away from COM
    COM = surf_positions.mean(axis=0)
    vec = surf_positions - COM
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    add_positions = surf_positions + vec * avg_bond_length * 0.5

    # Set positions of a fraction of the surface atoms
    if type(max_natoms) is float:
        max_natoms = int(max_natoms * len(move_indices))
    move_natoms = random.randint(0, max_natoms)
    move_indices = move_indices[:move_natoms]
    add_indices = np.random.choice(len(add_positions),
                                   len(move_indices),
                                   replace=False)
    for move_index, add_index in zip(move_indices, add_indices):
        positions[move_index] = add_positions[add_index]

    individual.set_positions(positions)

    return
def add_atom_random(individual, add_prob=None, cutoff=0.2, CN_factor=1.1):
    """Calculates the error per column of atoms in the z-direction"""

    avg_radii = get_avg_radii(individual)
    cutoff *= avg_radii * 2

    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:,0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:,0] != j]
            NNs = NNs[NNs[:,1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    for indices in column_indices:
        zs = pos[indices][:,2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
        else:
            avg_bond_length = np.average([zs[i+1] - zs[i] for i in range(len(zs)-1)])
            avg_bond_lengths.append(avg_bond_length)

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)), np.expand_dims(avg_bond_lengths, 1),  axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95
    add_pos = np.concatenate((bot_new_pos, top_new_pos), axis=0)

    add_pos = add_pos[np.random.choice(range(len(add_pos)))]

    # Choose the element to add
    if add_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in add_prob]
        p = [add_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)
    individual.append(Atom(element, new_position))

    return
Beispiel #29
0
def flip_surface_atom(individual, surf_CN=11, cutoff=0.5):
    """Randomly "flips" an atom from one side of the particle to the other side
    of the particle or to a defect within a column.

    Parameters
    ----------
    individual : structopt.Individual object
        The individual object to be modified in place
    surf_CN : int
        The maximum coordination number for determining an atom as a surface atom
    cutoff : float
        The factor of the average bond length for searching in the xy directions for
        atoms in the same column

    Output
    ------
    out : None
        Modifies individual in-place
    """

    if len(individual) == 0:
        return False

    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * cutoff
    
    # Analyze the individual
    CNs = CoordinationNumbers(individual)
    
    # Get all surface atoms
    positions = individual.get_positions()
    surf_indices_CNs = [[i, CN] for i, CN in enumerate(CNs)
                        if CN <= surf_CN and CN > 2]
    surf_indices, surf_CNs = list(zip(*surf_indices_CNs))
    surf_indices = list(surf_indices)
    surf_CNs = list(surf_CNs)

    # Pair each surface atom with a surface atom on the other side

    # First calculate the xy and z distances of each surface atom
    # with the other surface atoms. The xy coordinates are needed
    # to see if they're in the same column. The z coordinates are
    # needed to see if it is the top and or bottom atom in the column
    surf_positions = np.array([positions[i] for i in surf_indices])    
    surf_xys = np.array([surf_positions[:,:2]])
    surf_xy_vecs = surf_xys - np.transpose(surf_xys, [1, 0, 2])
    surf_xy_dists = np.linalg.norm(surf_xy_vecs, axis=2)

    surf_zs = np.array([surf_positions[:,-1]])
    surf_z_vecs = surf_zs - surf_zs.T
    surf_z_dists = np.absolute(surf_z_vecs)

    # For columns with multiple or zero surface atoms, we need to find
    # which one is on the other side of the particle
    flips = [np.where(dists < cutoff) for dists in surf_xy_dists]
    for i, column in enumerate(flips):
        column_z_vecs = surf_z_vecs[i][column]
        if (len(column_z_vecs) == 1
            or (not all(column_z_vecs >= 0) and not all(column_z_vecs <= 0))):
            flips[i] = None
        else:
            flips[i] = surf_indices[column[0][np.argmax(surf_z_dists[i][column])]]

    # Update the surface atom arrays
    for i, flip in reversed(list(enumerate(flips))):
        if flip is None:
            surf_positions = np.delete(surf_positions, i, axis=0)
            del surf_indices[i]
            del surf_CNs[i]
            del flips[i]

    flip_indices = [[surf_indices[i], j] for i, j in enumerate(flips)]
    flip_CN_diffs = [CNs[j] - CNs[i] for i, j in flip_indices]

    # Simple rank based selection based on coordination differences. Simply put
    # a move is more likely if a surface atom with a low coordination number
    # is moved on top of an atom with a high coordination number.
    min_CN_diff = np.min(flip_CN_diffs) - 1
    flip_CN_diffs -= min_CN_diff
    unique_CN_diffs = np.array(list(set(flip_CN_diffs)))
    flip_CN_ps =  2.0 ** unique_CN_diffs
    flip_CN_ps /= np.sum(flip_CN_ps)
    flip_CN = np.random.choice(unique_CN_diffs, p=flip_CN_ps)
    
    flip_indices_is = np.where(flip_CN_diffs == flip_CN)[0]
    flip_indices_i = random.choice(flip_indices_is)
    move_index, surf_index = flip_indices[flip_indices_i]

    # Add the atom at move_index to ontop/bottom of the surf_index
    move_atom = individual[move_index]
    surf_atom = individual[surf_index]
    if move_atom.z > surf_atom.z:
        move_atom.z = surf_atom.z - avg_bond_length
    else:
        move_atom.z = surf_atom.z + avg_bond_length

    return
def add_atom_STEM(individual,
                  STEM_parameters,
                  add_prob=None,
                  permute=0.5,
                  filter_size=1,
                  column_cutoff=0.2,
                  surf_cutoff=0.5,
                  min_cutoff=0.5):
    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    move_CN : int
        The maximum coordination number considered as a surface atom to move.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(
        module.get_image(individual))
    contrast = image - target
    min_min = np.min(contrast)

    # Determine filter size for locating local minimum
    cutoff = get_avg_radii(individual) * 2 * 1.1
    surf_cutoff *= cutoff
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # import sys; sys.exit()

    # Get xy coordinates of the minimum intensities
    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False
    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:, ::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray(
        [data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    low_xy_index = np.random.choice(np.arange(len(min_xys)), p=min_intensities)
    low_xy = min_xys[low_xy_index]

    # Get indices of atoms considered to be moved and sites to move to
    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < column_cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:, 0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:, 0] != j]
            NNs = NNs[NNs[:, 1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    for indices in column_indices:
        zs = pos[indices][:, 2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
        else:
            avg_bond_length = np.average(
                [zs[i + 1] - zs[i] for i in range(len(zs) - 1)])
            avg_bond_lengths.append(avg_bond_length)

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(
        np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)),
                                 np.expand_dims(avg_bond_lengths, 1),
                                 axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95
    surf_positions = np.concatenate((bot_new_pos, top_new_pos), axis=0)
    surf_xys = surf_positions[:, :2]

    dists_surf_xys = np.linalg.norm(surf_xys - low_xy, axis=1)
    indices_surf_xys = [
        i for i, d in enumerate(dists_surf_xys) if d < surf_cutoff
    ]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase import Atom
    # from ase.visualize import view
    # for i in indices_surf_xys:
    #     individual.append(Atom('Mo', surf_positions[i]))
    # view(individual)
    # import sys; sys.exit()

    # Pick the surface site to add the atom to
    if len(indices_surf_xys) == 0:
        surf_index = np.argmin(dists_surf_xys)
    else:
        surf_index = random.choice(indices_surf_xys)
    new_position = surf_positions[surf_index]
    new_xy = new_position[:2]

    # Choose the element to add
    if add_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in add_prob]
        p = [add_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)

    # Now maybe switch the surface element with a bulk element
    if random.random() < permute:
        individual.append(Atom(element, new_position))
        return

    xys = individual.get_positions()[:, :2]
    syms = individual.get_chemical_symbols()
    dists_xys = np.linalg.norm(xys - new_xy, axis=1)
    indices_to_switch = [
        i for i, d in enumerate(dists_xys)
        if d < column_cutoff and syms[i] != element
    ]

    if len(indices_to_switch) == 0:
        individual.append(Atom(element, new_position))
        return

    index_to_switch = random.choice(indices_to_switch)
    element_to_switch = syms[index_to_switch]
    individual[index_to_switch].symbol = element
    individual.append(Atom(element_to_switch, new_position))

    return
Beispiel #31
0
def move_surface_atoms(individual, max_natoms=0.2, move_CN=11, surf_CN=11):
    """Randomly moves atoms at the surface to other surface sites

    Parameters
    ----------
    individual : Individual
        The individual object to be modified in place
    max_natoms : float or int
        if float, the maximum number of atoms that will be moved is 
        max_natoms*len(individual)
        if int, the maximum number of atoms that will be moved is max_natoms
        default: 0.20
    move_CN : int
        The coordination number to determine which atoms can move moved. Any 
        atom with coordination number above move_CN will not be moved
    surf_CN : int
        The coordination number to determine which atoms are considered surface
        atoms. Surface atoms are used to estimating new surface sites
    """

    if len(individual) == 0:
        return False

    # Analyze the individual
    NNs = NeighborList(individual)
    CNs = [len(NN) for NN in NNs]
    
    # Get indices of atoms considered to be moved
    move_indices_CNs = [[i, CN] for i, CN in enumerate(CNs) if CN <= move_CN]
    if len(move_indices_CNs) == 0:
        return False
    move_indices_CNs.sort(key=lambda i: i[1])
    move_indices = list(zip(*move_indices_CNs))[0]

    # Get surface sites to move atoms to
    # First get all surface atoms
    positions = individual.get_positions()
    surf_indices_CNs = [[i, CN] for i, CN in enumerate(CNs)
                        if CN <= surf_CN and CN > 2]
    surf_indices_CNs.sort(key=lambda i: i[1])
    surf_indices = list(zip(*surf_indices_CNs))[0]
    surf_positions = np.array([positions[i] for i in surf_indices])

    # Get the average bond length of the particle
    chemical_symbols = individual.get_chemical_symbols()
    unique_symbols = set(chemical_symbols)
    atomlist = [[symbol, chemical_symbols.count(symbol)] for symbol in unique_symbols]
    avg_bond_length = get_avg_radii(atomlist) * 2

    # Choose sites as projections one bond length away from COM
    COM = surf_positions.mean(axis=0)
    vec = surf_positions - COM
    vec /= np.array([np.linalg.norm(vec, axis=1)]).T
    add_positions = surf_positions + vec * avg_bond_length * 0.5

    # Set positions of a fraction of the surface atoms
    if type(max_natoms) is float:
        max_natoms = int(max_natoms * len(move_indices))
    move_natoms = random.randint(0, max_natoms)
    move_indices = move_indices[:move_natoms]
    add_indices = np.random.choice(len(add_positions), len(move_indices), replace=False)
    for move_index, add_index in zip(move_indices, add_indices):
        positions[move_index] = add_positions[add_index]

    individual.set_positions(positions)

    return
def permutation_STEM(individual,
                     STEM_parameters,
                     filter_size=0.5,
                     move_cutoff=0.5,
                     max_cutoff=0.5,
                     min_cutoff=0.5):
    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(
        module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)
    min_min = np.min(contrast)

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))
    # plt.show()
    # import sys; sys.exit()

    # Find a list of local maximum and local minimum in the image
    cutoff = get_avg_radii(individual) * 2 * 1.1
    move_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > max_max * max_cutoff))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:, ::-1] -
               np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray(
        [data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))
    # print(len(max_intensities))

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(data_max, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))

    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False

    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:, ::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray(
        [data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots(num=1)
    # fig.colorbar(ax.pcolormesh(minima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))

    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Get atoms associated with each max and min column
    xys = individual.get_positions()[:, :2]
    max_dists = np.expand_dims(xys, 0) - np.transpose(
        np.expand_dims(max_xys, 0), (1, 0, 2))
    max_dists = np.linalg.norm(max_dists, axis=2)
    max_column_indices = [
        np.where(dists < move_cutoff)[0] for dists in max_dists
    ]

    min_dists = np.expand_dims(xys, 0) - np.transpose(
        np.expand_dims(min_xys, 0), (1, 0, 2))
    min_dists = np.linalg.norm(min_dists, axis=2)
    min_column_indices = [
        np.where(dists < move_cutoff)[0] for dists in min_dists
    ]

    if np.size(min_column_indices) == 0 or np.size(max_column_indices) == 0:
        return False

    # Eliminate columns that cannot be "improved" by a permutation
    syms = np.asarray(individual.get_chemical_symbols())
    unique_syms = np.unique(syms)
    unique_nums = [atomic_numbers[sym] for sym in unique_syms]
    max_sym = unique_syms[np.argmax(unique_nums)]
    min_sym = unique_syms[np.argmin(unique_nums)]

    for i, indices in reversed(list(enumerate(max_column_indices))):
        if all(syms[indices] == min_sym):
            max_column_indices = np.delete(max_column_indices, i, axis=0)
            max_intensities = np.delete(max_intensities, i)
            max_intensities /= np.sum(max_intensities)

    for i, indices in reversed(list(enumerate(min_column_indices))):
        if all(syms[indices] == max_sym):
            min_column_indices = np.delete(min_column_indices, i, axis=0)
            min_intensities = np.delete(min_intensities, i)
            min_intensities /= np.sum(min_intensities)

    # Pick a max column and min column based on their intensities
    if np.size(min_column_indices) == 0 or np.size(max_column_indices) == 0:
        return False

    max_column_indices = max_column_indices[np.random.choice(
        np.arange(len(max_intensities)), p=max_intensities)]
    min_column_indices = min_column_indices[np.random.choice(
        np.arange(len(min_intensities)), p=min_intensities)]

    # Pick a move between the two columns based on differences in atomic numbers
    max_column_numbers = [
        atomic_numbers[sym] for sym in syms[max_column_indices]
    ]
    min_column_numbers = [
        atomic_numbers[sym] for sym in syms[min_column_indices]
    ]

    max_column_numbers = np.expand_dims(max_column_numbers, 0)
    min_column_numbers = np.expand_dims(min_column_numbers, 0).T

    min_max_pairs = np.argwhere(max_column_numbers - min_column_numbers > 0)
    min_index, max_index = min_max_pairs[random.randint(
        0,
        len(min_max_pairs) - 1)]
    min_index, max_index = min_column_indices[min_index], max_column_indices[
        max_index]
    max_symbol = syms[max_index]
    min_symbol = syms[min_index]

    # Switch the atomic symbols
    individual[max_index].symbol = min_symbol
    individual[min_index].symbol = max_symbol

    return
def flip_surface_atom(individual, surf_CN=11, cutoff=0.5):
    """Randomly "flips" an atom from one side of the particle to the other side
    of the particle or to a defect within a column.

    Parameters
    ----------
    individual : structopt.Individual object
        The individual object to be modified in place
    surf_CN : int
        The maximum coordination number for determining an atom as a surface atom
    cutoff : float
        The factor of the average bond length for searching in the xy directions for
        atoms in the same column

    Output
    ------
    out : None
        Modifies individual in-place
    """

    if len(individual) == 0:
        return False

    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * cutoff

    # Analyze the individual
    CNs = CoordinationNumbers(individual)

    # Get all surface atoms
    positions = individual.get_positions()
    surf_indices_CNs = [[i, CN] for i, CN in enumerate(CNs)
                        if CN <= surf_CN and CN > 2]
    surf_indices, surf_CNs = list(zip(*surf_indices_CNs))
    surf_indices = list(surf_indices)
    surf_CNs = list(surf_CNs)

    # Pair each surface atom with a surface atom on the other side

    # First calculate the xy and z distances of each surface atom
    # with the other surface atoms. The xy coordinates are needed
    # to see if they're in the same column. The z coordinates are
    # needed to see if it is the top and or bottom atom in the column
    surf_positions = np.array([positions[i] for i in surf_indices])
    surf_xys = np.array([surf_positions[:, :2]])
    surf_xy_vecs = surf_xys - np.transpose(surf_xys, [1, 0, 2])
    surf_xy_dists = np.linalg.norm(surf_xy_vecs, axis=2)

    surf_zs = np.array([surf_positions[:, -1]])
    surf_z_vecs = surf_zs - surf_zs.T
    surf_z_dists = np.absolute(surf_z_vecs)

    # For columns with multiple or zero surface atoms, we need to find
    # which one is on the other side of the particle
    flips = [np.where(dists < cutoff) for dists in surf_xy_dists]
    for i, column in enumerate(flips):
        column_z_vecs = surf_z_vecs[i][column]
        if (len(column_z_vecs) == 1 or
            (not all(column_z_vecs >= 0) and not all(column_z_vecs <= 0))):
            flips[i] = None
        else:
            flips[i] = surf_indices[column[0][np.argmax(
                surf_z_dists[i][column])]]

    # Update the surface atom arrays
    for i, flip in reversed(list(enumerate(flips))):
        if flip is None:
            surf_positions = np.delete(surf_positions, i, axis=0)
            del surf_indices[i]
            del surf_CNs[i]
            del flips[i]

    flip_indices = [[surf_indices[i], j] for i, j in enumerate(flips)]
    flip_CN_diffs = [CNs[j] - CNs[i] for i, j in flip_indices]

    # Simple rank based selection based on coordination differences. Simply put
    # a move is more likely if a surface atom with a low coordination number
    # is moved on top of an atom with a high coordination number.
    min_CN_diff = np.min(flip_CN_diffs) - 1
    flip_CN_diffs -= min_CN_diff
    unique_CN_diffs = np.array(list(set(flip_CN_diffs)))
    flip_CN_ps = 2.0**unique_CN_diffs
    flip_CN_ps /= np.sum(flip_CN_ps)
    flip_CN = np.random.choice(unique_CN_diffs, p=flip_CN_ps)

    flip_indices_is = np.where(flip_CN_diffs == flip_CN)[0]
    flip_indices_i = random.choice(flip_indices_is)
    move_index, surf_index = flip_indices[flip_indices_i]

    # Add the atom at move_index to ontop/bottom of the surf_index
    move_atom = individual[move_index]
    surf_atom = individual[surf_index]
    if move_atom.z > surf_atom.z:
        move_atom.z = surf_atom.z - avg_bond_length
    else:
        move_atom.z = surf_atom.z + avg_bond_length

    return
def remove_atom_STEM(individual, STEM_parameters, permute=True, remove_prob=None,
                     filter_size=1, remove_CN=11, remove_cutoff=0.5, max_cutoff=0.5,
                     column_cutoff=0.2):

    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    remove_CN : int
        The maximum coordination number considered as a surface atom to remove.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)

    # Determine filter size for locating local minimum
    cutoff = get_avg_radii(individual) * 2 * 1.1
    remove_cutoff *= cutoff
    column_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size    

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # import sys; sys.exit()

    data_max = filters.maximum_filter(contrast, size=size)
    maxima = ((contrast == data_max) & (contrast > max_max * max_cutoff))
    if len(maxima) == 0:
        return False
    max_coords = np.argwhere(maxima)
    max_xys = (max_coords[:,::-1] - np.array([[x_shift, y_shift]])) / resolution
    max_intensities = np.asarray([data_max[tuple(coord)] for coord in max_coords])
    max_intensities /= sum(max_intensities)

    ###################################
    ## Code for testing the max find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(maxima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * 10))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * 10))
    # plt.show()
    # print(len(max_intensities))
    # import sys; sys.exit()

    # Get indices of atoms considered to be moved and sites to move too
    CNs = CoordinationNumbers(individual)
    positions = individual.get_positions()

    remove_indices = [i for i, CN in enumerate(CNs) if CN <= remove_CN]
    remove_xys = positions[list(remove_indices)][:, :2]

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    high_xy_index = np.random.choice(np.arange(len(max_xys)), p=max_intensities)
    high_xy = max_xys[high_xy_index]

    # Choose move_atom (surf_atom) from within the move_cutoff (surf_cutoff)
    # of high_xy (low_xy)
    dists_remove_xys = np.linalg.norm(remove_xys - high_xy, axis=1)
    indices_remove_xys = [i for i, d in zip(remove_indices, dists_remove_xys) if d < remove_cutoff]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase.visualize import view
    # for i in indices_move_xys:
    #     individual[i].symbol = 'Mo'
    # view(individual)
    # import sys; sys.exit()

    if len(indices_remove_xys) == 0:
        remove_index = np.argmin(dists_remove_xys)
    else:
        remove_index = random.choice(indices_remove_xys)

    remove_xy = positions[remove_index][:2]
    syms = individual.get_chemical_symbols()
    remove_element = syms[remove_index]

    # Sometimes the element at the surface isn't what we want to remove
    # So flip a random element in the same column to the one that
    if permute == False:
        individual.pop(remove_index)
        return

    # Choose an element to remove. If its the same as the one
    # we're already removing, just remove it and return
    if remove_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in remove_prob]
        p = [remove_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)
    if element == remove_element:
        individual.pop(remove_index)
        return

    # If the element is not the same, permute the column so the surface
    # atom to removed is the element chosen to be removed. If the column
    # doesn't contain the element to remove, then just remove it
    xys = positions[:,:2]
    dists_xys = np.linalg.norm(xys - remove_xy, axis=1)
    indices_to_switch = [i for i, d in enumerate(dists_xys) if d < column_cutoff and syms[i] == element]
    if len(indices_to_switch) == 0:
        individual.pop(remove_index)
        return

    index_to_switch = random.choice(indices_to_switch)
    individual[index_to_switch].symbol = remove_element
    individual.pop(remove_index)

    return
def increase_Z_STEM(individual, STEM_parameters, filter_size=0.5,
                    move_cutoff=0.5, min_cutoff=0.5):
    """Increaes the proton count of an atom to match a STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    move_cutoff : float
        The search radius for selecting an atom to move near a high intensity point.
        Defaults to the average bond distance
    """

    module = STEM(STEM_parameters)
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(module.get_image(individual))
    contrast = image - target
    max_max = np.max(contrast)
    min_min = np.min(contrast)

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))
    # plt.show()
    # import sys; sys.exit()

    # Find a list of local minimum
    cutoff = get_avg_radii(individual) * 2 * 1.1
    move_cutoff *= cutoff
    resolution = module.parameters['resolution']
    size = cutoff * resolution * filter_size

    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False

    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:,::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray([data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots(num=1)
    # fig.colorbar(ax.pcolormesh(minima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))

    # fig, ax = plt.subplots(num=2)
    # fig.colorbar(ax.pcolormesh(data_min, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0] * STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1] * STEM_parameters['resolution']))
    
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Get atoms associated with each max and min column
    xys = individual.get_positions()[:, :2]

    min_dists = np.expand_dims(xys, 0) - np.transpose(np.expand_dims(min_xys, 0), (1, 0, 2))
    min_dists = np.linalg.norm(min_dists, axis=2)
    min_column_indices = [np.where(dists < move_cutoff)[0] for dists in min_dists]

    if np.size(min_column_indices) == 0:
        return False

    # Eliminate columns that cannot be "improved" by a permutation
    syms = np.asarray(individual.get_chemical_symbols())
    unique_syms = np.unique(syms)
    unique_nums = [atomic_numbers[sym] for sym in unique_syms]
    max_sym = unique_syms[np.argmax(unique_nums)]
    min_sym = unique_syms[np.argmin(unique_nums)]

    for i, indices in reversed(list(enumerate(min_column_indices))):
        if all(syms[indices] == max_sym):
            min_column_indices = np.delete(min_column_indices, i, axis=0)
            min_intensities = np.delete(min_intensities, i)
            min_intensities /= np.sum(min_intensities)

    # Pick a max column and min column based on their intensities 
    if np.size(min_column_indices) == 0:
        return False
    min_column_indices = min_column_indices[np.random.choice(np.arange(len(min_intensities)), p=min_intensities)]

    # Take out elements with atomic numbers that cannot be increased
    min_column_numbers = [atomic_numbers[sym] for sym in syms[min_column_indices]]
    min_column_indices = [i for i, Z in zip(min_column_indices, min_column_numbers) if Z < np.max(unique_nums)]
    min_column_numbers = [atomic_numbers[sym] for sym in syms[min_column_indices]]

    # Choose a random indice
    min_index = np.random.choice(min_column_indices)
    min_number = atomic_numbers[syms[min_index]]
    new_symbol = chemical_symbols[np.random.choice([n for n in unique_nums if n > min_number])]

    # Switch the atomic symbols
    individual[min_index].symbol = new_symbol

    return
Beispiel #36
0
def add_atom_STEM(individual, STEM_parameters, add_prob=None, permute=0.5, 
                  filter_size=1, column_cutoff=0.2, surf_cutoff=0.5, min_cutoff=0.5):

    """Moves surface atoms around based on the difference in the target
    and individual STEM image

    Parameters
    ----------
    STEM_parameters : dict
        Parameters for the STEM calculation. Ideally should be the same as the ones
        used for the STEM fitness/relaxation
    move_CN : int
        The maximum coordination number considered as a surface atom to move.    
    surf_CN : int
        The maximum coordination number considered as a surface atom to move an
        atom to
    filter_size : float
        Filter size for choosing local maximum in the picture. Filter size is equal
        to average_bond_length * resolution * filter_size.
    surf_cutoff : float
        The search radius for selecting a surface site near a low intensity point
        Defaults to the average bond distance
    """

    module = STEM({'kwargs': STEM_parameters})
    module.generate_target()
    target = module.target

    image, x_shift, y_shift = module.cross_correlate(module.get_image(individual))
    contrast = image - target
    min_min = np.min(contrast)

    # Determine filter size for locating local minimum
    avg_bond_length = get_avg_radii(individual) * 2
    cutoff = avg_bond_length * 1.1
    surf_cutoff *= cutoff
    column_cutoff *= cutoff
    resolution = module.parameters['kwargs']['resolution']
    size = cutoff * resolution * filter_size

    ###################################
    ## Code for testing the contrast ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(contrast, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0]*STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1]*STEM_parameters['resolution']))
    # plt.show()
    # import sys; sys.exit()

    # Get xy coordinates of the minimum intensities
    data_min = filters.minimum_filter(contrast, size=size)
    minima = ((contrast == data_min) & (contrast < min_min * min_cutoff))
    if len(minima) == 0:
        return False
    min_coords = np.argwhere(minima)
    min_xys = (min_coords[:,::-1] - [x_shift, y_shift]) / resolution
    min_intensities = np.asarray([data_min[tuple(coord)] for coord in min_coords])
    min_intensities = np.absolute(min_intensities)
    min_intensities /= sum(min_intensities)

    ###################################
    ## Code for testing the min find ##
    ###################################
    # import matplotlib.pyplot as plt
    # import matplotlib.cm as cm
    # fig, ax = plt.subplots()
    # fig.colorbar(ax.pcolormesh(minima, cmap=cm.viridis, linewidths=0))
    # ax.set_xlim((0, STEM_parameters['dimensions'][0]*STEM_parameters['resolution']))
    # ax.set_ylim((0, STEM_parameters['dimensions'][1]*STEM_parameters['resolution']))
    # plt.show()
    # print(len(min_intensities))
    # import sys; sys.exit()

    # Randomly choose local maxima and minima locations from contrast weighted
    # by their intensity
    low_xy_index = np.random.choice(np.arange(len(min_xys)), p=min_intensities)
    low_xy = min_xys[low_xy_index]

    # Get indices of atoms considered to be moved and sites to move to
    # Organize atoms into columns
    pos = individual.get_positions()
    xys = np.expand_dims(pos[:, :2], 0)
    dists = np.linalg.norm(xys - np.transpose(xys, (1, 0, 2)), axis=2)

    NNs = np.sort(np.argwhere(dists < column_cutoff))
    column_indices = []
    atoms_to_be_sorted = list(range(len(individual)))
    while len(atoms_to_be_sorted) > 0:
        i = atoms_to_be_sorted[0]
        same_column_indices = np.unique(NNs[NNs[:,0] == i])
        column_indices.append(same_column_indices)
        for j in reversed(sorted(same_column_indices)):
            i_del = atoms_to_be_sorted.index(j)
            atoms_to_be_sorted.pop(i_del)
            NNs = NNs[NNs[:,0] != j]
            NNs = NNs[NNs[:,1] != j]

    # Make a list of the top and bottom atom of each column as well
    # the average bond length of atoms in the column
    top_indices, bot_indices, avg_bond_lengths = [], [], []
    vac_new_pos = []
    for indices in column_indices:
        zs = pos[indices][:,2]
        top_indices.append(indices[np.argmax(zs)])
        bot_indices.append(indices[np.argmin(zs)])

        # Get the average bond length of each column for adding
        zs = np.sort(zs)
        if len(zs) == 1:
            avg_bond_lengths.append(np.nan)
            continue
        else:
            avg_length = np.average([zs[i+1] - zs[i] for i in range(len(zs)-1)
                                     if zs[i+1] - zs[i] < avg_bond_length * 1.7])
            avg_bond_lengths.append(avg_length)

        # Check for vacancies in the column
        for i, z in enumerate(zs[:-1]):
            diff = zs[i+1] - z
            if diff < avg_length * 1.5:
                continue
            z += avg_bond_length
            xy = pos[indices][:,:2].mean(axis=0)
            vac_new_pos.append(np.append(xy, z))

    avg_bond_lengths = np.array(avg_bond_lengths)
    avg_bond_length = np.average(avg_bond_lengths[np.invert(np.isnan(avg_bond_lengths))])
    avg_bond_lengths[np.isnan(avg_bond_lengths)] = avg_bond_length
    avg_bond_lengths = np.append(np.zeros((len(avg_bond_lengths), 2)), np.expand_dims(avg_bond_lengths, 1),  axis=1)

    # Create a list of new surface sites
    bot_new_pos = pos[np.array(bot_indices)] - avg_bond_lengths * 0.95
    top_new_pos = pos[np.array(top_indices)] + avg_bond_lengths * 0.95
    if np.size(vac_new_pos) > 0:
        surf_positions = np.concatenate((bot_new_pos, top_new_pos, vac_new_pos), axis=0)
    else:
        surf_positions = np.concatenate((bot_new_pos, top_new_pos), axis=0)

    surf_xys = surf_positions[:,:2]

    dists_surf_xys = np.linalg.norm(surf_xys - low_xy, axis=1)
    indices_surf_xys = [i for i, d in enumerate(dists_surf_xys) if d < surf_cutoff]

    ########################
    ## Test atoms to move ##
    ########################
    # from ase import Atom
    # from ase.visualize import view
    # for i in indices_surf_xys:
    #     individual.append(Atom('Mo', surf_positions[i]))
    # view(individual)
    # import sys; sys.exit()

    # Pick the surface site to add the atom to
    if len(indices_surf_xys) == 0:
        surf_index = np.argmin(dists_surf_xys)
    else:
        surf_index = random.choice(indices_surf_xys)
    new_position = surf_positions[surf_index]
    new_xy = new_position[:2]

    # Choose the element to add
    if add_prob is None:
        syms = individual.get_chemical_symbols()
        elements = np.unique(syms)
        n = len(syms)
        p = [syms.count(element) / n for element in elements]
    else:
        elements = [key for key in add_prob]
        p = [add_prob[key] for key in elements]

    element = np.random.choice(elements, p=p)

    # Now maybe switch the surface element with a bulk element
    if random.random() < permute:
        individual.append(Atom(element, new_position))
        return

    xys = individual.get_positions()[:,:2]
    syms = individual.get_chemical_symbols()
    dists_xys = np.linalg.norm(xys - new_xy, axis=1)
    indices_to_switch = [i for i, d in enumerate(dists_xys) if d < column_cutoff and syms[i] != element]

    if len(indices_to_switch) == 0:
        individual.append(Atom(element, new_position))
        return

    index_to_switch = random.choice(indices_to_switch)
    element_to_switch = syms[index_to_switch]
    individual[index_to_switch].symbol = element
    individual.extend(Atoms([Atom(element_to_switch, new_position)]))

    return