def enrich_bulk(individual, surf_CN=11, species=None):
    """Mutation that selectively enriches the surface with a species

    Parameters
    ----------
    surf_CN : int
        The maximum coordination number of an atom to be considered surface
    species : str
        The surface to enrich with. If None, takes the lowest concentration
    """

    syms = individual.get_chemical_symbols()
    if species is None:
        unique_syms = list(set(syms))
        counts = [syms.count(sym) for sym in unique_syms]
        species = unique_syms[np.argmin(counts)]

    # Get a random surface site that is not the species and a bulk site that is
    CNs = CoordinationNumbers(individual)

    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN and syms[i] == species]
    bulk_indices = [i for i, CN in enumerate(CNs) if CN > surf_CN and syms[i] != species]

    if len(surf_indices) == 0 or len(bulk_indices) == 0:
        return False

    bulk_index = random.choice(bulk_indices)
    bulk_symbol = syms[bulk_index]    
    surf_index = random.choice(surf_indices)

    individual[bulk_index].symbol = species
    individual[surf_index].symbol = bulk_symbol    
    
    return
Beispiel #2
0
def remove_atom_random(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 : structopt.Individual object
        The individual object to be modified in place
    surf_CN : int
        The maximum coordination number to considered a surface atom

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

    if len(individual) == 0:
        return False

    # Analyze the individual
    CNs = CoordinationNumbers(individual)

    # Get indices, CNs, and positions of all surface sites
    surf_indices = [i for i, CN in enumerate(CNs) if CN <= surf_CN]
    surf_index = np.random.choice(surf_indices)

    individual.pop(surf_index)

    return
Beispiel #3
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 
Beispiel #4
0
def enrich_surface_defects(individual, surf_CN=11, species=None):
    """Mutation that selectively enriches defects with a species. Defects
    are defined as atoms atoms with lower coordination numbers

    Parameters
    ----------
    individual : Individual
        An individual
    surf_CN : int
        The maximum coordination number of an atom to be considered surface
    species : str
        The surface to enrich with. If None, takes the lowest concentration
    """

    syms = individual.get_chemical_symbols()
    if species is None:
        unique_syms = list(set(syms))
        counts = [syms.count(sym) for sym in unique_syms]
        species = unique_syms[np.argmin(counts)]

    # Get a random surface site that is not the species and a bulk site that is
    CNs = CoordinationNumbers(individual)

    defect_indices = [
        i for i, CN in enumerate(CNs) if CN <= surf_CN and syms[i] != species
    ]
    defect_CNs = [CNs[i] for i in defect_indices]
    facet_indices = [
        i for i, CN in enumerate(CNs) if CN <= surf_CN and syms[i] == species
    ]
    facet_CNs = [CNs[i] for i in facet_indices]

    if len(defect_indices) == 0 or len(facet_indices) == 0:
        return False

    unique_defect_CNs = np.unique(defect_CNs)
    defect_probs = 2.0**(surf_CN + 1 - unique_defect_CNs)
    defect_probs /= np.sum(defect_probs)
    defect_CN = np.random.choice(unique_defect_CNs, p=defect_probs)
    defect_index = defect_indices[random.choice(
        np.where(np.array(defect_CNs) == defect_CN)[0])]
    defect_symbol = syms[defect_index]

    unique_facet_CNs = np.unique(facet_CNs)
    facet_probs = 2.0**(unique_facet_CNs)
    facet_probs /= np.sum(facet_probs)
    facet_CN = np.random.choice(unique_facet_CNs, p=facet_probs)
    facet_index = facet_indices[random.choice(
        np.where(np.array(facet_CNs) == facet_CN)[0])]

    individual[defect_index].symbol = species
    individual[facet_index].symbol = defect_symbol

    return
Beispiel #5
0
def delete_atoms(individual, n_diff, surf_CN):
    '''Function that deletes atoms to make the total atoms correct'''

    syms = individual.get_chemical_symbols()

    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**(surf_CN + 1 - CNs)
    surf_probs /= surf_prob_factors
    surf_probs /= np.sum(surf_probs)
    n_delete = np.random.choice(surf_indices, n_diff, False, surf_probs)
    for i in reversed(sorted(n_delete)):
        individual.pop(i)

    return
Beispiel #6
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 #7
0
def remove_atom_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 : structopt.Individual object
        The individual object to be modified in place
    surf_CN : int
        The maximum coordination number to considered a surface atom

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

    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])
    surf_CN_counts = {CN: surf_CNs.count(CN) for CN in set(surf_CNs)}
    surf_probs = [2.0**-CN / surf_CN_counts[CN] for CN in surf_CNs]
    surf_probs /= sum(surf_probs)

    surf_index = np.random.choice(surf_indices, p=surf_probs)

    individual.pop(surf_index)

    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
