def mutate(self, atoms):
     """ Does the actual mutation. """
     slab = atoms[0:len(atoms) - self.n_top]
     pos_ref = atoms.get_positions()[-self.n_top:]
     num_top = atoms.numbers[-self.n_top:]
     st = 2. * self.rattle_strength
     count = 0
     tc = True
     while tc and count < 1000:
         pos = pos_ref.copy()
         for i in xrange(len(pos)):
             if random() < self.rattle_prop:
                 r = np.array([random() for r in xrange(3)])
                 pos[i] = pos[i] + st * (r - 0.5)
         top = Atoms(num_top,
                     positions=pos,
                     cell=slab.get_cell(),
                     pbc=slab.get_pbc())
         tc = atoms_too_close(top, self.blmin)
         if not tc:
             tc = atoms_too_close_two_sets(top, slab, self.blmin)
         count += 1
     if count == 1000:
         return None
     tot = slab + top
     return tot
    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
Example #3
0
    def mutate(self, atoms):
        """ Does the actual mutation. """
        a = atoms.copy()
        Natoms = len(a)
        Nslab = Natoms - self.n_top
        pos = a.get_positions()[-self.n_top:]
        num = a.numbers
        num_top = num[-self.n_top:]
        num_unique = list(set(num_top))
        assert len(
            num_unique) > 1, 'Permutations with one atomic type is not valid'
        m = int(ceil(float(self.n_top) * self.probability / 2.))
        for _ in range(m):
            swap_succesfull = False
            for i in range(100):
                # Find index pair of atoms to swap
                i = j = 0
                while num[i] == num[j]:
                    i = randrange(Nslab, Natoms)
                    j = randrange(Nslab, Natoms)

                # Swap two atoms
                pos_i = a.positions[i].copy()
                pos_j = a.positions[j].copy()
                a.positions[i] = pos_j
                a.positions[j] = pos_i

                # Check if atoms are too close
                tc_i = atoms_too_close_two_sets(a[:i] + a[i + 1:], a[i],
                                                self.blmin)
                tc_j = atoms_too_close_two_sets(a[:j] + a[j + 1:], a[j],
                                                self.blmin)
                if tc_i or tc_j:
                    # reset swap
                    a.positions[i] = pos_i
                    a.positions[j] = pos_j
                    continue
                else:
                    swap_succesfull = True
                    break
            if not swap_succesfull:
                # swap_back
                a.positions[i] = pos_i
                a.positions[j] = pos_j
        return a
Example #4
0
    def mutate(self, atoms):
        num = atoms.numbers
        atoms_mutated = atoms.copy()
        Natoms = len(atoms)
        Nslab = Natoms - self.n_top
        for i in range(Nslab, Natoms):
            if random() < self.rattle_prop:
                posi_0 = np.copy(atoms_mutated.positions[i])
                tc = False
                tf = False

                # Random order of atoms
                atom_indicies = np.random.permutation(np.arange(Nslab, Natoms))
                for j in atom_indicies:
                    if j == i:
                        continue
                    posi_j = np.copy(atoms_mutated.positions[j])
                    r_min = self.blmin[(num[i], num[j])]
                    r_max = 1.7 * r_min
                    for k in range(20):
                        atoms_mutated.positions[i] = posi_j
                        # Then Rattle within a circle

                        r = np.random.uniform(r_min**3, r_max**3)**(1 / 3)
                        theta = np.random.uniform(low=0, high=2 * np.pi)
                        phi = np.random.uniform(low=0, high=np.pi)
                        pos_add = r * np.array([
                            np.cos(theta) * np.sin(phi),
                            np.sin(theta) * np.sin(phi),
                            np.cos(phi)
                        ])
                        atoms_mutated.positions[i] += pos_add
                        # too close
                        tc = atoms_too_close_two_sets(
                            atoms_mutated[:i] + atoms_mutated[i + 1:],
                            atoms_mutated[i], self.blmin)
                        # too far
                        index_other = np.delete(np.arange(Natoms), i)
                        blmin_array = np.array(
                            [self.blmin[num[i], num[j]] for j in index_other])
                        tf = np.min(
                            atoms_mutated.get_distances(i, index_other) /
                            blmin_array) > 1.7

                        if not tc and not tf:
                            break
                    if tc or tf:
                        atoms_mutated.positions[i] = posi_0
        return atoms_mutated
