Beispiel #1
0
def cssm(metal, data_dict):  # cleave_stable_surfaces_from_metals
    name = 'POSCAR_' + metal
    if metal in bcc:  # For bcc metals, cleave 110 surface
        lattice_a = float(data_dict.get(metal)[2])
        for i in range(1, 4):
            name_out = name + '_' + str(i)
            slab = bcc110(metal, a=lattice_a, size=(i, i, 4), vacuum=7.5)
            '''(i,i,4) means repeat i i 4 in x y and z directions. vacuum will be 7.5 * 2 because it was added on the two sides.'''
            constraint_l = FixAtoms(indices=[
                atom.index for atom in slab if atom.index < i * i * 2
            ])
            slab.set_constraint(constraint_l)
            ase.io.write(name_out, slab, format='vasp')
            ### Add the element line to the POSCAR file ###
            subprocess.call(['sed -i ' + '\'5a' + metal + '\'  ' + name_out],
                            shell=True)
            bottom(name_out)
    elif metal in hcp:  # For hcp metals, cleave 0001 surface
        lattice_a, lattice_c = [float(i) for i in data_dict.get(metal)[2:]]
        for i in range(1, 4):
            name_out = name + '_' + str(i)
            slab = hcp0001(metal,
                           a=lattice_a,
                           c=lattice_c,
                           size=(i, i, 4),
                           vacuum=7.5)
            constraint_l = FixAtoms(indices=[
                atom.index for atom in slab if atom.index < i * i * 2
            ])
            slab.set_constraint(constraint_l)
            ase.io.write(name_out, slab, format='vasp')
            subprocess.call(['sed -i ' + '\'5a' + metal + '\'  ' + name_out],
                            shell=True)
            bottom(name_out)

    elif metal in fcc:  # For fcc metals, cleave 111 surface
        lattice_a = float(data_dict.get(metal)[2])
        for i in range(1, 4):
            name_out = name + '_' + str(i)
            slab = fcc111(metal, a=lattice_a, size=(i, i, 4), vacuum=7.5)
            #            slab.center(vacuum=7.5, axis = 2)
            constraint_l = FixAtoms(indices=[
                atom.index for atom in slab if atom.index < i * i * 2
            ])
            slab.set_constraint(constraint_l)
            ase.io.write(name_out, slab, format='vasp')
            subprocess.call(['sed -i ' + '\'5a' + metal + '\'  ' + name_out],
                            shell=True)
            bottom(name_out)
    else:
        print(
            'Please add your element in the crystal structure lists: bcc, hcp, and fcc'
        )
Beispiel #2
0
def hcp0001_root(symbol, root, size, a=None, c=None,
                 vacuum=None, orthogonal=False):
    """HCP(0001) surface maniupulated to have a x unit side length
    of *root* before repeating.  This also results in *root* number
    of repetitions of the cell.


    The first 20 valid roots for nonorthogonal are...
    1, 3, 4, 7, 9, 12, 13, 16, 19, 21, 25,
    27, 28, 31, 36, 37, 39, 43, 48, 49"""
    atoms = hcp0001(symbol=symbol, size=(1, 1, size[2]),
                    a=a, c=c, vacuum=vacuum, orthogonal=orthogonal)
    atoms = root_surface(atoms, root)
    atoms *= (size[0], size[1], 1)
    return atoms
Beispiel #3
0
def test_root_surf():
    from ase.build import fcc111
    from ase.build import bcc111
    from ase.build import hcp0001
    from ase.build import fcc111_root
    from ase.build import root_surface
    from ase.build import root_surface_analysis

    # Make samples of primitive cell
    prim_fcc111 = fcc111("H", (1, 1, 2), a=1)
    prim_bcc111 = bcc111("H", (1, 1, 2), a=1)
    prim_hcp0001 = hcp0001("H", (1, 1, 2), a=1)

    # Check valid roots up to root 21 (the 10th root cell)
    valid_fcc111 = root_surface_analysis(prim_fcc111, 21)
    valid_bcc111 = root_surface_analysis(prim_bcc111, 21)
    valid_hcp0001 = root_surface_analysis(prim_hcp0001, 21)

    # These should have different positions, but the same
    # cell geometry.
    assert valid_fcc111 == valid_bcc111 == valid_hcp0001

    # Make an easy sample to check code errors
    atoms1 = root_surface(prim_fcc111, 7)

    # Ensure the valid roots are the roots are valid against
    # a set of manually checked roots for this system
    assert valid_fcc111 == [1.0, 3.0, 4.0, 7.0, 9.0,
                            12.0, 13.0, 16.0, 19.0, 21.0]

    # Remake easy sample using surface function
    atoms2 = fcc111_root("H", 7, (1, 1, 2), a=1)

    # Right number of atoms
    assert len(atoms1) == len(atoms2) == 14

    # Same positions
    assert (atoms1.positions == atoms2.positions).all()

    # Same cell
    assert (atoms1.cell == atoms2.cell).all()
