Ejemplo n.º 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
 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
Ejemplo n.º 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:]
        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
Ejemplo n.º 4
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
Ejemplo n.º 5
0
pairing = CutAndSplicePairing(slab, n_top, cd)

c3, desc = pairing.get_new_individual([c1, c2])

# verify that the stoichiometry is preserved
assert np.all(c3.numbers == c1.numbers)
top1 = c1[-n_top:]
top2 = c2[-n_top:]
top3 = c3[-n_top:]


# verify that the positions in the new candidate come from c1 or c2
n1 = -1 * np.ones((n_top,))
n2 = -1 * np.ones((n_top,))
for i in range(n_top):
    for j in range(n_top):
        if np.all(top1.positions[j, :] == top3.positions[i, :]):
            n1[i] = j
            break
        elif np.all(top2.positions[j, :] == top3.positions[i, :]):
            n2[i] = j
            break
    assert (n1[i] > -1 and n2[i] == -1) or (n1[i] == -1 and n2[i] > -1)

# verify that c3 includes atoms from both c1 and c2
assert len(n1[n1 > -1]) > 0 and len(n2[n2 > -1]) > 0

# verify no atoms too close
assert not atoms_too_close(top3, cd)
Ejemplo n.º 6
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
Ejemplo n.º 7
0
def test_bulk_operators():
    h2 = Atoms('H2', positions=[[0, 0, 0], [0, 0, 0.75]])
    blocks = [('H', 4), ('H2O', 3), (h2, 2)]  # the building blocks
    volume = 40. * sum([x[1] for x in blocks])  # cell volume in angstrom^3
    splits = {(2,): 1, (1,): 1}  # cell splitting scheme

    stoichiometry = []
    for block, count in blocks:
        if type(block) == str:
            stoichiometry += list(Atoms(block).numbers) * count
        else:
            stoichiometry += list(block.numbers) * count

    atom_numbers = list(set(stoichiometry))
    blmin = closest_distances_generator(atom_numbers=atom_numbers,
                                        ratio_of_covalent_radii=1.3)

    cellbounds = CellBounds(bounds={'phi': [30, 150], 'chi': [30, 150],
                                    'psi': [30, 150], 'a': [3, 50],
                                    'b': [3, 50], 'c': [3, 50]})

    sg = StartGenerator(blocks, blmin, volume, cellbounds=cellbounds,
                        splits=splits)

    # Generate 2 candidates
    a1 = sg.get_new_candidate()
    a1.info['confid'] = 1
    a2 = sg.get_new_candidate()
    a2.info['confid'] = 2

    # Define and test genetic operators
    pairing = CutAndSplicePairing(blmin, p1=1., p2=0., minfrac=0.15,
                                  cellbounds=cellbounds, use_tags=True)

    a3, desc = pairing.get_new_individual([a1, a2])
    cell = a3.get_cell()
    assert cellbounds.is_within_bounds(cell)
    assert not atoms_too_close(a3, blmin, use_tags=True)

    n_top = len(a1)
    strainmut = StrainMutation(blmin, stddev=0.7, cellbounds=cellbounds,
                               use_tags=True)
    softmut = SoftMutation(blmin, bounds=[2., 5.], used_modes_file=None,
                           use_tags=True)
    rotmut = RotationalMutation(blmin, fraction=0.3, min_angle=0.5 * np.pi)
    rattlemut = RattleMutation(blmin, n_top, rattle_prop=0.3, rattle_strength=0.5,
                               use_tags=True, test_dist_to_slab=False)
    rattlerotmut = RattleRotationalMutation(rattlemut, rotmut)
    permut = PermutationMutation(n_top, probability=0.33, test_dist_to_slab=False,
                                 use_tags=True, blmin=blmin)
    combmut = CombinationMutation(rattlemut, rotmut, verbose=True)
    mutations = [strainmut, softmut, rotmut,
                 rattlemut, rattlerotmut, permut, combmut]

    for i, mut in enumerate(mutations):
        a = [a1, a2][i % 2]
        a3 = None
        while a3 is None:
            a3, desc = mut.get_new_individual([a])

        cell = a3.get_cell()
        assert cellbounds.is_within_bounds(cell)
        assert np.all(a3.numbers == a.numbers)
        assert not atoms_too_close(a3, blmin, use_tags=True)

    modes_file = 'modes.txt'
    softmut_with = SoftMutation(blmin, bounds=[2., 5.], use_tags=True,
                                used_modes_file=modes_file)
    no_muts = 3
    for _ in range(no_muts):
        softmut_with.get_new_individual([a1])
    softmut_with.read_used_modes(modes_file)
    assert len(list(softmut_with.used_modes.values())[0]) == no_muts
    os.remove(modes_file)

    comparator = OFPComparator(recalculate=True)
    gold = bulk('Au') * (2, 2, 2)
    assert comparator.looks_like(gold, gold)

    # This move should not exceed the default threshold
    gc = gold.copy()
    gc[0].x += .1
    assert comparator.looks_like(gold, gc)

    # An additional step will exceed the threshold
    gc[0].x += .2
    assert not comparator.looks_like(gold, gc)
