Beispiel #1
0
    def test_index_shape2(self, setup):
        g = setup.g.copy()
        n = 0
        for r in [0.5, 1., 1.5]:
            s = Cuboid(r)
            idx = g.index(s)
            assert len(idx) > n
            n = len(idx)

        # Check that if we place the sphere an integer
        # amount of cells away we retain an equal amount of indices
        # Also we can check whether they are the same if we add the
        # offset
        v = g.dcell.sum(0)
        vd = v * 0.001
        s = Cuboid(1.)
        idx0 = g.index(s)
        idx0.sort(0)
        for d in [10, 15, 20, 60, 100, 340]:
            idx = g.index(v * d + vd)
            s = Cuboid(1., center=v * d + vd)
            idx1 = g.index(s)
            idx1.sort(0)
            assert len(idx1) == len(idx0)
            assert np.all(idx0 == idx1 - idx.reshape(1, 3))
Beispiel #2
0
def test_sparse_orbital_replace_hole():
    """ Create a big graphene flake remove a hole (1 orbital system) """
    g = graphene(orthogonal=True)
    spo = SparseOrbital(g)
    # create the sparse-orbital
    spo.construct([(0.1, 1.45), (0, 2.7)])

    # create 10x10 geoemetry
    nx, ny = 10, 10
    big = spo.tile(10, 0).tile(10, 1)
    hole = spo.tile(6, 0).tile(6, 1)
    hole = hole.remove(hole.close(hole.center(), R=3))

    def create_sp(geom):
        spo = SparseOrbital(geom)
        # create the sparse-orbital
        spo.construct([(0.1, 1.45), (0, 2.7)])
        return spo

    # now replace every position that can be replaced
    for y in [0, 2, 3]:
        for x in [1, 2, 3]:
            cube = Cuboid(hole.sc.cell, origin=g.sc.offset([x, y, 0]) - 0.1)
            atoms = big.within(cube)
            assert len(atoms) == 4 * 6 * 6
            new = big.replace(atoms, hole)
            new_copy = create_sp(new.geometry)
            assert np.fabs((new - new_copy)._csr._D).sum() == 0.
Beispiel #3
0
def test_sparse_orbital_replace_hole_norbs():
    """ Create a big graphene flake remove a hole (multiple orbitals) """
    a1 = Atom(5, R=(1.44, 1.44))
    a2 = Atom(7, R=(1.44, 1.44, 1.44))
    g = graphene(atoms=[a1, a2], orthogonal=True)
    spo = SparseOrbital(g)

    def func(self, ia, atoms, atoms_xyz=None):
        geom = self.geometry

        def a2o(idx):
            return geom.a2o(idx, True)

        io = a2o(ia)
        idx = self.geometry.close(ia,
                                  R=[0.1, 1.44],
                                  atoms=atoms,
                                  atoms_xyz=atoms_xyz)
        idx = list(map(a2o, idx))
        self[io, idx[0]] = 0
        for i in io:
            self[i, idx[1]] = 2.7

    # create the sparse-orbital
    spo.construct(func)

    # create 10x10 geoemetry
    nx, ny = 10, 10
    big = spo.tile(10, 0).tile(10, 1)
    hole = spo.tile(6, 0).tile(6, 1)
    hole = hole.remove(hole.close(hole.center(), R=3))

    def create_sp(geom):
        spo = SparseOrbital(geom)
        # create the sparse-orbital
        spo.construct(func)
        return spo

    # now replace every position that can be replaced
    for y in [0, 3]:
        for x in [1, 3]:
            cube = Cuboid(hole.sc.cell, origin=g.sc.offset([x, y, 0]) - 0.1)
            atoms = big.within(cube)
            assert len(atoms) == 4 * 6 * 6
            new = big.replace(atoms, hole)
            new_copy = create_sp(new.geometry)
            assert np.fabs((new - new_copy)._csr._D).sum() == 0.
Beispiel #4
0
def test_geom_category_shape():
    hBN_gr = bilayer(1.42, Atom[5, 7], Atom[6]) * (4, 5, 1)
    mid_layer = np.average(hBN_gr.xyz[:, 2])

    A_site = AtomFracSite(graphene())
    bottom = AtomXYZ(Cuboid([10000, 10000, mid_layer], [-100, -100, 0]))
    top = ~bottom

    bottom_A = A_site & bottom
    top_A = A_site & top

    cat = (bottom_A | top_A).categorize(hBN_gr)
    cnull = 0
    for i, c in enumerate(cat):
        if c == NullCategory():
            cnull += 1
        elif hBN_gr.xyz[i, 2] < mid_layer:
            assert c == bottom_A
        else:
            assert False
    assert cnull == len(hBN_gr) // 4 * 3
