Ejemplo n.º 1
0
def rotate_all(atoms, vector=None, angle=None, center=None):
    """Rotate all atoms around a single point. Most suitable for
    cluster calculations.

    Parameters
    ---------
    individual : StructOpt individual object or ase atoms
        StructOpt Individual or ase Atoms object to be rotated.
    angle : string or list
        A list of angles that will be chosen to rotate. If None,
        is randomly generated. Angle must be given in radians.
        If 'random' in list, a random angle is included.
    vector : string or list
        The list of axes in which to rotate the atoms around. If 
        None, is a randomly chosen direction. If 'random' in list,
        a random vector can be chosen.
    center : str or xyz iterable
        The center in which to rotate the atoms around. If None,
        defaults to center of mass. Acceptable strings are
        COM = center of mass
        COP = center of positions
        COU = center of cell

    Returns
    -------
    out: None
        Modifies the individual in place
    """

    # Initialize variables for ase.Atoms.rotate
    if angle is None:
        angle = random.uniform(30, 180) * np.pi / 180.0
    elif hasattr(angle, '__iter__'):
        angle = random.choice(angle)
        if angle == 'random':
            angle = random.uniform(30, 180) * np.pi / 180

    if vector is None:
        vector = random_three_vector()
    elif hasattr(vector, '__iter__'):
        vector = random.choice(vector)
        if angle == 'random':
            vector = random_three_vector()

    if center is None:
        center = 'COM'

    # Perform the rotation
    atoms.rotate(v=vector, a=angle, center=center)

    return vector, angle
Ejemplo n.º 2
0
def get_vector_angle(orientation=None, v=None, angle=None):
    if (np.shape(v) == (3, ) and type(a) in [float, int]):
        v = np.asarray(v, dtype=float)
        v /= np.linalg.norm(v)
        return v, angle

    elif orientation is None:
        angle = np.random.uniform(0, np.pi * 2)
        v = random_three_vector()
        return v, angle

    elif orientation == '100':
        orientation_angle = 0.0
        orientation_v = np.array([1, 0, 0])
    elif orientation == '110':
        orientation_angle = np.pi / 4
        orientation_v = np.array([0, 1, 0])
    elif orientation == '111':
        orientation_angle = np.arcsin(1.0 / 3.0**0.5)
        orientation_v = np.array([-1.0 / 2.0**0.5, 1.0 / 2.0**0.5, 0])
    else:
        raise NotImplementedError('Orientation not implemented')

    # Sometimes we want another rotation after an orientation
    if v is None and angle is not None:
        v = [orientation_v, np.array([0, 0, 1])]
        angle = [orientation_angle, np.asarray(angle)]
    else:
        v = orientation_v
        angle = orientation_angle

    return v, angle
Ejemplo n.º 3
0
def get_vector_angle(orientation=None, v=None, angle=None):
    if (np.shape(v) == (3,) and type(a) in [float, int]):
        v = np.asarray(v, dtype=float)
        v /= np.linalg.norm(v)
        return v, angle

    elif orientation is None:
        angle = np.random.uniform(0,np.pi*2)
        v = random_three_vector()
        return v, angle

    elif orientation == '100':
        orientation_angle = 0.0
        orientation_v = np.array([1, 0, 0])
    elif orientation == '110':
        orientation_angle = np.pi / 4
        orientation_v = np.array([0, 1, 0])
    elif orientation == '111':
        orientation_angle = np.arcsin(1.0 / 3.0 ** 0.5)
        orientation_v = np.array([-1.0 / 2.0 ** 0.5, 1.0 / 2.0 ** 0.5, 0])
    else:
        raise NotImplementedError('Orientation not implemented')

    # Sometimes we want another rotation after an orientation
    if v is None and angle is not None:
        v = [orientation_v, np.array([0, 0, 1])]
        angle = [orientation_angle, np.asarray(angle)]
    else:
        v = orientation_v
        angle = orientation_angle        

    return v, angle
