def rich2poor(atoms, surf_CN=11): '''Moves an atom from a rich region to a poor region''' syms = np.asarray(atoms.get_chemical_symbols()) NN_list = NeighborList(atoms) NNs = [list(syms[i]) for i in NN_list] max_CN = max([len(NN) for NN in NNs]) # Pick an atom in a rich environment ns = np.array([NNs[i].count(sym) for i, sym in enumerate(syms)]) unique_ns = np.array(list(set(ns))) prob_rich = 2.0 ** unique_ns prob_rich /= np.sum(prob_rich) n = np.random.choice(unique_ns, p=prob_rich) indices_rich = np.where(ns == n)[0] index_rich = np.random.choice(indices_rich) symbol_rich = syms[index_rich] # Pick another atom in a poor environment indices_other = [i for i, sym in enumerate(syms) if sym != symbol_rich] ns_to_rich = np.array([max_CN - NNs[i].count(symbol_rich) for i in indices_other]) unique_ns = np.array(list(set(ns_to_rich))) prob_poor = 2.0 ** unique_ns prob_poor /= np.sum(prob_poor) n_to_rich = np.random.choice(unique_ns, p=prob_poor) indices_poor = np.where(ns_to_rich == n_to_rich)[0] index_poor = np.random.choice(indices_poor) index_poor = indices_other[index_poor] symbol_poor = syms[index_poor] atoms[index_poor].symbol = symbol_rich atoms[index_rich].symbol = symbol_poor return
def move_atoms(individual, max_natoms=0.20): """Randomly moves atoms within a cluster. Parameters ---------- individual : Individual An individual 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 """ if not len(individual): return False if isinstance(max_natoms, float): assert max_natoms <= 1 max_natoms = int(len(individual) * max_natoms) NNs = NeighborList(individual) CNs = [len(NN) for NN in NNs] # Get the surface atoms. These provide bounds for the moves # First get unit vectors and mags of all surface atoms positions = individual.get_positions() com = np.sum(positions.T, axis=1) / len(individual) surf_indices = [i for i, CN in enumerate(CNs) if CN < 11] surf_positions = np.array([positions[i] for i in surf_indices]) surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1) surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T #Weight probability of choosing a vector by its length from the center surf_probabilities = surf_magnitudes / sum(surf_magnitudes) move_vectors_magnitudes_indices = np.random.choice(list( range(len(surf_indices))), replace=True, size=max_natoms, p=surf_probabilities) # Move the atoms max_natoms = max(max_natoms, 1) natoms_to_move = random.randint(1, max_natoms) atom_indices = list(range(len(individual))) random.shuffle( atom_indices ) # Using random.shuffle on the indices guarantees no duplicates atom_indices = atom_indices[:natoms_to_move] for atom_index, move_index in zip(atom_indices, move_vectors_magnitudes_indices): vec, mag = surf_vectors[move_index], surf_magnitudes[move_index] atom = individual[atom_index] atom.x, atom.y, atom.z = com + vec * random.random() * mag return None
def rotate_cluster(individual, max_natoms=0.20): """Chooses a random number of atoms nearest to a random point in the cluster. These atoms are then rotated randomly around this point Parameters ---------- individual : Individual An individual object max_natoms : float The fraction of the total atoms to rotate """ if not len(individual): return None if isinstance(max_natoms, float): assert max_natoms <= 1 max_natoms = int(len(individual) * max_natoms) NNs = NeighborList(individual) CNs = [len(NN) for NN in NNs] # Get the surface atoms. These provide bounds for the moves # First get unit vectors and mags of all surface atoms positions = individual.get_positions() com = np.sum(positions.T, axis=1) / len(individual) surf_indices = [i for i, CN in enumerate(CNs) if CN < 11] surf_positions = np.array([positions[i] for i in surf_indices]) surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1) surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T # Weight probability of choosing a vector by its length from the center surf_probabilities = surf_magnitudes / sum(surf_magnitudes) # Choose a random point inside the particle and find nearest neighbors to that point i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities) point = com + surf_vectors[i] * surf_magnitudes[i] * random.random() atom = Atom('Si', point) nearest_indices = individual.get_nearest_atom_indices( atom_index=atom.index, count=max_natoms) # Extract out the atoms to be rotated. Popping changes the indices # so pop them out highest to lowest indice nearest_indices = np.sort(nearest_indices)[::-1] atoms = Atoms() for ind in nearest_indices: atoms.append(individual.pop(ind)) axis = random.choice(['x', '-x', 'y', '-y', 'z', '-z']) angle = random.uniform(30, 180) atoms.rotate(axis, a=angle, center='COM', rotate_cell=False) individual.extend(atoms) return None
def move_atoms_group(individual, max_natoms=0.20): """Randomly moves atoms within a cluster. Args: individual (Individual): an individual 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 """ if not len(individual): return None if isinstance(max_natoms, float): assert max_natoms <= 1 max_natoms = int(len(individual) * max_natoms) NNs = NeighborList(individual) CNs = [len(NN) for NN in NNs] # Get the surface atoms. These provide bounds for the moves # First get unit vectors and mags of all surface atoms positions = individual.get_positions() com = np.sum(positions.T, axis=1) / len(individual) surf_indices = [i for i, CN in enumerate(CNs) if CN < 11] surf_positions = np.array([positions[i] for i in surf_indices]) surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1) surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T # Weight probability of choosing a vector by its length from the center surf_probabilities = surf_magnitudes / sum(surf_magnitudes) # Choose a random point inside the particle and find nearest neighbors to that point i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities) center = com + surf_vectors[i] * surf_magnitudes[i] * random.random() dists = np.linalg.norm(positions - center, axis=1) indices_dists = [[i, dist] for i, dist in enumerate(dists)] indices_dists.sort(key=lambda i: i[1]) max_natoms = max(max_natoms, 1) natoms_to_move = random.randint(1, max_natoms) # Choose another random point inside the particle and move cluster to that point i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities) move = com + surf_vectors[i] * surf_magnitudes[i] * random.random() - center for index, dist in indices_dists[:natoms_to_move]: positions[index] += move individual.set_positions(positions) return None
def get_bulk_bonds(self, individual): """Chooses a random atom to orient around. The probability of choosing the atom is proportional to the atom's distance from the center of mass""" # Get a bulk atom near the center of the particle pos = individual.get_positions() com = individual.get_center_of_mass() 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(individual))), p=prob) bulk_atom_pos = pos[bulk_atom_index] # Get the neighbors of the bulk atom NNs = NeighborList(individual) bonds = np.asarray([pos[i] for i in NNs[bulk_atom_index]]) - bulk_atom_pos return bonds
def poor2rich(individual): '''Used for multi-component systems. Swaps atoms A and B so that atom A moves from a region with a low number of A-A bonds to a high number of A-A bonds. Parameters ---------- individual : Individual An individual ''' syms = np.asarray(individual.get_chemical_symbols()) NN_list = NeighborList(individual) NNs = [list(syms[i]) for i in NN_list] max_CN = max([len(NN) for NN in NNs]) # Pick an atom in a poor environment ns_to_rich = np.array( [max_CN - NNs[i].count(sym) for i, sym in enumerate(syms)]) unique_ns = np.array(list(set(ns_to_rich))) prob_poor = 2.0**unique_ns prob_poor /= np.sum(prob_poor) n_to_rich = np.random.choice(unique_ns, p=prob_poor) indices_poor = np.where(ns_to_rich == n_to_rich)[0] index_poor = np.random.choice(indices_poor) symbol_poor = syms[index_poor] # Pick another atom in a rich environment indices_other = [i for i, sym in enumerate(syms) if sym != symbol_poor] ns = np.array([NNs[i].count(symbol_poor) for i in indices_other]) unique_ns = np.array(list(set(ns))) prob_rich = 2.0**unique_ns prob_rich /= np.sum(prob_rich) n = np.random.choice(unique_ns, p=prob_rich) indices_rich = np.where(ns == n)[0] index_rich = np.random.choice(indices_rich) index_rich = indices_other[index_rich] symbol_rich = syms[index_rich] individual[index_poor].symbol = symbol_rich individual[index_rich].symbol = symbol_poor 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 twist(individual, max_radius=0.90): """Twists a random section of the particle. Parameters ---------- individual : structopt.Individual object Individual to be mutated max_natoms : float That maximum relative distance from the center of the particle the twist is initiated """ if not len(individual): return None NNs = NeighborList(individual) CNs = [len(NN) for NN in NNs] # Get the surface atoms. These provide bounds for the moves # First get unit vectors and mags of all surface atoms positions = individual.get_positions() com = np.sum(positions.T, axis=1) / len(individual) surf_indices = [i for i, CN in enumerate(CNs) if CN < 11] surf_positions = np.array([positions[i] for i in surf_indices]) surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1) surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T # Weight probability of choosing a vector by its length from the center surf_probabilities = surf_magnitudes / sum(surf_magnitudes) # Choose a random point inside the particle i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities) center = com + surf_vectors[i] * surf_magnitudes[i] * random.random( ) * max_radius atoms = individual.copy() atoms.translate(-center) v = random_three_vector() a = random.uniform(30, 180) * np.pi / 180 atoms.rotate(v=v, a=a, center=(0, 0, 0)) new_pos_indices = [] top_atoms = Atoms() for i, atom in enumerate(atoms): if atom.z > 0: new_pos_indices.append(i) top_atoms.extend(atom) xs, ys, zs = top_atoms.get_positions().T x = (max(xs) + min(xs)) * 0.5 y = (max(ys) + min(ys)) * 0.5 top_atoms.rotate('z', random.uniform(0, np.pi), center=(x, y, 0)) top_atoms.rotate(v=v, a=-a, center=(0, 0, 0)) top_atoms.translate(center) rotated_positions = top_atoms.get_positions() positions = individual.get_positions() for i, index in enumerate(new_pos_indices): positions[index] = rotated_positions[i] individual.set_positions(positions) return None
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