Beispiel #9
0
def enrich_surface_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"""

    CNs = CoordinationNumbers(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)

    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 increase 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.array(diffs)
    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
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 #11
0
def fcc_110_twin(atomlist,
                 cell,
                 a,
                 shape=[1, 1, 1],
                 roundness=0.5,
                 alpha=10,
                 rotation=0,
                 center=0.5):

    # Make a bigger particle to ensure child has more than required
    n = int(sum(list(zip(*atomlist))[1]) * 1.1)

    atoms1 = fcc([['Pt', n]],
                 cell,
                 a,
                 shape=shape,
                 orientation='110',
                 angle=rotation)
    rotation2 = rotation + (np.pi - atan(2**0.5) * 2)
    atoms2 = fcc([['Pt', n]],
                 cell,
                 a,
                 shape=shape,
                 orientation='110',
                 angle=rotation2)

    # Now rotate both atoms so the twin plane is parallel to the x axis
    align = rotation - atan(2**0.5)
    atoms1.rotate('z', -align, center='COP')
    atoms2.rotate('z', -align, center='COP')

    # Make a new atoms object from the two atoms.
    pos1 = atoms1.get_positions()
    pos2 = atoms2.get_positions()
    mid_x1 = (np.max(pos1[:, 0]) + np.min(pos1[:, 0])) * 0.5
    mid_y1 = (np.max(pos1[:, 1]) + np.min(pos1[:, 1])) * center
    mid_z1 = (np.max(pos1[:, 2]) + np.min(pos1[:, 2])) * 0.5
    center1 = np.array([mid_x1, mid_y1, mid_z1])

    mid_x2 = (np.max(pos2[:, 0]) + np.min(pos2[:, 0])) * 0.5
    mid_y2 = (np.max(pos2[:, 1]) + np.min(pos2[:, 1])) * center
    mid_z2 = (np.max(pos2[:, 2]) + np.min(pos2[:, 2])) * 0.5
    center2 = np.array([mid_z2, mid_y2, mid_z2])

    dists1 = np.linalg.norm(pos1 - center1, axis=1)
    center1 = pos1[np.argmin(dists1)]
    atoms1.translate(-center1)

    dists2 = np.linalg.norm(pos2 - center2, axis=1)
    center2 = pos2[np.argmin(dists2)]
    atoms2.translate(-center2)

    child = atoms1[atoms1.get_positions()[:, 1] <= 0.1] + atoms2[
        atoms2.get_positions()[:, 1] > 0.1]
    child.center()
    child.rotate('z', align, center='COP')

    # Delete any extra atoms on the surface
    cutoff = np.linalg.norm([a, a]) / 4.0 * 2 * 1.1
    CNs = CoordinationNumbers(child, cutoff)
    indices_CNs = list(zip(range(len(CNs)), CNs))
    indices_CNs = sorted(indices_CNs, key=lambda i: i[1])
    assert len(CNs) - sum(list(zip(*atomlist))[1])
    to_del = list(zip(*indices_CNs))[0][:len(CNs) -
                                        sum(list(zip(*atomlist))[1])]
    to_del = sorted(to_del)
    for i in reversed(to_del):
        child.pop(i)

    # Make the composition correct
    symbols = []
    for sym, n in atomlist:
        symbols += [sym] * n
    random.shuffle(symbols)
    child.set_chemical_symbols(symbols)

    return child
Beispiel #12
0
def swap_core_shell(individual, surf_CN=11):
    """Swaps atoms on the surface with an atom in the core. Only does it
    for different element types"""

    if not len(individual):
        return None

    CNs = CoordinationNumbers(individual)

    surf_indices = [i for i, CN in enumerate(CNs) if CN < surf_CN and CN > 3]
    bulk_indices = [i for i in range(len(individual)) if i not in surf_indices]

    # Construct surface and bulk dictionaries of elements and their indices
    syms = individual.get_chemical_symbols()
    surf_elements = list(set([syms[i] for i in surf_indices]))
    bulk_elements = list(set([syms[i] for i in bulk_indices]))

    surf_dict = {element: [] for element in surf_elements}
    bulk_dict = {element: [] for element in bulk_elements}

    for i in surf_indices:
        surf_dict[syms[i]].append(i)

    for i in bulk_indices:
        bulk_dict[syms[i]].append(i)

    # Get a list of bulk indices that CAN be swapped for each
    # unique surface element
    swap_dict = {}
    for surf_element in surf_dict:
        swap_dict[surf_element] = []
        for bulk_element in bulk_dict:
            if bulk_element == surf_element:
                continue
            swap_dict[surf_element] += bulk_dict[bulk_element]

    # Get a list of all swaps, taken based on their probability of happening
    swap_list = []

    # First pick an element to swap based on the concentration in
    # the available surface swap atoms and atoms it can swap with
    surf_prob = np.asarray([
        len(surf_dict[element]) * len(swap_dict[element])
        for element in surf_elements
    ],
                           dtype=float)
    if np.sum(surf_prob) == 0:
        return
    surf_prob /= np.sum(surf_prob)
    surf_element = np.random.choice(surf_elements, p=surf_prob)
    surf_index = np.random.choice(surf_dict[surf_element])

    # Pick a random bulk atom to swap to
    bulk_index = np.random.choice(swap_dict[surf_element])
    bulk_element = individual[bulk_index].symbol

    # Swap the elements
    individual[surf_index].symbol = bulk_element
    individual[bulk_index].symbol = surf_element

    return None
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
Beispiel #14
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