Ejemplo n.º 4
0
def sphere(atomlist, fill_factor=0.74, radius=None, cell=None):
    """Generates a random sphere of particles given an
    atomlist and radius. If radius is None, one is 
    automatically estimated. min_dist and tries_b4_expand
    are parameters that govern how stricly the proximity
    of atoms are enforced.
    """

    if radius is None:
        radius = get_particle_radius(atomlist, fill_factor)

    # Create a list of random order of the atoms
    chemical_symbols = []
    for atom in atomlist:
        chemical_symbols += [atom[0]] * atom[1]

    random.shuffle(chemical_symbols)

    unit_vec = np.array([random_three_vector() for i in range(len(chemical_symbols))])
    D = radius * np.random.sample(size=len(chemical_symbols)) ** (1.0/3.0)
    positions = np.array([D]).T * unit_vec

    indiv = Atoms(symbols=chemical_symbols, positions=positions)

    if cell is not None:
        indiv.set_cell(cell)
        cell_center = np.sum(indiv.get_cell(), axis=1) / 2.0
        indiv.translate(indiv.get_center_of_mass() + cell_center)
        indiv.set_pbc(True)

    return indiv
Ejemplo n.º 5
0
def rotate_all(atoms, vector=None, angle=None, center=None):
    """Rotate all atoms around a single point. Most suitable for
    cluster calculations.

    Parameters
    ---------
    individual : Individual
        An individual.
    vector : string or list
        The list of axes in which to rotate the atoms around. If 
        None, is a randomly chosen direction. If 'random' in list,
        a random vector can be chosen.
    angle : string or list
        A list of angles that will be chosen to rotate. If None,
        is randomly generated. Angle must be given in radians.
        If 'random' in list, a random angle is included.
    center : string or xyz iterable
        The center in which to rotate the atoms around. If None,
        defaults to center of mass. Acceptable strings are
        COM = center of mass
        COP = center of positions
        COU = center of cell
    """

    # Initialize variables for ase.Atoms.rotate
    if angle is None:
        angle = random.uniform(30, 180) * np.pi / 180.0
    elif hasattr(angle, '__iter__'):
        angle = random.choice(angle)
        if angle == 'random':
            angle = random.uniform(30, 180) * np.pi / 180
        
    if vector is None:
        vector = random_three_vector()
    elif hasattr(vector, '__iter__'):
        vector = random.choice(vector)
        if angle == 'random':
            vector = random_three_vector()

    if center is None:
        center = 'COM'

    # Perform the rotation
    atoms.rotate(v=vector, a=angle, center=center)

    return vector, angle
Ejemplo n.º 6
0
def sphere(atomlist, cell, fill_factor=0.74, radius=None):
    """Generates a random sphere of particles given an
    atomlist and radius.

    Parameters
    ----------
    atomlist : list
        A list of [sym, n] pairs where sym is the chemical symbol
        and n is the number of of sym's to include in the individual
    cell : list
        The x, y, and z dimensions of the cell which holds the particle.
    fill_factor : float
        How densely packed the sphere should be. Ranges from 0 to 1.
    radius : float
        The radius of the sphere. If None, estimated from the
        atomic radii
    """

    if radius is None:
        radius = get_particle_radius(atomlist, fill_factor)

    # Create a list of random order of the atoms
    chemical_symbols = []
    for atom in atomlist:
        chemical_symbols += [atom[0]] * atom[1]

    random.shuffle(chemical_symbols)

    unit_vec = np.array([random_three_vector() for i in range(len(chemical_symbols))])
    D = radius * np.random.sample(size=len(chemical_symbols)) ** (1.0/3.0)
    positions = np.array([D]).T * unit_vec

    indiv = Atoms(symbols=chemical_symbols, positions=positions)

    if cell is not None:
        indiv.set_cell(cell)
        cell_center = np.sum(indiv.get_cell(), axis=1) / 2.0
        indiv.translate(indiv.get_center_of_mass() + cell_center)
        indiv.set_pbc(True)

    return indiv
