def rotate_all(atoms, vector=None, angle=None, center=None): """Rotate all atoms around a single point. Most suitable for cluster calculations. Parameters --------- individual : StructOpt individual object or ase atoms StructOpt Individual or ase Atoms object to be rotated. angle : string or list A list of angles that will be chosen to rotate. If None, is randomly generated. Angle must be given in radians. If 'random' in list, a random angle is included. vector : string or list The list of axes in which to rotate the atoms around. If None, is a randomly chosen direction. If 'random' in list, a random vector can be chosen. center : str or xyz iterable The center in which to rotate the atoms around. If None, defaults to center of mass. Acceptable strings are COM = center of mass COP = center of positions COU = center of cell Returns ------- out: None Modifies the individual in place """ # Initialize variables for ase.Atoms.rotate if angle is None: angle = random.uniform(30, 180) * np.pi / 180.0 elif hasattr(angle, '__iter__'): angle = random.choice(angle) if angle == 'random': angle = random.uniform(30, 180) * np.pi / 180 if vector is None: vector = random_three_vector() elif hasattr(vector, '__iter__'): vector = random.choice(vector) if angle == 'random': vector = random_three_vector() if center is None: center = 'COM' # Perform the rotation atoms.rotate(v=vector, a=angle, center=center) return vector, angle
def get_vector_angle(orientation=None, v=None, angle=None): if (np.shape(v) == (3, ) and type(a) in [float, int]): v = np.asarray(v, dtype=float) v /= np.linalg.norm(v) return v, angle elif orientation is None: angle = np.random.uniform(0, np.pi * 2) v = random_three_vector() return v, angle elif orientation == '100': orientation_angle = 0.0 orientation_v = np.array([1, 0, 0]) elif orientation == '110': orientation_angle = np.pi / 4 orientation_v = np.array([0, 1, 0]) elif orientation == '111': orientation_angle = np.arcsin(1.0 / 3.0**0.5) orientation_v = np.array([-1.0 / 2.0**0.5, 1.0 / 2.0**0.5, 0]) else: raise NotImplementedError('Orientation not implemented') # Sometimes we want another rotation after an orientation if v is None and angle is not None: v = [orientation_v, np.array([0, 0, 1])] angle = [orientation_angle, np.asarray(angle)] else: v = orientation_v angle = orientation_angle return v, angle
def get_vector_angle(orientation=None, v=None, angle=None): if (np.shape(v) == (3,) and type(a) in [float, int]): v = np.asarray(v, dtype=float) v /= np.linalg.norm(v) return v, angle elif orientation is None: angle = np.random.uniform(0,np.pi*2) v = random_three_vector() return v, angle elif orientation == '100': orientation_angle = 0.0 orientation_v = np.array([1, 0, 0]) elif orientation == '110': orientation_angle = np.pi / 4 orientation_v = np.array([0, 1, 0]) elif orientation == '111': orientation_angle = np.arcsin(1.0 / 3.0 ** 0.5) orientation_v = np.array([-1.0 / 2.0 ** 0.5, 1.0 / 2.0 ** 0.5, 0]) else: raise NotImplementedError('Orientation not implemented') # Sometimes we want another rotation after an orientation if v is None and angle is not None: v = [orientation_v, np.array([0, 0, 1])] angle = [orientation_angle, np.asarray(angle)] else: v = orientation_v angle = orientation_angle return v, angle
def sphere(atomlist, fill_factor=0.74, radius=None, cell=None): """Generates a random sphere of particles given an atomlist and radius. If radius is None, one is automatically estimated. min_dist and tries_b4_expand are parameters that govern how stricly the proximity of atoms are enforced. """ if radius is None: radius = get_particle_radius(atomlist, fill_factor) # Create a list of random order of the atoms chemical_symbols = [] for atom in atomlist: chemical_symbols += [atom[0]] * atom[1] random.shuffle(chemical_symbols) unit_vec = np.array([random_three_vector() for i in range(len(chemical_symbols))]) D = radius * np.random.sample(size=len(chemical_symbols)) ** (1.0/3.0) positions = np.array([D]).T * unit_vec indiv = Atoms(symbols=chemical_symbols, positions=positions) if cell is not None: indiv.set_cell(cell) cell_center = np.sum(indiv.get_cell(), axis=1) / 2.0 indiv.translate(indiv.get_center_of_mass() + cell_center) indiv.set_pbc(True) return indiv
def rotate_all(atoms, vector=None, angle=None, center=None): """Rotate all atoms around a single point. Most suitable for cluster calculations. Parameters --------- individual : Individual An individual. vector : string or list The list of axes in which to rotate the atoms around. If None, is a randomly chosen direction. If 'random' in list, a random vector can be chosen. angle : string or list A list of angles that will be chosen to rotate. If None, is randomly generated. Angle must be given in radians. If 'random' in list, a random angle is included. center : string or xyz iterable The center in which to rotate the atoms around. If None, defaults to center of mass. Acceptable strings are COM = center of mass COP = center of positions COU = center of cell """ # Initialize variables for ase.Atoms.rotate if angle is None: angle = random.uniform(30, 180) * np.pi / 180.0 elif hasattr(angle, '__iter__'): angle = random.choice(angle) if angle == 'random': angle = random.uniform(30, 180) * np.pi / 180 if vector is None: vector = random_three_vector() elif hasattr(vector, '__iter__'): vector = random.choice(vector) if angle == 'random': vector = random_three_vector() if center is None: center = 'COM' # Perform the rotation atoms.rotate(v=vector, a=angle, center=center) return vector, angle
def sphere(atomlist, cell, fill_factor=0.74, radius=None): """Generates a random sphere of particles given an atomlist and radius. Parameters ---------- atomlist : list A list of [sym, n] pairs where sym is the chemical symbol and n is the number of of sym's to include in the individual cell : list The x, y, and z dimensions of the cell which holds the particle. fill_factor : float How densely packed the sphere should be. Ranges from 0 to 1. radius : float The radius of the sphere. If None, estimated from the atomic radii """ if radius is None: radius = get_particle_radius(atomlist, fill_factor) # Create a list of random order of the atoms chemical_symbols = [] for atom in atomlist: chemical_symbols += [atom[0]] * atom[1] random.shuffle(chemical_symbols) unit_vec = np.array([random_three_vector() for i in range(len(chemical_symbols))]) D = radius * np.random.sample(size=len(chemical_symbols)) ** (1.0/3.0) positions = np.array([D]).T * unit_vec indiv = Atoms(symbols=chemical_symbols, positions=positions) if cell is not None: indiv.set_cell(cell) cell_center = np.sum(indiv.get_cell(), axis=1) / 2.0 indiv.translate(indiv.get_center_of_mass() + cell_center) indiv.set_pbc(True) return indiv
def rotate_fixed(individual1, individual2, align_cutoff=0.1, repair_composition=True): """Similar to rotate except the children aren't borne out of cut out versions. Parameters ---------- individual1 : Individual The first parent individual2 : Individual The second parent center_at_atom : bool This centers the cut at an atom. This is particularly useful when one desires a crystalline solution. If both parents are crystalline, the children will likely not have grain boundaries. repair_composition : bool If True, conserves composition. For crossovers that create children with more (less) atoms, atoms are taken from (added to) the surface of the particle. For incorrect atomic ratios, atomic symbols are randomly interchanged throughout the particle Returns ------- Individual: The first child Individual: The second child """ # Translate individuals so atoms are most aligned # Pick a random rotation angle and vector rot_vec = random_three_vector() rot_angle = random.uniform(0, 180) * np.pi / 180.0 # Rotate both individuals ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) # Create the children child1 = Atoms(cell=ind1c.get_cell()) child2 = Atoms(cell=ind2c.get_cell()) child1.extend(ind1c[ind1c.positions[:, 2] >= 0]) child1.extend(ind2c[ind2c.positions[:, 2] < 0]) child2.extend(ind2c[ind2c.positions[:, 2] >= 0]) child2.extend(ind1c[ind1c.positions[:, 2] < 0]) # Repair the children if repair_composition: syms = ind1c.get_chemical_symbols() atomlist = [[sym, syms.count(sym)] for sym in set(syms)] repair_cluster(child1, atomlist) repair_cluster(child2, atomlist) # Reorient the children child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child1.center() child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child2.center() # Convert the children to an Individual with the parent # module parameters full_child1 = ind1c.copy(include_atoms=False) full_child1.extend(child1) full_child2 = ind2c.copy(include_atoms=False) full_child2.extend(child2) return full_child1, full_child2
def rotate(individual1, individual2, center_at_atom=True, repair_composition=True): """Rotates the two individuals around their centers of mass, splits them in half at the xy-plane, then splices them together. Maintains number of atoms. Note, both individuals are rotated in the same way. Parameters ---------- individual1 : Individual The first parent individual2 : Individual The second parent center_at_atom : bool This centers the cut at an atom. This is particularly useful when one desires a crystalline solution. If both parents are crystalline, the children will likely not have grain boundaries. repair_composition : bool If True, conserves composition. For crossovers that create children with more (less) atoms, atoms are taken from (added to) the surface of the particle. For incorrect atomic ratios, atomic symbols are randomly interchanged throughout the particle Returns ------- Individual: The first child Individual: The second child """ # Translate individuals so COP is at (0, 0, 0) ind1c = individual1.copy() ind2c = individual2.copy() cop1 = ind1c.get_positions().mean(axis=0) cop2 = ind2c.get_positions().mean(axis=0) if center_at_atom: offset = get_offset(ind1c, ind2c, r=1.0, HWHM=0.4) ind1c.translate(offset) cop = ind1c.get_positions().mean(axis=0) ind1c.translate(-cop) ind2c.translate(-cop) else: ind1c.translate(ind1c.get_positions().mean(axis=0)) ind2c.translate(ind2c.get_positions().mean(axis=0)) # Pick a random rotation angle and vector rot_vec = random_three_vector() rot_angle = random.uniform(0, 180) * np.pi / 180.0 # Rotate both individuals ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) # Create the children child1 = Atoms(cell=ind1c.get_cell()) child2 = Atoms(cell=ind2c.get_cell()) child1.extend(ind1c[ind1c.positions[:,2] >= 0]) child1.extend(ind2c[ind2c.positions[:,2] < 0]) child2.extend(ind2c[ind2c.positions[:,2] >= 0]) child2.extend(ind1c[ind1c.positions[:,2] < 0]) # Repair the children if repair_composition: syms = ind1c.get_chemical_symbols() atomlist = [[sym, syms.count(sym)] for sym in set(syms)] repair_cluster(child1, atomlist) repair_cluster(child2, atomlist) # Reorient the children child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child1.center() child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child2.center() # Convert the children to an Individual with the parent # module parameters full_child1 = ind1c.copy(include_atoms=False) full_child1.extend(child1) full_child2 = ind2c.copy(include_atoms=False) full_child2.extend(child2) return full_child1, full_child2
def rotate(individual1, individual2, center_at_atom=True, repair_composition=True): """Rotates the two individuals around their centers of mass, splits them in half at the xy-plane, then splices them together. Maintains number of atoms. Parameters ---------- individual1 : Individual The first parent individual2 : Individual The second parent conserve_composition : bool If True, conserves composition. For crossovers that create children with more (less) atoms, atoms are taken from (added to) the surface of the particle. For incorrect atomic ratios, atomic symbols are randomly interchanged throughout the particle Returns: Individual: The first child Individual: The second child The children are returned without indicies. """ # Translate individuals so COP is at (0, 0, 0) ind1c = individual1.copy() ind2c = individual2.copy() cop1 = ind1c.get_positions().mean(axis=0) cop2 = ind2c.get_positions().mean(axis=0) if center_at_atom: pos1 = ind1c.get_positions() dists1 = np.linalg.norm(pos1 - cop1, axis=1) cop1 = pos1[np.argmin(dists1)] pos2 = ind2c.get_positions() dists2 = np.linalg.norm(pos2 - cop2, axis=1) cop2 = pos2[np.argmin(dists2)] ind1c.translate(-cop1) ind2c.translate(-cop2) # Pick a random rotation angle and vector rot_vec = random_three_vector() rot_angle = random.uniform(0, 180) * np.pi / 180.0 # Rotate both individuals ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) # Create the children child1 = Atoms(cell=ind1c.get_cell()) child2 = Atoms(cell=ind2c.get_cell()) child1.extend(ind1c[ind1c.positions[:, 2] >= 0]) child1.extend(ind2c[ind2c.positions[:, 2] < 0]) child2.extend(ind2c[ind2c.positions[:, 2] >= 0]) child2.extend(ind1c[ind1c.positions[:, 2] < 0]) # Repair the children if repair_composition: syms = ind1c.get_chemical_symbols() atomlist = [[sym, syms.count(sym)] for sym in set(syms)] repair_cluster(child1, atomlist) repair_cluster(child2, atomlist) # Reorient the children child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child1.center() child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child2.center() # Convert the children to an Individual with the parent # module parameters full_child1 = ind1c.copy(include_atoms=False) full_child1.extend(child1) full_child2 = ind2c.copy(include_atoms=False) full_child2.extend(child2) return full_child1, full_child2
def rotate_fixed(individual1, individual2, align_cutoff=0.1, repair_composition=True): """Similar to rotate except the children aren't borne out of cut out versions. Parameters ---------- individual1 : Individual The first parent individual2 : Individual The second parent center_at_atom : bool This centers the cut at an atom. This is particularly useful when one desires a crystalline solution. If both parents are crystalline, the children will likely not have grain boundaries. repair_composition : bool If True, conserves composition. For crossovers that create children with more (less) atoms, atoms are taken from (added to) the surface of the particle. For incorrect atomic ratios, atomic symbols are randomly interchanged throughout the particle Returns ------- Individual: The first child Individual: The second child """ # Translate individuals so atoms are most aligned # Pick a random rotation angle and vector rot_vec = random_three_vector() rot_angle = random.uniform(0, 180) * np.pi / 180.0 # Rotate both individuals ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) # Create the children child1 = Atoms(cell=ind1c.get_cell()) child2 = Atoms(cell=ind2c.get_cell()) child1.extend(ind1c[ind1c.positions[:,2] >= 0]) child1.extend(ind2c[ind2c.positions[:,2] < 0]) child2.extend(ind2c[ind2c.positions[:,2] >= 0]) child2.extend(ind1c[ind1c.positions[:,2] < 0]) # Repair the children if repair_composition: syms = ind1c.get_chemical_symbols() atomlist = [[sym, syms.count(sym)] for sym in set(syms)] repair_cluster(child1, atomlist) repair_cluster(child2, atomlist) # Reorient the children child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child1.center() child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child2.center() # Convert the children to an Individual with the parent # module parameters full_child1 = ind1c.copy(include_atoms=False) full_child1.extend(child1) full_child2 = ind2c.copy(include_atoms=False) full_child2.extend(child2) return full_child1, full_child2
def twist(individual, max_radius=0.90): """Splits the particle randomly in half and rotates one half. 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 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 rotate(individual1, individual2, center_at_atom=True, repair_composition=True): """Rotates the two individuals around their centers of mass, splits them in half at the xy-plane, then splices them together. Maintains number of atoms. Note, both individuals are rotated in the same way. Parameters ---------- individual1 : Individual The first parent individual2 : Individual The second parent center_at_atom : bool This centers the cut at an atom. This is particularly useful when one desires a crystalline solution. If both parents are crystalline, the children will likely not have grain boundaries. repair_composition : bool If True, conserves composition. For crossovers that create children with more (less) atoms, atoms are taken from (added to) the surface of the particle. For incorrect atomic ratios, atomic symbols are randomly interchanged throughout the particle Returns ------- Individual: The first child Individual: The second child """ # Translate individuals so COP is at (0, 0, 0) ind1c = individual1.copy() ind2c = individual2.copy() cop1 = ind1c.get_positions().mean(axis=0) cop2 = ind2c.get_positions().mean(axis=0) if center_at_atom: offset = get_offset(ind1c, ind2c, r=1.0, HWHM=0.4) ind1c.translate(offset) cop = ind1c.get_positions().mean(axis=0) ind1c.translate(-cop) ind2c.translate(-cop) else: ind1c.translate(ind1c.get_positions().mean(axis=0)) ind2c.translate(ind2c.get_positions().mean(axis=0)) # Pick a random rotation angle and vector rot_vec = random_three_vector() rot_angle = random.uniform(0, 180) * np.pi / 180.0 # Rotate both individuals ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0)) # Create the children child1 = Atoms(cell=ind1c.get_cell()) child2 = Atoms(cell=ind2c.get_cell()) child1.extend(ind1c[ind1c.positions[:, 2] >= 0]) child1.extend(ind2c[ind2c.positions[:, 2] < 0]) child2.extend(ind2c[ind2c.positions[:, 2] >= 0]) child2.extend(ind1c[ind1c.positions[:, 2] < 0]) # Repair the children if repair_composition: syms = ind1c.get_chemical_symbols() atomlist = [[sym, syms.count(sym)] for sym in set(syms)] repair_cluster(child1, atomlist) repair_cluster(child2, atomlist) # Reorient the children child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child1.center() child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0)) child2.center() # Convert the children to an Individual with the parent # module parameters full_child1 = ind1c.copy(include_atoms=False) full_child1.extend(child1) full_child2 = ind2c.copy(include_atoms=False) full_child2.extend(child2) return full_child1, full_child2