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/abinit_reader.py +1094 -1094
- bandu/bandu.py +320 -313
- bandu/brillouin_zone.py +185 -185
- bandu/colors.py +46 -46
- bandu/isosurface_class.py +235 -235
- bandu/plotter.py +599 -599
- bandu/translate.py +37 -37
- bandu/wfk_class.py +557 -556
- bandu/xsf_reader.py +91 -91
- {bandu-1.3.6.dist-info → bandu-1.3.7.dist-info}/METADATA +192 -192
- bandu-1.3.7.dist-info/RECORD +14 -0
- {bandu-1.3.6.dist-info → bandu-1.3.7.dist-info}/WHEEL +1 -1
- {bandu-1.3.6.dist-info → bandu-1.3.7.dist-info}/licenses/LICENSE +21 -21
- bandu-1.3.6.dist-info/RECORD +0 -14
- {bandu-1.3.6.dist-info → bandu-1.3.7.dist-info}/top_level.txt +0 -0
bandu/plotter.py
CHANGED
|
@@ -1,600 +1,600 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pyvista as pv
|
|
3
|
-
from pyvistaqt import BackgroundPlotter
|
|
4
|
-
from copy import copy
|
|
5
|
-
import pickle as pkl
|
|
6
|
-
from matplotlib.colors import ListedColormap
|
|
7
|
-
from . import brillouin_zone as brlzn
|
|
8
|
-
from . import isosurface_class as ic
|
|
9
|
-
from . import wfk_class as wc
|
|
10
|
-
from . import translate as trslt
|
|
11
|
-
|
|
12
|
-
class Plotter():
|
|
13
|
-
'''
|
|
14
|
-
Class for creating 3D plot of Fermi surface and projection of BandU functions on Fermi surface.
|
|
15
|
-
|
|
16
|
-
Parameters
|
|
17
|
-
----------
|
|
18
|
-
isosurface : Isosurface
|
|
19
|
-
An Isosurface object that contains the contours to be plotted
|
|
20
|
-
save : bool
|
|
21
|
-
Create save file of isosurface plot that can be loaded with the Load method
|
|
22
|
-
Default will save the plot (True)
|
|
23
|
-
save_file : str
|
|
24
|
-
Name of save file
|
|
25
|
-
Default is "Fermi_surface.pkl"
|
|
26
|
-
empty_mesh : bool
|
|
27
|
-
Allow PyVista plotter to plot meshes even if no surface is present
|
|
28
|
-
Default will throw exception of an empty surface is being plotted (False)
|
|
29
|
-
plot : bool
|
|
30
|
-
Enable creation of PyVista Plotter
|
|
31
|
-
Must be enabled for plotting, default enables plotter (True)
|
|
32
|
-
|
|
33
|
-
Methods
|
|
34
|
-
-------
|
|
35
|
-
Plot
|
|
36
|
-
Generates 3D plot of Fermi surface from contours of Isosurface object
|
|
37
|
-
SurfaceColor
|
|
38
|
-
Reads in a BandU XSF file and Fermi surface ABINIT WFK file to calculate the overlap between the BandU
|
|
39
|
-
function and the states at the Fermi energy
|
|
40
|
-
Load
|
|
41
|
-
Loads a save file
|
|
42
|
-
'''
|
|
43
|
-
def __init__(
|
|
44
|
-
self, isosurface:ic.Isosurface=ic.Isosurface(points=np.ones((1,3))), save:bool=True,
|
|
45
|
-
empty_mesh:bool=False, _debug:bool=False, save_file:str='Fermi_surface.pkl', plot:bool=True
|
|
46
|
-
):
|
|
47
|
-
self.isosurface=isosurface
|
|
48
|
-
self.save=save
|
|
49
|
-
self.save_file=save_file
|
|
50
|
-
self._debug=_debug
|
|
51
|
-
self.plot=plot
|
|
52
|
-
if self.plot:
|
|
53
|
-
self.p:pv.Plotter=BackgroundPlotter(window_size=(600,400))
|
|
54
|
-
pv.global_theme.allow_empty_mesh=empty_mesh
|
|
55
|
-
self.p.enable_depth_peeling(number_of_peels=10)
|
|
56
|
-
#---------------------------------------------------------------------------------------------------------------------#
|
|
57
|
-
#------------------------------------------------------ METHODS ------------------------------------------------------#
|
|
58
|
-
#---------------------------------------------------------------------------------------------------------------------#
|
|
59
|
-
# method for turning opacity to zero outside of BZ
|
|
60
|
-
def _SetOpacities(
|
|
61
|
-
self, contour:pv.PolyData
|
|
62
|
-
)->np.ndarray:
|
|
63
|
-
bz = brlzn.BZ(self.isosurface.rec_latt)
|
|
64
|
-
opacities = bz.PointLocate(contour.points, cart=False)
|
|
65
|
-
opacities[opacities >= 0] = 1
|
|
66
|
-
opacities[opacities < 0] = 0
|
|
67
|
-
return opacities
|
|
68
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
69
|
-
# method to add nesting vector to isosurface plot
|
|
70
|
-
def _AddArrow(
|
|
71
|
-
self, arrow:list, rec_lattice:np.ndarray, show_endpoints:bool, color:str
|
|
72
|
-
)->None:
|
|
73
|
-
tail = np.array(arrow[0])
|
|
74
|
-
shift = np.array(arrow[1])
|
|
75
|
-
tail = np.matmul(tail, rec_lattice)
|
|
76
|
-
shift = np.matmul(shift, rec_lattice)
|
|
77
|
-
scale = np.linalg.norm(shift).astype(float)
|
|
78
|
-
py_arrow = pv.Arrow(
|
|
79
|
-
start=tail,
|
|
80
|
-
direction=shift,
|
|
81
|
-
tip_radius=0.05/scale,
|
|
82
|
-
tip_length=0.15/scale,
|
|
83
|
-
shaft_radius=0.025/scale,
|
|
84
|
-
scale=scale
|
|
85
|
-
)
|
|
86
|
-
if show_endpoints:
|
|
87
|
-
points = np.array([tail, shift+tail])
|
|
88
|
-
points = pv.PolyData(points)
|
|
89
|
-
self.p.add_mesh(points.points, point_size=20, color='black', render_points_as_spheres=True)
|
|
90
|
-
self.p.add_mesh(py_arrow, color=color)
|
|
91
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
92
|
-
# method to visualize cross-section of surface
|
|
93
|
-
def _CrossSection(
|
|
94
|
-
self, vecs:list, points:np.ndarray, width:float, rec_lattice:np.ndarray, bz_points:np.ndarray,
|
|
95
|
-
linear:bool=False, two_dim:bool=False
|
|
96
|
-
)->np.ndarray:
|
|
97
|
-
from scipy.spatial import Delaunay
|
|
98
|
-
# cross section is defined by plane of two perpendicular vectors
|
|
99
|
-
if len(vecs) == 2:
|
|
100
|
-
vec1 = np.matmul(vecs[0], rec_lattice)
|
|
101
|
-
vec2 = np.matmul(vecs[1], rec_lattice)
|
|
102
|
-
norm = np.cross(vec1, vec2)
|
|
103
|
-
norm /= np.linalg.norm(norm)
|
|
104
|
-
# cross section is defined by normal vector
|
|
105
|
-
else:
|
|
106
|
-
vec = np.matmul(vecs, rec_lattice)
|
|
107
|
-
norm = vec/np.linalg.norm(vec)
|
|
108
|
-
# surface points can be thought of vectors in 3D space, here we normalize the vectors
|
|
109
|
-
norm_points = np.array(points/np.linalg.norm(points, axis=1).reshape((len(points),1)))
|
|
110
|
-
# find dot product between vector normal to cross section and all normalized points
|
|
111
|
-
angs = np.matmul(norm, norm_points.T)
|
|
112
|
-
# opacity linearly fades out as points get farther from cross section
|
|
113
|
-
if linear:
|
|
114
|
-
angs[angs <= width] = 2
|
|
115
|
-
angs -= 1
|
|
116
|
-
angs = np.abs(angs)
|
|
117
|
-
# opacity is zero beyond width of cross section
|
|
118
|
-
else:
|
|
119
|
-
# opacity is zero beyond width on both sides of cross section
|
|
120
|
-
if two_dim:
|
|
121
|
-
angs = np.abs(angs)
|
|
122
|
-
opacities = np.zeros(len(points))
|
|
123
|
-
opacities[angs <= width] = 1
|
|
124
|
-
angs = opacities
|
|
125
|
-
# set opacity to zero for points outside of the BZ
|
|
126
|
-
beyond_bz = Delaunay(bz_points).find_simplex(points)
|
|
127
|
-
angs[beyond_bz < 0] = 0
|
|
128
|
-
return angs
|
|
129
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
130
|
-
# method for plotting isosurface contours
|
|
131
|
-
def Plot(
|
|
132
|
-
self, show_points:bool=False, show_outline:bool=False, show_axes:bool=True, show_isosurf:bool=True,
|
|
133
|
-
smooth:bool=True, lighting:bool=True, ambient:float=0.5, diffuse:float=0.5, specular:float=1.0,
|
|
134
|
-
specular_power:float=128.0, pbr:bool=False, metallic:float=0.5, roughness:float=0.5,
|
|
135
|
-
colormap:str|ListedColormap='plasma', color:str='white', bz_show:bool=True, bz_width:int=3, arrow:list=[],
|
|
136
|
-
show_endpoints:bool=False, arrow_color:str='yellow', periodic_arrow:list=[], camera_position:list=[],
|
|
137
|
-
cross_section:list=[], cross_width:float=0.1, linear:bool=False, two_dim:bool=True, show_bands:float|list=1.0,
|
|
138
|
-
surface_vals:list|None=None, show_ird_points:bool=False
|
|
139
|
-
):
|
|
140
|
-
'''
|
|
141
|
-
Method for plotting contours made from the Isosurface object
|
|
142
|
-
|
|
143
|
-
Parameters
|
|
144
|
-
----------
|
|
145
|
-
colormap : str | ListedColormap
|
|
146
|
-
Choose colormap for isosurface that colors according to assigned scalars of surface\n
|
|
147
|
-
Can use Colors class to use default or create custom colormaps\n
|
|
148
|
-
Default is matplotlib's plasma
|
|
149
|
-
bz_width : int
|
|
150
|
-
Line width of Brillouin Zone\n
|
|
151
|
-
Default is 3
|
|
152
|
-
bz_show : bool
|
|
153
|
-
Show the Brillouin Zone
|
|
154
|
-
Default is to show (True)
|
|
155
|
-
smooth : bool
|
|
156
|
-
Use smooth lightning techniques\n
|
|
157
|
-
Default is to use smoothing (True)
|
|
158
|
-
lighting : bool
|
|
159
|
-
Apply directional lighting to surface\n
|
|
160
|
-
Default is to enable directional lighting (True)
|
|
161
|
-
ambient : float
|
|
162
|
-
Intensity of light on surface\n
|
|
163
|
-
Default is 0.5
|
|
164
|
-
diffuse : float
|
|
165
|
-
Amount of light scattering\n
|
|
166
|
-
Default is 0.5
|
|
167
|
-
specular : float
|
|
168
|
-
Amount of reflected light\n
|
|
169
|
-
Default is 1.0 (max)
|
|
170
|
-
specular_power : float
|
|
171
|
-
Determines how sharply light is reflected\n
|
|
172
|
-
Default is 128.0 (max)
|
|
173
|
-
pbr : bool
|
|
174
|
-
Apply physics based rendering\n
|
|
175
|
-
Default is no physics based rendering (False)
|
|
176
|
-
metallic : float
|
|
177
|
-
Determine how metallic-looking the surface is, only considered with pbr\n
|
|
178
|
-
Default is 0.5
|
|
179
|
-
roughness : float
|
|
180
|
-
Determine how smooth/rough surface appear, only considered with pbr\n
|
|
181
|
-
Default is 0.5
|
|
182
|
-
color : str
|
|
183
|
-
Sets color of surface (colormap overwrites this)\n
|
|
184
|
-
Sets color of reflected light (colormap does not overwrite this)\n
|
|
185
|
-
Default is white
|
|
186
|
-
arrow : list
|
|
187
|
-
Parameters for plotting nesting vector on top of Fermi surface\n
|
|
188
|
-
Element_0 of list should be starting (or tail) position of arrow\n
|
|
189
|
-
Element_1 of list should be orientation of arrow with desired magnitude\n
|
|
190
|
-
Both the tail and orientation should be specified in reduced reciprocal space coordinates
|
|
191
|
-
arrow_color : str
|
|
192
|
-
Color of nesting arrow\n
|
|
193
|
-
Default is black
|
|
194
|
-
show_endpoints : bool
|
|
195
|
-
Plot points on the end of the arrow to make visualizing start and end easier\n
|
|
196
|
-
Default is to not show endpoints (False)
|
|
197
|
-
periodic_arrow : list
|
|
198
|
-
Adds periodic image of arrow that is translated [X,Y,Z] cells\n
|
|
199
|
-
Where X, Y, and Z are the cell indices
|
|
200
|
-
show_bands : float | list
|
|
201
|
-
Specifies the opacity of each band\n
|
|
202
|
-
If a single float is provided, all bands will be plotted with the same opacity\n
|
|
203
|
-
If a list is provided, each band will be plotted with the opacity of the respective list element
|
|
204
|
-
show_axes : bool
|
|
205
|
-
Plots reciprocal cell axes with a* as red, b* as green, and c* as blue\n
|
|
206
|
-
Default is to show axes (True)
|
|
207
|
-
cross_section : list
|
|
208
|
-
Plot cross section through surface\n
|
|
209
|
-
If one vector is provided, it is assumed to be the normal to the cross section plane\n
|
|
210
|
-
Else, cross section is defined by plane made by two vectors\n
|
|
211
|
-
Vectors should be specified in reduced coordinates
|
|
212
|
-
cross_width : float
|
|
213
|
-
Width of cross section\n
|
|
214
|
-
Default is 0.15
|
|
215
|
-
linear : bool
|
|
216
|
-
Cross section linearly fades out\n
|
|
217
|
-
Default is not fade out linearly (False)
|
|
218
|
-
two_dim : bool
|
|
219
|
-
Cross section is a 2D slice instead of a section\n
|
|
220
|
-
Default is to show cross section as 2D slice (True)
|
|
221
|
-
surface_vals : np.ndarray
|
|
222
|
-
A list of values defining the coloration of the isosurface
|
|
223
|
-
Default plots no coloration
|
|
224
|
-
'''
|
|
225
|
-
# save file
|
|
226
|
-
if self.save:
|
|
227
|
-
with open(self.save_file, 'wb') as f:
|
|
228
|
-
kwargs = locals()
|
|
229
|
-
kwargs.pop('self', None)
|
|
230
|
-
kwargs.pop('f', None)
|
|
231
|
-
pkl.dump(kwargs, f)
|
|
232
|
-
pkl.dump(self.isosurface, f)
|
|
233
|
-
if not self.plot:
|
|
234
|
-
raise SystemExit()
|
|
235
|
-
# plot BZ boundary
|
|
236
|
-
vor_verts = brlzn.BZ(self.isosurface.rec_latt).vertices
|
|
237
|
-
if bz_show:
|
|
238
|
-
self.p.add_lines(vor_verts, color='black', width=bz_width)
|
|
239
|
-
# set opacity of each band
|
|
240
|
-
if type(show_bands) is float:
|
|
241
|
-
band_ops = show_bands*np.ones(len(self.isosurface.contours), dtype=float)
|
|
242
|
-
show_bands = band_ops.tolist()
|
|
243
|
-
# loop through contours of isosurface object and plot each
|
|
244
|
-
for i, contour in enumerate(self.isosurface.contours):
|
|
245
|
-
if surface_vals is None:
|
|
246
|
-
scalars = None
|
|
247
|
-
surf_max = 0.0
|
|
248
|
-
else:
|
|
249
|
-
scalars = surface_vals[i]
|
|
250
|
-
surf_max = np.max(surface_vals[i])
|
|
251
|
-
# opacities can be adjusted to show a cross section of contours
|
|
252
|
-
if cross_section != []:
|
|
253
|
-
opacities = self._CrossSection(
|
|
254
|
-
cross_section,
|
|
255
|
-
contour.points,
|
|
256
|
-
cross_width,
|
|
257
|
-
self.isosurface.rec_latt,
|
|
258
|
-
vor_verts,
|
|
259
|
-
linear=linear,
|
|
260
|
-
two_dim=two_dim
|
|
261
|
-
)
|
|
262
|
-
# otherwise opacities are set to just render contours in the BZ
|
|
263
|
-
else:
|
|
264
|
-
opacities = self._SetOpacities(contour=contour)
|
|
265
|
-
# set opacity of bands
|
|
266
|
-
opacities = [op*show_bands[i] for op in opacities] # type: ignore
|
|
267
|
-
# lighting is set to make surface appear more smooth
|
|
268
|
-
if smooth:
|
|
269
|
-
contour = contour.smooth_taubin(n_iter=100, pass_band=0.05)
|
|
270
|
-
# plot contours
|
|
271
|
-
if show_isosurf:
|
|
272
|
-
self.p.add_mesh(
|
|
273
|
-
contour,
|
|
274
|
-
style='surface',
|
|
275
|
-
smooth_shading=smooth,
|
|
276
|
-
lighting=lighting,
|
|
277
|
-
ambient=ambient,
|
|
278
|
-
diffuse=diffuse,
|
|
279
|
-
specular=specular,
|
|
280
|
-
specular_power=specular_power,
|
|
281
|
-
pbr=pbr,
|
|
282
|
-
metallic=metallic,
|
|
283
|
-
roughness=roughness,
|
|
284
|
-
scalars=scalars,
|
|
285
|
-
clim=[0.0,surf_max],
|
|
286
|
-
cmap=colormap,
|
|
287
|
-
opacity=opacities,
|
|
288
|
-
color=color,
|
|
289
|
-
show_scalar_bar=True,
|
|
290
|
-
)
|
|
291
|
-
# plot irreducible kpoints
|
|
292
|
-
if show_ird_points:
|
|
293
|
-
pts = pv.PolyData(self.isosurface.ir_kpts)
|
|
294
|
-
self.p.add_mesh(pts.points, color='black')
|
|
295
|
-
# plot points that are used to construct isosurface
|
|
296
|
-
if show_points:
|
|
297
|
-
pts = pv.PolyData(self.isosurface.points)
|
|
298
|
-
self.p.add_mesh(pts.points, color='black')
|
|
299
|
-
# plot outline of grid used in interpolation
|
|
300
|
-
if show_outline:
|
|
301
|
-
self.p.add_mesh(self.isosurface.grid.outline())
|
|
302
|
-
# plot reciprocal space axes
|
|
303
|
-
if show_axes:
|
|
304
|
-
axes_colors = ['red', 'green', 'blue']
|
|
305
|
-
for i in range(3):
|
|
306
|
-
axis = self.isosurface.rec_latt[i,:]
|
|
307
|
-
color = axes_colors[i]
|
|
308
|
-
self._AddArrow([[0,0,0], axis], np.identity(3), False, color)
|
|
309
|
-
# plot nesting vector
|
|
310
|
-
if arrow != []:
|
|
311
|
-
self._AddArrow(arrow, self.isosurface.rec_latt, show_endpoints, arrow_color)
|
|
312
|
-
# plot a periodic image of the nesting vector
|
|
313
|
-
if periodic_arrow != []:
|
|
314
|
-
tail = np.array(arrow[0])
|
|
315
|
-
cell = np.array(periodic_arrow, dtype=float)
|
|
316
|
-
tail += cell
|
|
317
|
-
arrow[0] = tail
|
|
318
|
-
self._AddArrow(arrow, self.isosurface.rec_latt, show_endpoints, arrow_color)
|
|
319
|
-
# set camera position
|
|
320
|
-
if camera_position != []:
|
|
321
|
-
camera_pos = np.array(camera_position).reshape((3,3))
|
|
322
|
-
camera_pos = np.matmul(camera_pos, self.isosurface.rec_latt)
|
|
323
|
-
self.p.camera_position = camera_pos
|
|
324
|
-
self._Render()
|
|
325
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
326
|
-
# Render isosurfaces
|
|
327
|
-
def _Render(
|
|
328
|
-
self
|
|
329
|
-
):
|
|
330
|
-
self.p.enable_parallel_projection() # type: ignore
|
|
331
|
-
self.p.enable_custom_trackball_style(
|
|
332
|
-
left='rotate',
|
|
333
|
-
shift_left='spin',
|
|
334
|
-
right='pan',
|
|
335
|
-
) # type: ignore
|
|
336
|
-
self.p.set_focus([0.0,0.0,0.0]) # type: ignore
|
|
337
|
-
self.p.show()
|
|
338
|
-
self.p.app.exec_() # type: ignore
|
|
339
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
340
|
-
# method to compute bandu fxn and one electron wfk overlaps
|
|
341
|
-
def _OverlapsWithSym(
|
|
342
|
-
self, ir_wfk:wc.WFK, bandu:wc.WFK
|
|
343
|
-
)->np.ndarray:
|
|
344
|
-
# kpoint to symmetrically generate
|
|
345
|
-
kpt = ir_wfk.kpoints
|
|
346
|
-
# find number of distinct symmetries
|
|
347
|
-
sym_kpoints, _ = ir_wfk.Symmetrize(kpt, unique=False, reciprocal=True)
|
|
348
|
-
dupes, unique_inds = ir_wfk._FindOrbit(sym_kpoints)
|
|
349
|
-
count = sum([1 for i, _ in enumerate(unique_inds) if i not in dupes])
|
|
350
|
-
# initialize array for overlap values
|
|
351
|
-
num_bands = len(self.isosurface.nbands)
|
|
352
|
-
overlap_vals = np.zeros((count,num_bands), dtype=float)
|
|
353
|
-
# loop over bands
|
|
354
|
-
for i, band in enumerate(self.isosurface.nbands):
|
|
355
|
-
# generate symmetric coefficients for each band
|
|
356
|
-
for j, wfk in enumerate(ir_wfk.SymWFKs(kpoint=kpt, band=band)):
|
|
357
|
-
wfk = wfk.GridWFK()
|
|
358
|
-
wfk = wfk.IFFT()
|
|
359
|
-
wfk = wfk.Normalize()
|
|
360
|
-
overlap = np.sum(np.conj(bandu.wfk_coeffs)*wfk.wfk_coeffs)
|
|
361
|
-
overlap = np.square(np.abs(overlap))
|
|
362
|
-
overlap_vals[j,i] = overlap
|
|
363
|
-
return overlap_vals
|
|
364
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
365
|
-
# method to compute bandu fxn and one electron wfk overlaps
|
|
366
|
-
def _OverlapsNoSym(
|
|
367
|
-
self, ir_wfk:wc.WFK, bandu:wc.WFK
|
|
368
|
-
)->np.ndarray:
|
|
369
|
-
# initialize array for overlap values
|
|
370
|
-
num_bands = len(self.isosurface.nbands)
|
|
371
|
-
overlap_vals = np.zeros((1,num_bands), dtype=float)
|
|
372
|
-
# loop over bands
|
|
373
|
-
for i, band in enumerate(self.isosurface.nbands):
|
|
374
|
-
wfk = ir_wfk.GridWFK(band_index=band)
|
|
375
|
-
wfk = wfk.IFFT()
|
|
376
|
-
wfk = wfk.Normalize()
|
|
377
|
-
overlap = np.sum(np.conj(bandu.wfk_coeffs)*wfk.wfk_coeffs)
|
|
378
|
-
overlap = np.square(np.abs(overlap))
|
|
379
|
-
overlap_vals[0,i] = overlap
|
|
380
|
-
return overlap_vals
|
|
381
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
382
|
-
# method to interpolate overlap values
|
|
383
|
-
def _InterpolateOverlaps(
|
|
384
|
-
self, contour:pv.PolyData, overlap_values:np.ndarray
|
|
385
|
-
)->np.ndarray:
|
|
386
|
-
# recreate eigenvalue grid as base for surface color grid
|
|
387
|
-
color_grid = copy(self.isosurface.grid)
|
|
388
|
-
# translate overlap values to 3x3x3 grid
|
|
389
|
-
trans_color_points, trans_color_values = trslt.TranslatePoints(
|
|
390
|
-
self.isosurface.points,
|
|
391
|
-
overlap_values.reshape((-1,1)),
|
|
392
|
-
self.isosurface.rec_latt
|
|
393
|
-
)
|
|
394
|
-
# construct PyVista object from translated overlap grid
|
|
395
|
-
trans_color_points = pv.PolyData(trans_color_points)
|
|
396
|
-
trans_color_points['values'] = trans_color_values
|
|
397
|
-
# interpolate translated grid
|
|
398
|
-
color_grid = color_grid.interpolate(
|
|
399
|
-
trans_color_points,
|
|
400
|
-
sharpness=2.0,
|
|
401
|
-
radius=self.isosurface.radius,
|
|
402
|
-
strategy='null_value'
|
|
403
|
-
)
|
|
404
|
-
# sample color values from interpolated color grid
|
|
405
|
-
color_sample = contour.sample(color_grid)
|
|
406
|
-
return color_sample.active_scalars
|
|
407
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
408
|
-
# method to calculate surface color values from XSF and WFK
|
|
409
|
-
def SurfaceColor(
|
|
410
|
-
self, wfk_path:str, xsf_path:str, sym:bool=False
|
|
411
|
-
)->list:
|
|
412
|
-
'''
|
|
413
|
-
Method for calculating BandU function overlap with states at a specified isoenergy.
|
|
414
|
-
Requires BandU XSF file and ABINIT WFK file.
|
|
415
|
-
|
|
416
|
-
Parameters
|
|
417
|
-
----------
|
|
418
|
-
wfk_path : str
|
|
419
|
-
Path to ABINIT WFK file
|
|
420
|
-
xsf_path : str
|
|
421
|
-
Path to BandU XSF file
|
|
422
|
-
sym : bool
|
|
423
|
-
Symmetrically generate full Brillouin Zone
|
|
424
|
-
Default will not generate Brillouin Zone and instead symmetrize surface colors (False)
|
|
425
|
-
'''
|
|
426
|
-
from . import abinit_reader as ar
|
|
427
|
-
from . import xsf_reader as xsfr
|
|
428
|
-
# list of overlap values
|
|
429
|
-
overlaps = np.zeros(1)
|
|
430
|
-
# read fermi surface wavefunction
|
|
431
|
-
fermi_wfk = ar.AbinitWFK(filename=wfk_path)
|
|
432
|
-
# get number of bands
|
|
433
|
-
nbands = len(self.isosurface.nbands)
|
|
434
|
-
# paths to real and imaginary bandu xsf files
|
|
435
|
-
real_path = xsf_path + '_real.xsf'
|
|
436
|
-
imag_path = xsf_path + '_imag.xsf'
|
|
437
|
-
# read in xsf
|
|
438
|
-
real_fxn = xsfr.XSF(xsf_file=real_path)
|
|
439
|
-
imag_fxn = xsfr.XSF(xsf_file=imag_path)
|
|
440
|
-
print('XSF read')
|
|
441
|
-
# convert xsf to wfk object
|
|
442
|
-
bandu_fxn = wc.WFK(
|
|
443
|
-
wfk_coeffs=real_fxn.ReadGrid() + 1j*imag_fxn.ReadGrid(),
|
|
444
|
-
ngfftx=real_fxn.ngfftx,
|
|
445
|
-
ngffty=real_fxn.ngffty,
|
|
446
|
-
ngfftz=real_fxn.ngfftz
|
|
447
|
-
)
|
|
448
|
-
# remove xsf format
|
|
449
|
-
bandu_fxn = bandu_fxn.RemoveXSF()
|
|
450
|
-
# loop through fermi surface kpoints and calc overlap with bandu fxn
|
|
451
|
-
for i, kpt in enumerate(fermi_wfk.ReadWFK()):
|
|
452
|
-
if sym:
|
|
453
|
-
vals = self._OverlapsWithSym(kpt, bandu_fxn)
|
|
454
|
-
else:
|
|
455
|
-
vals = self._OverlapsNoSym(kpt, bandu_fxn)
|
|
456
|
-
if i == 0:
|
|
457
|
-
overlaps = vals
|
|
458
|
-
else:
|
|
459
|
-
overlaps = np.concatenate((overlaps, vals), axis=0)
|
|
460
|
-
# symmetrically permute overlap values if they were only calculated on irreducible BZ wedge when sym==False
|
|
461
|
-
if not sym:
|
|
462
|
-
symrel = fermi_wfk.symrel
|
|
463
|
-
nsym = fermi_wfk.nsym
|
|
464
|
-
kpts = fermi_wfk.kpts
|
|
465
|
-
all_kpts = np.zeros((1,3))
|
|
466
|
-
all_overlaps = np.zeros((1,nbands))
|
|
467
|
-
new_wfk = wc.WFK(symrel=np.array(symrel), nsym=nsym, nbands=nbands)
|
|
468
|
-
for i, kpt in enumerate(kpts):
|
|
469
|
-
unique_kpts, _ = new_wfk.Symmetrize(
|
|
470
|
-
points=kpt,
|
|
471
|
-
reciprocal=True
|
|
472
|
-
)
|
|
473
|
-
all_kpts = np.concatenate((all_kpts, unique_kpts), axis=0)
|
|
474
|
-
new_overlaps = np.repeat(overlaps[i,:].reshape((1,-1)), unique_kpts.shape[0], axis=0)
|
|
475
|
-
all_overlaps = np.concatenate((all_overlaps, new_overlaps), axis=0)
|
|
476
|
-
kpts = np.delete(all_kpts, 0, axis=0)
|
|
477
|
-
overlaps = np.delete(all_overlaps, 0, axis=0)
|
|
478
|
-
self.isosurface.points = np.matmul(kpts, self.isosurface.rec_latt)
|
|
479
|
-
# interpolate overlap values for smooth coloration
|
|
480
|
-
scalars = []
|
|
481
|
-
for i in range(overlaps.shape[1]):
|
|
482
|
-
interp_vals = self._InterpolateOverlaps(self.isosurface.contours[i], overlaps[:,i])
|
|
483
|
-
scalars.append(interp_vals)
|
|
484
|
-
return scalars
|
|
485
|
-
#-----------------------------------------------------------------------------------------------------------------#
|
|
486
|
-
# method to load save file
|
|
487
|
-
def Load(
|
|
488
|
-
self, save_path:str|list, **kwargs
|
|
489
|
-
):
|
|
490
|
-
'''
|
|
491
|
-
Method for loading saved contours
|
|
492
|
-
|
|
493
|
-
Parameters
|
|
494
|
-
----------
|
|
495
|
-
colormap : str | ListedColormap
|
|
496
|
-
Choose colormap for isosurface that colors according to assigned scalars of surface\n
|
|
497
|
-
Can use Colors class to use default or create custom colormaps\n
|
|
498
|
-
Default is matplotlib's plasma
|
|
499
|
-
bz_width : int
|
|
500
|
-
Line width of Brillouin Zone\n
|
|
501
|
-
Default is 3
|
|
502
|
-
bz_show : bool
|
|
503
|
-
Show the Brillouin Zone
|
|
504
|
-
Default is to show (True)
|
|
505
|
-
smooth : bool
|
|
506
|
-
Use smooth lightning techniques\n
|
|
507
|
-
Default is to use smoothing (True)
|
|
508
|
-
lighting : bool
|
|
509
|
-
Apply directional lighting to surface\n
|
|
510
|
-
Default is to enable directional lighting (True)
|
|
511
|
-
ambient : float
|
|
512
|
-
Intensity of light on surface\n
|
|
513
|
-
Default is 0.5
|
|
514
|
-
diffuse : float
|
|
515
|
-
Amount of light scattering\n
|
|
516
|
-
Default is 0.5
|
|
517
|
-
specular : float
|
|
518
|
-
Amount of reflected light\n
|
|
519
|
-
Default is 1.0 (max)
|
|
520
|
-
specular_power : float
|
|
521
|
-
Determines how sharply light is reflected\n
|
|
522
|
-
Default is 128.0 (max)
|
|
523
|
-
pbr : bool
|
|
524
|
-
Apply physics based rendering\n
|
|
525
|
-
Default is no physics based rendering (False)
|
|
526
|
-
metallic : float
|
|
527
|
-
Determine how metallic-looking the surface is, only considered with pbr\n
|
|
528
|
-
Default is 0.5
|
|
529
|
-
roughness : float
|
|
530
|
-
Determine how smooth/rough surface appear, only considered with pbr\n
|
|
531
|
-
Default is 0.5
|
|
532
|
-
color : str
|
|
533
|
-
Sets color of surface (colormap overwrites this)\n
|
|
534
|
-
Sets color of reflected light (colormap does not overwrite this)\n
|
|
535
|
-
Default is white
|
|
536
|
-
arrow : list
|
|
537
|
-
Parameters for plotting nesting vector on top of Fermi surface\n
|
|
538
|
-
Element_0 of list should be starting (or tail) position of arrow\n
|
|
539
|
-
Element_1 of list should be orientation of arrow with desired magnitude\n
|
|
540
|
-
Both the tail and orientation should be specified in reduced reciprocal space coordinates
|
|
541
|
-
arrow_color : str
|
|
542
|
-
Color of nesting arrow\n
|
|
543
|
-
Default is black
|
|
544
|
-
show_endpoints : bool
|
|
545
|
-
Plot points on the end of the arrow to make visualizing start and end easier\n
|
|
546
|
-
Default is to not show endpoints (False)
|
|
547
|
-
periodic_arrow : list
|
|
548
|
-
Adds periodic image of arrow that is translated [X,Y,Z] cells\n
|
|
549
|
-
Where X, Y, and Z are the cell indices
|
|
550
|
-
show_bands : float | list
|
|
551
|
-
Specifies the opacity of each band\n
|
|
552
|
-
If a single float is provided, all bands will be plotted with the same opacity\n
|
|
553
|
-
If a list is provided, each band will be plotted with the opacity of the respective list element
|
|
554
|
-
show_axes : bool
|
|
555
|
-
Plots reciprocal cell axes with a* as red, b* as green, and c* as blue\n
|
|
556
|
-
Default is to show axes (True)
|
|
557
|
-
cross_section : list
|
|
558
|
-
Plot cross section through surface\n
|
|
559
|
-
If one vector is provided, it is assumed to be the normal to the cross section plane\n
|
|
560
|
-
Else, cross section is defined by plane made by two vectors\n
|
|
561
|
-
Vectors should be specified in reduced coordinates
|
|
562
|
-
cross_width : float
|
|
563
|
-
Width of cross section\n
|
|
564
|
-
Default is 0.15
|
|
565
|
-
linear : bool
|
|
566
|
-
Cross section linearly fades out\n
|
|
567
|
-
Default is not fade out linearly (False)
|
|
568
|
-
two_dim : bool
|
|
569
|
-
Cross section is a 2D slice instead of a section\n
|
|
570
|
-
Default is to show cross section as 2D slice (True)
|
|
571
|
-
surface_vals : np.ndarray
|
|
572
|
-
A list of values defining the coloration of the isosurface
|
|
573
|
-
Default plots no coloration
|
|
574
|
-
'''
|
|
575
|
-
# functionality for adding multiple projections onto one surface
|
|
576
|
-
if type(save_path) == list:
|
|
577
|
-
all_scalars = []
|
|
578
|
-
for save in save_path:
|
|
579
|
-
with open(save, 'rb') as f:
|
|
580
|
-
kwargs_dict:dict = pkl.load(f)
|
|
581
|
-
self.isosurface:ic.Isosurface = pkl.load(f)
|
|
582
|
-
all_scalars.append(kwargs_dict['surface_vals'])
|
|
583
|
-
sizes = []
|
|
584
|
-
for surface_vals in all_scalars[0]:
|
|
585
|
-
sizes.append(surface_vals.shape)
|
|
586
|
-
new_scalars = [np.zeros((size)) for size in sizes]
|
|
587
|
-
for scalars in all_scalars:
|
|
588
|
-
for i, surface_vals in enumerate(scalars):
|
|
589
|
-
new_scalars[i] += surface_vals
|
|
590
|
-
kwargs_dict['surface_vals'] = new_scalars # type: ignore
|
|
591
|
-
# replot a single surface
|
|
592
|
-
else:
|
|
593
|
-
with open(save_path, 'rb') as f: # type: ignore
|
|
594
|
-
kwargs_dict:dict = pkl.load(f)
|
|
595
|
-
self.isosurface:ic.Isosurface = pkl.load(f)
|
|
596
|
-
for k, val in kwargs.items(): # type: ignore
|
|
597
|
-
kwargs_dict[k] = val # type: ignore
|
|
598
|
-
self.save = False
|
|
599
|
-
self.plot = True
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pyvista as pv
|
|
3
|
+
from pyvistaqt import BackgroundPlotter
|
|
4
|
+
from copy import copy
|
|
5
|
+
import pickle as pkl
|
|
6
|
+
from matplotlib.colors import ListedColormap
|
|
7
|
+
from . import brillouin_zone as brlzn
|
|
8
|
+
from . import isosurface_class as ic
|
|
9
|
+
from . import wfk_class as wc
|
|
10
|
+
from . import translate as trslt
|
|
11
|
+
|
|
12
|
+
class Plotter():
|
|
13
|
+
'''
|
|
14
|
+
Class for creating 3D plot of Fermi surface and projection of BandU functions on Fermi surface.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
isosurface : Isosurface
|
|
19
|
+
An Isosurface object that contains the contours to be plotted
|
|
20
|
+
save : bool
|
|
21
|
+
Create save file of isosurface plot that can be loaded with the Load method
|
|
22
|
+
Default will save the plot (True)
|
|
23
|
+
save_file : str
|
|
24
|
+
Name of save file
|
|
25
|
+
Default is "Fermi_surface.pkl"
|
|
26
|
+
empty_mesh : bool
|
|
27
|
+
Allow PyVista plotter to plot meshes even if no surface is present
|
|
28
|
+
Default will throw exception of an empty surface is being plotted (False)
|
|
29
|
+
plot : bool
|
|
30
|
+
Enable creation of PyVista Plotter
|
|
31
|
+
Must be enabled for plotting, default enables plotter (True)
|
|
32
|
+
|
|
33
|
+
Methods
|
|
34
|
+
-------
|
|
35
|
+
Plot
|
|
36
|
+
Generates 3D plot of Fermi surface from contours of Isosurface object
|
|
37
|
+
SurfaceColor
|
|
38
|
+
Reads in a BandU XSF file and Fermi surface ABINIT WFK file to calculate the overlap between the BandU
|
|
39
|
+
function and the states at the Fermi energy
|
|
40
|
+
Load
|
|
41
|
+
Loads a save file
|
|
42
|
+
'''
|
|
43
|
+
def __init__(
|
|
44
|
+
self, isosurface:ic.Isosurface=ic.Isosurface(points=np.ones((1,3))), save:bool=True,
|
|
45
|
+
empty_mesh:bool=False, _debug:bool=False, save_file:str='Fermi_surface.pkl', plot:bool=True
|
|
46
|
+
):
|
|
47
|
+
self.isosurface=isosurface
|
|
48
|
+
self.save=save
|
|
49
|
+
self.save_file=save_file
|
|
50
|
+
self._debug=_debug
|
|
51
|
+
self.plot=plot
|
|
52
|
+
if self.plot:
|
|
53
|
+
self.p:pv.Plotter=BackgroundPlotter(window_size=(600,400))
|
|
54
|
+
pv.global_theme.allow_empty_mesh=empty_mesh
|
|
55
|
+
self.p.enable_depth_peeling(number_of_peels=10)
|
|
56
|
+
#---------------------------------------------------------------------------------------------------------------------#
|
|
57
|
+
#------------------------------------------------------ METHODS ------------------------------------------------------#
|
|
58
|
+
#---------------------------------------------------------------------------------------------------------------------#
|
|
59
|
+
# method for turning opacity to zero outside of BZ
|
|
60
|
+
def _SetOpacities(
|
|
61
|
+
self, contour:pv.PolyData
|
|
62
|
+
)->np.ndarray:
|
|
63
|
+
bz = brlzn.BZ(self.isosurface.rec_latt)
|
|
64
|
+
opacities = bz.PointLocate(contour.points, cart=False)
|
|
65
|
+
opacities[opacities >= 0] = 1
|
|
66
|
+
opacities[opacities < 0] = 0
|
|
67
|
+
return opacities
|
|
68
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
69
|
+
# method to add nesting vector to isosurface plot
|
|
70
|
+
def _AddArrow(
|
|
71
|
+
self, arrow:list, rec_lattice:np.ndarray, show_endpoints:bool, color:str
|
|
72
|
+
)->None:
|
|
73
|
+
tail = np.array(arrow[0])
|
|
74
|
+
shift = np.array(arrow[1])
|
|
75
|
+
tail = np.matmul(tail, rec_lattice)
|
|
76
|
+
shift = np.matmul(shift, rec_lattice)
|
|
77
|
+
scale = np.linalg.norm(shift).astype(float)
|
|
78
|
+
py_arrow = pv.Arrow(
|
|
79
|
+
start=tail,
|
|
80
|
+
direction=shift,
|
|
81
|
+
tip_radius=0.05/scale,
|
|
82
|
+
tip_length=0.15/scale,
|
|
83
|
+
shaft_radius=0.025/scale,
|
|
84
|
+
scale=scale
|
|
85
|
+
)
|
|
86
|
+
if show_endpoints:
|
|
87
|
+
points = np.array([tail, shift+tail])
|
|
88
|
+
points = pv.PolyData(points)
|
|
89
|
+
self.p.add_mesh(points.points, point_size=20, color='black', render_points_as_spheres=True)
|
|
90
|
+
self.p.add_mesh(py_arrow, color=color)
|
|
91
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
92
|
+
# method to visualize cross-section of surface
|
|
93
|
+
def _CrossSection(
|
|
94
|
+
self, vecs:list, points:np.ndarray, width:float, rec_lattice:np.ndarray, bz_points:np.ndarray,
|
|
95
|
+
linear:bool=False, two_dim:bool=False
|
|
96
|
+
)->np.ndarray:
|
|
97
|
+
from scipy.spatial import Delaunay
|
|
98
|
+
# cross section is defined by plane of two perpendicular vectors
|
|
99
|
+
if len(vecs) == 2:
|
|
100
|
+
vec1 = np.matmul(vecs[0], rec_lattice)
|
|
101
|
+
vec2 = np.matmul(vecs[1], rec_lattice)
|
|
102
|
+
norm = np.cross(vec1, vec2)
|
|
103
|
+
norm /= np.linalg.norm(norm)
|
|
104
|
+
# cross section is defined by normal vector
|
|
105
|
+
else:
|
|
106
|
+
vec = np.matmul(vecs, rec_lattice)
|
|
107
|
+
norm = vec/np.linalg.norm(vec)
|
|
108
|
+
# surface points can be thought of vectors in 3D space, here we normalize the vectors
|
|
109
|
+
norm_points = np.array(points/np.linalg.norm(points, axis=1).reshape((len(points),1)))
|
|
110
|
+
# find dot product between vector normal to cross section and all normalized points
|
|
111
|
+
angs = np.matmul(norm, norm_points.T)
|
|
112
|
+
# opacity linearly fades out as points get farther from cross section
|
|
113
|
+
if linear:
|
|
114
|
+
angs[angs <= width] = 2
|
|
115
|
+
angs -= 1
|
|
116
|
+
angs = np.abs(angs)
|
|
117
|
+
# opacity is zero beyond width of cross section
|
|
118
|
+
else:
|
|
119
|
+
# opacity is zero beyond width on both sides of cross section
|
|
120
|
+
if two_dim:
|
|
121
|
+
angs = np.abs(angs)
|
|
122
|
+
opacities = np.zeros(len(points))
|
|
123
|
+
opacities[angs <= width] = 1
|
|
124
|
+
angs = opacities
|
|
125
|
+
# set opacity to zero for points outside of the BZ
|
|
126
|
+
beyond_bz = Delaunay(bz_points).find_simplex(points)
|
|
127
|
+
angs[beyond_bz < 0] = 0
|
|
128
|
+
return angs
|
|
129
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
130
|
+
# method for plotting isosurface contours
|
|
131
|
+
def Plot(
|
|
132
|
+
self, show_points:bool=False, show_outline:bool=False, show_axes:bool=True, show_isosurf:bool=True,
|
|
133
|
+
smooth:bool=True, lighting:bool=True, ambient:float=0.5, diffuse:float=0.5, specular:float=1.0,
|
|
134
|
+
specular_power:float=128.0, pbr:bool=False, metallic:float=0.5, roughness:float=0.5,
|
|
135
|
+
colormap:str|ListedColormap='plasma', color:str='white', bz_show:bool=True, bz_width:int=3, arrow:list=[],
|
|
136
|
+
show_endpoints:bool=False, arrow_color:str='yellow', periodic_arrow:list=[], camera_position:list=[],
|
|
137
|
+
cross_section:list=[], cross_width:float=0.1, linear:bool=False, two_dim:bool=True, show_bands:float|list=1.0,
|
|
138
|
+
surface_vals:list|None=None, show_ird_points:bool=False
|
|
139
|
+
):
|
|
140
|
+
'''
|
|
141
|
+
Method for plotting contours made from the Isosurface object
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
colormap : str | ListedColormap
|
|
146
|
+
Choose colormap for isosurface that colors according to assigned scalars of surface\n
|
|
147
|
+
Can use Colors class to use default or create custom colormaps\n
|
|
148
|
+
Default is matplotlib's plasma
|
|
149
|
+
bz_width : int
|
|
150
|
+
Line width of Brillouin Zone\n
|
|
151
|
+
Default is 3
|
|
152
|
+
bz_show : bool
|
|
153
|
+
Show the Brillouin Zone
|
|
154
|
+
Default is to show (True)
|
|
155
|
+
smooth : bool
|
|
156
|
+
Use smooth lightning techniques\n
|
|
157
|
+
Default is to use smoothing (True)
|
|
158
|
+
lighting : bool
|
|
159
|
+
Apply directional lighting to surface\n
|
|
160
|
+
Default is to enable directional lighting (True)
|
|
161
|
+
ambient : float
|
|
162
|
+
Intensity of light on surface\n
|
|
163
|
+
Default is 0.5
|
|
164
|
+
diffuse : float
|
|
165
|
+
Amount of light scattering\n
|
|
166
|
+
Default is 0.5
|
|
167
|
+
specular : float
|
|
168
|
+
Amount of reflected light\n
|
|
169
|
+
Default is 1.0 (max)
|
|
170
|
+
specular_power : float
|
|
171
|
+
Determines how sharply light is reflected\n
|
|
172
|
+
Default is 128.0 (max)
|
|
173
|
+
pbr : bool
|
|
174
|
+
Apply physics based rendering\n
|
|
175
|
+
Default is no physics based rendering (False)
|
|
176
|
+
metallic : float
|
|
177
|
+
Determine how metallic-looking the surface is, only considered with pbr\n
|
|
178
|
+
Default is 0.5
|
|
179
|
+
roughness : float
|
|
180
|
+
Determine how smooth/rough surface appear, only considered with pbr\n
|
|
181
|
+
Default is 0.5
|
|
182
|
+
color : str
|
|
183
|
+
Sets color of surface (colormap overwrites this)\n
|
|
184
|
+
Sets color of reflected light (colormap does not overwrite this)\n
|
|
185
|
+
Default is white
|
|
186
|
+
arrow : list
|
|
187
|
+
Parameters for plotting nesting vector on top of Fermi surface\n
|
|
188
|
+
Element_0 of list should be starting (or tail) position of arrow\n
|
|
189
|
+
Element_1 of list should be orientation of arrow with desired magnitude\n
|
|
190
|
+
Both the tail and orientation should be specified in reduced reciprocal space coordinates
|
|
191
|
+
arrow_color : str
|
|
192
|
+
Color of nesting arrow\n
|
|
193
|
+
Default is black
|
|
194
|
+
show_endpoints : bool
|
|
195
|
+
Plot points on the end of the arrow to make visualizing start and end easier\n
|
|
196
|
+
Default is to not show endpoints (False)
|
|
197
|
+
periodic_arrow : list
|
|
198
|
+
Adds periodic image of arrow that is translated [X,Y,Z] cells\n
|
|
199
|
+
Where X, Y, and Z are the cell indices
|
|
200
|
+
show_bands : float | list
|
|
201
|
+
Specifies the opacity of each band\n
|
|
202
|
+
If a single float is provided, all bands will be plotted with the same opacity\n
|
|
203
|
+
If a list is provided, each band will be plotted with the opacity of the respective list element
|
|
204
|
+
show_axes : bool
|
|
205
|
+
Plots reciprocal cell axes with a* as red, b* as green, and c* as blue\n
|
|
206
|
+
Default is to show axes (True)
|
|
207
|
+
cross_section : list
|
|
208
|
+
Plot cross section through surface\n
|
|
209
|
+
If one vector is provided, it is assumed to be the normal to the cross section plane\n
|
|
210
|
+
Else, cross section is defined by plane made by two vectors\n
|
|
211
|
+
Vectors should be specified in reduced coordinates
|
|
212
|
+
cross_width : float
|
|
213
|
+
Width of cross section\n
|
|
214
|
+
Default is 0.15
|
|
215
|
+
linear : bool
|
|
216
|
+
Cross section linearly fades out\n
|
|
217
|
+
Default is not fade out linearly (False)
|
|
218
|
+
two_dim : bool
|
|
219
|
+
Cross section is a 2D slice instead of a section\n
|
|
220
|
+
Default is to show cross section as 2D slice (True)
|
|
221
|
+
surface_vals : np.ndarray
|
|
222
|
+
A list of values defining the coloration of the isosurface
|
|
223
|
+
Default plots no coloration
|
|
224
|
+
'''
|
|
225
|
+
# save file
|
|
226
|
+
if self.save:
|
|
227
|
+
with open(self.save_file, 'wb') as f:
|
|
228
|
+
kwargs = locals()
|
|
229
|
+
kwargs.pop('self', None)
|
|
230
|
+
kwargs.pop('f', None)
|
|
231
|
+
pkl.dump(kwargs, f)
|
|
232
|
+
pkl.dump(self.isosurface, f)
|
|
233
|
+
if not self.plot:
|
|
234
|
+
raise SystemExit()
|
|
235
|
+
# plot BZ boundary
|
|
236
|
+
vor_verts = brlzn.BZ(self.isosurface.rec_latt).vertices
|
|
237
|
+
if bz_show:
|
|
238
|
+
self.p.add_lines(vor_verts, color='black', width=bz_width)
|
|
239
|
+
# set opacity of each band
|
|
240
|
+
if type(show_bands) is float:
|
|
241
|
+
band_ops = show_bands*np.ones(len(self.isosurface.contours), dtype=float)
|
|
242
|
+
show_bands = band_ops.tolist()
|
|
243
|
+
# loop through contours of isosurface object and plot each
|
|
244
|
+
for i, contour in enumerate(self.isosurface.contours):
|
|
245
|
+
if surface_vals is None:
|
|
246
|
+
scalars = None
|
|
247
|
+
surf_max = 0.0
|
|
248
|
+
else:
|
|
249
|
+
scalars = surface_vals[i]
|
|
250
|
+
surf_max = np.max(surface_vals[i])
|
|
251
|
+
# opacities can be adjusted to show a cross section of contours
|
|
252
|
+
if cross_section != []:
|
|
253
|
+
opacities = self._CrossSection(
|
|
254
|
+
cross_section,
|
|
255
|
+
contour.points,
|
|
256
|
+
cross_width,
|
|
257
|
+
self.isosurface.rec_latt,
|
|
258
|
+
vor_verts,
|
|
259
|
+
linear=linear,
|
|
260
|
+
two_dim=two_dim
|
|
261
|
+
)
|
|
262
|
+
# otherwise opacities are set to just render contours in the BZ
|
|
263
|
+
else:
|
|
264
|
+
opacities = self._SetOpacities(contour=contour)
|
|
265
|
+
# set opacity of bands
|
|
266
|
+
opacities = [op*show_bands[i] for op in opacities] # type: ignore
|
|
267
|
+
# lighting is set to make surface appear more smooth
|
|
268
|
+
if smooth:
|
|
269
|
+
contour = contour.smooth_taubin(n_iter=100, pass_band=0.05)
|
|
270
|
+
# plot contours
|
|
271
|
+
if show_isosurf:
|
|
272
|
+
self.p.add_mesh(
|
|
273
|
+
contour, # type: ignore
|
|
274
|
+
style='surface',
|
|
275
|
+
smooth_shading=smooth,
|
|
276
|
+
lighting=lighting,
|
|
277
|
+
ambient=ambient,
|
|
278
|
+
diffuse=diffuse,
|
|
279
|
+
specular=specular,
|
|
280
|
+
specular_power=specular_power,
|
|
281
|
+
pbr=pbr,
|
|
282
|
+
metallic=metallic,
|
|
283
|
+
roughness=roughness,
|
|
284
|
+
scalars=scalars,
|
|
285
|
+
clim=[0.0,surf_max],
|
|
286
|
+
cmap=colormap,
|
|
287
|
+
opacity=opacities,
|
|
288
|
+
color=color,
|
|
289
|
+
show_scalar_bar=True,
|
|
290
|
+
)
|
|
291
|
+
# plot irreducible kpoints
|
|
292
|
+
if show_ird_points:
|
|
293
|
+
pts = pv.PolyData(self.isosurface.ir_kpts)
|
|
294
|
+
self.p.add_mesh(pts.points, color='black')
|
|
295
|
+
# plot points that are used to construct isosurface
|
|
296
|
+
if show_points:
|
|
297
|
+
pts = pv.PolyData(self.isosurface.points)
|
|
298
|
+
self.p.add_mesh(pts.points, color='black')
|
|
299
|
+
# plot outline of grid used in interpolation
|
|
300
|
+
if show_outline:
|
|
301
|
+
self.p.add_mesh(self.isosurface.grid.outline())
|
|
302
|
+
# plot reciprocal space axes
|
|
303
|
+
if show_axes:
|
|
304
|
+
axes_colors = ['red', 'green', 'blue']
|
|
305
|
+
for i in range(3):
|
|
306
|
+
axis = self.isosurface.rec_latt[i,:]
|
|
307
|
+
color = axes_colors[i]
|
|
308
|
+
self._AddArrow([[0,0,0], axis], np.identity(3), False, color)
|
|
309
|
+
# plot nesting vector
|
|
310
|
+
if arrow != []:
|
|
311
|
+
self._AddArrow(arrow, self.isosurface.rec_latt, show_endpoints, arrow_color)
|
|
312
|
+
# plot a periodic image of the nesting vector
|
|
313
|
+
if periodic_arrow != []:
|
|
314
|
+
tail = np.array(arrow[0])
|
|
315
|
+
cell = np.array(periodic_arrow, dtype=float)
|
|
316
|
+
tail += cell
|
|
317
|
+
arrow[0] = tail
|
|
318
|
+
self._AddArrow(arrow, self.isosurface.rec_latt, show_endpoints, arrow_color)
|
|
319
|
+
# set camera position
|
|
320
|
+
if camera_position != []:
|
|
321
|
+
camera_pos = np.array(camera_position).reshape((3,3))
|
|
322
|
+
camera_pos = np.matmul(camera_pos, self.isosurface.rec_latt)
|
|
323
|
+
self.p.camera_position = camera_pos
|
|
324
|
+
self._Render()
|
|
325
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
326
|
+
# Render isosurfaces
|
|
327
|
+
def _Render(
|
|
328
|
+
self
|
|
329
|
+
):
|
|
330
|
+
self.p.enable_parallel_projection() # type: ignore
|
|
331
|
+
self.p.enable_custom_trackball_style(
|
|
332
|
+
left='rotate',
|
|
333
|
+
shift_left='spin',
|
|
334
|
+
right='pan',
|
|
335
|
+
) # type: ignore
|
|
336
|
+
self.p.set_focus([0.0,0.0,0.0]) # type: ignore
|
|
337
|
+
self.p.show()
|
|
338
|
+
self.p.app.exec_() # type: ignore
|
|
339
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
340
|
+
# method to compute bandu fxn and one electron wfk overlaps
|
|
341
|
+
def _OverlapsWithSym(
|
|
342
|
+
self, ir_wfk:wc.WFK, bandu:wc.WFK
|
|
343
|
+
)->np.ndarray:
|
|
344
|
+
# kpoint to symmetrically generate
|
|
345
|
+
kpt = ir_wfk.kpoints
|
|
346
|
+
# find number of distinct symmetries
|
|
347
|
+
sym_kpoints, _ = ir_wfk.Symmetrize(kpt, unique=False, reciprocal=True)
|
|
348
|
+
dupes, unique_inds = ir_wfk._FindOrbit(sym_kpoints)
|
|
349
|
+
count = sum([1 for i, _ in enumerate(unique_inds) if i not in dupes])
|
|
350
|
+
# initialize array for overlap values
|
|
351
|
+
num_bands = len(self.isosurface.nbands)
|
|
352
|
+
overlap_vals = np.zeros((count,num_bands), dtype=float)
|
|
353
|
+
# loop over bands
|
|
354
|
+
for i, band in enumerate(self.isosurface.nbands):
|
|
355
|
+
# generate symmetric coefficients for each band
|
|
356
|
+
for j, wfk in enumerate(ir_wfk.SymWFKs(kpoint=kpt, band=band)):
|
|
357
|
+
wfk = wfk.GridWFK()
|
|
358
|
+
wfk = wfk.IFFT()
|
|
359
|
+
wfk = wfk.Normalize()
|
|
360
|
+
overlap = np.sum(np.conj(bandu.wfk_coeffs)*wfk.wfk_coeffs)
|
|
361
|
+
overlap = np.square(np.abs(overlap))
|
|
362
|
+
overlap_vals[j,i] = overlap
|
|
363
|
+
return overlap_vals
|
|
364
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
365
|
+
# method to compute bandu fxn and one electron wfk overlaps
|
|
366
|
+
def _OverlapsNoSym(
|
|
367
|
+
self, ir_wfk:wc.WFK, bandu:wc.WFK
|
|
368
|
+
)->np.ndarray:
|
|
369
|
+
# initialize array for overlap values
|
|
370
|
+
num_bands = len(self.isosurface.nbands)
|
|
371
|
+
overlap_vals = np.zeros((1,num_bands), dtype=float)
|
|
372
|
+
# loop over bands
|
|
373
|
+
for i, band in enumerate(self.isosurface.nbands):
|
|
374
|
+
wfk = ir_wfk.GridWFK(band_index=band)
|
|
375
|
+
wfk = wfk.IFFT()
|
|
376
|
+
wfk = wfk.Normalize()
|
|
377
|
+
overlap = np.sum(np.conj(bandu.wfk_coeffs)*wfk.wfk_coeffs)
|
|
378
|
+
overlap = np.square(np.abs(overlap))
|
|
379
|
+
overlap_vals[0,i] = overlap
|
|
380
|
+
return overlap_vals
|
|
381
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
382
|
+
# method to interpolate overlap values
|
|
383
|
+
def _InterpolateOverlaps(
|
|
384
|
+
self, contour:pv.PolyData, overlap_values:np.ndarray
|
|
385
|
+
)->np.ndarray:
|
|
386
|
+
# recreate eigenvalue grid as base for surface color grid
|
|
387
|
+
color_grid = copy(self.isosurface.grid)
|
|
388
|
+
# translate overlap values to 3x3x3 grid
|
|
389
|
+
trans_color_points, trans_color_values = trslt.TranslatePoints(
|
|
390
|
+
self.isosurface.points,
|
|
391
|
+
overlap_values.reshape((-1,1)),
|
|
392
|
+
self.isosurface.rec_latt
|
|
393
|
+
)
|
|
394
|
+
# construct PyVista object from translated overlap grid
|
|
395
|
+
trans_color_points = pv.PolyData(trans_color_points)
|
|
396
|
+
trans_color_points['values'] = trans_color_values
|
|
397
|
+
# interpolate translated grid
|
|
398
|
+
color_grid = color_grid.interpolate(
|
|
399
|
+
trans_color_points,
|
|
400
|
+
sharpness=2.0,
|
|
401
|
+
radius=self.isosurface.radius,
|
|
402
|
+
strategy='null_value'
|
|
403
|
+
)
|
|
404
|
+
# sample color values from interpolated color grid
|
|
405
|
+
color_sample = contour.sample(color_grid) # type: ignore
|
|
406
|
+
return color_sample.active_scalars
|
|
407
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
408
|
+
# method to calculate surface color values from XSF and WFK
|
|
409
|
+
def SurfaceColor(
|
|
410
|
+
self, wfk_path:str, xsf_path:str, sym:bool=False
|
|
411
|
+
)->list:
|
|
412
|
+
'''
|
|
413
|
+
Method for calculating BandU function overlap with states at a specified isoenergy.
|
|
414
|
+
Requires BandU XSF file and ABINIT WFK file.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
wfk_path : str
|
|
419
|
+
Path to ABINIT WFK file
|
|
420
|
+
xsf_path : str
|
|
421
|
+
Path to BandU XSF file
|
|
422
|
+
sym : bool
|
|
423
|
+
Symmetrically generate full Brillouin Zone
|
|
424
|
+
Default will not generate Brillouin Zone and instead symmetrize surface colors (False)
|
|
425
|
+
'''
|
|
426
|
+
from . import abinit_reader as ar
|
|
427
|
+
from . import xsf_reader as xsfr
|
|
428
|
+
# list of overlap values
|
|
429
|
+
overlaps = np.zeros(1)
|
|
430
|
+
# read fermi surface wavefunction
|
|
431
|
+
fermi_wfk = ar.AbinitWFK(filename=wfk_path)
|
|
432
|
+
# get number of bands
|
|
433
|
+
nbands = len(self.isosurface.nbands)
|
|
434
|
+
# paths to real and imaginary bandu xsf files
|
|
435
|
+
real_path = xsf_path + '_real.xsf'
|
|
436
|
+
imag_path = xsf_path + '_imag.xsf'
|
|
437
|
+
# read in xsf
|
|
438
|
+
real_fxn = xsfr.XSF(xsf_file=real_path)
|
|
439
|
+
imag_fxn = xsfr.XSF(xsf_file=imag_path)
|
|
440
|
+
print('XSF read')
|
|
441
|
+
# convert xsf to wfk object
|
|
442
|
+
bandu_fxn = wc.WFK(
|
|
443
|
+
wfk_coeffs=real_fxn.ReadGrid() + 1j*imag_fxn.ReadGrid(),
|
|
444
|
+
ngfftx=real_fxn.ngfftx,
|
|
445
|
+
ngffty=real_fxn.ngffty,
|
|
446
|
+
ngfftz=real_fxn.ngfftz
|
|
447
|
+
)
|
|
448
|
+
# remove xsf format
|
|
449
|
+
bandu_fxn = bandu_fxn.RemoveXSF()
|
|
450
|
+
# loop through fermi surface kpoints and calc overlap with bandu fxn
|
|
451
|
+
for i, kpt in enumerate(fermi_wfk.ReadWFK()):
|
|
452
|
+
if sym:
|
|
453
|
+
vals = self._OverlapsWithSym(kpt, bandu_fxn)
|
|
454
|
+
else:
|
|
455
|
+
vals = self._OverlapsNoSym(kpt, bandu_fxn)
|
|
456
|
+
if i == 0:
|
|
457
|
+
overlaps = vals
|
|
458
|
+
else:
|
|
459
|
+
overlaps = np.concatenate((overlaps, vals), axis=0)
|
|
460
|
+
# symmetrically permute overlap values if they were only calculated on irreducible BZ wedge when sym==False
|
|
461
|
+
if not sym:
|
|
462
|
+
symrel = fermi_wfk.symrel
|
|
463
|
+
nsym = fermi_wfk.nsym
|
|
464
|
+
kpts = fermi_wfk.kpts
|
|
465
|
+
all_kpts = np.zeros((1,3))
|
|
466
|
+
all_overlaps = np.zeros((1,nbands))
|
|
467
|
+
new_wfk = wc.WFK(symrel=np.array(symrel), nsym=nsym, nbands=nbands)
|
|
468
|
+
for i, kpt in enumerate(kpts):
|
|
469
|
+
unique_kpts, _ = new_wfk.Symmetrize(
|
|
470
|
+
points=kpt,
|
|
471
|
+
reciprocal=True
|
|
472
|
+
)
|
|
473
|
+
all_kpts = np.concatenate((all_kpts, unique_kpts), axis=0)
|
|
474
|
+
new_overlaps = np.repeat(overlaps[i,:].reshape((1,-1)), unique_kpts.shape[0], axis=0)
|
|
475
|
+
all_overlaps = np.concatenate((all_overlaps, new_overlaps), axis=0)
|
|
476
|
+
kpts = np.delete(all_kpts, 0, axis=0)
|
|
477
|
+
overlaps = np.delete(all_overlaps, 0, axis=0)
|
|
478
|
+
self.isosurface.points = np.matmul(kpts, self.isosurface.rec_latt)
|
|
479
|
+
# interpolate overlap values for smooth coloration
|
|
480
|
+
scalars = []
|
|
481
|
+
for i in range(overlaps.shape[1]):
|
|
482
|
+
interp_vals = self._InterpolateOverlaps(self.isosurface.contours[i], overlaps[:,i])
|
|
483
|
+
scalars.append(interp_vals)
|
|
484
|
+
return scalars
|
|
485
|
+
#-----------------------------------------------------------------------------------------------------------------#
|
|
486
|
+
# method to load save file
|
|
487
|
+
def Load(
|
|
488
|
+
self, save_path:str|list, **kwargs
|
|
489
|
+
):
|
|
490
|
+
'''
|
|
491
|
+
Method for loading saved contours
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
colormap : str | ListedColormap
|
|
496
|
+
Choose colormap for isosurface that colors according to assigned scalars of surface\n
|
|
497
|
+
Can use Colors class to use default or create custom colormaps\n
|
|
498
|
+
Default is matplotlib's plasma
|
|
499
|
+
bz_width : int
|
|
500
|
+
Line width of Brillouin Zone\n
|
|
501
|
+
Default is 3
|
|
502
|
+
bz_show : bool
|
|
503
|
+
Show the Brillouin Zone
|
|
504
|
+
Default is to show (True)
|
|
505
|
+
smooth : bool
|
|
506
|
+
Use smooth lightning techniques\n
|
|
507
|
+
Default is to use smoothing (True)
|
|
508
|
+
lighting : bool
|
|
509
|
+
Apply directional lighting to surface\n
|
|
510
|
+
Default is to enable directional lighting (True)
|
|
511
|
+
ambient : float
|
|
512
|
+
Intensity of light on surface\n
|
|
513
|
+
Default is 0.5
|
|
514
|
+
diffuse : float
|
|
515
|
+
Amount of light scattering\n
|
|
516
|
+
Default is 0.5
|
|
517
|
+
specular : float
|
|
518
|
+
Amount of reflected light\n
|
|
519
|
+
Default is 1.0 (max)
|
|
520
|
+
specular_power : float
|
|
521
|
+
Determines how sharply light is reflected\n
|
|
522
|
+
Default is 128.0 (max)
|
|
523
|
+
pbr : bool
|
|
524
|
+
Apply physics based rendering\n
|
|
525
|
+
Default is no physics based rendering (False)
|
|
526
|
+
metallic : float
|
|
527
|
+
Determine how metallic-looking the surface is, only considered with pbr\n
|
|
528
|
+
Default is 0.5
|
|
529
|
+
roughness : float
|
|
530
|
+
Determine how smooth/rough surface appear, only considered with pbr\n
|
|
531
|
+
Default is 0.5
|
|
532
|
+
color : str
|
|
533
|
+
Sets color of surface (colormap overwrites this)\n
|
|
534
|
+
Sets color of reflected light (colormap does not overwrite this)\n
|
|
535
|
+
Default is white
|
|
536
|
+
arrow : list
|
|
537
|
+
Parameters for plotting nesting vector on top of Fermi surface\n
|
|
538
|
+
Element_0 of list should be starting (or tail) position of arrow\n
|
|
539
|
+
Element_1 of list should be orientation of arrow with desired magnitude\n
|
|
540
|
+
Both the tail and orientation should be specified in reduced reciprocal space coordinates
|
|
541
|
+
arrow_color : str
|
|
542
|
+
Color of nesting arrow\n
|
|
543
|
+
Default is black
|
|
544
|
+
show_endpoints : bool
|
|
545
|
+
Plot points on the end of the arrow to make visualizing start and end easier\n
|
|
546
|
+
Default is to not show endpoints (False)
|
|
547
|
+
periodic_arrow : list
|
|
548
|
+
Adds periodic image of arrow that is translated [X,Y,Z] cells\n
|
|
549
|
+
Where X, Y, and Z are the cell indices
|
|
550
|
+
show_bands : float | list
|
|
551
|
+
Specifies the opacity of each band\n
|
|
552
|
+
If a single float is provided, all bands will be plotted with the same opacity\n
|
|
553
|
+
If a list is provided, each band will be plotted with the opacity of the respective list element
|
|
554
|
+
show_axes : bool
|
|
555
|
+
Plots reciprocal cell axes with a* as red, b* as green, and c* as blue\n
|
|
556
|
+
Default is to show axes (True)
|
|
557
|
+
cross_section : list
|
|
558
|
+
Plot cross section through surface\n
|
|
559
|
+
If one vector is provided, it is assumed to be the normal to the cross section plane\n
|
|
560
|
+
Else, cross section is defined by plane made by two vectors\n
|
|
561
|
+
Vectors should be specified in reduced coordinates
|
|
562
|
+
cross_width : float
|
|
563
|
+
Width of cross section\n
|
|
564
|
+
Default is 0.15
|
|
565
|
+
linear : bool
|
|
566
|
+
Cross section linearly fades out\n
|
|
567
|
+
Default is not fade out linearly (False)
|
|
568
|
+
two_dim : bool
|
|
569
|
+
Cross section is a 2D slice instead of a section\n
|
|
570
|
+
Default is to show cross section as 2D slice (True)
|
|
571
|
+
surface_vals : np.ndarray
|
|
572
|
+
A list of values defining the coloration of the isosurface
|
|
573
|
+
Default plots no coloration
|
|
574
|
+
'''
|
|
575
|
+
# functionality for adding multiple projections onto one surface
|
|
576
|
+
if type(save_path) == list:
|
|
577
|
+
all_scalars = []
|
|
578
|
+
for save in save_path:
|
|
579
|
+
with open(save, 'rb') as f:
|
|
580
|
+
kwargs_dict:dict = pkl.load(f)
|
|
581
|
+
self.isosurface:ic.Isosurface = pkl.load(f)
|
|
582
|
+
all_scalars.append(kwargs_dict['surface_vals'])
|
|
583
|
+
sizes = []
|
|
584
|
+
for surface_vals in all_scalars[0]:
|
|
585
|
+
sizes.append(surface_vals.shape)
|
|
586
|
+
new_scalars = [np.zeros((size)) for size in sizes]
|
|
587
|
+
for scalars in all_scalars:
|
|
588
|
+
for i, surface_vals in enumerate(scalars):
|
|
589
|
+
new_scalars[i] += surface_vals
|
|
590
|
+
kwargs_dict['surface_vals'] = new_scalars # type: ignore
|
|
591
|
+
# replot a single surface
|
|
592
|
+
else:
|
|
593
|
+
with open(save_path, 'rb') as f: # type: ignore
|
|
594
|
+
kwargs_dict:dict = pkl.load(f)
|
|
595
|
+
self.isosurface:ic.Isosurface = pkl.load(f)
|
|
596
|
+
for k, val in kwargs.items(): # type: ignore
|
|
597
|
+
kwargs_dict[k] = val # type: ignore
|
|
598
|
+
self.save = False
|
|
599
|
+
self.plot = True
|
|
600
600
|
self.Plot(**kwargs_dict) # type: ignore
|