Ejemplo n.º 7
0
def rotate_fixed(individual1,
                 individual2,
                 align_cutoff=0.1,
                 repair_composition=True):
    """Similar to rotate except the children aren't borne out of cut out
    versions.

    Parameters
    ----------
    individual1 : Individual
        The first parent
    individual2 : Individual 
        The second parent
    center_at_atom : bool
        This centers the cut at an atom. This is particularly useful 
        when one desires a crystalline solution. If both parents
        are crystalline, the children will likely not have grain boundaries.
    repair_composition : bool 
        If True, conserves composition. For crossovers that create children
        with more (less) atoms, atoms are taken from (added to) the surface
        of the particle. For incorrect atomic ratios, atomic symbols are
        randomly interchanged throughout the particle

    Returns
    -------
    Individual: The first child
    Individual: The second child
    """

    # Translate individuals so atoms are most aligned

    # Pick a random rotation angle and vector
    rot_vec = random_three_vector()
    rot_angle = random.uniform(0, 180) * np.pi / 180.0

    # Rotate both individuals
    ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))
    ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))

    # Create the children
    child1 = Atoms(cell=ind1c.get_cell())
    child2 = Atoms(cell=ind2c.get_cell())
    child1.extend(ind1c[ind1c.positions[:, 2] >= 0])
    child1.extend(ind2c[ind2c.positions[:, 2] < 0])
    child2.extend(ind2c[ind2c.positions[:, 2] >= 0])
    child2.extend(ind1c[ind1c.positions[:, 2] < 0])

    # Repair the children
    if repair_composition:
        syms = ind1c.get_chemical_symbols()
        atomlist = [[sym, syms.count(sym)] for sym in set(syms)]
        repair_cluster(child1, atomlist)
        repair_cluster(child2, atomlist)

    # Reorient the children
    child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child1.center()
    child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child2.center()

    # Convert the children to an Individual with the parent
    # module parameters
    full_child1 = ind1c.copy(include_atoms=False)
    full_child1.extend(child1)
    full_child2 = ind2c.copy(include_atoms=False)
    full_child2.extend(child2)

    return full_child1, full_child2
Ejemplo n.º 8
0
def rotate(individual1, individual2, center_at_atom=True, repair_composition=True):
    """Rotates the two individuals around their centers of mass,
    splits them in half at the xy-plane, then splices them together.
    Maintains number of atoms. Note, both individuals are rotated
    in the same way.

    Parameters
    ----------
    individual1 : Individual
        The first parent
    individual2 : Individual 
        The second parent
    center_at_atom : bool
        This centers the cut at an atom. This is particularly useful 
        when one desires a crystalline solution. If both parents
        are crystalline, the children will likely not have grain boundaries.
    repair_composition : bool 
        If True, conserves composition. For crossovers that create children
        with more (less) atoms, atoms are taken from (added to) the surface
        of the particle. For incorrect atomic ratios, atomic symbols are
        randomly interchanged throughout the particle

    Returns
    -------
    Individual: The first child
    Individual: The second child
    """

    # Translate individuals so COP is at (0, 0, 0)
    ind1c = individual1.copy()
    ind2c = individual2.copy()
    cop1 = ind1c.get_positions().mean(axis=0)
    cop2 = ind2c.get_positions().mean(axis=0)

    if center_at_atom:
        offset = get_offset(ind1c, ind2c, r=1.0, HWHM=0.4)
        ind1c.translate(offset)
        cop = ind1c.get_positions().mean(axis=0)

        ind1c.translate(-cop)
        ind2c.translate(-cop)
    else:
        ind1c.translate(ind1c.get_positions().mean(axis=0))
        ind2c.translate(ind2c.get_positions().mean(axis=0))

    # Pick a random rotation angle and vector
    rot_vec = random_three_vector()
    rot_angle = random.uniform(0, 180) * np.pi / 180.0

    # Rotate both individuals
    ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))
    ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))

    # Create the children
    child1 = Atoms(cell=ind1c.get_cell())
    child2 = Atoms(cell=ind2c.get_cell())
    child1.extend(ind1c[ind1c.positions[:,2] >= 0])
    child1.extend(ind2c[ind2c.positions[:,2] < 0])
    child2.extend(ind2c[ind2c.positions[:,2] >= 0])
    child2.extend(ind1c[ind1c.positions[:,2] < 0])

    # Repair the children
    if repair_composition:
        syms = ind1c.get_chemical_symbols()
        atomlist = [[sym, syms.count(sym)] for sym in set(syms)]
        repair_cluster(child1, atomlist)
        repair_cluster(child2, atomlist)

    # Reorient the children
    child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child1.center()
    child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child2.center()

    # Convert the children to an Individual with the parent
    # module parameters
    full_child1 = ind1c.copy(include_atoms=False)
    full_child1.extend(child1)
    full_child2 = ind2c.copy(include_atoms=False)
    full_child2.extend(child2)
    
    return full_child1, full_child2