Beispiel #4
0
def hcp0001_root(symbol,
                 root,
                 size,
                 a=None,
                 c=None,
                 vacuum=None,
                 orthogonal=False):
    """HCP(0001) surface maniupulated to have a x unit side length
    of *root* before repeating.This also results in *root* number
    of repetitions of the cell.


    The first 20 valid roots for nonorthogonal are...
    1, 3, 4, 7, 9, 12, 13, 16, 19, 21, 25,
    27, 28, 31, 36, 37, 39, 43, 48, 49"""
    atoms = hcp0001(symbol=symbol,
                    size=(1, 1, size[2]),
                    a=a,
                    c=c,
                    vacuum=vacuum,
                    orthogonal=orthogonal)
    atoms = root_surface(atoms, root)
    atoms *= (size[0], size[1], 1)
    return atoms
Beispiel #5
0
# Vacuum and hcp lattice parameter for graphene
d = 4.0
a = 2.4437

calc = GPAW(h=0.15,
            xc='LDA',
            nbands=-4,
            txt='-',
            basis='dzp',
            convergence={
                'energy': 1e-5,
                'density': 1e-5
            })

# Calculate potential energy per atom for orthogonal unitcell
atoms = hcp0001('C', a=a / sqrt(3), vacuum=d, size=(3, 2, 1), orthogonal=True)
del atoms[[1, -1]]
atoms.center(axis=0)
atoms.set_calculator(calc)

kpts_c = np.ceil(50 / np.sum(atoms.get_cell()**2, axis=1)**0.5).astype(int)
kpts_c[~atoms.get_pbc()] = 1
calc.set(kpts=kpts_c)
eppa1 = atoms.get_potential_energy() / len(atoms)
F1_av = atoms.get_forces()
equal(np.abs(F1_av).max(), 0, 5e-3)

