vaspparser 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vaspparser/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ import vaspparser._version
2
+
3
+ __version__ = vaspparser._version.__version__
vaspparser/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.0.1'
32
+ __version_tuple__ = version_tuple = (0, 0, 1)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,149 @@
1
+ # coding: utf-8
2
+ # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
3
+ # Distributed under the terms of "New BSD License", see the LICENSE file.
4
+
5
+ import os
6
+ import subprocess
7
+
8
+ import numpy as np
9
+
10
+ from vaspparser.vasp.volumetric_data import VaspVolumetricData
11
+
12
+ __author__ = "Sudarsan Surendralal"
13
+ __copyright__ = (
14
+ "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
15
+ "Computational Materials Design (CM) Department"
16
+ )
17
+ __version__ = "1.0"
18
+ __maintainer__ = "Sudarsan Surendralal"
19
+ __email__ = "surendralal@mpie.de"
20
+ __status__ = "production"
21
+ __date__ = "May 1, 2021"
22
+
23
+
24
+ class Bader:
25
+ """
26
+ Module to apply the Bader charge partitioning scheme to finished DFT jobs. This module is interfaced with the
27
+ `Bader code`_ from the Greame Henkelmann group.
28
+
29
+ .. _Bader code: http://theory.cm.utexas.edu/henkelman/code/bader
30
+ """
31
+
32
+ def __init__(self, structure, working_directory):
33
+ """
34
+ Initialize the Bader module
35
+
36
+ Args:
37
+ job (pyiron_atomistics.dft.job.generic.GenericDFTJob): A DFT job instance (finished/converged job)
38
+ """
39
+ self._working_directory = working_directory
40
+ self._structure = structure
41
+
42
+ def _create_cube_files(self):
43
+ """
44
+ Create CUBE format files of the total and valce charges to be used by the Bader program
45
+ """
46
+ cd_val, cd_total = get_valence_and_total_charge_density(
47
+ working_directory=self._working_directory
48
+ )
49
+ cd_val.write_cube_file(
50
+ filename=os.path.join(self._working_directory, "valence_charge.CUBE")
51
+ )
52
+ cd_total.write_cube_file(
53
+ filename=os.path.join(self._working_directory, "total_charge.CUBE")
54
+ )
55
+
56
+ def compute_bader_charges(self, extra_arguments=None):
57
+ """
58
+ Run Bader analysis on the output from the DFT job
59
+
60
+ Args:
61
+ extra_arguments (str): Extra arguments to the Bader program
62
+
63
+ Returns:
64
+ tuple: Charges and volumes as numpy arrays
65
+
66
+ """
67
+ self._create_cube_files()
68
+ error_code = call_bader(
69
+ foldername=self._working_directory, extra_arguments=extra_arguments
70
+ )
71
+ if error_code > 0:
72
+ self._remove_cube_files()
73
+ raise ValueError("Invoking Bader charge analysis failed!")
74
+ self._remove_cube_files()
75
+ return self._parse_charge_vol()
76
+
77
+ def _remove_cube_files(self):
78
+ """
79
+ Delete created CUBE files
80
+ """
81
+ os.remove(os.path.join(self._working_directory, "valence_charge.CUBE"))
82
+ os.remove(os.path.join(self._working_directory, "total_charge.CUBE"))
83
+
84
+ def _parse_charge_vol(self):
85
+ """
86
+ Parse Bader charges and volumes
87
+
88
+ Returns:
89
+ tuple: charges and volumes
90
+
91
+ """
92
+ filename = os.path.join(self._working_directory, "ACF.dat")
93
+ return parse_charge_vol_file(structure=self._structure, filename=filename)
94
+
95
+
96
+ def call_bader(foldername, extra_arguments=None):
97
+ """
98
+ Call the Bader program inside a given folder
99
+
100
+ Args:
101
+ foldername (str): Folder path
102
+ extra_arguments (str): Extra arguments to the Bader program
103
+
104
+ Returns:
105
+ int: Result from the subprocess call (>0 if an error occurs)
106
+
107
+ """
108
+ if extra_arguments is None:
109
+ extra_arguments = ""
110
+ cmd = "bader valence_charge.CUBE -ref total_charge.CUBE {0}".format(extra_arguments)
111
+ return subprocess.call(cmd, shell=True, cwd=foldername)
112
+
113
+
114
+ def parse_charge_vol_file(structure, filename="ACF.dat"):
115
+ """
116
+ Parse charges and volumes from the output file
117
+
118
+ Args:
119
+ structure (pyiron_atomistics.atomistics.structure.atoms.Atoms): The snapshot to be analyzed
120
+ filename (str): Filename of the output file
121
+
122
+ Returns:
123
+ tuple: charges and volumes
124
+
125
+ """
126
+ with open(filename, errors="ignore") as f:
127
+ lines = f.readlines()
128
+ charges = np.genfromtxt(lines[2:], max_rows=len(structure))[:, 4]
129
+ volumes = np.genfromtxt(lines[2:], max_rows=len(structure))[:, 6]
130
+ return charges, volumes
131
+
132
+
133
+ def get_valence_and_total_charge_density(working_directory):
134
+ """
135
+ Gives the valence and total charge densities
136
+
137
+ Returns:
138
+ tuple: The required charge densities
139
+ """
140
+ cd_core = VaspVolumetricData()
141
+ cd_total = VaspVolumetricData()
142
+ cd_val = VaspVolumetricData()
143
+ if os.path.isfile(working_directory + "/AECCAR0"):
144
+ cd_core.from_file(working_directory + "/AECCAR0")
145
+ cd_val.from_file(working_directory + "/AECCAR2")
146
+ cd_val.atoms = cd_val.atoms
147
+ cd_total.total_data = cd_core.total_data + cd_val.total_data
148
+ cd_total.atoms = cd_val.atoms
149
+ return cd_val, cd_total
@@ -0,0 +1,412 @@
1
+ # coding: utf-8
2
+ # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
3
+ # Distributed under the terms of "New BSD License", see the LICENSE file.
4
+
5
+ import numpy as np
6
+ from ase.atoms import Atoms
7
+
8
+ from vaspparser.vasp.structure import write_poscar
9
+
10
+ __author__ = "Sudarsan Surendralal, Su-Hyun Yoo"
11
+ __copyright__ = (
12
+ "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH "
13
+ "- Computational Materials Design (CM) Department"
14
+ )
15
+ __version__ = "1.0"
16
+ __maintainer__ = "Sudarsan Surendralal"
17
+ __email__ = "surendralal@mpie.de"
18
+ __status__ = "development"
19
+ __date__ = "Sep 1, 2017"
20
+
21
+
22
+ class VolumetricData(object):
23
+ """
24
+ A new class to handle 3-dimensional volumetric data elegantly (charge densities, electrostatic potentials etc) based
25
+ on the numpy.ndarray instance. This module is adapted from the pymatgen vasp VolumtricData class
26
+
27
+ http://pymatgen.org/_modules/pymatgen/io/vasp/outputs.html#VolumetricData
28
+
29
+ Attributes:
30
+
31
+ total_data (numpy.ndarray): A 3D array containing the data
32
+
33
+ """
34
+
35
+ def __init__(self):
36
+ self._total_data = None
37
+ self._atoms = None
38
+
39
+ @property
40
+ def atoms(self):
41
+ """
42
+ The structure related to the volumeric data
43
+
44
+ Returns:
45
+ pyiron_atomistics.atomistics.structure.Atoms: The structure associated with the data
46
+
47
+ """
48
+ return self._atoms
49
+
50
+ @atoms.setter
51
+ def atoms(self, val):
52
+ self._atoms = val
53
+
54
+ @property
55
+ def total_data(self):
56
+ """
57
+ numpy.ndarray: The Nx x Ny x Nz sized array for the total data
58
+ """
59
+ return self._total_data
60
+
61
+ @total_data.setter
62
+ def total_data(self, val):
63
+ if not (isinstance(val, (np.ndarray, list))):
64
+ raise TypeError(
65
+ "Attribute total_data should be a numpy.ndarray instance or a list and "
66
+ "not {}".format(type(val))
67
+ )
68
+ val = np.array(val)
69
+ shape = np.array(np.shape(val))
70
+ if not (len(shape) == 3):
71
+ raise ValueError("Attribute total_data should be a 3D array")
72
+ self._total_data = val
73
+
74
+ @staticmethod
75
+ def gauss_f(d, fwhm=0.529177):
76
+ """
77
+ Generates a Gaussian distribution for a given distance and full width half maximum value
78
+
79
+ Args:
80
+ d (float): distance between target point and reference point
81
+ fwhm (float): Full width half maximum in angstrom
82
+
83
+ Returns:
84
+ float: Gaussian reduction constant
85
+
86
+ """
87
+ sigma = fwhm / (2 * np.sqrt(2 * np.log(2)))
88
+ d2 = d * d
89
+ return np.exp(-1 / (2 * sigma**2) * d2)
90
+
91
+ @staticmethod
92
+ def dist_between_two_grid_points(
93
+ target_grid_point, n_grid_at_center, lattice, grid_shape
94
+ ):
95
+ """
96
+ Calculates the distance between a target grid point and another grid point
97
+
98
+ Args:
99
+ target_grid_point (numpy.ndarray/list): Target grid point
100
+ n_grid_at_center (numpy.ndarray/list): coordinate of center of sphere
101
+ lattice (numpy.ndarray/list): lattice vector
102
+ grid_shape (tuple/list/numpy.ndarray): size of grid
103
+
104
+ Returns:
105
+
106
+ float: Distance between target grid and center of sphere in angstrom
107
+
108
+ """
109
+ unit_dist_in_grid = [
110
+ np.sqrt(np.dot(lattice[0], lattice[0])) / grid_shape[0],
111
+ np.sqrt(np.dot(lattice[1], lattice[1])) / grid_shape[1],
112
+ np.sqrt(np.dot(lattice[2], lattice[2])) / grid_shape[2],
113
+ ]
114
+ dn = np.multiply(
115
+ np.subtract(target_grid_point, n_grid_at_center), unit_dist_in_grid
116
+ )
117
+ dist = np.linalg.norm(dn)
118
+ return dist
119
+
120
+ def spherical_average_potential(
121
+ self, structure, spherical_center, rad=2, fwhm=0.529177
122
+ ):
123
+ """
124
+ Calculates the spherical average about a given point in space
125
+
126
+ Args:
127
+ structure (pyiron_atomistics.atomistics.structure.Atoms): Input structure
128
+ spherical_center (list/numpy.ndarray): position of spherical_center in direct coordinate
129
+ rad (float): radius of sphere to be considered in Angstrom (recommended value: 2)
130
+ fwhm (float): Full width half maximum of gaussian function in Angstrom (recommended value: 0.529177)
131
+
132
+ Returns:
133
+ float: Spherical average at the target center
134
+
135
+ """
136
+ grid_shape = self._total_data.shape
137
+
138
+ # Position of center of sphere at grid coordinates
139
+ n_grid_at_center = [
140
+ int(np.ceil(spherical_center[0] * grid_shape[0])),
141
+ int(np.ceil(spherical_center[1] * grid_shape[1])),
142
+ int(np.ceil(spherical_center[2] * grid_shape[2])),
143
+ ]
144
+
145
+ # Unit distance between grids
146
+ dist_in_grid = [
147
+ np.linalg.norm(structure.cell[0]) / grid_shape[0],
148
+ np.linalg.norm(structure.cell[1]) / grid_shape[1],
149
+ np.linalg.norm(structure.cell[2]) / grid_shape[2],
150
+ ]
151
+
152
+ # Range of grids to be considered within the provided radius w.r.t. center of sphere
153
+ num_grid_in_sph = [[], []]
154
+ for i, dist in enumerate(dist_in_grid):
155
+ num_grid_in_sph[0].append(n_grid_at_center[i] - int(np.ceil(rad / dist)))
156
+ num_grid_in_sph[1].append(n_grid_at_center[i] + int(np.ceil(rad / dist)))
157
+
158
+ sph_avg_tmp = []
159
+ weight = 0
160
+ for k in range(num_grid_in_sph[0][0], num_grid_in_sph[1][0]):
161
+ for l in range(num_grid_in_sph[0][1], num_grid_in_sph[1][1]):
162
+ for m in range(num_grid_in_sph[0][2], num_grid_in_sph[1][2]):
163
+ target_grid_point = [k, l, m]
164
+ dist = self.dist_between_two_grid_points(
165
+ target_grid_point, n_grid_at_center, structure.cell, grid_shape
166
+ )
167
+ if dist <= rad:
168
+ sph_avg_tmp.append(
169
+ self._total_data[
170
+ k % grid_shape[0], l % grid_shape[1], m % grid_shape[2]
171
+ ]
172
+ * self.gauss_f(dist, fwhm)
173
+ )
174
+ weight += self.gauss_f(dist, fwhm)
175
+ else:
176
+ pass
177
+ sum_list = np.sum(sph_avg_tmp)
178
+ sph_avg = sum_list / weight
179
+ return sph_avg
180
+
181
+ @staticmethod
182
+ def dist_between_two_grid_points_cyl(
183
+ target_grid_point, n_grid_at_center, lattice, grid_shape, direction_of_cyl
184
+ ):
185
+ """
186
+ Distance between a target grid point and the center of a cylinder
187
+
188
+ Args:
189
+ target_grid_point (numpy.ndarray/list): Target grid point
190
+ n_grid_at_center (numpy.ndarray/list): coordinate of center of sphere
191
+ lattice (numpy.ndarray/list): lattice vector
192
+ grid_shape (tuple/list/numpy.ndarray): size of grid
193
+ direction_of_cyl (int): Axis of cylinder (0 (x) or 1 (y) or 2 (z))
194
+
195
+ Returns:
196
+ float: Distance between target grid and in-plane center of cylinder
197
+
198
+ """
199
+ unit_dist_in_grid = [
200
+ np.sqrt(np.dot(lattice[0], lattice[0])) / grid_shape[0],
201
+ np.sqrt(np.dot(lattice[1], lattice[1])) / grid_shape[1],
202
+ np.sqrt(np.dot(lattice[2], lattice[2])) / grid_shape[2],
203
+ ]
204
+ dn = np.multiply(
205
+ np.subtract(target_grid_point, n_grid_at_center), unit_dist_in_grid
206
+ )
207
+ if direction_of_cyl == 0:
208
+ dn[0] = 0
209
+ elif direction_of_cyl == 1:
210
+ dn[1] = 0
211
+ elif direction_of_cyl == 2:
212
+ dn[2] = 0
213
+ else:
214
+ print("check the direction of cylindrical axis")
215
+ dist = np.linalg.norm(dn)
216
+ return dist
217
+
218
+ def cylindrical_average_potential(
219
+ self, structure, spherical_center, axis_of_cyl, rad=2, fwhm=0.529177
220
+ ):
221
+ """
222
+ Calculates the cylindrical average about a given point in space
223
+
224
+ Args:
225
+ structure (pyiron_atomistics.atomistics.structure.Atoms): Input structure
226
+ spherical_center (list/numpy.ndarray): position of spherical_center in direct coordinate
227
+ rad (float): radius of sphere to be considered in Angstrom (recommended value: 2)
228
+ fwhm (float): Full width half maximum of gaussian function in Angstrom (recommended value: 0.529177)
229
+ axis_of_cyl (int): Axis of cylinder (0 (x) or 1 (y) or 2 (z))
230
+
231
+ Returns:
232
+ float: Cylindrical average at the target center
233
+
234
+ """
235
+ grid_shape = self._total_data.shape
236
+
237
+ # Position of center of sphere at grid coordinates
238
+ n_grid_at_center = [
239
+ int(np.ceil(spherical_center[0] * grid_shape[0])),
240
+ int(np.ceil(spherical_center[1] * grid_shape[1])),
241
+ int(np.ceil(spherical_center[2] * grid_shape[2])),
242
+ ]
243
+
244
+ # Unit distance between grids
245
+ dist_in_grid = [
246
+ np.linalg.norm(structure.cell[0]) / grid_shape[0],
247
+ np.linalg.norm(structure.cell[1]) / grid_shape[1],
248
+ np.linalg.norm(structure.cell[2]) / grid_shape[2],
249
+ ]
250
+
251
+ # Range of grids to be considered within the provided radius w.r.t. center of sphere
252
+ num_grid_in_cyl = [[], []]
253
+
254
+ for i, dist in enumerate(dist_in_grid):
255
+ if i == axis_of_cyl:
256
+ num_grid_in_cyl[0].append(0)
257
+ num_grid_in_cyl[1].append(grid_shape[i])
258
+ else:
259
+ num_grid_in_cyl[0].append(
260
+ n_grid_at_center[i] - int(np.ceil(rad / dist))
261
+ )
262
+ num_grid_in_cyl[1].append(
263
+ n_grid_at_center[i] + int(np.ceil(rad / dist))
264
+ )
265
+
266
+ cyl_avg_tmp = []
267
+ weight = 0
268
+ for k in range(num_grid_in_cyl[0][0], num_grid_in_cyl[1][0]):
269
+ for l in range(num_grid_in_cyl[0][1], num_grid_in_cyl[1][1]):
270
+ for m in range(num_grid_in_cyl[0][2], num_grid_in_cyl[1][2]):
271
+ target_grid_point = [k, l, m]
272
+ dist = self.dist_between_two_grid_points_cyl(
273
+ target_grid_point,
274
+ n_grid_at_center,
275
+ structure.cell,
276
+ grid_shape,
277
+ axis_of_cyl,
278
+ )
279
+ if dist <= rad:
280
+ cyl_avg_tmp.append(
281
+ self._total_data[
282
+ k % grid_shape[0], l % grid_shape[1], m % grid_shape[2]
283
+ ]
284
+ * self.gauss_f(dist, fwhm)
285
+ )
286
+ weight += self.gauss_f(dist, fwhm)
287
+ else:
288
+ pass
289
+ sum_list = np.sum(cyl_avg_tmp)
290
+ cyl_avg = sum_list / weight
291
+
292
+ return cyl_avg
293
+
294
+ def get_average_along_axis(self, ind=2):
295
+ """
296
+ Get the lateral average along a certain axis direction. This function is adapted from the pymatgen vasp
297
+ VolumetricData class
298
+
299
+ http://pymatgen.org/_modules/pymatgen/io/vasp/outputs.html#VolumetricData.get_average_along_axis
300
+
301
+ Args:
302
+ ind (int): Index of axis (0, 1 and 2 for the x, y, and z axis respectively)
303
+
304
+ Returns:
305
+ numpy.ndarray: A 1D vector with the laterally averaged values of the volumetric data
306
+ """
307
+ if ind == 0:
308
+ return np.average(np.average(self._total_data, axis=1), 1)
309
+ elif ind == 1:
310
+ return np.average(np.average(self._total_data, axis=0), 1)
311
+ else:
312
+ return np.average(np.average(self._total_data, axis=0), 0)
313
+
314
+ def write_cube_file(self, filename="cube_file.cube", cell_scaling=1.0):
315
+ """
316
+ Write the volumetric data into the CUBE file format
317
+
318
+ Args:
319
+ filename (str): Filename
320
+ cell_scaling (float): Scale the cell by this fraction
321
+
322
+ """
323
+ if self._atoms is None:
324
+ raise ValueError(
325
+ "The volumetric data object must have a valid structure assigned to it before writing "
326
+ "to the cube format"
327
+ )
328
+ data = self.total_data
329
+ n_x, n_y, _ = data.shape
330
+ origin = np.zeros(3)
331
+ flattened_data = np.hstack(
332
+ [data[i, j, :] for i in range(n_x) for j in range(n_y)]
333
+ )
334
+ n_atoms = len(self.atoms)
335
+ total_lines = int(len(flattened_data) / 6) * 6
336
+ reshaped_data = np.reshape(flattened_data[0:total_lines], (-1, 6))
337
+ last_line = [flattened_data[total_lines:]]
338
+ head_array = np.zeros((4, 4))
339
+ head_array[0] = np.append([n_atoms], origin)
340
+ head_array[1:, 0] = data.shape
341
+ head_array[1:, 1:] = self.atoms.cell / data.shape * cell_scaling
342
+ position_array = np.zeros((len(self.atoms.positions), 5))
343
+ position_array[:, 0] = self.atoms.get_atomic_numbers()
344
+ position_array[:, 2:] = self.atoms.positions
345
+ with open(filename, "w") as f:
346
+ f.write("Cube file generated by pyiron (http://pyiron.org) \n")
347
+ f.write("z is the fastest index \n")
348
+ np.savetxt(f, head_array, fmt="%4d %.6f %.6f %.6f")
349
+ np.savetxt(f, position_array, fmt="%4d %.6f %.6f %.6f %.6f")
350
+ np.savetxt(f, reshaped_data, fmt="%.5e")
351
+ np.savetxt(f, last_line, fmt="%.5e")
352
+
353
+ def read_cube_file(self, filename="cube_file.cube"):
354
+ """
355
+ Generate data from a CUBE file
356
+
357
+ Args:
358
+ filename (str): Filename to parse
359
+
360
+ """
361
+ with open(filename, "r", errors="ignore") as f:
362
+ lines = f.readlines()
363
+ n_atoms = int(lines[2].strip().split()[0])
364
+ cell_data = np.genfromtxt(lines[3:6])
365
+ cell_grid = cell_data[:, 1:]
366
+ grid_shape = np.array(cell_data[:, 0], dtype=int)
367
+ # total_data = np.zeros(grid_shape)
368
+ cell = np.array([val * grid_shape[i] for i, val in enumerate(cell_grid)])
369
+ if n_atoms > 0:
370
+ pos_data = np.genfromtxt(lines[6 : n_atoms + 6])
371
+ if n_atoms == 1:
372
+ pos_data = np.array([pos_data])
373
+ atomic_numbers = np.array(pos_data[:, 0], dtype=int)
374
+ positions = pos_data[:, 2:]
375
+ self._atoms = Atoms(
376
+ numbers=atomic_numbers, positions=positions, cell=cell
377
+ )
378
+ end_int = n_atoms + 6 + int(np.prod(grid_shape) / 6)
379
+ data = np.genfromtxt(lines[n_atoms + 6 : end_int])
380
+ data_flatten = np.hstack(data)
381
+ if np.prod(grid_shape) % 6 > 0:
382
+ data_flatten = np.append(
383
+ data_flatten, [float(val) for val in lines[end_int].split()]
384
+ )
385
+ n_x, n_y, n_z = grid_shape
386
+ self._total_data = data_flatten.reshape((n_x, n_y, n_z))
387
+
388
+ def write_vasp_volumetric(self, filename="CHGCAR", normalize=False):
389
+ """
390
+ Writes volumetric data into a VASP CHGCAR format
391
+
392
+ Args:
393
+ filename (str): Filename of the new file
394
+ normalize (bool): True if the data is to be normalized by the volume
395
+
396
+ """
397
+ write_poscar(structure=self.atoms, filename=filename)
398
+ with open(filename, "a") as f:
399
+ f.write("\n")
400
+ f.write(" ".join(list(np.array(self.total_data.shape, dtype=str))))
401
+ f.write("\n")
402
+ _, n_y, n_z = self.total_data.shape
403
+ flattened_data = np.hstack(
404
+ [self.total_data[:, i, j] for j in range(n_z) for i in range(n_y)]
405
+ )
406
+ if normalize:
407
+ flattened_data /= self.atoms.get_volume()
408
+ num_lines = int(len(flattened_data) / 5) * 5
409
+ reshaped_data = np.reshape(flattened_data[0:num_lines], (-1, 5))
410
+ np.savetxt(f, reshaped_data, fmt="%.12f")
411
+ if len(flattened_data) % 5 > 0:
412
+ np.savetxt(f, [flattened_data[num_lines:]], fmt="%.12f")
File without changes