Beispiel #5
0
def bilayer(bond=1.42,
            bottom_atom=None,
            top_atom=None,
            stacking='AB',
            twist=(0, 0),
            separation=3.35,
            ret_angle=False,
            layer='both'):
    r""" Commensurate unit cell of a hexagonal bilayer structure, possibly with a twist angle.

    This routine follows the prescription of twisted bilayer graphene found in [1]_.

    Notes
    -----
    This routine may change in the future to force some of the arguments.

    Parameters
    ----------
    bond : float, optional
       bond length between atoms in the honeycomb lattice
    bottom_atom : Atom, optional
       atom (or atoms) in the bottom layer. Defaults to ``Atom(6)``
    top_atom : Atom, optional
       atom (or atoms) in the top layer, defaults to `bottom_atom`
    stacking : {'AB', 'AA', 'BA'}
       stacking sequence of the bilayer, where XY means that site X in bottom layer coincides with site Y in top layer
    twist : tuple of int, optional
       integer coordinates (m, n) defining a commensurate twist angle
    separation : float, optional
       distance between the two layers
    ret_angle : bool, optional
       return the twist angle (in degrees) in addition to the geometry instance
    layer : {'both', 'bottom', 'top'}
       control which layer(s) to return

    See Also
    --------
    honeycomb: honeycomb lattices
    graphene: graphene geometry

    References
    ----------
    .. [1] G. Trambly de Laissardiere, D. Mayou, L. Magaud, "Localization of Dirac Electrons in Rotated Graphene Bilayers", Nano Letts. 10, 804-808 (2010)
    """
    if bottom_atom is None:
        bottom_atom = top_atom
    if bottom_atom is None:
        bottom_atom = Atom(Z=6, R=bond * 1.01)
    if top_atom is None:
        top_atom = bottom_atom

    # Construct two layers
    bottom = geom.honeycomb(bond, bottom_atom)
    top = geom.honeycomb(bond, top_atom)
    ref_cell = bottom.cell.copy()

    stacking = stacking.lower()
    if stacking == 'aa':
        top = top.move([0, 0, separation])
    elif stacking == 'ab':
        top = top.move([-bond, 0, separation])
    elif stacking == 'ba':
        top = top.move([bond, 0, separation])
    else:
        raise ValueError("bilayer: stacking must be one of {AA, AB, BA}")

    # Compute twist angle
    m, n = twist
    m, n = abs(m), abs(n)
    if m > n:
        # Set m as the smaller integer
        m, n = n, m

    if not (isinstance(n, int) and isinstance(m, int)):
        raise ValueError("bilayer: twist coordinates need to be integers!")

    if m == n:
        # No twisting
        theta = 0
        rep = 1
        natoms = 2
    else:
        # Twisting
        cos_theta = (n**2 + 4 * n * m + m**2) / (2 * (n**2 + n * m + m**2))
        theta = acos(cos_theta) * 180 / pi
        rep = 4 * (n + m)
        natoms = 2 * (n**2 + n * m + m**2)

    if rep > 1:
        # Set origo through an A atom near the middle of the geometry
        align_vec = -rep * (ref_cell[0] + ref_cell[1]) / 2

        bottom = (bottom.tile(rep, axis=0).tile(rep, axis=1).move(align_vec))

        # Set new lattice vectors
        bottom.cell[0] = n * ref_cell[0] + m * ref_cell[1]
        bottom.cell[1] = -m * ref_cell[0] + (n + m) * ref_cell[1]

        # Remove atoms outside cell
        cell_box = Cuboid(bottom.cell, center=[-bond * 1e-4] * 3)

        # Reduce atoms in bottom
        inside_idx = cell_box.within_index(bottom.xyz)
        bottom = bottom.sub(inside_idx)

        # Rotate top layer around A atom in bottom layer
        top = (top.tile(rep, axis=0).tile(rep, axis=1).move(align_vec).rotate(
            theta, [0, 0, 1]))

        inside_idx = cell_box.within_index(top.xyz)
        top = top.sub(inside_idx)

        # Ensure the cells are commensurate
        top.cell[:] = bottom.cell[:]

    # Which layers to be returned
    layer = layer.lower()
    if layer == 'bottom':
        bilayer = bottom
    elif layer == 'top':
        bilayer = top
    elif layer == 'both':
        bilayer = bottom.add(top)
        natoms *= 2
    else:
        raise ValueError("bilayer: layer must be one of {both, bottom, top}")

    if rep > 1:
        # Rotate and shift unit cell back
        fxyz_min = bilayer.fxyz.min(axis=0)
        fxyz_min[2] = 0.
        # This is a small hack since rotate is not numerically
        # precise.
        # TODO We could consider using mpfmath in Quaternion for increased
        # precision...
        fxyz_min[np.fabs(fxyz_min) > 1.e-7] *= 0.49
        offset = fxyz_min.dot(bilayer.cell)
        vec = bilayer.cell[0] + bilayer.cell[1]
        vec_costh = vec[0] / vec.dot(vec)**0.5
        vec_th = acos(vec_costh) * 180 / pi
        bilayer = bilayer.move(-offset).rotate(vec_th, [0, 0, 1])

    # Sanity check
    assert len(bilayer) == natoms

    if ret_angle:
        return bilayer, theta
    else:
        return bilayer