1from io import StringIO
2
3import numpy as np
4import pytest
5
6from ase.io import read, write
7from ase.build import bulk
8from ase.calculators.calculator import compare_atoms
9from ase.io.abinit import read_abinit_out, read_eig, match_kpt_header
10from ase.units import Hartree, Bohr
11
12
13def test_abinit_inputfile_roundtrip(testdir):
14    m1 = bulk('Ti')
15    m1.set_initial_magnetic_moments(range(len(m1)))
16    write('abinit_save.in', images=m1, format='abinit-in')
17    m2 = read('abinit_save.in', format='abinit-in')
18
19    # (How many decimals?)
20    assert not compare_atoms(m1, m2, tol=1e-7)
21
22
23# "Hand-written" (reduced) abinit txt file based on v8.0.8 format:
24sample_outfile = """\
25
26.Version 8.0.8 of ABINIT
27
28 -outvars: echo values of preprocessed input variables --------
29            natom           2
30           ntypat           1
31            rprim      5.0  0.0  0.1
32                       0.0  6.0  0.0
33                       0.0  0.0  7.0
34            typat      1  1
35            znucl        8.0
36
37================================
38
39 ----iterations are completed or convergence reached----
40
41 cartesian coordinates (angstrom) at end:
42    1      2.5     2.5     3.7
43    2      2.5     2.5     2.5
44
45 cartesian forces (eV/Angstrom) at end:
46    1     -0.1    -0.3    0.4
47    2     -0.2    -0.4   -0.5
48
49 Components of total free energy (in Hartree) :
50
51    >>>>>>>>> Etotal= -42.5
52
53 Cartesian components of stress tensor (hartree/bohr^3)
54  sigma(1 1)=  2.3  sigma(3 2)=  3.1
55  sigma(2 2)=  2.4  sigma(3 1)=  3.2
56  sigma(3 3)=  2.5  sigma(2 1)=  3.3
57
58END DATASET(S)
59"""
60
61
62def test_read_abinit_output():
63    fd = StringIO(sample_outfile)
64    results = read_abinit_out(fd)
65
66    assert results.pop('version') == '8.0.8'
67
68    atoms = results.pop('atoms')
69    assert all(atoms.symbols == 'OO')
70    assert atoms.positions == pytest.approx(
71        np.array([[2.5, 2.5, 3.7], [2.5, 2.5, 2.5]]))
72    assert all(atoms.pbc)
73    assert atoms.cell[:] == pytest.approx(
74        np.array([[5.0, 0.0, 0.1], [0.0, 6.0, 0.0], [0.0, 0.0, 7.0]]))
75
76    ref_stress = pytest.approx([2.3, 2.4, 2.5, 3.1, 3.2, 3.3])
77    assert results.pop('stress') / (Hartree / Bohr**3) == ref_stress
78    assert results.pop('forces') == pytest.approx(
79        np.array([[-0.1, -0.3, 0.4], [-0.2, -0.4, -0.5]]))
80
81    for name in 'energy', 'free_energy':
82        assert results.pop(name) / Hartree == -42.5
83
84    assert not results
85
86
87eig_text = """\
88 Fermi (or HOMO) energy (hartree) =   0.123   Average Vxc (hartree)=  -0.456
89 Eigenvalues (hartree) for nkpt=  2  k points:
90 kpt#   1, nband=  3, wtk=  0.1, kpt=  0.2  0.3  0.4 (reduced coord)
91  -0.2 0.2 0.3
92 kpt#   2, nband=  3, wtk=  0.2, kpt=  0.3  0.4  0.5 (reduced coord)
93  -0.3 0.4 0.5
94"""
95
96
97def test_parse_eig_with_fermiheader():
98    eigval_ref = np.array([
99        [-0.2, 0.2, 0.3],
100        [-0.3, 0.4, 0.5]
101    ]).reshape(1, 2, 3)  # spin x kpts x bands
102
103    kpts_ref = np.array([
104        [0.2, 0.3, 0.4],
105        [0.3, 0.4, 0.5]
106    ])
107
108    weights_ref = [0.1, 0.2]
109
110    eig_buf = StringIO(eig_text)
111    data = read_eig(eig_buf)
112
113    assert data['eigenvalues'] / Hartree == pytest.approx(eigval_ref)
114    assert data['ibz_kpoints'] == pytest.approx(kpts_ref)
115    assert data['kpoint_weights'] == pytest.approx(weights_ref)
116    assert data['fermilevel'] / Hartree == pytest.approx(0.123)
117
118
119def test_parse_eig_without_fermiheader():
120    fd = StringIO(eig_text)
121    next(fd)  # Header is omitted e.g. in non-selfconsistent calculations.
122
123    data = read_eig(fd)
124    assert 'fermilevel' not in data
125    assert {'eigenvalues', 'ibz_kpoints', 'kpoint_weights'} == set(data)
126
127
128def test_match_kpt_header():
129    header_line = """\
130kpt#  12, nband=  5, wtk=  0.02778, \
131kpt=  0.4167  0.4167  0.0833 (reduced coord)
132"""
133
134    nbands, weight, vector = match_kpt_header(header_line)
135    assert nbands == 5
136    assert weight == pytest.approx(0.02778)
137    assert vector == pytest.approx([0.4167, 0.4167, 0.0833])
138