Ejemplo n.º 8
0
def test_film_operators(seed):
    from ase.ga.startgenerator import StartGenerator
    from ase.ga.cutandsplicepairing import CutAndSplicePairing
    from ase.ga.standardmutations import StrainMutation
    from ase.ga.utilities import (closest_distances_generator, atoms_too_close,
                                  CellBounds)
    import numpy as np
    from ase import Atoms
    from ase.build import molecule

    # set up the random number generator
    rng = np.random.RandomState(seed)

    slab = Atoms('', cell=(0, 0, 15), pbc=[True, True, False])

    cation, anion = 'Mg', molecule('OH')
    d_oh = anion.get_distance(0, 1)
    blocks = [(cation, 4), (anion, 8)]
    n_top = 4 + 8 * len(anion)

    use_tags = True
    num_vcv = 2
    box_volume = 8. * n_top

    blmin = closest_distances_generator(atom_numbers=[1, 8, 12],
                                        ratio_of_covalent_radii=0.6)

    cellbounds = CellBounds(
        bounds={
            'phi': [0.1 * 180., 0.9 * 180.],
            'chi': [0.1 * 180., 0.9 * 180.],
            'psi': [0.1 * 180., 0.9 * 180.],
            'a': [2, 8],
            'b': [2, 8]
        })

    box_to_place_in = [[None, None, 3.], [None, None, [0., 0., 5.]]]

    sg = StartGenerator(slab,
                        blocks,
                        blmin,
                        box_volume=box_volume,
                        splits={(2, 1): 1},
                        box_to_place_in=box_to_place_in,
                        number_of_variable_cell_vectors=num_vcv,
                        cellbounds=cellbounds,
                        test_too_far=True,
                        test_dist_to_slab=False,
                        rng=rng)

    parents = []
    for i in range(2):
        a = None
        while a is None:
            a = sg.get_new_candidate()

        a.info['confid'] = i
        parents.append(a)

        assert len(a) == n_top
        assert len(np.unique(a.get_tags())) == 4 + 8
        assert np.allclose(a.get_pbc(), slab.get_pbc())

        p = a.get_positions()
        assert np.min(p[:, 2]) > 3. - 0.5 * d_oh
        assert np.max(p[:, 2]) < 3. + 5. + 0.5 * d_oh
        assert not atoms_too_close(a, blmin, use_tags=use_tags)

        c = a.get_cell()
        assert np.allclose(c[2], slab.get_cell()[2])
        assert cellbounds.is_within_bounds(c)

        v = a.get_volume() * 5. / 15.
        assert abs(v - box_volume) < 1e-5

    # Test cut-and-splice pairing and strain mutation
    pairing = CutAndSplicePairing(slab,
                                  n_top,
                                  blmin,
                                  number_of_variable_cell_vectors=num_vcv,
                                  p1=1.,
                                  p2=0.,
                                  minfrac=0.15,
                                  cellbounds=cellbounds,
                                  use_tags=use_tags,
                                  rng=rng)

    strainmut = StrainMutation(blmin,
                               cellbounds=cellbounds,
                               number_of_variable_cell_vectors=num_vcv,
                               use_tags=use_tags,
                               rng=rng)
    strainmut.update_scaling_volume(parents)

    for operator in [pairing, strainmut]:
        child = None
        while child is None:
            child, desc = operator.get_new_individual(parents)

        assert not atoms_too_close(child, blmin, use_tags=use_tags)
        cell = child.get_cell()
        assert cellbounds.is_within_bounds(cell)
        assert np.allclose(cell[2], slab.get_cell()[2])
