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 +3 -0
- vaspparser/_version.py +34 -0
- vaspparser/dft/__init__.py +0 -0
- vaspparser/dft/bader.py +149 -0
- vaspparser/dft/volumetric.py +412 -0
- vaspparser/dft/waves/__init__.py +0 -0
- vaspparser/dft/waves/dos.py +323 -0
- vaspparser/dft/waves/electronic.py +741 -0
- vaspparser/vasp/__init__.py +0 -0
- vaspparser/vasp/output.py +537 -0
- vaspparser/vasp/parser/__init__.py +0 -0
- vaspparser/vasp/parser/oszicar.py +46 -0
- vaspparser/vasp/parser/outcar.py +1251 -0
- vaspparser/vasp/parser/report.py +50 -0
- vaspparser/vasp/procar.py +115 -0
- vaspparser/vasp/structure.py +482 -0
- vaspparser/vasp/vasprun.py +884 -0
- vaspparser/vasp/volumetric_data.py +288 -0
- vaspparser-0.0.1.dist-info/METADATA +85 -0
- vaspparser-0.0.1.dist-info/RECORD +22 -0
- vaspparser-0.0.1.dist-info/WHEEL +4 -0
- vaspparser-0.0.1.dist-info/licenses/LICENSE +29 -0
vaspparser/__init__.py
ADDED
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
|
vaspparser/dft/bader.py
ADDED
|
@@ -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
|