def mutate(self, atoms): """Does the actual mutation.""" N = len(atoms) if self.n_top is None else self.n_top slab = atoms[:len(atoms) - N] atoms = atoms[-N:] if self.use_tags: gather_atoms_by_tag(atoms) tags = atoms.get_tags() if self.use_tags else np.arange(N) pos_ref = atoms.get_positions() num = atoms.get_atomic_numbers() cell = atoms.get_cell() pbc = atoms.get_pbc() symbols = atoms.get_chemical_symbols() unique_tags = np.unique(tags) n = len(unique_tags) swaps = int(np.ceil(n * self.probability / 2.)) sym = [] for tag in unique_tags: indices = np.where(tags == tag)[0] s = ''.join([symbols[j] for j in indices]) sym.append(s) assert len(np.unique(sym)) > 1, \ 'Permutations with one atom (or molecule) type is not valid' count = 0 maxcount = 1000 too_close = True while too_close and count < maxcount: count += 1 pos = pos_ref.copy() for _ in range(swaps): i = j = 0 while sym[i] == sym[j]: i = self.rng.randint(0, high=n) j = self.rng.randint(0, high=n) ind1 = np.where(tags == i) ind2 = np.where(tags == j) cop1 = np.mean(pos[ind1], axis=0) cop2 = np.mean(pos[ind2], axis=0) pos[ind1] += cop2 - cop1 pos[ind2] += cop1 - cop2 top = Atoms(num, positions=pos, cell=cell, pbc=pbc, tags=tags) if self.blmin is None: too_close = False else: too_close = atoms_too_close(top, self.blmin, use_tags=self.use_tags) if not too_close and self.test_dist_to_slab: too_close = atoms_too_close_two_sets(top, slab, self.blmin) if count == maxcount: return None mutant = slab + top return mutant
def mutate(self, atoms): """ Does the actual mutation. """ cell_ref = atoms.get_cell() pos_ref = atoms.get_positions() vol = atoms.get_volume() if self.use_tags: tags = atoms.get_tags() gather_atoms_by_tag(atoms) pos = atoms.get_positions() mutant = atoms.copy() if self.cellbounds is not None: if not self.cellbounds.is_within_bounds(cell_ref): niggli_reduce(mutant) count = 0 too_close = True maxcount = 1000 while too_close and count < maxcount: mutant.set_cell(cell_ref, scale_atoms=False) mutant.set_positions(pos_ref) # generating the strain matrix: strain = np.identity(3) for i in range(3): for j in range(i + 1): if i == j: strain[i, j] += gauss(0, self.stddev) else: epsilon = 0.5 * gauss(0, self.stddev) strain[i, j] += epsilon strain[j, i] += epsilon # applying the strain: cell_new = np.dot(strain, cell_ref) # volume scaling: v = abs(np.linalg.det(cell_new)) if self.scaling_volume is None: cell_new *= (vol / v)**(1. / 3) else: cell_new *= (self.scaling_volume / v)**(1. / 3) # check cell dimensions: if not self.cellbounds.is_within_bounds(cell_new): continue if self.use_tags: transfo = np.linalg.solve(cell_ref, cell_new) for tag in np.unique(tags): select = np.where(tags == tag) cop = np.mean(pos[select], axis=0) disp = np.dot(cop, transfo) - cop mutant.positions[select] += disp mutant.set_cell(cell_new, scale_atoms=not self.use_tags) # check distances: too_close = atoms_too_close(mutant, self.blmin, use_tags=self.use_tags) count += 1 if count == maxcount: mutant = None return mutant
def mutate(self, atoms): """ Does the actual mutation. """ N = len(atoms) if self.n_top is None else self.n_top slab = atoms[:len(atoms) - N] atoms = atoms[-N:] mutant = atoms.copy() gather_atoms_by_tag(mutant) pos = mutant.get_positions() tags = mutant.get_tags() eligible_tags = tags if self.tags is None else self.tags indices = {} for tag in np.unique(tags): hits = np.where(tags == tag)[0] if len(hits) > 1 and tag in eligible_tags: indices[tag] = hits n_rot = int(np.ceil(len(indices) * self.fraction)) chosen_tags = np.random.choice(list(indices.keys()), size=n_rot, replace=False) too_close = True count = 0 maxcount = 10000 while too_close and count < maxcount: newpos = np.copy(pos) for tag in chosen_tags: p = np.copy(newpos[indices[tag]]) cop = np.mean(p, axis=0) if len(p) == 2: line = (p[1] - p[0]) / np.linalg.norm(p[1] - p[0]) while True: axis = np.random.random(3) axis /= np.linalg.norm(axis) a = np.arccos(np.dot(axis, line)) if np.pi / 4 < a < np.pi * 3 / 4: break else: axis = np.random.random(3) axis /= np.linalg.norm(axis) angle = self.min_angle angle += 2 * (np.pi - self.min_angle) * np.random.random() m = get_rotation_matrix(axis, angle) newpos[indices[tag]] = np.dot(m, (p - cop).T).T + cop mutant.set_positions(newpos) mutant.wrap() too_close = atoms_too_close(mutant, self.blmin, use_tags=True) count += 1 if not too_close and self.test_dist_to_slab: too_close = atoms_too_close_two_sets(slab, mutant, self.blmin) if count == maxcount: mutant = None else: mutant = slab + mutant return mutant
def __init__(self, atoms): self.atoms = atoms gather_atoms_by_tag(self.atoms) self.tags = self.atoms.get_tags() self.unique_tags = np.unique(self.tags) self.n = len(self.unique_tags)
def mutate(self, atoms): """ Does the actual mutation. """ cell_ref = atoms.get_cell() pos_ref = atoms.get_positions() vol_ref = atoms.get_volume() if self.use_tags: tags = atoms.get_tags() gather_atoms_by_tag(atoms) pos = atoms.get_positions() mutant = atoms.copy() count = 0 too_close = True maxcount = 1000 while too_close and count < maxcount: count += 1 # generating the strain matrix: strain = np.identity(3) for i in range(self.number_of_variable_cell_vectors): for j in range(i + 1): r = self.rng.normal(loc=0., scale=self.stddev) if i == j: strain[i, j] += r else: epsilon = 0.5 * r strain[i, j] += epsilon strain[j, i] += epsilon # applying the strain: cell_new = np.dot(strain, cell_ref) # convert to lower triangular form cell_new = convert_cell(cell_new)[0].T # volume scaling: if self.number_of_variable_cell_vectors > 0: volume = abs(np.linalg.det(cell_new)) if self.scaling_volume is None: # The scaling_volume has not been set (yet), # so we give it the same volume as the parent scaling = vol_ref / volume else: scaling = self.scaling_volume / volume scaling **= 1. / self.number_of_variable_cell_vectors cell_new[:self.number_of_variable_cell_vectors] *= scaling # check cell dimensions: if not self.cellbounds.is_within_bounds(cell_new): continue # ensure non-variable cell vectors are indeed unchanged for i in range(self.number_of_variable_cell_vectors, 3): assert np.allclose(cell_new[i], cell_ref[i]) # apply the new unit cell and scale # the atomic positions accordingly mutant.set_cell(cell_ref, scale_atoms=False) if self.use_tags: transfo = np.linalg.solve(cell_ref, cell_new) for tag in np.unique(tags): select = np.where(tags == tag) cop = np.mean(pos[select], axis=0) disp = np.dot(cop, transfo) - cop mutant.positions[select] += disp else: mutant.set_positions(pos_ref) mutant.set_cell(cell_new, scale_atoms=not self.use_tags) mutant.wrap() # check the interatomic distances too_close = atoms_too_close(mutant, self.blmin, use_tags=self.use_tags) if count == maxcount: mutant = None return mutant
def cross(self, a1, a2): """Crosses the two atoms objects and returns one""" if len(a1) != len(self.slab) + self.n_top: raise ValueError('Wrong size of structure to optimize') if len(a1) != len(a2): raise ValueError('The two structures do not have the same length') N = self.n_top # Only consider the atoms to optimize a1 = a1[len(a1) - N:len(a1)] a2 = a2[len(a2) - N:len(a2)] if not np.array_equal(a1.numbers, a2.numbers): err = 'Trying to pair two structures with different stoichiometry' raise ValueError(err) if self.use_tags and not np.array_equal(a1.get_tags(), a2.get_tags()): err = 'Trying to pair two structures with different tags' raise ValueError(err) cell1 = a1.get_cell() cell2 = a2.get_cell() for i in range(self.number_of_variable_cell_vectors, 3): err = 'Unit cells are supposed to be identical in direction %d' assert np.allclose(cell1[i], cell2[i]), (err % i, cell1, cell2) invalid = True counter = 0 maxcount = 1000 a1_copy = a1.copy() a2_copy = a2.copy() # Run until a valid pairing is made or maxcount pairings are tested. while invalid and counter < maxcount: counter += 1 newcell = self.generate_unit_cell(cell1, cell2) if newcell is None: # No valid unit cell could be generated. # This strongly suggests that it is near-impossible # to generate one from these parent cells and it is # better to abort now. break # Choose direction of cutting plane normal if self.number_of_variable_cell_vectors == 0: # Will be generated entirely at random theta = np.pi * self.rng.rand() phi = 2. * np.pi * self.rng.rand() cut_n = np.array([ np.cos(phi) * np.sin(theta), np.sin(phi) * np.sin(theta), np.cos(theta) ]) else: # Pick one of the 'variable' cell vectors cut_n = self.rng.choice(self.number_of_variable_cell_vectors) # Randomly translate parent structures for a_copy, a in zip([a1_copy, a2_copy], [a1, a2]): a_copy.set_positions(a.get_positions()) cell = a_copy.get_cell() for i in range(self.number_of_variable_cell_vectors): r = self.rng.rand() cond1 = i == cut_n and r < self.p1 cond2 = i != cut_n and r < self.p2 if cond1 or cond2: a_copy.positions += self.rng.rand() * cell[i] if self.use_tags: # For correct determination of the center- # of-position of the multi-atom blocks, # we need to group their constituent atoms # together gather_atoms_by_tag(a_copy) else: a_copy.wrap() # Generate the cutting point in scaled coordinates cosp1 = np.average(a1_copy.get_scaled_positions(), axis=0) cosp2 = np.average(a2_copy.get_scaled_positions(), axis=0) cut_p = np.zeros((1, 3)) for i in range(3): if i < self.number_of_variable_cell_vectors: cut_p[0, i] = self.rng.rand() else: cut_p[0, i] = 0.5 * (cosp1[i] + cosp2[i]) # Perform the pairing: child = self._get_pairing(a1_copy, a2_copy, cut_p, cut_n, newcell) if child is None: continue # Verify whether the atoms are too close or not: if atoms_too_close(child, self.blmin, use_tags=self.use_tags): continue if self.test_dist_to_slab and len(self.slab) > 0: if atoms_too_close_two_sets(self.slab, child, self.blmin): continue # Passed all the tests child = self.slab + child child.set_cell(newcell, scale_atoms=False) child.wrap() return child return None
def cross(self, a1, a2): """Crosses the two atoms objects and returns one""" if len(a1) != len(a2): raise ValueError('The two structures do not have the same length') N = len(a1) if self.n_top is None else self.n_top slab = a1[:len(a1) - N] a1 = a1[-N:] a2 = a2[-N:] if not np.array_equal(a1.numbers, a2.numbers): err = 'Trying to pair two structures with different stoichiometry' raise ValueError(err) if self.use_tags and not np.array_equal(a1.get_tags(), a2.get_tags()): err = 'Trying to pair two structures with different tags' raise ValueError(err) a1_copy = a1.copy() a2_copy = a2.copy() if self.cellbounds is not None: if not self.cellbounds.is_within_bounds(a1_copy.get_cell()): niggli_reduce(a1_copy) if not self.cellbounds.is_within_bounds(a2_copy.get_cell()): niggli_reduce(a2_copy) pos1_ref = a1_copy.get_positions() pos2_ref = a2_copy.get_positions() invalid = True counter = 0 maxcount = 1000 # Run until a valid pairing is made or 1000 pairings are tested. while invalid and counter < maxcount: counter += 1 # Choose direction of cutting plane normal (0, 1, or 2): direction = randrange(3) # Randomly translate parent structures: for a, pos in zip([a1_copy, a2_copy], [pos1_ref, pos2_ref]): a.set_positions(pos) cell = a.get_cell() for i in range(3): r = random() cond1 = i == direction and r < self.p1 cond2 = i != direction and r < self.p2 if cond1 or cond2: a.positions += random() * cell[i, :] if self.use_tags: gather_atoms_by_tag(a) else: a.wrap() # Perform the pairing: fraction = random() child = self._get_pairing(a1_copy, a2_copy, direction=direction, fraction=fraction) if child is None: continue # Verify whether the atoms are too close or not: invalid = atoms_too_close(child, self.blmin, use_tags=self.use_tags) if invalid: continue elif self.test_dist_to_slab: invalid = atoms_too_close_two_sets(slab, child, self.blmin) if counter == maxcount: return None return child