def test_cutandsplicepairing(seed):
    from ase.ga.startgenerator import StartGenerator
    from ase.ga.utilities import closest_distances_generator, atoms_too_close
    from ase.ga.cutandsplicepairing import CutAndSplicePairing
    import numpy as np
    from ase.build import fcc111
    from ase.constraints import FixAtoms

    # set up the random number generator
    rng = np.random.RandomState(seed)

    # first create two random starting candidates
    slab = fcc111('Au', size=(4, 4, 2), vacuum=10.0, orthogonal=True)
    slab.set_constraint(FixAtoms(mask=slab.positions[:, 2] <= 10.))

    pos = slab.get_positions()
    cell = slab.get_cell()
    p0 = np.array([0., 0., max(pos[:, 2]) + 2.])
    v1 = cell[0, :] * 0.8
    v2 = cell[1, :] * 0.8
    v3 = cell[2, :]
    v3[2] = 3.

    blmin = closest_distances_generator(atom_numbers=[47, 79],
                                        ratio_of_covalent_radii=0.7)

    atom_numbers = 2 * [47] + 2 * [79]

    sg = StartGenerator(slab=slab,
                        blocks=atom_numbers,
                        blmin=blmin,
                        box_to_place_in=[p0, [v1, v2, v3]],
                        rng=rng)

    c1 = sg.get_new_candidate()
    c1.info['confid'] = 1
    c2 = sg.get_new_candidate()
    c2.info['confid'] = 2

    n_top = len(atom_numbers)

    pairing = CutAndSplicePairing(slab, n_top, blmin, rng=rng)

    c3, desc = pairing.get_new_individual([c1, c2])

    # verify that the stoichiometry is preserved
    assert np.all(c3.numbers == c1.numbers)
    top1 = c1[-n_top:]
    top2 = c2[-n_top:]
    top3 = c3[-n_top:]

    # verify that the positions in the new candidate come from c1 or c2
    n1 = -1 * np.ones((n_top, ))
    n2 = -1 * np.ones((n_top, ))
    for i in range(n_top):
        for j in range(n_top):
            if np.allclose(top1.positions[j, :], top3.positions[i, :], 1e-12):
                n1[i] = j
                break
            elif np.allclose(top2.positions[j, :], top3.positions[i, :],
                             1e-12):
                n2[i] = j
                break
        assert (n1[i] > -1 and n2[i] == -1) or (n1[i] == -1 and n2[i] > -1)

    # verify that c3 includes atoms from both c1 and c2
    assert len(n1[n1 > -1]) > 0 and len(n2[n2 > -1]) > 0

    # verify no atoms too close
    assert not atoms_too_close(top3, blmin)