Example #5
0
    def mutate(self, atoms):
        num = atoms.numbers
        atoms_mutated = atoms.copy()
        Natoms = len(atoms)
        Nslab = Natoms - self.n_top
        for i in range(Nslab, Natoms):
            if random() < self.rattle_prop:
                posi_0 = np.copy(atoms_mutated.positions[i])
                tc = False
                tf = False
                for k in range(100):
                    atoms_mutated.positions[i] = posi_0
                    # Then Rattle within a circle
                    r = self.rattle_strength * np.random.rand()**(1 / 3)
                    theta = np.random.uniform(low=0, high=2 * np.pi)
                    phi = np.random.uniform(low=0, high=np.pi)
                    pos_add = r * np.array([
                        np.cos(theta) * np.sin(phi),
                        np.sin(theta) * np.sin(phi),
                        np.cos(phi)
                    ])
                    atoms_mutated.positions[i] += pos_add
                    # Too low - made to avoid that atom is placed below slab
                    if self.min_z is not None:
                        tl = atoms_mutated.positions[i][2] < self.min_z
                        if tl:
                            continue
                    else:
                        tl = False

                    # too close
                    tc = atoms_too_close_two_sets(
                        atoms_mutated[:i] + atoms_mutated[i + 1:],
                        atoms_mutated[i], self.blmin)
                    # too far
                    index_other = np.delete(np.arange(Natoms), i)
                    blmin_array = np.array(
                        [self.blmin[num[i], num[j]] for j in index_other])
                    tf = np.min(
                        atoms_mutated.get_distances(i, index_other) /
                        blmin_array) > 1.7

                    if not tc and not tf:
                        break
                if tc or tf or tl:
                    atoms_mutated.positions[i] = posi_0
        return atoms_mutated
    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:]
        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()
        st = 2. * self.rattle_strength

        count = 0
        maxcount = 1000
        too_close = True
        while too_close and count < maxcount:
            count += 1
            pos = pos_ref.copy()
            ok = False
            for tag in np.unique(tags):
                select = np.where(tags == tag)
                if self.rng.rand() < self.rattle_prop:
                    ok = True
                    r = self.rng.rand(3)
                    pos[select] += st * (r - 0.5)

            if not ok:
                # Nothing got rattled
                continue

            top = Atoms(num, positions=pos, cell=cell, pbc=pbc, tags=tags)
            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
Example #7
0
 def mutate(self, atoms):
     """ Does the actual mutation. """
     slab = atoms[0:len(atoms) - self.n_top]
     pos_ref = atoms.get_positions()[-self.n_top:]
     num_top = atoms.numbers[-self.n_top:]
     st = 2. * self.rattle_strength
     count = 0
     tc = True
     while tc and count < 1000:
         pos = pos_ref.copy()
         for i in xrange(len(pos)):
             if random() < self.rattle_prop:
                 r = np.array([random() for r in xrange(3)])
                 pos[i] = pos[i] + st * (r - 0.5)
         top = Atoms(num_top, positions=pos,
                     cell=slab.get_cell(), pbc=slab.get_pbc())
         tc = atoms_too_close(top, self.blmin)
         if not tc:
             tc = atoms_too_close_two_sets(top, slab, self.blmin)
         count += 1
     if count == 1000:
         return None, 'rattle'
     tot = slab + top
     return tot
