1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
# fmt: off
import numpy as np
from ase.units import Bohr, Ry
class OutputReader:
def __init__(self, prefix, directory, bandpath=None):
self.prefix = prefix
self.directory = directory
self.bandpath = bandpath
def read_results(self):
results = {}
results['n_grid_point'] = self.read_number_of_grid_points()
results.update(self.read_energy())
results.update(self.read_forces_stress())
results.update(self.read_eigenvalues())
results.update(self.read_kpoints())
results['dipole'] = self.read_dipole()
if self.bandpath is not None and len(self.bandpath.kpts):
results['bandstructure'] = self.read_bands(self.bandpath)
return results
def _prefixed(self, extension):
return self.directory / f'{self.prefix}.{extension}'
def read_bands(self, bandpath):
fname = self._prefixed('bands')
with open(fname) as fd:
kpts, energies, efermi = read_bands_file(fd)
return resolve_band_structure(bandpath, kpts, energies, efermi)
def read_number_of_grid_points(self):
"""Read number of grid points from SIESTA's text-output file. """
fname = self.directory / f'{self.prefix}.out'
with open(fname) as fd:
for line in fd:
line = line.strip().lower()
if line.startswith('initmesh: mesh ='):
return [int(word) for word in line.split()[3:8:2]]
raise RuntimeError
def read_energy(self):
"""Read energy from SIESTA's text-output file.
"""
text = self._prefixed('out').read_text().lower()
assert 'final energy' in text
lines = iter(text.split('\n'))
# Get the energy and free energy the last time it appears
for line in lines:
has_energy = line.startswith('siesta: etot =')
if has_energy:
energy = float(line.split()[-1])
line = next(lines)
# XXX dangerous, this should test the string in question.
free_energy = float(line.split()[-1])
return {'energy': energy, 'free_energy': free_energy}
def read_forces_stress(self):
"""Read the forces and stress from the FORCE_STRESS file.
"""
fname = self.directory / 'FORCE_STRESS'
with open(fname) as fd:
lines = fd.readlines()
stress_lines = lines[1:4]
stress = np.empty((3, 3))
for i in range(3):
line = stress_lines[i].strip().split(' ')
line = [s for s in line if len(s) > 0]
stress[i] = [float(s) for s in line]
results = {}
results['stress'] = np.array(
[stress[0, 0], stress[1, 1], stress[2, 2],
stress[1, 2], stress[0, 2], stress[0, 1]])
results['stress'] *= Ry / Bohr**3
start = 5
results['forces'] = np.zeros((len(lines) - start, 3), float)
for i in range(start, len(lines)):
line = [s for s in lines[i].strip().split(' ') if len(s) > 0]
results['forces'][i - start] = [float(s) for s in line[2:5]]
results['forces'] *= Ry / Bohr
return results
def read_eigenvalues(self):
""" A robust procedure using the suggestion by Federico Marchesin """
file_name = self._prefixed('EIG')
try:
with open(file_name) as fd:
fermi_energy = float(fd.readline())
n, num_hamilton_dim, nkp = map(int, fd.readline().split())
_ee = np.split(
np.array(fd.read().split()).astype(float), nkp)
except OSError:
return {}
n_spin = 1 if num_hamilton_dim > 2 else num_hamilton_dim
ksn2e = np.delete(_ee, 0, 1).reshape([nkp, n_spin, n])
eig_array = np.empty((n_spin, nkp, n))
eig_array[:] = np.inf
for k, sn2e in enumerate(ksn2e):
for s, n2e in enumerate(sn2e):
eig_array[s, k, :] = n2e
assert np.isfinite(eig_array).all()
return {'eigenvalues': eig_array, 'fermi_energy': fermi_energy}
def read_kpoints(self):
""" Reader of the .KP files """
fname = self._prefixed('KP')
try:
with open(fname) as fd:
nkp = int(next(fd))
kpoints = np.empty((nkp, 3))
kweights = np.empty(nkp)
for i in range(nkp):
line = next(fd)
tokens = line.split()
numbers = np.array(tokens[1:]).astype(float)
kpoints[i] = numbers[:3]
kweights[i] = numbers[3]
except OSError:
return {}
return {'kpoints': kpoints, 'kpoint_weights': kweights}
def read_dipole(self):
"""Read dipole moment. """
dipole = np.zeros([1, 3])
with open(self._prefixed('out')) as fd:
for line in fd:
if line.rfind('Electric dipole (Debye)') > -1:
dipole = np.array([float(f) for f in line.split()[5:8]])
# debye to e*Ang
return dipole * 0.2081943482534
def read_bands_file(fd):
efermi = float(next(fd))
next(fd) # Appears to be max/min energy. Not important for us
header = next(fd) # Array shape: nbands, nspins, nkpoints
nbands, nspins, nkpts = np.array(header.split()).astype(int)
# three fields for kpt coords, then all the energies
ntokens = nbands * nspins + 3
# Read energies for each kpoint:
data = []
for _ in range(nkpts):
line = next(fd)
tokens = line.split()
while len(tokens) < ntokens:
# Multirow table. Keep adding lines until the table ends,
# which should happen exactly when we have all the energies
# for this kpoint.
line = next(fd)
tokens += line.split()
assert len(tokens) == ntokens
values = np.array(tokens).astype(float)
data.append(values)
data = np.array(data)
assert len(data) == nkpts
kpts = data[:, :3]
energies = data[:, 3:]
energies = energies.reshape(nkpts, nspins, nbands)
assert energies.shape == (nkpts, nspins, nbands)
return kpts, energies, efermi
def resolve_band_structure(path, kpts, energies, efermi):
"""Convert input BandPath along with Siesta outputs into BS object."""
# Right now this function doesn't do much.
#
# Not sure how the output kpoints in the siesta.bands file are derived.
# They appear to be related to the lattice parameter.
#
# We should verify that they are consistent with our input path,
# but since their meaning is unclear, we can't quite do so.
#
# Also we should perhaps verify the cell. If we had the cell, we
# could construct the bandpath from scratch (i.e., pure outputs).
from ase.spectrum.band_structure import BandStructure
ksn2e = energies
skn2e = np.swapaxes(ksn2e, 0, 1)
bs = BandStructure(path, skn2e, reference=efermi)
return bs
|