Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
    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
Пример #4
0
 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)
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
    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