# web-page: convergence.db
import numpy as np
from ase import Atoms
from ase.build import hcp0001
from ase.constraints import FixAtoms
from ase.optimize import BFGSLineSearch
from gpaw import GPAW, PW, Davidson

a = 2.72
c = 1.58 * a
vacuum = 4.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


def run():
    h = 1.2
    from ase.db import connect
    db = connect('convergence.db')
    for n in range(1, 10):
        h = adsorb(db, h, n, 7, 400)
    for k in range(4, 18):
        h = adsorb(db, h, 2, k, 400)
    for ecut in range(350, 801, 50):
        h = adsorb(db, h, 2, 7, ecut)


if __name__ == '__main__':
    run()
