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