Ejemplo n.º 10
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
Ejemplo n.º 11
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
Ejemplo n.º 12
0
    def mutate(self, atoms):
        """ Does the actual mutation. """
        a = atoms.copy()

        if inspect.isclass(self.calc):
            assert issubclass(self.calc, PairwiseHarmonicPotential)
            calc = self.calc(atoms, rcut=self.rcut)
        else:
            calc = self.calc
        a.set_calculator(calc)

        if self.use_tags:
            a = TagFilter(a)

        pos = a.get_positions()
        modes = self._calculate_normal_modes(a)

        # Select the mode along which we want to move the atoms;
        # The first 3 translational modes as well as previously
        # applied modes are discarded.

        keys = np.array(sorted(modes))
        index = 3
        confid = atoms.info['confid']
        if confid in self.used_modes:
            while index in self.used_modes[confid]:
                index += 1
            self.used_modes[confid].append(index)
        else:
            self.used_modes[confid] = [index]

        if self.used_modes_file is not None:
            self.write_used_modes(self.used_modes_file)

        key = keys[index]
        mode = modes[key].reshape(np.shape(pos))

        # Find a suitable amplitude for translation along the mode;
        # at every trial amplitude both positive and negative
        # directions are tried.

        mutant = atoms.copy()
        amplitude = 0.
        increment = 0.1
        direction = 1
        largest_norm = np.max(np.apply_along_axis(np.linalg.norm, 1, mode))

        def expand(atoms, positions):
            if isinstance(atoms, TagFilter):
                a.set_positions(positions)
                return a.atoms.get_positions()
            else:
                return positions

        while amplitude * largest_norm < self.bounds[1]:
            pos_new = pos + direction * amplitude * mode
            pos_new = expand(a, pos_new)
            mutant.set_positions(pos_new)
            mutant.wrap()
            too_close = atoms_too_close(mutant,
                                        self.blmin,
                                        use_tags=self.use_tags)
            if too_close:
                amplitude -= increment
                pos_new = pos + direction * amplitude * mode
                pos_new = expand(a, pos_new)
                mutant.set_positions(pos_new)
                mutant.wrap()
                break

            if direction == 1:
                direction = -1
            else:
                direction = 1
                amplitude += increment

        if amplitude * largest_norm < self.bounds[0]:
            mutant = None

        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
Ejemplo n.º 14
0
n_top = len(atom_numbers)

pairing = CutAndSplicePairing(slab, n_top, cd)

c3, desc = pairing.get_new_individual([c1, c2])

# verify that the stoichiometry is preserved
assert np.all(c3.numbers == c1.numbers)
top1 = c1[-n_top:]
top2 = c2[-n_top:]
top3 = c3[-n_top:]

# verify that the positions in the new candidate come from c1 or c2
n1 = -1 * np.ones((n_top, ))
n2 = -1 * np.ones((n_top, ))
for i in range(n_top):
    for j in range(n_top):
        if np.all(top1.positions[j, :] == top3.positions[i, :]):
            n1[i] = j
            break
        elif np.all(top2.positions[j, :] == top3.positions[i, :]):
            n2[i] = j
            break
    assert (n1[i] > -1 and n2[i] == -1) or (n1[i] == -1 and n2[i] > -1)

# verify that c3 includes atoms from both c1 and c2
assert len(n1[n1 > -1]) > 0 and len(n2[n2 > -1]) > 0

# verify no atoms too close
assert not atoms_too_close(top3, cd)
Ejemplo n.º 15
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
Ejemplo n.º 16
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
Ejemplo n.º 18
0
a1.info['confid'] = 1
a2 = sg.get_new_candidate()
a2.info['confid'] = 2

# Define and test genetic operators
pairing = CutAndSplicePairing(blmin,
                              p1=1.,
                              p2=0.,
                              minfrac=0.15,
                              cellbounds=cellbounds,
                              use_tags=True)

a3, desc = pairing.get_new_individual([a1, a2])
cell = a3.get_cell()
assert cellbounds.is_within_bounds(cell)
assert not atoms_too_close(a3, blmin, use_tags=True)

n_top = len(a1)
strainmut = StrainMutation(blmin,
                           stddev=0.7,
                           cellbounds=cellbounds,
                           use_tags=True)