Ejemplo n.º 9
0
def rotate(individual1,
           individual2,
           center_at_atom=True,
           repair_composition=True):
    """Rotates the two individuals around their centers of mass,
    splits them in half at the xy-plane, then splices them together.
    Maintains number of atoms.

    Parameters
    ----------
    individual1 : Individual
        The first parent
    individual2 : Individual 
        The second parent
    conserve_composition : bool 
        If True, conserves composition. For crossovers that create children
        with more (less) atoms, atoms are taken from (added to) the surface
        of the particle. For incorrect atomic ratios, atomic symbols are
        randomly interchanged throughout the particle

    Returns:
        Individual: The first child
        Individual: The second child

    The children are returned without indicies.
    """

    # Translate individuals so COP is at (0, 0, 0)
    ind1c = individual1.copy()
    ind2c = individual2.copy()
    cop1 = ind1c.get_positions().mean(axis=0)
    cop2 = ind2c.get_positions().mean(axis=0)

    if center_at_atom:
        pos1 = ind1c.get_positions()
        dists1 = np.linalg.norm(pos1 - cop1, axis=1)
        cop1 = pos1[np.argmin(dists1)]

        pos2 = ind2c.get_positions()
        dists2 = np.linalg.norm(pos2 - cop2, axis=1)
        cop2 = pos2[np.argmin(dists2)]

    ind1c.translate(-cop1)
    ind2c.translate(-cop2)

    # Pick a random rotation angle and vector
    rot_vec = random_three_vector()
    rot_angle = random.uniform(0, 180) * np.pi / 180.0

    # Rotate both individuals
    ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))
    ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))

    # Create the children
    child1 = Atoms(cell=ind1c.get_cell())
    child2 = Atoms(cell=ind2c.get_cell())
    child1.extend(ind1c[ind1c.positions[:, 2] >= 0])
    child1.extend(ind2c[ind2c.positions[:, 2] < 0])
    child2.extend(ind2c[ind2c.positions[:, 2] >= 0])
    child2.extend(ind1c[ind1c.positions[:, 2] < 0])

    # Repair the children
    if repair_composition:
        syms = ind1c.get_chemical_symbols()
        atomlist = [[sym, syms.count(sym)] for sym in set(syms)]
        repair_cluster(child1, atomlist)
        repair_cluster(child2, atomlist)

    # Reorient the children
    child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child1.center()
    child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child2.center()

    # Convert the children to an Individual with the parent
    # module parameters
    full_child1 = ind1c.copy(include_atoms=False)
    full_child1.extend(child1)
    full_child2 = ind2c.copy(include_atoms=False)
    full_child2.extend(child2)

    return full_child1, full_child2
Ejemplo n.º 10
0
def rotate_fixed(individual1, individual2, align_cutoff=0.1, repair_composition=True):
    """Similar to rotate except the children aren't borne out of cut out
    versions.

    Parameters
    ----------
    individual1 : Individual
        The first parent
    individual2 : Individual 
        The second parent
    center_at_atom : bool
        This centers the cut at an atom. This is particularly useful 
        when one desires a crystalline solution. If both parents
        are crystalline, the children will likely not have grain boundaries.
    repair_composition : bool 
        If True, conserves composition. For crossovers that create children
        with more (less) atoms, atoms are taken from (added to) the surface
        of the particle. For incorrect atomic ratios, atomic symbols are
        randomly interchanged throughout the particle

    Returns
    -------
    Individual: The first child
    Individual: The second child
    """

    # Translate individuals so atoms are most aligned
    

    # Pick a random rotation angle and vector
    rot_vec = random_three_vector()
    rot_angle = random.uniform(0, 180) * np.pi / 180.0

    # Rotate both individuals
    ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))
    ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))

    # Create the children
    child1 = Atoms(cell=ind1c.get_cell())
    child2 = Atoms(cell=ind2c.get_cell())
    child1.extend(ind1c[ind1c.positions[:,2] >= 0])
    child1.extend(ind2c[ind2c.positions[:,2] < 0])
    child2.extend(ind2c[ind2c.positions[:,2] >= 0])
    child2.extend(ind1c[ind1c.positions[:,2] < 0])

    # Repair the children
    if repair_composition:
        syms = ind1c.get_chemical_symbols()
        atomlist = [[sym, syms.count(sym)] for sym in set(syms)]
        repair_cluster(child1, atomlist)
        repair_cluster(child2, atomlist)

    # Reorient the children
    child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child1.center()
    child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child2.center()

    # Convert the children to an Individual with the parent
    # module parameters
    full_child1 = ind1c.copy(include_atoms=False)
    full_child1.extend(child1)
    full_child2 = ind2c.copy(include_atoms=False)
    full_child2.extend(child2)
    
    return full_child1, full_child2