# Redo calculation with non-orthogonal unitcell
atoms = Atoms(symbols='C2',
              pbc=(True, True, False),
              positions=[(a / 2, -sqrt(3) / 6 * a, d),
Beispiel #6
0
from ase.io import Trajectory
from ase.optimize.mdmin import MDMin
try:
    from asap3 import EMT
except ImportError:
    pass
else:
    a = 3.6
    b = a / 2
    cu = Atoms('Cu', cell=[(0, b, b), (b, 0, b), (b, b, 0)], pbc=1) * (6, 6, 6)
    cu.set_calculator(EMT())
    f = UnitCellFilter(cu, [1, 1, 1, 0, 0, 0])
    opt = LBFGS(f)
    t = Trajectory('Cu-fcc.traj', 'w', cu)
    opt.attach(t)
    opt.run(5.0)

    # HCP:
    from ase.build import hcp0001
    cu = hcp0001('Cu', (1, 1, 2), a=a / sqrt(2))
    cu.cell[1, 0] += 0.05
    cu *= (6, 6, 3)
    cu.set_calculator(EMT())
    print(cu.get_forces())
    print(cu.get_stress())
    f = UnitCellFilter(cu)
    opt = MDMin(f, dt=0.01)
    t = Trajectory('Cu-hcp.traj', 'w', cu)
    opt.attach(t)
    opt.run(0.2)
Beispiel #7
0
 def opt_hcp0001(self, c: int = None) -> None:
     ''' Optimize hcp0001 slab '''
     slab = hcp0001(self.symbol, self.repeats_surface, self.a, c,
                    self.vacuum)
     self.prepare_slab_opt(slab)
try:
    from asap3 import EMT
except ImportError:
    pass
else:
    a = 3.6
    b = a / 2
    cu = Atoms('Cu',
               cell=[(0, b, b), (b, 0, b), (b, b, 0)],
               pbc=1) * (6, 6, 6)
    cu.set_calculator(EMT())
    f = UnitCellFilter(cu, [1, 1, 1, 0, 0, 0])
    opt = LBFGS(f)
    t = Trajectory('Cu-fcc.traj', 'w', cu)
    opt.attach(t)
    opt.run(5.0)

    # HCP:
    from ase.build import hcp0001
    cu = hcp0001('Cu', (1, 1, 2), a=a / sqrt(2))
    cu.cell[1, 0] += 0.05
    cu *= (6, 6, 3)
    cu.set_calculator(EMT())
    print(cu.get_forces())
    print(cu.get_stress())
    f = UnitCellFilter(cu)
    opt = MDMin(f, dt=0.01)
    t = Trajectory('Cu-hcp.traj', 'w', cu)
    opt.attach(t)
    opt.run(0.2)
Beispiel #9
0
from ase.build import fcc111
from ase.build import bcc111
from ase.build import hcp0001
from ase.build import fcc111_root
from ase.build import root_surface
from ase.build import root_surface_analysis

# Make samples of primitive cell
prim_fcc111 = fcc111("H", (1, 1, 2), a=1)
prim_bcc111 = bcc111("H", (1, 1, 2), a=1)
prim_hcp0001 = hcp0001("H", (1, 1, 2), a=1)

# Check valid roots up to root 21 (the 10th root cell)
valid_fcc111 = root_surface_analysis(prim_fcc111, 21)
valid_bcc111 = root_surface_analysis(prim_bcc111, 21)
valid_hcp0001 = root_surface_analysis(prim_hcp0001, 21)

# These should have different positions, but the same
# cell geometry.
assert valid_fcc111 == valid_bcc111 == valid_hcp0001

# Make an easy sample to check code errors
atoms1 = root_surface(prim_fcc111, 7)

# Ensure the valid roots are the roots are valid against
# a set of manually checked roots for this system
assert valid_fcc111 == [1.0, 3.0, 4.0, 7.0, 9.0,
                        12.0, 13.0, 16.0, 19.0, 21.0]

# Remake easy sample using surface function
atoms2 = fcc111_root("H", 7, (1, 1, 2), a=1)
Beispiel #10
0
from __future__ import print_function
import numpy as np
from ase.dft.kpoints import monkhorst_pack
from ase.parallel import paropen
from ase.build import hcp0001, add_adsorbate
from gpaw import GPAW, PW, FermiDirac, MixerSum
from gpaw.xc.exx import EXX

kpts = monkhorst_pack((16, 16, 1))
kpts += np.array([1 / 32., 1 / 32., 0])

a = 2.51  # Lattice parameter of Co
slab = hcp0001('Co', a=a, c=4.07, size=(1, 1, 4))
pos = slab.get_positions()
cell = slab.get_cell()
cell[2, 2] = 20. + pos[-1, 2]
slab.set_cell(cell)
slab.set_initial_magnetic_moments([0.7, 0.7, 0.7, 0.7])

ds = [1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0, 5.0, 6.0, 10.0]

for d in ds:
    pos = slab.get_positions()
    add_adsorbate(slab, 'C', d, position=(pos[3, 0], pos[3, 1]))
    add_adsorbate(slab,
                  'C',
                  d,
                  position=(cell[0, 0] / 3 + cell[1, 0] / 3,
                            cell[0, 1] / 3 + cell[1, 1] / 3))
    #view(slab)
    calc = GPAW(xc='PBE',
Beispiel #11
0
def create_bimetal_surf(composition,
                        surf_type,
                        size,
                        latt='',
                        fix_layers=2,
                        vacuum=12.0):
    """
    Create bimetallic surfaces 
       composition:  "M1" - single-metal 
                     "M1,M2,config" - bimetallic surface with the configuration 
       latt: 

    """

    # extract lattice constants if available
    if latt == '':
        if composition == 'Co' and surf_type == 'fcc111':
            a_latt = 3.544
        else:
            a_latt, c_latt = (None, None)
    else:
        tmp = latt.split(',')
        a_latt = float(tmp[0])
        if len(tmp) > 1:
            c_latt = float(tmp[1])

    # set the surface type name
    if surf_type == 'fcc111':
        surf_type_name = '111'
    elif surf_type == 'hcp0001':
        surf_type_name = '0001'

    # set the metallic surface slab
    tmp = composition.split(',')
    M1_name = tmp[0]
    if len(tmp) > 1:
        M2_name = tmp[1]
        config = tmp[2]
    else:
        M2_name = ''
        config = ''

    if not "bulk" in config:
        if surf_type == 'fcc111':
            surf = fcc111(M1_name, a=a_latt, size=size, vacuum=vacuum)
        elif surf_type == 'hcp0001':
            surf = hcp0001(M1_name,
                           a=a_latt,
                           c=c_latt,
                           size=size,
                           vacuum=vacuum)

        surf_name = M1_name + surf_type_name + "-%d%d%d" % (size[0], size[1],
                                                            size[2])
        if M2_name != '':
            surf_name = surf_name + '-' + config_bimetal + "-" + M2_name

            if config_bimetal == 'top':
                for atom in surf:
                    if atom.tag == 1:
                        atom.symbol = M2_name
            elif config_bimetal == 'sub':
                for atom in surf:
                    if atom.tag == 2:
                        atom.symbol = M2_name
            elif config_bimetal == 'mixtop':
                for atom in surf:
                    if atom.tag == 1:
                        atom.symbol = M2_name
                        break
            elif config_bimetal == 'mixsub':
                for atom in surf:
                    if atom.tag == 2:
                        atom.symbol = M2_name
                        break
    else:
        print "ERROR: Not implemented yet for the bimetal configuration %s!!!" % (
            config)

    # get the number of layers
    nlayers = max(surf.get_tags())

    mask = [atom.tag > nlayers - fix_layers for atom in surf]
    constr_bottom = FixAtoms(mask=mask)
    surf.set_constraint([constr_bottom])

    return surf, surf_name
Beispiel #12
0
def build():
    p = OptionParser(usage='%prog  [options] [ads@]surf [output file]',
                     version='%prog 0.1',
                     description='Example ads/surf: fcc-CO@2x2Ru0001')
    p.add_option('-l', '--layers', type='int',
                 default=4,
                 help='Number of layers.')
    p.add_option('-v', '--vacuum', type='float',
                 default=5.0,
                 help='Vacuum.')
    p.add_option('-x', '--crystal-structure',
                 help='Crystal structure.',
                 choices=['sc', 'fcc', 'bcc', 'hcp'])
    p.add_option('-a', '--lattice-constant', type='float',
                 help='Lattice constant in Angstrom.')
    p.add_option('--c-over-a', type='float',
                 help='c/a ratio.')
    p.add_option('--height', type='float',
                 help='Height of adsorbate over surface.')
    p.add_option('--distance', type='float',
                 help='Distance between adsorbate and nearest surface atoms.')
    p.add_option('-M', '--magnetic-moment', type='float', default=0.0,
                 help='Magnetic moment.')
    p.add_option('-G', '--gui', action='store_true',
                 help="Pop up ASE's GUI.")
    p.add_option('-P', '--python', action='store_true',
                 help="Write Python script.")

    opt, args = p.parse_args()

    if not 1 <= len(args) <= 2:
        p.error("incorrect number of arguments")

    if '@' in args[0]:
        ads, surf = args[0].split('@')
    else:
        ads = None
        surf = args[0]

    if surf[0].isdigit():
        i1 = surf.index('x')
        n = int(surf[:i1])
        i2 = i1 + 1
        while surf[i2].isdigit():
            i2 += 1
        m = int(surf[i1 + 1:i2])
        surf = surf[i2:]
    else:
        n = 1
        m = 1

    if surf[-1].isdigit():
        if surf[1].isdigit():
            face = surf[1:]
            surf = surf[0]
        else:
            face = surf[2:]
            surf = surf[:2]
    else:
        face = None

    Z = atomic_numbers[surf]
    state = reference_states[Z]

    if opt.crystal_structure:
        x = opt.crystal_structure
    else:
        x = state['symmetry']
    
    if opt.lattice_constant:
        a = opt.lattice_constant
    else:
        a = estimate_lattice_constant(surf, x, opt.c_over_a)

    script = ['from ase.build import ',
              'vac = %r' % opt.vacuum,
              'a = %r' % a]
    
    if x == 'fcc':
        if face is None:
            face = '111'
        slab = fcc111(surf, (n, m, opt.layers), a, opt.vacuum)
        script[0] += 'fcc111'
        script += ['slab = fcc111(%r, (%d, %d, %d), a, vac)' %
                   (surf, n, m, opt.layers)]
        r = a / np.sqrt(2) / 2
    elif x == 'bcc':
        if face is None:
            face = '110'
        if face == '110':
            slab = bcc110(surf, (n, m, opt.layers), a, opt.vacuum)
        elif face == '100':
            slab = bcc100(surf, (n, m, opt.layers), a, opt.vacuum)
        script[0] += 'bcc' + face
        script += ['slab = bcc%s(%r, (%d, %d, %d), a, vac)' %
                   (face, surf, n, m, opt.layers)]
        r = a * np.sqrt(3) / 4
    elif x == 'hcp':
        if face is None:
            face = '0001'
        if opt.c_over_a is None:
            c = np.sqrt(8 / 3.0) * a
        else:
            c = opt.c_over_a * a
        slab = hcp0001(surf, (n, m, opt.layers), a, c, opt.vacuum)
        script[0] += 'hcp0001'
        script += ['c = %r * a' % (c / a),
                   'slab = hcp0001(%r, (%d, %d, %d), a, c, vac)' %
                   (surf, n, m, opt.layers)]
        r = a / 2
    elif x == 'diamond':
        if face is None:
            face = '111'
        slab = diamond111(surf, (n, m, opt.layers), a, opt.vacuum)
        script[0] += 'diamond111'
        script += ['slab = diamond111(%r, (%d, %d, %d), a, vac)' %
                   (surf, n, m, opt.layers)]
        r = a * np.sqrt(3) / 8
    else:
        raise NotImplementedError

    magmom = opt.magnetic_moment
    if magmom is None:
        magmom = {'Ni': 0.6, 'Co': 1.2, 'Fe': 2.3}.get(surf, 0.0)
    slab.set_initial_magnetic_moments([magmom] * len(slab))
    if magmom != 0:
        script += ['slab.set_initial_magnetic_moments([%r] * len(slab))' %
                   magmom]
    
    slab.pbc = 1
    script += ['slab.pbc = True']
    
    name = '%dx%d%s%s' % (n, m, surf, face)

    if ads:
        site = 'ontop'
        if '-' in ads:
            site, ads = ads.split('-')

        name = site + '-' + ads + '@' + name
        symbols = string2symbols(ads)
        nads = len(symbols)
        if nads == 1:
            script[:0] = ['from ase import Atoms']
            script += ['ads = Atoms(%r)' % ads]
            ads = Atoms(ads)
        else:
            script[:0] = ['from ase.build import molecule']
            script += ['ads = molecule(%r)' % ads]
            ads = molecule(ads)

        add_adsorbate(slab, ads, 0.0, site)

        d = opt.distance
        if d is None:
            d = r + covalent_radii[ads[0].number] / 2
        
        h = opt.height
        if h is None:
            R = slab.positions
            y = ((R[:-nads] - R[-nads])**2).sum(1).min()**0.5
            h = (d**2 - y**2)**0.5
        else:
            assert opt.distance is None
        
        slab.positions[-nads:, 2] += h

        script[1] += ', add_adsorbate'
        script += ['add_adsorbate(slab, ads, %r, %r)' % (h, site)]
        
    if len(args) == 2:
        write(args[1], slab)
        script[1:1] = ['from ase.io import write']
        script += ['write(%r, slab)' % args[1]]
    elif not opt.gui:
        write(name + '.traj', slab)
        script[1:1] = ['from ase.io import write']
        script += ['write(%r, slab)' % (name + '.traj')]
        
    if opt.gui:
        view(slab)
        script[1:1] = ['from ase.visualize import view']
        script += ['view(slab)']

    if opt.python:
        print('\n'.join(script))
Beispiel #13
0
def adsorb(db, height=1.2, nlayers=3, nkpts=7, ecut=400):
    """Adsorb nitrogen in hcp-site on Ru(0001) surface.

    Do calculations for N/Ru(0001), Ru(0001) and a nitrogen atom
    if they have not already been done.

    db: Database
        Database for collecting results.
    height: float
        Height of N-atom above top Ru-layer.
    nlayers: int
        Number of Ru-layers.
    nkpts: int
        Use a (nkpts * nkpts) Monkhorst-Pack grid that includes the
        Gamma point.
    ecut: float
        Cutoff energy for plane waves.

    Returns height.
    """

    name = f'Ru{nlayers}-{nkpts}x{nkpts}-{ecut:.0f}'

    parameters = dict(mode=PW(ecut),
                      eigensolver=Davidson(niter=2),
                      poissonsolver={'dipolelayer': 'xy'},
                      kpts={'size': (nkpts, nkpts, 1), 'gamma': True},
                      xc='PBE')

    # N/Ru(0001):
    slab = hcp0001('Ru', a=a, c=c, size=(1, 1, nlayers))
    z = slab.positions[:, 2].max() + height
    x, y = np.dot([2 / 3, 2 / 3], slab.cell[:2, :2])
    slab.append('N')
    slab.positions[-1] = [x, y, z]
    slab.center(vacuum=vacuum, axis=2)  # 2: z-axis

    # Fix first nlayer atoms:
    slab.constraints = FixAtoms(indices=list(range(nlayers)))

    id = db.reserve(name=f'N/{nlayers}Ru(0001)', nkpts=nkpts, ecut=ecut)
    if id is not None:  # skip calculation if already done
        slab.calc = GPAW(txt='N' + name + '.txt',
                         **parameters)
        optimizer = BFGSLineSearch(slab, logfile='N' + name + '.opt')
        optimizer.run(fmax=0.01)
        height = slab.positions[-1, 2] - slab.positions[:-1, 2].max()
        del db[id]
        db.write(slab,
                 name=f'N/{nlayers}Ru(0001)', nkpts=nkpts, ecut=ecut,
                 height=height)

    # Clean surface (single point calculation):
    id = db.reserve(name=f'{nlayers}Ru(0001)', nkpts=nkpts, ecut=ecut)
    if id is not None:
        del slab[-1]  # remove nitrogen atom
        slab.calc = GPAW(txt=name + '.txt',
                         **parameters)
        slab.get_forces()
        del db[id]
        db.write(slab,
                 name=f'{nlayers}Ru(0001)', nkpts=nkpts, ecut=ecut)

    # Nitrogen atom:
    id = db.reserve(name='N-atom', ecut=ecut)
    if id is not None:
        # Create spin-polarized nitrogen atom:
        molecule = Atoms('N', magmoms=[3])
        molecule.center(vacuum=4.0)
        # Remove parameters that make no sense for an isolated atom:
        del parameters['kpts']
        del parameters['poissonsolver']
        # Calculate energy:
        molecule.calc = GPAW(txt=name + '.txt', **parameters)
        molecule.get_potential_energy()
        del db[id]
        db.write(molecule, name='N-atom', ecut=ecut)

    return height
Beispiel #14
0
from sys import argv
from ase.build import hcp0001, add_adsorbate
from ase.constraints import FixAtoms
from ase.optimize.lbfgs import LBFGS
from gpaw import GPAW, Mixer, FermiDirac

tag = 'Ru001_Ru8'

adsorbate_heights = {'H': 1.0, 'N': 1.108, 'O': 1.257}

slab = hcp0001('Ru',
               size=(2, 2, 4),
               a=2.72,
               c=1.58 * 2.72,
               vacuum=7.0,
               orthogonal=True)
slab.center(axis=2)

if len(argv) > 1:
    adsorbate = argv[1]
    tag = adsorbate + tag
    add_adsorbate(slab, adsorbate, adsorbate_heights[adsorbate], 'hcp')

slab.set_constraint(FixAtoms(mask=slab.get_tags() >= 3))

calc = GPAW(xc='PBE',
            h=0.2,
            mixer=Mixer(0.1, 5, weight=100.0),
            occupations=FermiDirac(width=0.1),
            kpts=[4, 4, 1],
            setups={'Ru': '8'},
Beispiel #15
0
from ase.build import fcc111
from ase.build import bcc111
from ase.build import hcp0001
from ase.build import fcc111_root
from ase.build import root_surface
from ase.build import root_surface_analysis

# Make samples of primitive cell
prim_fcc111 = fcc111("H", (1, 1, 2), a=1)
prim_bcc111 = bcc111("H", (1, 1, 2), a=1)
prim_hcp0001 = hcp0001("H", (1, 1, 2), a=1)

# Check valid roots up to root 21 (the 10th root cell)
valid_fcc111 = root_surface_analysis(prim_fcc111, 21)
valid_bcc111 = root_surface_analysis(prim_bcc111, 21)
valid_hcp0001 = root_surface_analysis(prim_hcp0001, 21)

# These should have different positions, but the same
# cell geometry.
assert valid_fcc111 == valid_bcc111 == valid_hcp0001

# Make an easy sample to check code errors
atoms1 = root_surface(prim_fcc111, 7)

# Ensure the valid roots are the roots are valid against
# a set of manually checked roots for this system
assert valid_fcc111 == [1.0, 3.0, 4.0, 7.0, 9.0,
                        12.0, 13.0, 16.0, 19.0, 21.0]

# Remake easy sample using surface function
atoms2 = fcc111_root("H", 7, (1, 1, 2), a=1)