Example #8
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
    def mutate(self, atoms):
        """ Do the mutation of the atoms input. """

        reflect = self.reflect
        tc = True
        slab = atoms[0:len(atoms) - self.n_top]
        top = atoms[len(atoms) - self.n_top:len(atoms)]
        num = top.numbers
        unique_types = list(set(num))
        nu = dict()
        for u in unique_types:
            nu[u] = sum(num == u)

        n_tries = 1000
        counter = 0
        changed = False

        while tc and counter < n_tries:
            counter += 1
            cand = top.copy()
            pos = cand.get_positions()

            cm = np.average(top.get_positions(), axis=0)

            # first select a randomly oriented cutting plane
            theta = pi * random()
            phi = 2. * pi * random()
            n = (cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta))
            n = np.array(n)

            # Calculate all atoms signed distance to the cutting plane
            D = []
            for (i, p) in enumerate(pos):
                d = np.dot(p - cm, n)
                D.append((i, d))

            # Sort the atoms by their signed distance
            D.sort(key=lambda x: x[1])
            nu_taken = dict()

            # Select half of the atoms needed for a full cluster
            p_use = []
            n_use = []
            for (i, d) in D:
                if num[i] not in nu_taken.keys():
                    nu_taken[num[i]] = 0
                if nu_taken[num[i]] < nu[num[i]] / 2.:
                    p_use.append(pos[i])
                    n_use.append(num[i])
                    nu_taken[num[i]] += 1

            # calculate the mirrored position and add these.
            pn = []
            for p in p_use:
                pt = p - 2. * np.dot(p - cm, n) * n
                if reflect:
                    pt = -pt + 2 * cm + 2 * n * np.dot(pt - cm, n)
                pn.append(pt)

            n_use.extend(n_use)
            p_use.extend(pn)

            # In the case of an uneven number of
            # atoms we need to add one extra
            for n in nu.keys():
                if nu[n] % 2 == 0:
                    continue
                while sum(n_use == n) > nu[n]:
                    for i in xrange(int(len(n_use) / 2), len(n_use)):
                        if n_use[i] == n:
                            del p_use[i]
                            del n_use[i]
                            break
                assert sum(n_use == n) == nu[n]

            # Make sure we have the correct number of atoms
            # and rearrange the atoms so they are in the right order
            for i in xrange(len(n_use)):
                if num[i] == n_use[i]:
                    continue
                for j in xrange(i + 1, len(n_use)):
                    if n_use[j] == num[i]:
                        tn = n_use[i]
                        tp = p_use[i]
                        n_use[i] = n_use[j]
                        p_use[i] = p_use[j]
                        p_use[j] = tp
                        n_use[j] = tn

            # Finally we check that nothing is too close in the end product.
            cand = Atoms(num, p_use, cell=slab.get_cell(), pbc=slab.get_pbc())
            tc = atoms_too_close(cand, self.blmin)
            if tc:
                continue
            tc = atoms_too_close_two_sets(slab, cand, self.blmin)
            if not changed and counter > int(n_tries / 2):
                reflect = not reflect
                changed = True
            tot = slab + cand
        if counter == n_tries:
            return None
        return tot