Ejemplo n.º 11
0
def twist(individual, max_radius=0.90):
    """Splits the particle randomly in half and rotates one half.
    
    Parameters
    ----------
    individual : structopt.Individual object
        Individual to be mutated
    max_natoms : float
        That maximum relative distance from the center of the particle 
        the twist is initiated
    """

    if not len(individual):
        return None

    NNs = NeighborList(individual)
    CNs = [len(NN) for NN in NNs]
    
    # Get the surface atoms. These provide bounds for the moves
    # First get unit vectors and mags of all surface atoms
    positions = individual.get_positions()
    com = np.sum(positions.T, axis=1) / len(individual)
    surf_indices = [i for i, CN in enumerate(CNs) if CN < 11]
    surf_positions = np.array([positions[i] for i in surf_indices])
    surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1)
    surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T

    # Weight probability of choosing a vector by its length from the center
    surf_probabilities = surf_magnitudes / sum(surf_magnitudes)

    # Choose a random point inside the particle
    i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities)
    center = com + surf_vectors[i] * surf_magnitudes[i] * random.random() * max_radius

    atoms = individual.copy()
    atoms.translate(-center)
    v = random_three_vector()
    a = random.uniform(30, 180) * np.pi / 180
    atoms.rotate(v=v, a=a, center=(0, 0, 0))
    new_pos_indices = []
    top_atoms = Atoms()
    for i, atom in enumerate(atoms):
        if atom.z > 0:
            new_pos_indices.append(i)
            top_atoms.extend(atom)

    xs, ys, zs = top_atoms.get_positions().T
    x = (max(xs) + min(xs)) * 0.5
    y = (max(ys) + min(ys)) * 0.5
    
    top_atoms.rotate('z', random.uniform(0, np.pi), center=(x, y, 0))
    top_atoms.rotate(v=v, a=-a, center=(0, 0, 0))
    top_atoms.translate(center)
    rotated_positions = top_atoms.get_positions()
    positions = individual.get_positions()
    for i, index in enumerate(new_pos_indices):
        positions[index] = rotated_positions[i]
                     
    individual.set_positions(positions)

    return None
