Coverage for /builds/kinetik161/ase/ase/io/xsf.py: 96.32%
190 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1import numpy as np
3from ase.atoms import Atoms
4from ase.calculators.singlepoint import SinglePointCalculator
5from ase.data import atomic_numbers
6from ase.units import Hartree
7from ase.utils import reader, writer
10@writer
11def write_xsf(fileobj, images, data=None, origin=None, span_vectors=None):
12 is_anim = len(images) > 1
14 if is_anim:
15 fileobj.write('ANIMSTEPS %d\n' % len(images))
17 numbers = images[0].get_atomic_numbers()
19 pbc = images[0].get_pbc()
20 npbc = sum(pbc)
21 if pbc[2]:
22 fileobj.write('CRYSTAL\n')
23 assert npbc == 3
24 elif pbc[1]:
25 fileobj.write('SLAB\n')
26 assert npbc == 2
27 elif pbc[0]:
28 fileobj.write('POLYMER\n')
29 assert npbc == 1
30 else:
31 # (Header written as part of image loop)
32 assert npbc == 0
34 cell_variable = False
35 for image in images[1:]:
36 if np.abs(images[0].cell - image.cell).max() > 1e-14:
37 cell_variable = True
38 break
40 for n, atoms in enumerate(images):
41 anim_token = ' %d' % (n + 1) if is_anim else ''
42 if pbc.any():
43 write_cell = (n == 0 or cell_variable)
44 if write_cell:
45 if cell_variable:
46 fileobj.write(f'PRIMVEC{anim_token}\n')
47 else:
48 fileobj.write('PRIMVEC\n')
49 cell = atoms.get_cell()
50 for i in range(3):
51 fileobj.write(' %.14f %.14f %.14f\n' % tuple(cell[i]))
53 fileobj.write(f'PRIMCOORD{anim_token}\n')
54 else:
55 fileobj.write(f'ATOMS{anim_token}\n')
57 # Get the forces if it's not too expensive:
58 calc = atoms.calc
59 if (calc is not None and
60 (hasattr(calc, 'calculation_required') and
61 not calc.calculation_required(atoms, ['forces']))):
62 forces = atoms.get_forces() / Hartree
63 else:
64 forces = None
66 pos = atoms.get_positions()
68 if pbc.any():
69 fileobj.write(' %d 1\n' % len(pos))
70 for a in range(len(pos)):
71 fileobj.write(' %2d' % numbers[a])
72 fileobj.write(' %20.14f %20.14f %20.14f' % tuple(pos[a]))
73 if forces is None:
74 fileobj.write('\n')
75 else:
76 fileobj.write(' %20.14f %20.14f %20.14f\n' % tuple(forces[a]))
78 if data is None:
79 return
81 fileobj.write('BEGIN_BLOCK_DATAGRID_3D\n')
82 fileobj.write(' data\n')
83 fileobj.write(' BEGIN_DATAGRID_3Dgrid#1\n')
85 data = np.asarray(data)
86 if data.dtype == complex:
87 data = np.abs(data)
89 shape = data.shape
90 fileobj.write(' %d %d %d\n' % shape)
92 cell = atoms.get_cell()
93 if origin is None:
94 origin = np.zeros(3)
95 for i in range(3):
96 if not pbc[i]:
97 origin += cell[i] / shape[i]
98 fileobj.write(' %f %f %f\n' % tuple(origin))
100 for i in range(3):
101 # XXXX is this not just supposed to be the cell?
102 # What's with the strange division?
103 # This disagrees with the output of Octopus. Investigate
104 if span_vectors is None:
105 fileobj.write(' %f %f %f\n' %
106 tuple(cell[i] * (shape[i] + 1) / shape[i]))
107 else:
108 fileobj.write(' %f %f %f\n' % tuple(span_vectors[i]))
110 for k in range(shape[2]):
111 for j in range(shape[1]):
112 fileobj.write(' ')
113 fileobj.write(' '.join(['%f' % d for d in data[:, j, k]]))
114 fileobj.write('\n')
115 fileobj.write('\n')
117 fileobj.write(' END_DATAGRID_3D\n')
118 fileobj.write('END_BLOCK_DATAGRID_3D\n')
121@reader
122def iread_xsf(fileobj, read_data=False):
123 """Yield images and optionally data from xsf file.
125 Yields image1, image2, ..., imageN[, data, origin,
126 span_vectors].
128 Images are Atoms objects and data is a numpy array.
130 It also returns the origin of the simulation box
131 as a numpy array and its spanning vectors as a
132 list of numpy arrays, if data is returned.
134 Presently supports only a single 3D datagrid."""
135 def _line_generator_func():
136 for line in fileobj:
137 line = line.strip()
138 if not line or line.startswith('#'):
139 continue # Discard comments and empty lines
140 yield line
142 _line_generator = _line_generator_func()
144 def readline():
145 return next(_line_generator)
147 line = readline()
149 if line.startswith('ANIMSTEPS'):
150 nimages = int(line.split()[1])
151 line = readline()
152 else:
153 nimages = 1
155 if line == 'CRYSTAL':
156 pbc = (True, True, True)
157 elif line == 'SLAB':
158 pbc = (True, True, False)
159 elif line == 'POLYMER':
160 pbc = (True, False, False)
161 else:
162 assert line.startswith('ATOMS'), line # can also be ATOMS 1
163 pbc = (False, False, False)
165 cell = None
166 for n in range(nimages):
167 if any(pbc):
168 line = readline()
169 if line.startswith('PRIMCOORD'):
170 assert cell is not None # cell read from previous image
171 else:
172 assert line.startswith('PRIMVEC')
173 cell = []
174 for i in range(3):
175 cell.append([float(x) for x in readline().split()])
177 line = readline()
178 if line.startswith('CONVVEC'): # ignored;
179 for i in range(3):
180 readline()
181 line = readline()
183 assert line.startswith('PRIMCOORD')
184 natoms = int(readline().split()[0])
185 lines = [readline() for _ in range(natoms)]
186 else:
187 assert line.startswith('ATOMS'), line
188 line = readline()
189 lines = []
190 while not (line.startswith('ATOMS') or line.startswith('BEGIN')):
191 lines.append(line)
192 try:
193 line = readline()
194 except StopIteration:
195 break
196 if line.startswith('BEGIN'):
197 # We read "too far" and accidentally got the header
198 # of the data section. This happens only when parsing
199 # ATOMS blocks, because one cannot infer their length.
200 # We will remember the line until later then.
201 data_header_line = line
203 numbers = []
204 positions = []
205 for positionline in lines:
206 tokens = positionline.split()
207 symbol = tokens[0]
208 if symbol.isdigit():
209 numbers.append(int(symbol))
210 else:
211 numbers.append(atomic_numbers[symbol.capitalize()])
212 positions.append([float(x) for x in tokens[1:]])
214 positions = np.array(positions)
215 if len(positions[0]) == 3:
216 forces = None
217 else:
218 forces = positions[:, 3:] * Hartree
219 positions = positions[:, :3]
221 image = Atoms(numbers, positions, cell=cell, pbc=pbc)
223 if forces is not None:
224 image.calc = SinglePointCalculator(image, forces=forces)
225 yield image
227 if read_data:
228 if any(pbc):
229 line = readline()
230 else:
231 line = data_header_line
232 assert line.startswith('BEGIN_BLOCK_DATAGRID_3D')
233 readline() # name
234 line = readline()
235 assert line.startswith('BEGIN_DATAGRID_3D')
237 shape = [int(x) for x in readline().split()]
238 assert len(shape) == 3
239 origin = [float(x) for x in readline().split()]
240 origin = np.array(origin)
242 span_vectors = []
243 for i in range(3):
244 span_vector = [float(x) for x in readline().split()]
245 span_vector = np.array(span_vector)
246 span_vectors.append(span_vector)
247 span_vectors = np.array(span_vectors)
248 assert len(span_vectors) == len(shape)
250 npoints = np.prod(shape)
252 data = []
253 line = readline() # First line of data
254 while not line.startswith('END_DATAGRID_3D'):
255 data.extend([float(x) for x in line.split()])
256 line = readline()
257 assert len(data) == npoints
258 data = np.array(data, float).reshape(shape[::-1]).T
259 # Note that data array is Fortran-ordered
260 yield data, origin, span_vectors
263def read_xsf(fileobj, index=-1, read_data=False):
264 images = list(iread_xsf(fileobj, read_data=read_data))
265 if read_data:
266 array, origin, span_vectors = images[-1]
267 images = images[:-1]
268 return array, origin, span_vectors, images[index]
269 return images[index]