Example #10
0
    def cross(self, a1, a2, test_dist_to_slab=True):
        """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)

        # Find the common center of the two clusters
        c1cm = np.average(a1.get_positions(), axis=0)
        c2cm = np.average(a2.get_positions(), axis=0)
        cutting_point = (c1cm + c2cm) / 2.

        counter = 0
        too_close = True
        n_max = 1000
        # Run until a valid pairing is made or 1000 pairings are tested.
        while too_close and counter < n_max:

            # Generate the cutting plane
            theta = pi * random()
            phi = 2. * pi * random()
            n = (cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta))
            n = np.array(n)

            # Get the pairing
            top = self._get_pairing_(a1, a2,
                                     cutting_plane=n,
                                     cutting_point=cutting_point)

            # Check if the candidate is valid
            too_close = atoms_too_close(top, self.blmin)
            if not too_close and test_dist_to_slab:
                too_close = atoms_too_close_two_sets(self.slab,
                                                     top, self.blmin)

            # Verify that the generated structure contains atoms from
            # both parents
            n1 = -1 * np.ones((N, ))
            n2 = -1 * np.ones((N, ))
            for i in xrange(N):
                for j in xrange(N):
                    if np.all(a1.positions[j, :] == top.positions[i, :]):
                        n1[i] = j
                        break
                    elif np.all(a2.positions[j, :] == top.positions[i, :]):
                        n2[i] = j
                        break
                assert (n1[i] > -1 and n2[i] == -1) or (n1[i] == -1 and
                                                        n2[i] > -1)

            if not (len(n1[n1 > -1]) > 0 and len(n2[n2 > -1]) > 0):
                too_close = True

            counter += 1

        if counter == n_max:
            return None
        
        return self.slab + top
Example #11
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
Example #12
0
    def mutate(self, atoms):
        """ Do the mutation of the atoms input. """

        reflect = self.reflect
        tc = True
        slab = atoms[0:len(atoms) - self.n_top]
        top = atoms[len(atoms) - self.n_top: len(atoms)]
        num = top.numbers
        unique_types = list(set(num))
        nu = dict()
        for u in unique_types:
            nu[u] = sum(num == u)
            
        n_tries = 1000
        counter = 0
        changed = False

        while tc and counter < n_tries:
            counter += 1
            cand = top.copy()
            pos = cand.get_positions()

            cm = np.average(top.get_positions(), axis=0)

            # first select a randomly oriented cutting plane
            theta = pi * random()
            phi = 2. * pi * random()
            n = (cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta))
            n = np.array(n)

            # Calculate all atoms signed distance to the cutting plane
            D = []
            for (i, p) in enumerate(pos):
                d = np.dot(p - cm, n)
                D.append((i, d))

            # Sort the atoms by their signed distance
            D.sort(key=lambda x: x[1])
            nu_taken = dict()

            # Select half of the atoms needed for a full cluster
            p_use = []
            n_use = []
            for (i, d) in D:
                if num[i] not in nu_taken.keys():
                    nu_taken[num[i]] = 0
                if nu_taken[num[i]] < nu[num[i]] / 2.:
                    p_use.append(pos[i])
                    n_use.append(num[i])
                    nu_taken[num[i]] += 1

            # calculate the mirrored position and add these.
            pn = []
            for p in p_use:
                pt = p - 2. * np.dot(p - cm, n) * n
                if reflect:
                    pt = -pt + 2 * cm + 2 * n * np.dot(pt - cm, n)
                pn.append(pt)

            n_use.extend(n_use)
            p_use.extend(pn)

            # In the case of an uneven number of
            # atoms we need to add one extra
            for n in nu.keys():
                if nu[n] % 2 == 0:
                    continue
                while sum(n_use == n) > nu[n]:
                    for i in xrange(int(len(n_use) / 2), len(n_use)):
                        if n_use[i] == n:
                            del p_use[i]
                            del n_use[i]
                            break
                assert sum(n_use == n) == nu[n]

            # Make sure we have the correct number of atoms
            # and rearrange the atoms so they are in the right order
            for i in xrange(len(n_use)):
                if num[i] == n_use[i]:
                    continue
                for j in xrange(i + 1, len(n_use)):
                    if n_use[j] == num[i]:
                        tn = n_use[i]
                        tp = p_use[i]
                        n_use[i] = n_use[j]
                        p_use[i] = p_use[j]
                        p_use[j] = tp
                        n_use[j] = tn

            # Finally we check that nothing is too close in the end product.
            cand = Atoms(num, p_use, cell=slab.get_cell(), pbc=slab.get_pbc())
            tc = atoms_too_close(cand, self.blmin)
            if tc:
                continue
            tc = atoms_too_close_two_sets(slab, cand, self.blmin)
            if not changed and counter > int(n_tries / 2):
                reflect = not reflect
                changed = True
            tot = slab + cand
        if counter == n_tries:
            return None
        return tot
    def cross(self, a1, a2, test_dist_to_slab=True):
        """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)

        # Find the common center of the two clusters
        c1cm = np.average(a1.get_positions(), axis=0)
        c2cm = np.average(a2.get_positions(), axis=0)
        cutting_point = (c1cm + c2cm) / 2.

        counter = 0
        too_close = True
        n_max = 1000
        # Run until a valid pairing is made or 1000 pairings are tested.
        while too_close and counter < n_max:

            # Generate the cutting plane
            theta = pi * random()
            phi = 2. * pi * random()
            n = (cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta))
            n = np.array(n)

            # Get the pairing
            top = self._get_pairing_(a1,
                                     a2,
                                     cutting_plane=n,
                                     cutting_point=cutting_point)

            # Check if the candidate is valid
            too_close = atoms_too_close(top, self.blmin)
            if not too_close and test_dist_to_slab:
                too_close = atoms_too_close_two_sets(self.slab, top,
                                                     self.blmin)

            # Verify that the generated structure contains atoms from
            # both parents
            n1 = -1 * np.ones((N, ))
            n2 = -1 * np.ones((N, ))
            for i in xrange(N):
                for j in xrange(N):
                    if np.all(a1.positions[j, :] == top.positions[i, :]):
                        n1[i] = j
                        break
                    elif np.all(a2.positions[j, :] == top.positions[i, :]):
                        n2[i] = j
                        break
                assert (n1[i] > -1 and n2[i] == -1) or (n1[i] == -1
                                                        and n2[i] > -1)

            if not (len(n1[n1 > -1]) > 0 and len(n2[n2 > -1]) > 0):
                too_close = True

            counter += 1

        if counter == n_max:
            return None

        return self.slab + top
