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
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
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 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
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
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
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
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
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
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
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