softmut = SoftMutation(blmin,
                       bounds=[2., 5.],
                       used_modes_file=None,
                       use_tags=True)
rotmut = RotationalMutation(blmin, fraction=0.3, min_angle=0.5 * np.pi)
rattlemut = RattleMutation(blmin,
                           n_top,
                           rattle_prop=0.3,
                           rattle_strength=0.5,
Ejemplo n.º 19
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 = [True] * 3
        blmin = self.blmin

        # generating the cell
        # cell splitting:
        # choose factors according to the probabilities
        r = random()
        cumprob = 0
        for split, prob in self.splits.items():
            cumprob += prob
            if cumprob > r:
                break

        directions = sample(range(3), len(split))
        repeat = [1, 1, 1]
        for i, d in enumerate(directions):
            repeat[d] = split[i]
        repeat = tuple(repeat)

        nparts = np.product(repeat)
        target_volume = self.volume / nparts

        # Randomly create a cell; without loss of generality,
        # a lower triangular form can be used, with tilt factors
        # within certain bounds.
        # For a cell to be valid, the full cell has to satisfy
        # the cellbounds constraints. Additionally, the length of
        # each subcell vector has to be greater than the largest
        # (X,X)-minimal-interatomic-distance in blmin.

        if self.cell is not None:
            full_cell = np.copy(self.cell)
            cell = (full_cell.T / repeat).T
            valid = True
        else:
            valid = False

        while not valid:
            blminmax = max([blmin[k] for k in blmin if k[0] == k[1]])
            cell = np.zeros((3, 3))
            l = target_volume**0.33
            cell[0, 0] = random() * l
            cell[1, 0] = (random() - 0.5) * cell[0, 0]
            cell[1, 1] = random() * l
            cell[2, 0] = (random() - 0.5) * cell[0, 0]
            cell[2, 1] = (random() - 0.5) * cell[1, 1]
            cell[2, 2] = random() * l

            volume = abs(np.linalg.det(cell))
            cell *= (target_volume / volume)**0.33

            full_cell = (repeat * cell.T).T

            valid = True
            if self.cellbounds is not None:
                if not self.cellbounds.is_within_bounds(full_cell):
                    valid = False
            for i in range(3):
                if np.linalg.norm(cell[i, :]) < blminmax:
                    valid = False

        # generating the atomic positions
        blocks = []
        surplus = []
        indices = []
        for i, (block, count) in enumerate(self.blocks):
            count_part = int(np.ceil(count * 1. / nparts))
            surplus.append(nparts * count_part - count)
            blocks.extend([block] * count_part)
            indices.extend([i] * count_part)

        N_blocks = len(blocks)

        # The ordering is shuffled so different blocks
        # are added in random order.
        order = list(range(N_blocks))
        shuffle(order)
        blocks = [blocks[i] for i in order]
        indices = np.array(indices)[order]

        # Runs until we have found a valid candidate.
        cand = Atoms('', cell=cell, pbc=pbc)
        niter = 0
        for i in range(N_blocks):
            atoms = blocks[i].copy()
            atoms.set_tags(i)
            rotate = len(atoms) > 1

            # Make each new position one at a time.
            while maxiter is None or niter < maxiter:
                cop = atoms.get_positions().mean(axis=0)
                pos = random_pos(cell)
                atoms.translate(pos - cop)
                if rotate:
                    phi, theta, psi = 360 * np.random.random(3)
                    atoms.euler_rotate(phi=phi,
                                       theta=0.5 * theta,
                                       psi=psi,
                                       center=pos)
                # add if it fits:
                attempt = cand + atoms
                attempt.wrap()
                too_close = atoms_too_close(attempt, blmin, use_tags=True)
                if not too_close:
                    cand += atoms
                    break
                niter += 1
            else:
                # Reached upper bound on iteration count
                cand = None
                break

        if cand is None:
            return None

        # rebuild the candidate after repeating,
        # randomly deleting surplus blocks and
        # sorting back to the original order
        tags = cand.get_tags()
        nrep = int(np.prod(repeat))
        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=full_cell, pbc=pbc)
        indices_full = np.tile(indices, nrep)
        tag_counter = 0
        for i, (block, count) in enumerate(self.blocks):
            tags = np.where(indices_full == i)[0]
            bad = np.random.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

        return cand