Ejemplo n.º 12
0
def twist(individual, max_radius=0.90):
    """Twists a random section of the particle.
    
    Parameters
    ----------
    individual : structopt.Individual object
        Individual to be mutated
    max_natoms : float
        That maximum relative distance from the center of the particle 
        the twist is initiated
    """

    if not len(individual):
        return None

    NNs = NeighborList(individual)
    CNs = [len(NN) for NN in NNs]

    # Get the surface atoms. These provide bounds for the moves
    # First get unit vectors and mags of all surface atoms
    positions = individual.get_positions()
    com = np.sum(positions.T, axis=1) / len(individual)
    surf_indices = [i for i, CN in enumerate(CNs) if CN < 11]
    surf_positions = np.array([positions[i] for i in surf_indices])
    surf_magnitudes = np.linalg.norm(surf_positions - com, axis=1)
    surf_vectors = (surf_positions - com) / np.array([surf_magnitudes]).T

    # Weight probability of choosing a vector by its length from the center
    surf_probabilities = surf_magnitudes / sum(surf_magnitudes)

    # Choose a random point inside the particle
    i = np.random.choice(list(range(len(surf_vectors))), p=surf_probabilities)
    center = com + surf_vectors[i] * surf_magnitudes[i] * random.random(
    ) * max_radius

    atoms = individual.copy()
    atoms.translate(-center)
    v = random_three_vector()
    a = random.uniform(30, 180) * np.pi / 180
    atoms.rotate(v=v, a=a, center=(0, 0, 0))
    new_pos_indices = []
    top_atoms = Atoms()
    for i, atom in enumerate(atoms):
        if atom.z > 0:
            new_pos_indices.append(i)
            top_atoms.extend(atom)

    xs, ys, zs = top_atoms.get_positions().T
    x = (max(xs) + min(xs)) * 0.5
    y = (max(ys) + min(ys)) * 0.5

    top_atoms.rotate('z', random.uniform(0, np.pi), center=(x, y, 0))
    top_atoms.rotate(v=v, a=-a, center=(0, 0, 0))
    top_atoms.translate(center)
    rotated_positions = top_atoms.get_positions()
    positions = individual.get_positions()
    for i, index in enumerate(new_pos_indices):
        positions[index] = rotated_positions[i]

    individual.set_positions(positions)

    return None
Ejemplo n.º 13
0
def rotate(individual1,
           individual2,
           center_at_atom=True,
           repair_composition=True):
    """Rotates the two individuals around their centers of mass,
    splits them in half at the xy-plane, then splices them together.
    Maintains number of atoms. Note, both individuals are rotated
    in the same way.

    Parameters
    ----------
    individual1 : Individual
        The first parent
    individual2 : Individual 
        The second parent
    center_at_atom : bool
        This centers the cut at an atom. This is particularly useful 
        when one desires a crystalline solution. If both parents
        are crystalline, the children will likely not have grain boundaries.
    repair_composition : bool 
        If True, conserves composition. For crossovers that create children
        with more (less) atoms, atoms are taken from (added to) the surface
        of the particle. For incorrect atomic ratios, atomic symbols are
        randomly interchanged throughout the particle

    Returns
    -------
    Individual: The first child
    Individual: The second child
    """

    # Translate individuals so COP is at (0, 0, 0)
    ind1c = individual1.copy()
    ind2c = individual2.copy()
    cop1 = ind1c.get_positions().mean(axis=0)
    cop2 = ind2c.get_positions().mean(axis=0)

    if center_at_atom:
        offset = get_offset(ind1c, ind2c, r=1.0, HWHM=0.4)
        ind1c.translate(offset)
        cop = ind1c.get_positions().mean(axis=0)

        ind1c.translate(-cop)
        ind2c.translate(-cop)
    else:
        ind1c.translate(ind1c.get_positions().mean(axis=0))
        ind2c.translate(ind2c.get_positions().mean(axis=0))

    # Pick a random rotation angle and vector
    rot_vec = random_three_vector()
    rot_angle = random.uniform(0, 180) * np.pi / 180.0

    # Rotate both individuals
    ind1c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))
    ind2c.rotate(rot_vec, a=rot_angle, center=(0, 0, 0))

    # Create the children
    child1 = Atoms(cell=ind1c.get_cell())
    child2 = Atoms(cell=ind2c.get_cell())
    child1.extend(ind1c[ind1c.positions[:, 2] >= 0])
    child1.extend(ind2c[ind2c.positions[:, 2] < 0])
    child2.extend(ind2c[ind2c.positions[:, 2] >= 0])
    child2.extend(ind1c[ind1c.positions[:, 2] < 0])

    # Repair the children
    if repair_composition:
        syms = ind1c.get_chemical_symbols()
        atomlist = [[sym, syms.count(sym)] for sym in set(syms)]
        repair_cluster(child1, atomlist)
        repair_cluster(child2, atomlist)

    # Reorient the children
    child1.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child1.center()
    child2.rotate(rot_vec, a=-rot_angle, center=(0, 0, 0))
    child2.center()

    # Convert the children to an Individual with the parent
    # module parameters
    full_child1 = ind1c.copy(include_atoms=False)
    full_child1.extend(child1)
    full_child2 = ind2c.copy(include_atoms=False)
    full_child2.extend(child2)

    return full_child1, full_child2