Coverage for /builds/ase/ase/ase/io/dftb.py: 92.91%
127 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3from typing import Sequence, Union
5import numpy as np
7from ase.atoms import Atoms
8from ase.utils import reader, writer
11@reader
12def read_dftb(fd):
13 """Method to read coordinates from the Geometry section
14 of a DFTB+ input file (typically called "dftb_in.hsd").
16 As described in the DFTB+ manual, this section can be
17 in a number of different formats. This reader supports
18 the GEN format and the so-called "explicit" format.
20 The "explicit" format is unique to DFTB+ input files.
21 The GEN format can also be used in a stand-alone fashion,
22 as coordinate files with a `.gen` extension. Reading and
23 writing such files is implemented in `ase.io.gen`.
24 """
25 lines = fd.readlines()
27 atoms_pos = []
28 atom_symbols = []
29 type_names = []
30 my_pbc = False
31 fractional = False
32 mycell = []
34 for iline, line in enumerate(lines):
35 if line.strip().startswith('#'):
36 pass
37 elif 'genformat' in line.lower():
38 natoms = int(lines[iline + 1].split()[0])
39 if lines[iline + 1].split()[1].lower() == 's':
40 my_pbc = True
41 elif lines[iline + 1].split()[1].lower() == 'f':
42 my_pbc = True
43 fractional = True
45 symbols = lines[iline + 2].split()
47 for i in range(natoms):
48 index = iline + 3 + i
49 aindex = int(lines[index].split()[1]) - 1
50 atom_symbols.append(symbols[aindex])
52 position = [float(p) for p in lines[index].split()[2:]]
53 atoms_pos.append(position)
55 if my_pbc:
56 for i in range(3):
57 index = iline + 4 + natoms + i
58 cell = [float(c) for c in lines[index].split()]
59 mycell.append(cell)
60 else:
61 if 'TypeNames' in line:
62 col = line.split()
63 for i in range(3, len(col) - 1):
64 type_names.append(col[i].strip("\""))
65 elif 'Periodic' in line:
66 if 'Yes' in line:
67 my_pbc = True
68 elif 'LatticeVectors' in line:
69 for imycell in range(3):
70 extraline = lines[iline + imycell + 1]
71 cols = extraline.split()
72 mycell.append(
73 [float(cols[0]), float(cols[1]), float(cols[2])])
74 else:
75 pass
77 if not my_pbc:
78 mycell = [0.] * 3
80 start_reading_coords = False
81 stop_reading_coords = False
82 for line in lines:
83 if line.strip().startswith('#'):
84 pass
85 else:
86 if 'TypesAndCoordinates' in line:
87 start_reading_coords = True
88 if start_reading_coords:
89 if '}' in line:
90 stop_reading_coords = True
91 if (start_reading_coords and not stop_reading_coords
92 and 'TypesAndCoordinates' not in line):
93 typeindexstr, xxx, yyy, zzz = line.split()[:4]
94 typeindex = int(typeindexstr)
95 symbol = type_names[typeindex - 1]
96 atom_symbols.append(symbol)
97 atoms_pos.append([float(xxx), float(yyy), float(zzz)])
99 if fractional:
100 atoms = Atoms(scaled_positions=atoms_pos, symbols=atom_symbols,
101 cell=mycell, pbc=my_pbc)
102 elif not fractional:
103 atoms = Atoms(positions=atoms_pos, symbols=atom_symbols,
104 cell=mycell, pbc=my_pbc)
106 return atoms
109def read_dftb_velocities(atoms, filename):
110 """Method to read velocities (AA/ps) from DFTB+ output file geo_end.xyz
111 """
112 from ase.units import second
114 # AA/ps -> ase units
115 AngdivPs2ASE = 1.0 / (1e-12 * second)
117 with open(filename) as fd:
118 lines = fd.readlines()
120 # remove empty lines
121 lines_ok = []
122 for line in lines:
123 if line.rstrip():
124 lines_ok.append(line)
126 velocities = []
127 natoms = len(atoms)
128 last_lines = lines_ok[-natoms:]
129 for iline, line in enumerate(last_lines):
130 inp = line.split()
131 velocities.append([float(inp[5]) * AngdivPs2ASE,
132 float(inp[6]) * AngdivPs2ASE,
133 float(inp[7]) * AngdivPs2ASE])
135 atoms.set_velocities(velocities)
136 return atoms
139@reader
140def read_dftb_lattice(fileobj, images=None):
141 """Read lattice vectors from MD and return them as a list.
143 If a molecules are parsed add them there."""
144 if images is not None:
145 append = True
146 if hasattr(images, 'get_positions'):
147 images = [images]
148 else:
149 append = False
151 fileobj.seek(0)
152 lattices = []
153 for line in fileobj:
154 if 'Lattice vectors' in line:
155 vec = []
156 for _ in range(3):
157 line = fileobj.readline().split()
158 try:
159 line = [float(x) for x in line]
160 except ValueError:
161 raise ValueError('Lattice vector elements should be of '
162 'type float.')
163 vec.extend(line)
164 lattices.append(np.array(vec).reshape((3, 3)))
166 if append:
167 if len(images) != len(lattices):
168 raise ValueError('Length of images given does not match number of '
169 'cell vectors found')
171 for i, atoms in enumerate(images):
172 atoms.set_cell(lattices[i])
173 # DFTB+ only supports 3D PBC
174 atoms.set_pbc(True)
175 return None
176 else:
177 return lattices
180@writer
181def write_dftb(
182 fileobj,
183 images: Union[Atoms, Sequence[Atoms]],
184 fractional: bool = False,
185):
186 """Write structure in GEN format (refer to DFTB+ manual).
187 Multiple snapshots are not allowed. """
188 from ase.io.gen import write_gen
189 write_gen(fileobj, images, fractional=fractional)
192def write_dftb_velocities(atoms, filename):
193 """Method to write velocities (in atomic units) from ASE
194 to a file to be read by dftb+
195 """
196 from ase.units import AUT, Bohr
198 # ase units -> atomic units
199 ASE2au = Bohr / AUT
201 with open(filename, 'w') as fd:
202 velocities = atoms.get_velocities()
203 for velocity in velocities:
204 fd.write(' %19.16f %19.16f %19.16f \n'
205 % (velocity[0] / ASE2au,
206 velocity[1] / ASE2au,
207 velocity[2] / ASE2au))