Ejemplo n.º 20
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
Ejemplo n.º 21
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
Ejemplo n.º 22
0
def test_chain_operators(seed):
    from ase.ga.startgenerator import StartGenerator
    from ase.ga.cutandsplicepairing import CutAndSplicePairing
    from ase.ga.standardmutations import StrainMutation
    from ase.ga.utilities import (closest_distances_generator, atoms_too_close,
                                  CellBounds)
    import numpy as np
    from ase import Atoms

    # set up the random number generator
    rng = np.random.RandomState(seed)

    slab = Atoms('', cell=(0, 16, 16), pbc=[True, False, False])
    blocks = ['C'] * 8
    n_top = 8

    use_tags = False
    num_vcv = 1
    box_volume = 8. * n_top

    blmin = closest_distances_generator(atom_numbers=[6],
                                        ratio_of_covalent_radii=0.6)

    cellbounds = CellBounds(
        bounds={
            'phi': [0.1 * 180., 0.9 * 180.],
            'chi': [0.1 * 180., 0.9 * 180.],
            'psi': [0.1 * 180., 0.9 * 180.],
            'a': [1, 6]
        })

    box_to_place_in = [[None, 6., 6.], [None, [0., 4., 0.], [0., 0., 4.]]]

    sg = StartGenerator(slab,
                        blocks,
                        blmin,
                        box_volume=box_volume,
                        splits=None,
                        box_to_place_in=box_to_place_in,
                        number_of_variable_cell_vectors=num_vcv,
                        cellbounds=cellbounds,
                        test_too_far=True,
                        test_dist_to_slab=False,
                        rng=rng)

    parents = []
    for i in range(2):
        a = None
        while a is None:
            a = sg.get_new_candidate()

        a.info['confid'] = i
        parents.append(a)

        assert len(a) == n_top
        assert len(np.unique(a.get_tags())) == 8
        assert np.allclose(a.get_pbc(), slab.get_pbc())

        p = a.get_positions()
        assert np.min(p[:, 1:]) > 6.
        assert np.max(p[:, 1:]) < 6. + 4.
        assert not atoms_too_close(a, blmin, use_tags=use_tags)

        c = a.get_cell()
        assert np.allclose(c[1:], slab.get_cell()[1:])
        assert cellbounds.is_within_bounds(c)

        v = a.get_volume() * (4. / 16.)**2
        assert abs(v - box_volume) < 1e-5

    # Test cut-and-splice pairing and strain mutation
    pairing = CutAndSplicePairing(slab,
                                  n_top,
                                  blmin,
                                  number_of_variable_cell_vectors=num_vcv,
                                  p1=1.,
                                  p2=0.,
                                  minfrac=0.15,
                                  cellbounds=cellbounds,
                                  use_tags=use_tags,
                                  rng=rng)

    strainmut = StrainMutation(blmin,
                               cellbounds=cellbounds,
                               number_of_variable_cell_vectors=num_vcv,
                               use_tags=use_tags,
                               rng=rng)
    strainmut.update_scaling_volume(parents)

    for operator in [pairing, strainmut]:
        child = None
        while child is None:
            child, desc = operator.get_new_individual(parents)

        assert not atoms_too_close(child, blmin, use_tags=use_tags)
        cell = child.get_cell()
        assert cellbounds.is_within_bounds(cell)
        assert np.allclose(cell[1:], slab.get_cell()[1:])
Ejemplo n.º 23
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