bandu 1.3.6__py3-none-any.whl → 1.3.7__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.
bandu/isosurface_class.py CHANGED
@@ -1,236 +1,236 @@
1
- import pyvista as pv
2
- import numpy as np
3
- from . import translate as trnslt
4
- from . import abinit_reader as ar
5
- from . import wfk_class as wc
6
- from . import brillouin_zone as brlzn
7
-
8
- # object for creating reciprocal space energy isosurfaces
9
- class Isosurface():
10
- '''
11
- Class for constructing contours from WFK eigenvalues
12
-
13
- Parameters
14
- ----------
15
- points : np.ndarray
16
- Array of reciprocal space points to construct contour over
17
- Shape (N,3)
18
- values : np.ndarray
19
- Array of eigenvalues for each reciprocal space point
20
- Shape (N,1) or (N,#Bands)
21
- nbands : list[int]
22
- List of bands to create contours for, aka indices of bands that cross energy isosurface
23
- rec_latt : np.ndarray
24
- Reciprocal lattice vectors
25
- wfk_name : str
26
- If points and values arrays are not provided, then construct contours from reading in an ABINIT WFK file
27
- Path to wfk file
28
- grid_steps : tuple
29
- Set fineness of grid that contours are interpolated on
30
- Default (50,50,50)
31
- fermi_energy : float
32
- Fermi energy
33
- Default 0.0
34
- energy_level : float
35
- Sets energy isosurface relative to the Fermi energy
36
- Default 0.0 (this samples the Fermi energy)
37
- width : float
38
- Sets the range above and below the energy_level that eigenvalues are sampled from
39
- Default 0.005 (Assumes units of Hartree)
40
- radius : float
41
- Radius of gaussian interpolation
42
- Default 0.05, this scales inversely with kpoint grid size (larger grids require smaller radius)
43
- sym_ops : np.ndarray
44
- Array of symmetry operations for generating points in Brillouin Zone
45
- grid_stretch : float
46
- Value that stretches grid that is used to interpolate bands
47
- If portions of Brillouin Zone are cropped off, increase this value
48
- Default is 0.15
49
-
50
- Methods
51
- -------
52
- Contour
53
- Generates energy isosurface contours
54
- '''
55
- def __init__(
56
- self, points:np.ndarray=np.zeros(1), values:np.ndarray=np.zeros(1), rec_latt:np.ndarray=np.zeros(1),
57
- wfk_name:str='', grid_steps:tuple=(50,50,50), fermi_energy:float=0.0, energy_level:float=0.0,
58
- width:float=0.005, radius:float=0.05, nbands:list[int]=[], sym_ops:np.ndarray=np.zeros(1),
59
- grid_stretch:float=0.15
60
- )->None:
61
- # define attributes
62
- self.points=points
63
- self.values=values
64
- self.wfk_name=wfk_name
65
- self.rec_latt=rec_latt
66
- self.fermi_energy=fermi_energy
67
- self.energy_level=energy_level
68
- self.width=width
69
- self.radius=radius
70
- self.nbands=nbands
71
- self.sym_ops=sym_ops
72
- self.grid_stretch=grid_stretch
73
- # check if enough information is provided to class to construct isosurfaces
74
- if self.points.shape == (1,) and self.values.shape == (1,):
75
- if wfk_name == '':
76
- raise ValueError((
77
- 'Either the points and the values attributes must be defined or the wfk_name attribute '
78
- 'must be defined'
79
- ))
80
- else:
81
- self.points, self.values, self.nbands, self.ir_kpts = self._ReadAbinit(wfk_name)
82
- # check if information provided is in correct format
83
- if len(self.values.shape) > 2:
84
- raise ValueError((
85
- f'Provided values have dimension {len(self.values.shape)}'
86
- 'Values must be 1 or 2 dimensional'
87
- ))
88
- else:
89
- self._valdim=len(self.values.shape)
90
- # more attributes, these rely on points and values attributes
91
- self.contours:list[pv.PolyData] = []
92
- self.grid_steps=grid_steps
93
- self.grid = self._MakeGrid()
94
- #---------------------------------------------------------------------------------------------------------------------#
95
- #------------------------------------------------------ METHODS ------------------------------------------------------#
96
- #---------------------------------------------------------------------------------------------------------------------#
97
- # method for creating grid to interplote on
98
- def _MakeGrid(
99
- self
100
- )->pv.ImageData:
101
- '''
102
- Method for constructing PyVista grid used in interpolation prior to plotting
103
- *This method assumes your points are in Cartesian format*
104
-
105
- Parameters
106
- ----------
107
- points : np.ndarry
108
- Numpy array with shape (N, 3) where N is number of points\n
109
- These are the points that lie within your energy range, generally from GetValAndKpt method\n
110
- These are NOT the points of the grid
111
- steps : tuple
112
- Define the number of grid points along (x,y,z)\n
113
- Default is 50 points along each axis\n
114
- Spacing of grid is automatically calculated from steps and points
115
- '''
116
- # helper functions for finding grid origin and setting grid spacing
117
- def _GetMax(pts:np.ndarray):
118
- xmax = pts[:,0].max()
119
- ymax = pts[:,1].max()
120
- zmax = pts[:,2].max()
121
- return xmax, ymax, zmax
122
- def _GetMin(pts:np.ndarray):
123
- xmin = pts[:,0].min()
124
- ymin = pts[:,1].min()
125
- zmin = pts[:,2].min()
126
- return xmin, ymin, zmin
127
- # begin method for making grid
128
- xmax, ymax, zmax = _GetMax(self.points)
129
- xmin, ymin, zmin = _GetMin(self.points)
130
- dimx = self.grid_steps[0]
131
- dimy = self.grid_steps[1]
132
- dimz = self.grid_steps[2]
133
- grid = pv.ImageData()
134
- stretch_factor = self.grid_stretch
135
- dim_stretch = [2*stretch_factor/dim for dim in self.grid_steps]
136
- grid.origin = (
137
- xmin - 0.5*stretch_factor,
138
- ymin - 0.5*stretch_factor,
139
- zmin - 0.5*stretch_factor
140
- )
141
- grid.spacing = (
142
- 2*xmax/dimx + dim_stretch[0],
143
- 2*ymax/dimy + dim_stretch[1],
144
- 2*zmax/dimz + dim_stretch[2]
145
- )
146
- grid.dimensions = (dimx, dimy, dimz)
147
- return grid
148
- #-----------------------------------------------------------------------------------------------------------------#
149
- # method for selecting which contour to perform depending on if a single band or many bands are passed
150
- def Contour(
151
- self
152
- ):
153
- if self._valdim == 1:
154
- self._Contour(points=self.points, values=self.values)
155
- else:
156
- for band in self.values.T:
157
- self._Contour(points=self.points, values=band)
158
- #-----------------------------------------------------------------------------------------------------------------#
159
- # method for drawing contour from list of values
160
- def _Contour(
161
- self, points:np.ndarray, values:np.ndarray
162
- ):
163
- # setup grid interpolation
164
- null_value = self.fermi_energy + self.energy_level + self.width/2 * 1.05
165
- trans_pts, trans_vals = trnslt.TranslatePoints(
166
- points=points,
167
- values=values.reshape((values.shape[0],1)),
168
- lattice_vecs=self.rec_latt
169
- )
170
- trans_pts = pv.PolyData(trans_pts)
171
- trans_pts['values'] = trans_vals
172
- inter_grid:pv.ImageData = self.grid.interpolate(
173
- trans_pts,
174
- radius=self.radius,
175
- sharpness=2.0,
176
- strategy='null_value',
177
- null_value=null_value
178
- )
179
- # create contour
180
- iso_range = [self.fermi_energy + self.energy_level, self.fermi_energy + self.energy_level]
181
- contour:pv.PolyData = inter_grid.contour(
182
- isosurfaces=2,
183
- rng=iso_range,
184
- method='contour'
185
- )
186
- self.contours.append(contour)
187
- #-----------------------------------------------------------------------------------------------------------------#
188
- # method for reading kpoints and eigenvalues from ABINIT WFK file
189
- def _ReadAbinit(
190
- self, filename:str
191
- )->tuple[np.ndarray,np.ndarray, list, np.ndarray]:
192
- # collect kpoints and eigenvalues from wfk file
193
- wfk = ar.AbinitWFK(filename=filename)
194
- eigs = []
195
- syms = np.array(wfk.symrel)
196
- nsym = wfk.nsym
197
- kpoints = np.array(wfk.kpts)
198
- ir_kpts = kpoints
199
- nbands = wfk.bands[0]
200
- nkpt = wfk.nkpt
201
- self.fermi_energy = wfk.fermi
202
- self.rec_latt = wc.WFK(lattice=wfk.real_lattice).Real2Reciprocal()
203
- for wfk_obj in wfk.ReadEigenvalues():
204
- eigs.append(wfk_obj.eigenvalues)
205
- eigs = np.array(eigs).reshape((nkpt,nbands))
206
- # look through eigenvalues to find which bands to contour
207
- min_val = self.fermi_energy + self.energy_level - self.width/2
208
- max_val = self.fermi_energy + self.energy_level + self.width/2
209
- band_num = []
210
- for i in range(nbands):
211
- for eigval in eigs[:,i]:
212
- if min_val <= eigval <= max_val:
213
- band_num.append(i)
214
- break
215
- # return kpoints and bands of interest
216
- bands = np.take(eigs, band_num, axis=1)
217
- shifts = brlzn.BZ(self.rec_latt).GetShifts(kpoints)
218
- kpoints = kpoints - shifts
219
- ir_kpts -= shifts
220
- ir_kpts = np.matmul(ir_kpts,self.rec_latt)
221
- wfk = wc.WFK(symrel=syms, nsym=nsym, nbands=len(band_num), nkpt=nkpt)
222
- all_kpts = np.zeros((1,3))
223
- all_eigs = np.zeros((1,bands.shape[1]))
224
- # symmetrize kpoints
225
- for i, kpt in enumerate(kpoints):
226
- unique_kpts, _ = wfk.Symmetrize(
227
- points=kpt,
228
- reciprocal=True,
229
- )
230
- all_kpts = np.concatenate((all_kpts, unique_kpts), axis=0)
231
- new_eigs = np.repeat(bands[i,:].reshape((1,-1)), unique_kpts.shape[0], axis=0)
232
- all_eigs = np.concatenate((all_eigs, new_eigs), axis=0)
233
- kpoints = np.delete(all_kpts, 0, axis=0)
234
- bands = np.delete(all_eigs, 0, axis=0)
235
- kpoints = np.matmul(kpoints, self.rec_latt)
1
+ import pyvista as pv
2
+ import numpy as np
3
+ from . import translate as trnslt
4
+ from . import abinit_reader as ar
5
+ from . import wfk_class as wc
6
+ from . import brillouin_zone as brlzn
7
+
8
+ # object for creating reciprocal space energy isosurfaces
9
+ class Isosurface():
10
+ '''
11
+ Class for constructing contours from WFK eigenvalues
12
+
13
+ Parameters
14
+ ----------
15
+ points : np.ndarray
16
+ Array of reciprocal space points to construct contour over
17
+ Shape (N,3)
18
+ values : np.ndarray
19
+ Array of eigenvalues for each reciprocal space point
20
+ Shape (N,1) or (N,#Bands)
21
+ nbands : list[int]
22
+ List of bands to create contours for, aka indices of bands that cross energy isosurface
23
+ rec_latt : np.ndarray
24
+ Reciprocal lattice vectors
25
+ wfk_name : str
26
+ If points and values arrays are not provided, then construct contours from reading in an ABINIT WFK file
27
+ Path to wfk file
28
+ grid_steps : tuple
29
+ Set fineness of grid that contours are interpolated on
30
+ Default (50,50,50)
31
+ fermi_energy : float
32
+ Fermi energy
33
+ Default 0.0
34
+ energy_level : float
35
+ Sets energy isosurface relative to the Fermi energy
36
+ Default 0.0 (this samples the Fermi energy)
37
+ width : float
38
+ Sets the range above and below the energy_level that eigenvalues are sampled from
39
+ Default 0.005 (Assumes units of Hartree)
40
+ radius : float
41
+ Radius of gaussian interpolation
42
+ Default 0.05, this scales inversely with kpoint grid size (larger grids require smaller radius)
43
+ sym_ops : np.ndarray
44
+ Array of symmetry operations for generating points in Brillouin Zone
45
+ grid_stretch : float
46
+ Value that stretches grid that is used to interpolate bands
47
+ If portions of Brillouin Zone are cropped off, increase this value
48
+ Default is 0.15
49
+
50
+ Methods
51
+ -------
52
+ Contour
53
+ Generates energy isosurface contours
54
+ '''
55
+ def __init__(
56
+ self, points:np.ndarray=np.zeros(1), values:np.ndarray=np.zeros(1), rec_latt:np.ndarray=np.zeros(1),
57
+ wfk_name:str='', grid_steps:tuple=(50,50,50), fermi_energy:float=0.0, energy_level:float=0.0,
58
+ width:float=0.005, radius:float=0.05, nbands:list[int]=[], sym_ops:np.ndarray=np.zeros(1),
59
+ grid_stretch:float=0.15
60
+ )->None:
61
+ # define attributes
62
+ self.points=points
63
+ self.values=values
64
+ self.wfk_name=wfk_name
65
+ self.rec_latt=rec_latt
66
+ self.fermi_energy=fermi_energy
67
+ self.energy_level=energy_level
68
+ self.width=width
69
+ self.radius=radius
70
+ self.nbands=nbands
71
+ self.sym_ops=sym_ops
72
+ self.grid_stretch=grid_stretch
73
+ # check if enough information is provided to class to construct isosurfaces
74
+ if self.points.shape == (1,) and self.values.shape == (1,):
75
+ if wfk_name == '':
76
+ raise ValueError((
77
+ 'Either the points and the values attributes must be defined or the wfk_name attribute '
78
+ 'must be defined'
79
+ ))
80
+ else:
81
+ self.points, self.values, self.nbands, self.ir_kpts = self._ReadAbinit(wfk_name)
82
+ # check if information provided is in correct format
83
+ if len(self.values.shape) > 2:
84
+ raise ValueError((
85
+ f'Provided values have dimension {len(self.values.shape)}'
86
+ 'Values must be 1 or 2 dimensional'
87
+ ))
88
+ else:
89
+ self._valdim=len(self.values.shape)
90
+ # more attributes, these rely on points and values attributes
91
+ self.contours:list[pv.PolyData] = []
92
+ self.grid_steps=grid_steps
93
+ self.grid = self._MakeGrid()
94
+ #---------------------------------------------------------------------------------------------------------------------#
95
+ #------------------------------------------------------ METHODS ------------------------------------------------------#
96
+ #---------------------------------------------------------------------------------------------------------------------#
97
+ # method for creating grid to interplote on
98
+ def _MakeGrid(
99
+ self
100
+ )->pv.ImageData:
101
+ '''
102
+ Method for constructing PyVista grid used in interpolation prior to plotting
103
+ *This method assumes your points are in Cartesian format*
104
+
105
+ Parameters
106
+ ----------
107
+ points : np.ndarry
108
+ Numpy array with shape (N, 3) where N is number of points\n
109
+ These are the points that lie within your energy range, generally from GetValAndKpt method\n
110
+ These are NOT the points of the grid
111
+ steps : tuple
112
+ Define the number of grid points along (x,y,z)\n
113
+ Default is 50 points along each axis\n
114
+ Spacing of grid is automatically calculated from steps and points
115
+ '''
116
+ # helper functions for finding grid origin and setting grid spacing
117
+ def _GetMax(pts:np.ndarray):
118
+ xmax = pts[:,0].max()
119
+ ymax = pts[:,1].max()
120
+ zmax = pts[:,2].max()
121
+ return xmax, ymax, zmax
122
+ def _GetMin(pts:np.ndarray):
123
+ xmin = pts[:,0].min()
124
+ ymin = pts[:,1].min()
125
+ zmin = pts[:,2].min()
126
+ return xmin, ymin, zmin
127
+ # begin method for making grid
128
+ xmax, ymax, zmax = _GetMax(self.points)
129
+ xmin, ymin, zmin = _GetMin(self.points)
130
+ dimx = self.grid_steps[0]
131
+ dimy = self.grid_steps[1]
132
+ dimz = self.grid_steps[2]
133
+ grid = pv.ImageData()
134
+ stretch_factor = self.grid_stretch
135
+ dim_stretch = [2*stretch_factor/dim for dim in self.grid_steps]
136
+ grid.origin = (
137
+ xmin - 0.5*stretch_factor,
138
+ ymin - 0.5*stretch_factor,
139
+ zmin - 0.5*stretch_factor
140
+ )
141
+ grid.spacing = (
142
+ 2*xmax/dimx + dim_stretch[0],
143
+ 2*ymax/dimy + dim_stretch[1],
144
+ 2*zmax/dimz + dim_stretch[2]
145
+ )
146
+ grid.dimensions = (dimx, dimy, dimz)
147
+ return grid
148
+ #-----------------------------------------------------------------------------------------------------------------#
149
+ # method for selecting which contour to perform depending on if a single band or many bands are passed
150
+ def Contour(
151
+ self
152
+ ):
153
+ if self._valdim == 1:
154
+ self._Contour(points=self.points, values=self.values)
155
+ else:
156
+ for band in self.values.T:
157
+ self._Contour(points=self.points, values=band)
158
+ #-----------------------------------------------------------------------------------------------------------------#
159
+ # method for drawing contour from list of values
160
+ def _Contour(
161
+ self, points:np.ndarray, values:np.ndarray
162
+ ):
163
+ # setup grid interpolation
164
+ null_value = self.fermi_energy + self.energy_level + self.width/2 * 1.05
165
+ trans_pts, trans_vals = trnslt.TranslatePoints(
166
+ points=points,
167
+ values=values.reshape((values.shape[0],1)),
168
+ lattice_vecs=self.rec_latt
169
+ )
170
+ trans_pts = pv.PolyData(trans_pts)
171
+ trans_pts['values'] = trans_vals
172
+ inter_grid:pv.ImageData = self.grid.interpolate(
173
+ trans_pts,
174
+ radius=self.radius,
175
+ sharpness=2.0,
176
+ strategy='null_value',
177
+ null_value=null_value
178
+ ) # type: ignore
179
+ # create contour
180
+ iso_range = [self.fermi_energy + self.energy_level, self.fermi_energy + self.energy_level]
181
+ contour:pv.PolyData = inter_grid.contour(
182
+ isosurfaces=2,
183
+ rng=iso_range,
184
+ method='contour'
185
+ ) # type: ignore
186
+ self.contours.append(contour)
187
+ #-----------------------------------------------------------------------------------------------------------------#
188
+ # method for reading kpoints and eigenvalues from ABINIT WFK file
189
+ def _ReadAbinit(
190
+ self, filename:str
191
+ )->tuple[np.ndarray,np.ndarray, list, np.ndarray]:
192
+ # collect kpoints and eigenvalues from wfk file
193
+ wfk = ar.AbinitWFK(filename=filename)
194
+ eigs = []
195
+ syms = np.array(wfk.symrel)
196
+ nsym = wfk.nsym
197
+ kpoints = np.array(wfk.kpts)
198
+ ir_kpts = kpoints
199
+ nbands = wfk.bands[0]
200
+ nkpt = wfk.nkpt
201
+ self.fermi_energy = wfk.fermi
202
+ self.rec_latt = wc.WFK(lattice=wfk.real_lattice).rec_latt
203
+ for wfk_obj in wfk.ReadEigenvalues():
204
+ eigs.append(wfk_obj.eigenvalues)
205
+ eigs = np.array(eigs).reshape((nkpt,nbands))
206
+ # look through eigenvalues to find which bands to contour
207
+ min_val = self.fermi_energy + self.energy_level - self.width/2
208
+ max_val = self.fermi_energy + self.energy_level + self.width/2
209
+ band_num = []
210
+ for i in range(nbands):
211
+ for eigval in eigs[:,i]:
212
+ if min_val <= eigval <= max_val:
213
+ band_num.append(i)
214
+ break
215
+ # return kpoints and bands of interest
216
+ bands = np.take(eigs, band_num, axis=1)
217
+ shifts = brlzn.BZ(self.rec_latt).GetShifts(kpoints)
218
+ kpoints = kpoints - shifts
219
+ ir_kpts -= shifts
220
+ ir_kpts = np.matmul(ir_kpts,self.rec_latt)
221
+ wfk = wc.WFK(symrel=syms, nsym=nsym, nbands=len(band_num), nkpt=nkpt)
222
+ all_kpts = np.zeros((1,3))
223
+ all_eigs = np.zeros((1,bands.shape[1]))
224
+ # symmetrize kpoints
225
+ for i, kpt in enumerate(kpoints):
226
+ unique_kpts, _ = wfk.Symmetrize(
227
+ points=kpt,
228
+ reciprocal=True,
229
+ )
230
+ all_kpts = np.concatenate((all_kpts, unique_kpts), axis=0)
231
+ new_eigs = np.repeat(bands[i,:].reshape((1,-1)), unique_kpts.shape[0], axis=0)
232
+ all_eigs = np.concatenate((all_eigs, new_eigs), axis=0)
233
+ kpoints = np.delete(all_kpts, 0, axis=0)
234
+ bands = np.delete(all_eigs, 0, axis=0)
235
+ kpoints = np.matmul(kpoints, self.rec_latt)
236
236
  return kpoints, bands, band_num, ir_kpts