Example #14
0
    def get_new_candidate(self, maxiter=None):
        """Returns a new candidate.

        maxiter: upper bound on the total number of times
             the random position generator is called
             when generating the new candidate.

             By default (maxiter=None) no such bound
             is imposed. If the generator takes too
             long time to create a new candidate, it
             may be suitable to specify a finite value.
             When the bound is exceeded, None is returned.
        """
        pbc = self.slab.get_pbc()

        # Choose cell splitting
        r = self.rng.rand()
        cumprob = 0
        for split, prob in self.splits.items():
            cumprob += prob
            if cumprob > r:
                break

        # Choose direction(s) along which to split
        # and by how much
        directions = [i for i in range(3) if pbc[i]]
        repeat = [1, 1, 1]
        if len(directions) > 0:
            for number in split:
                d = self.rng.choice(directions)
                repeat[d] = number
        repeat = tuple(repeat)

        # Generate the 'full' unit cell
        # for the eventual candidates
        cell = self.generate_unit_cell(repeat)
        if self.number_of_variable_cell_vectors == 0:
            assert np.allclose(cell, self.slab.get_cell())

        # Make the smaller 'box' in which we are
        # allowed to place the atoms and which will
        # then be repeated to fill the 'full' unit cell
        box = np.copy(cell)
        for i in range(self.number_of_variable_cell_vectors, 3):
            box[i] = np.array(self.box_to_place_in[1][i])
        box /= np.array([repeat]).T

        # Here we gather the (reduced) number of blocks
        # to put in the smaller box, and the 'surplus'
        # occurring when the block count is not divisible
        # by the number of repetitions.
        # E.g. if we have a ('Ti', 4) block and do a
        # [2, 3, 1] repetition, we employ a ('Ti', 1)
        # block in the smaller box and delete 2 out 6
        # Ti atoms afterwards
        nrep = int(np.prod(repeat))
        blocks, ids, surplus = [], [], []
        for i, (block, count) in enumerate(self.blocks):
            count_part = int(np.ceil(count * 1. / nrep))
            blocks.extend([block] * count_part)
            surplus.append(nrep * count_part - count)
            ids.extend([i] * count_part)

        N_blocks = len(blocks)

        # Shuffle the ordering so different blocks
        # are added in random order
        order = np.arange(N_blocks)
        self.rng.shuffle(order)
        blocks = [blocks[i] for i in order]
        ids = np.array(ids)[order]

        # Add blocks one by one until we have found
        # a valid candidate
        blmin = self.blmin
        blmin_too_far = {key: 2 * val for key, val in blmin.items()}

        niter = 0
        while maxiter is None or niter < maxiter:
            cand = Atoms('', cell=box, pbc=pbc)

            for i in range(N_blocks):
                atoms = blocks[i].copy()
                atoms.set_tags(i)
                atoms.set_pbc(pbc)
                atoms.set_cell(box, scale_atoms=False)

                while maxiter is None or niter < maxiter:
                    niter += 1
                    cop = atoms.get_positions().mean(axis=0)
                    pos = np.dot(self.rng.rand(1, 3), box)
                    atoms.translate(pos - cop)

                    if len(atoms) > 1:
                        # Apply a random rotation to multi-atom blocks
                        phi, theta, psi = 360 * self.rng.rand(3)
                        atoms.euler_rotate(phi=phi,
                                           theta=0.5 * theta,
                                           psi=psi,
                                           center=pos)

                    if not atoms_too_close_two_sets(cand, atoms, blmin):
                        cand += atoms
                        break
                else:
                    # Reached maximum iteration number
                    # Break out of the for loop above
                    cand = None
                    break

            if cand is None:
                # Exit the main while loop
                break

            # Rebuild the candidate after repeating,
            # randomly deleting surplus blocks and
            # sorting back to the original order
            cand_full = cand.repeat(repeat)

            tags_full = cand_full.get_tags()
            for i in range(nrep):
                tags_full[len(cand) * i:len(cand) * (i + 1)] += i * N_blocks
            cand_full.set_tags(tags_full)

            cand = Atoms('', cell=cell, pbc=pbc)
            ids_full = np.tile(ids, nrep)

            tag_counter = 0
            if len(self.slab) > 0:
                tag_counter = int(max(self.slab.get_tags())) + 1

            for i, (block, count) in enumerate(self.blocks):
                tags = np.where(ids_full == i)[0]
                bad = self.rng.choice(tags, size=surplus[i], replace=False)
                for tag in tags:
                    if tag not in bad:
                        select = [a.index for a in cand_full if a.tag == tag]
                        atoms = cand_full[select]  # is indeed a copy!
                        atoms.set_tags(tag_counter)
                        assert len(atoms) == len(block)
                        cand += atoms
                        tag_counter += 1

            for i in range(self.number_of_variable_cell_vectors, 3):
                cand.positions[:, i] += self.box_to_place_in[0][i]

            # By construction, the minimal interatomic distances
            # within the structure should already be respected
            assert not atoms_too_close(cand, blmin, use_tags=True), \
                   'This is not supposed to happen; please report this bug'

            if self.test_dist_to_slab and len(self.slab) > 0:
                if atoms_too_close_two_sets(self.slab, cand, blmin):
                    continue

            if self.test_too_far:
                tags = cand.get_tags()

                for tag in np.unique(tags):
                    too_far = True
                    indices_i = np.where(tags == tag)[0]
                    indices_j = np.where(tags != tag)[0]
                    too_far = not atoms_too_close_two_sets(
                        cand[indices_i], cand[indices_j], blmin_too_far)

                    if too_far and len(self.slab) > 0:
                        # the block is too far from the rest
                        # but might still be sufficiently
                        # close to the slab
                        too_far = not atoms_too_close_two_sets(
                            cand[indices_i], self.slab, blmin_too_far)
                    if too_far:
                        break
                else:
                    too_far = False

                if too_far:
                    continue

            # Passed all the tests
            cand = self.slab + cand
            cand.set_cell(cell, scale_atoms=False)
            break
        else:
            # Reached max iteration count in the while loop
            return None

        return cand
Example #15
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