fsvisual 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.
fsvisual/__init__.py ADDED
File without changes
@@ -0,0 +1,35 @@
1
+ from pymatgen.core import Lattice
2
+
3
+
4
+ def first_bz(rez_lattice):
5
+
6
+ """
7
+ calculates the edge points of a Brillouin zone for any given reciprocal lattice.
8
+
9
+ :param rez_lattice: coordinates for the reciprocal lattice vectors (list of three 3 Dimensional coordinates)
10
+ :return: 1. Brillouin zone as a list containing x, y, z coordinates on separate lists for each
11
+ """
12
+
13
+ new_lattice = Lattice(rez_lattice)
14
+ brillouin_zone = new_lattice.get_wigner_seitz_cell()
15
+
16
+ # Prepare data for Plotly
17
+ x, y, z = [], [], []
18
+ for facet in brillouin_zone:
19
+ # brillouin zone consists of many facets -> facets consists of vertices (edge points -> koordinates)
20
+ # Ensure each facet is closed by adding the first vertex at the end
21
+ facet.append(facet[0])
22
+ for vertex in facet:
23
+ x.append(vertex[0])
24
+ y.append(vertex[1])
25
+ z.append(vertex[2])
26
+ # Add None to create a break in the plot lines between facets
27
+ x.append(None)
28
+ y.append(None)
29
+ z.append(None)
30
+ brillouin_zone_xyz = [x, y, z]
31
+ return brillouin_zone_xyz, brillouin_zone
32
+
33
+
34
+
35
+
fsvisual/cli.py ADDED
@@ -0,0 +1,68 @@
1
+ from .fermisurface import FermiSurface
2
+ import os
3
+ import argparse
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser()
7
+ parser.add_argument( "bxsf_files_directory", help="directory (folder) where the .bxsf files (Fermi"
8
+ "surface files) are stored")
9
+
10
+ # optional arguments
11
+
12
+ parser.add_argument("-sf","--save_fermisurfaces", help="directory where visualized Fermi surfaces "
13
+ "are stored")
14
+
15
+ parser.add_argument("-s","--subdivision_surface", help="divides every triangle of the Fermi surface mesh"
16
+ " into two triangles; executes as many times as the input says",
17
+ default=0, type=int)
18
+
19
+ parser.add_argument("-dp","--downsampling_surface_percentage", help="lowers the resolution of the "
20
+ "Fermi surface mesh (number of faces) to a given percentage "
21
+ "(from original face count)",default=100, type=int)
22
+
23
+ parser.add_argument("-df","--downsampling_surface_face", help="lowers the resolution of the "
24
+ "Fermi surface mesh (number of faces) to a given face number",
25
+ default=None, type=int)
26
+
27
+
28
+ parser.add_argument("-c","--create_SVG", help="boolean whether to create SVG files ",
29
+ action="store_true")
30
+
31
+ args = parser.parse_args()
32
+
33
+ # you can either parse a whole directory of .bxsf files or just the path to a single .bxsf file
34
+ file_list = []
35
+ if os.path.isfile(args.bxsf_files_directory):
36
+ file_list.append(os.path.basename(args.bxsf_files_directory))
37
+ path = os.path.abspath(os.path.dirname(args.bxsf_files_directory))
38
+ if args.save_fermisurfaces is not None:
39
+ save_path = args.save_fermisurfaces
40
+ else:
41
+ save_path = path
42
+ else:
43
+ file_list.extend(file_ for file_ in os.listdir(args.bxsf_files_directory) if file_.endswith('.bxsf'))
44
+ path = args.bxsf_files_directory
45
+ if args.save_fermisurfaces is not None:
46
+ save_path = args.save_fermisurfaces
47
+ else:
48
+ save_path = args.bxsf_files_directory
49
+
50
+ for filename in file_list:
51
+ filepath = os.path.join(path, filename)
52
+ # check if path leads to file
53
+ if not os.path.isfile(filepath):
54
+ continue
55
+
56
+ new_fermisurface = FermiSurface()
57
+
58
+ if args.subdivision_surface != 0 and args.downsampling_surface != 100:
59
+ raise ValueError("subdivision_surface and downsampling_surface are contrary functions")
60
+
61
+ new_fermisurface.build_surface_with_bxsf_files(filepath, args.subdivision_surface,
62
+ args.downsampling_surface_percentage,
63
+ args.downsampling_surface_face)
64
+
65
+ new_fermisurface.visualization(filepath, save_path, svg=args.create_SVG)
66
+
67
+ if __name__ == "__main__":
68
+ main()
@@ -0,0 +1,251 @@
1
+ from .input import read_energy_numbers
2
+ from .brillouin_zone import first_bz
3
+ from .visualisation import build_plotly_figure, write_figure_to_file
4
+ from .mesh import create_cartesian_mesh, face_center_BZ
5
+ from skimage import measure
6
+ import numpy as np
7
+ import trimesh
8
+ import pymeshlab
9
+
10
+
11
+ class FermiSurface:
12
+ """
13
+ Class for computing three-dimensional, interactive Fermi surfaces from .bxsf files, a filetype established by the
14
+ visualization software XCrsSDen. A Fermi surface is an object in k-space, that separates the occupied from the
15
+ unoccupied states (at the Fermi energy). Fermi surfaces are often shown within the first brillouin zone.
16
+
17
+ usage: If the Fermi surface data is present as a .bxsf file, which is widely adopted as an output for Fermi
18
+ surface calculations e.g. by Wannier90 or exciting, it is sufficient to just create an object of the FermiSurface
19
+ class and call the build_surface_with_bxsf_files method for building a Fermi surface. Afterwards the visualization
20
+ method can be called to create 3D interactive plots of the Fermi surface.
21
+
22
+ For information concerning the .bxsf file format, and also if your fermi surface data is stored in a different format,
23
+ please consult the documentation of FSvisual.
24
+ """
25
+
26
+ def __init__(self):
27
+ self.energy_values = None
28
+ self.fermi_energy = None
29
+ self.rez_base_vect = None
30
+ self.grid_size = None
31
+ self.brillouin_zone = None
32
+ self.surface = None
33
+ self.fermi_surface_list = None
34
+ self.band_index = None
35
+
36
+
37
+ @property
38
+ def cartesian_mesh(self):
39
+ return create_cartesian_mesh(self.grid_size)
40
+
41
+ def set_energy_values(self, energy_values):
42
+ self.energy_values = energy_values
43
+
44
+ def set_fermi_energy(self, fermi_energy):
45
+ self.fermi_energy = fermi_energy
46
+
47
+ def set_rez_base_vect(self, rez_base_vect):
48
+ self.rez_base_vect = rez_base_vect
49
+
50
+ def set_k_grid_by_size(self, grid_size):
51
+ self.grid_size = grid_size
52
+
53
+ def compute_brillouin_zone(self):
54
+ self.brillouin_zone = first_bz(self.rez_base_vect)
55
+
56
+ def marching_cubes(self, energyColumn):
57
+ grid_size = self.grid_size
58
+ new_basevect_grid_size = np.array([grid_size[0] * 2 - 1, grid_size[1] * 2 - 1, grid_size[2] * 2 - 1])
59
+
60
+ # creates an array with energies taken by the corresponding indices of new_cart_mesh_helper -> created array is
61
+ # as big as the indexing array
62
+ new_cart_mesh_helper = self.energy_values[energyColumn][self.cartesian_mesh.astype(int)]
63
+ new_cart_mesh_helper = np.array(new_cart_mesh_helper).reshape(
64
+ (new_basevect_grid_size[0], new_basevect_grid_size[1],
65
+ new_basevect_grid_size[2]))
66
+
67
+ # Apply the Marching Cubes algorithm
68
+ vertices, faces, normals, values = measure.marching_cubes(new_cart_mesh_helper, level=self.fermi_energy)
69
+
70
+ # coordinate transformation
71
+ new_basevect_mesh = np.dot(vertices, np.array(self.rez_base_vect))
72
+ ms = pymeshlab.MeshSet()
73
+ ms.add_mesh(pymeshlab.Mesh(new_basevect_mesh, faces))
74
+ new_mesh = ms.current_mesh()
75
+
76
+ self.surface = trimesh.Trimesh(vertices=np.asarray(new_mesh.vertex_matrix()),
77
+ faces=np.asarray(new_mesh.face_matrix()), process=False)
78
+
79
+ return self
80
+
81
+ def scale_surface(self, scale_factor):
82
+ """
83
+ Scales the Fermi surface according to the given `scale_factor`.
84
+ :param scale_factor: factor to scale the Fermi surface by
85
+ :return: self
86
+ """
87
+ if self.surface is None:
88
+ raise ValueError("surface is not yet defined")
89
+ # Create a scaling matrix
90
+ scaling_matrix = np.eye(4)
91
+ scaling_matrix[:3, :3] *= scale_factor
92
+
93
+ # Apply the scaling transformation to the mesh
94
+ self.surface.apply_transform(scaling_matrix)
95
+ return self
96
+
97
+ def center_surface(self):
98
+ """
99
+ centers the Fermi surface to the origin
100
+ :return: centered Fermi surface as Trimesh object
101
+ """
102
+ if self.surface is None:
103
+ raise ValueError("surface is not yet defined")
104
+
105
+ self.surface.apply_translation([-self.surface.centroid[i] for i in range(3)])
106
+ return self
107
+
108
+ def slice_surface(self):
109
+ """
110
+ Slices parts of the surface that extend beyond the brillouin zone
111
+ Note: Fermi surface needs to be centered and scaled according to the brillouin zone
112
+ :return: Sliced Fermi surface
113
+ """
114
+ if self.brillouin_zone is None:
115
+ raise ValueError("Brillouin Zone is not yet defined")
116
+ if self.surface is None:
117
+ raise ValueError("surface is not yet defined")
118
+
119
+ facet_centers = face_center_BZ(self.brillouin_zone[1])
120
+
121
+ # cutting off the surface area outside the 1. BZ
122
+ for i in range(len(self.brillouin_zone[1])):
123
+ facets_normal = np.array(facet_centers[i]) + 1 / 2 * np.array(facet_centers[i])
124
+
125
+ self.surface = self.surface.slice_plane(plane_origin=self.brillouin_zone[1][i][0],
126
+ plane_normal=facets_normal * (-1))
127
+ return self
128
+
129
+ def subdivide_surface(self, iterations):
130
+ """
131
+ divides each triangle of the parsed triangle mesh in to two triangles and therefore
132
+ providing a higher resolution
133
+ :param iterations: how many times this algorithm is applied
134
+ :return: the higher resolution triangle mesh
135
+ """
136
+
137
+ if self.surface is None:
138
+ raise ValueError("surface is not yet defined")
139
+
140
+
141
+ vertices = self.surface.vertices
142
+ faces = self.surface.faces
143
+
144
+ ms = pymeshlab.MeshSet()
145
+
146
+ # Add it to the MeshSet
147
+ ms.add_mesh(pymeshlab.Mesh(vertex_matrix=vertices, face_matrix=faces))
148
+ ms.meshing_surface_subdivision_loop(iterations=iterations, threshold=pymeshlab.PercentageValue(0))
149
+
150
+ smoothed_mesh = ms.current_mesh()
151
+ self.surface = trimesh.Trimesh(vertices=np.asarray(smoothed_mesh.vertex_matrix()),
152
+ faces=np.asarray(smoothed_mesh.face_matrix()), process=False)
153
+
154
+ return self
155
+
156
+ def downsample_surface(self, face_percentage, face_numbers):
157
+ """
158
+ lowers the resolution of the Fermi surface mesh (number of faces) to a given percentage
159
+ (from original face count)
160
+ :param face_percentage: targeted face percentage
161
+ :return: self
162
+ """
163
+
164
+ if self.surface is None:
165
+ raise ValueError("surface is not yet defined")
166
+
167
+
168
+ if face_percentage == 100 and face_numbers is None:
169
+ return self
170
+ vertices = self.surface.vertices
171
+ faces = self.surface.faces
172
+
173
+ ms = pymeshlab.MeshSet()
174
+ # Add it to the MeshSet
175
+ ms.add_mesh(pymeshlab.Mesh(vertex_matrix=vertices, face_matrix=faces))
176
+
177
+ if face_numbers is not None and face_percentage != 100:
178
+ raise ValueError("You can only either provide a face_percentage or face_numbers, not both")
179
+ elif face_numbers is not None:
180
+ numFaces = face_numbers
181
+ else:
182
+ facenum = len(faces) * face_percentage / 100
183
+ numFaces = int(facenum)
184
+
185
+ ms.meshing_decimation_quadric_edge_collapse(targetfacenum=numFaces)
186
+
187
+ smoothed_mesh = ms.current_mesh()
188
+ self.surface = trimesh.Trimesh(vertices=np.asarray(smoothed_mesh.vertex_matrix()),
189
+ faces=np.asarray(smoothed_mesh.face_matrix()), process=False)
190
+
191
+ return self
192
+
193
+ def build_surface_with_bxsf_files(self, filepath, subdivide_iterations=0, down_sampling_percentage=100,
194
+ downsampling_surface_face=None):
195
+ """
196
+ whole fermi surface construction process for bxsf files, including reading out the input data, building the
197
+ first brillouin zone and applying the marching cubes' algorithm.
198
+ :param filepath: path to bxsf file.
199
+ :param subdivide_iterations: number of iterations to subdivide the Fermi surface by subdivide_surface
200
+ :param down_sampling_percentage: percentage to which the downsampling_surface method reduces the number of faces
201
+ :param downsampling_surface_face: number of faces to which the downsampling_surface method reduces the mesh
202
+ :return: self
203
+ """
204
+
205
+ data = read_energy_numbers(filepath)
206
+ self.set_energy_values(data[0])
207
+ self.set_fermi_energy(data[1])
208
+ self.set_rez_base_vect(data[2])
209
+ self.set_k_grid_by_size(data[3])
210
+
211
+ self.compute_brillouin_zone()
212
+
213
+ grid_size = self.grid_size
214
+ new_basevect_grid_size = np.array([grid_size[0] * 2 - 1, grid_size[1] * 2 - 1, grid_size[2] * 2 - 1])
215
+
216
+ self.band_index = []
217
+ self.fermi_surface_list = []
218
+ for index, columnName in enumerate(self.energy_values.columns):
219
+
220
+ # Apply the Marching Cubes algorithm
221
+ try:
222
+ self.marching_cubes(columnName)
223
+ except ValueError:
224
+ continue
225
+
226
+
227
+ self.subdivide_surface(subdivide_iterations)
228
+ self.downsample_surface(face_percentage=down_sampling_percentage,face_numbers=downsampling_surface_face)
229
+
230
+ # translation and shrinkage
231
+ self.scale_surface(2 / new_basevect_grid_size)
232
+
233
+ # translation
234
+ self.center_surface()
235
+
236
+ self.slice_surface()
237
+
238
+ self.fermi_surface_list.append(self.surface)
239
+ self.band_index.append(index + 1) # for the plot
240
+ return self
241
+
242
+ def visualization(self, filepath, save_fermisurf_path, svg=False):
243
+ """
244
+ Visualizes the Fermi surface as a 3D interactive plot saved as an html file. Also allows for creating
245
+ an SVG Image of the Fermi surface along the html file.
246
+ :param filepath: path to bxsf file.
247
+ :param save_fermisurf_path: directory where the created files and imaged will be stored
248
+ :param svg: boolean whether to create the SVG image
249
+ """
250
+ figure = build_plotly_figure(self.fermi_surface_list, self.brillouin_zone, self.band_index)
251
+ write_figure_to_file(figure, filepath, save_fermisurf_path, create_SVG=svg)
fsvisual/input.py ADDED
@@ -0,0 +1,52 @@
1
+ import pandas as pd
2
+
3
+
4
+ # Define a function to process the text file
5
+ def read_energy_numbers(filepath):
6
+ """
7
+ function that extracts all necessary data out of the FERMISURF.bxsf file
8
+ :param filepath: path of the FERMISURF.bxsf file
9
+ :return: energy DataFrame, fermi energy, reciprocal base vectors, grid size
10
+ """
11
+
12
+ with open(filepath, 'r') as file:
13
+ lines = file.readlines()
14
+
15
+ # Filter out the lines containing energy numbers
16
+
17
+ values_in_series = []
18
+ my_dict = {}
19
+ fermi_energy = 0
20
+ rez_base_vect = []
21
+ grid_size = []
22
+ j = 0
23
+ for i, line in enumerate(lines):
24
+ if i == 3: # fermi_energy
25
+ fermi_energy = float(line[19:31])
26
+ if i == 9: # grid_size
27
+ grid = line.split(" ")
28
+ grid = [int(num) for num in grid if num != "" and num != "\n"]
29
+ grid_size.append(grid)
30
+
31
+ if 10 < i < 14: # base vectors
32
+ vect = [line.split(" ")]
33
+ vect[0] = [float(num) for num in vect[0] if num != "" and num != "\n"]
34
+ rez_base_vect.extend(vect)
35
+ if i >= 13: # algorythm that collects all the energies and stores them into separate bands
36
+ if line.startswith(" BAND"):
37
+ if j == 0:
38
+ pass
39
+ else:
40
+ my_dict[f"Band {j}"] = values_in_series
41
+ values_in_series = []
42
+ j += 1
43
+ try:
44
+ energy = float(line.strip())
45
+ values_in_series.append(energy)
46
+ except ValueError:
47
+ pass # Ignore lines that cannot be converted to float
48
+
49
+ i += 1
50
+ my_dict[f"Band {j}"] = values_in_series
51
+ df = pd.DataFrame(my_dict)
52
+ return df, fermi_energy, rez_base_vect, grid_size[0]
fsvisual/mesh.py ADDED
@@ -0,0 +1,134 @@
1
+ import numpy as np
2
+
3
+
4
+ def create_cartesian_mesh(grid_size):
5
+ """
6
+ creates a mesh within the reciprocal unit cell for any reciprocal lattice
7
+ :param grid_size: number of datapoints the grid should have (list of 3 number for each lattice vector)
8
+ :return: standard mesh
9
+ """
10
+
11
+ grid_size = [int(grid_size_n) for grid_size_n in grid_size]
12
+ Imin = [0] * 3
13
+ Imax = [0] * 3
14
+
15
+ # loop that creates the mesh
16
+
17
+ band_grid = np.arange((grid_size[0]) * (grid_size[1]) * (grid_size[2]))
18
+ band_grid = band_grid.reshape((grid_size[0], grid_size[1], grid_size[2]))
19
+
20
+ grid_size_minus_one = [grid - 1 for grid in grid_size]
21
+
22
+ for k in range(3):
23
+ Imin[k] = -int(grid_size_minus_one[k])
24
+ Imax[k] = int(grid_size_minus_one[k])
25
+
26
+ # creating the mols
27
+ mesh_axis_one = np.arange(Imin[0], Imax[0] + 1)
28
+ mesh_axis_two = np.arange(Imin[1], Imax[1] + 1)
29
+ mesh_axis_three = np.arange(Imin[2], Imax[2] + 1)
30
+
31
+ mesh_axis_one = np.where(mesh_axis_one >= 0, mesh_axis_one, mesh_axis_one + grid_size_minus_one[0])
32
+ mesh_axis_two = np.where(mesh_axis_two >= 0, mesh_axis_two, mesh_axis_two + grid_size_minus_one[1])
33
+ mesh_axis_three = np.where(mesh_axis_three >= 0, mesh_axis_three, mesh_axis_three + grid_size_minus_one[2])
34
+
35
+ index_mesh = np.ix_(mesh_axis_one, mesh_axis_two, mesh_axis_three)
36
+
37
+ cartesian_mesh = band_grid[index_mesh].flatten()
38
+
39
+ return cartesian_mesh
40
+
41
+
42
+ def triangulate_faces(facets):
43
+ """
44
+ splits every facet of the 1. BZ into triangles for Trimesh to read -> function is used by brillouin_intersect_mesh()
45
+ :param facets: list of all facets with vertices counted from 0 to last vertex
46
+ :return:
47
+ """
48
+ triangles = []
49
+ for facet in facets:
50
+ if len(facet) == 3:
51
+ triangles.append(facet)
52
+ elif len(facet) == 4:
53
+ triangles.append([facet[0], facet[1], facet[2]])
54
+ triangles.append([facet[0], facet[2], facet[3]])
55
+ else:
56
+ # For facets with more than 4 vertices, use a fan triangulation
57
+ for i in range(1, len(facet) - 1):
58
+ triangles.append([facet[0], facet[i], facet[i + 1]])
59
+ return triangles
60
+
61
+
62
+ def abs_vect(vector):
63
+ """
64
+ :param vector: takes 3D vector as a list or array
65
+ :return: absolute value of that vector
66
+ """
67
+ return np.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2)
68
+
69
+
70
+ def triangle_area(triangle):
71
+ """
72
+ calculates the area of a triangle via vertices
73
+ :param triangle: list of 3 3D vertices
74
+ :return: area of a triangle in FE
75
+ """
76
+
77
+ # from coordinates in space
78
+
79
+ P1 = np.array(triangle[0])
80
+ P2 = np.array(triangle[1])
81
+ P3 = np.array(triangle[2])
82
+
83
+ area = 1 / 2 * np.sqrt(abs_vect(P2 - P1) ** 2 * abs_vect(P3 - P1) ** 2 - np.dot((P2 - P1), (P3 - P1)) ** 2)
84
+ return area
85
+
86
+
87
+ def triangle_center(triangle):
88
+ """
89
+ calculates the center of every triangle
90
+ :param triangle: list of 3 vertices
91
+ :return: center point of a triangle
92
+ """
93
+
94
+ xS = 1 / 3 * (triangle[0][0] + triangle[1][0] + triangle[2][0])
95
+ yS = 1 / 3 * (triangle[0][1] + triangle[1][1] + triangle[2][1])
96
+ zS = 1 / 3 * (triangle[0][2] + triangle[1][2] + triangle[2][2])
97
+
98
+ return [xS, yS, zS]
99
+
100
+
101
+ def face_center_BZ(brillouin_zone_facets):
102
+ """
103
+ function to calculate the center of every facet
104
+ :param brillouin_zone_facets: list with all facets (filled with points, not indices)
105
+ :return: list of 3D center points for every facet
106
+ """
107
+
108
+ # triangulate the facets:
109
+ triangle_list = triangulate_faces(brillouin_zone_facets)
110
+
111
+ face_centers = []
112
+ j = 0
113
+ for facet in brillouin_zone_facets: # goes through every facet and calculates the center of it
114
+ triangle_areas = []
115
+ triangle_centers = []
116
+ for triangle in triangle_list[j:j + len(facet) - 2]: # goes through every triangle of each facet
117
+ # triangle area:
118
+ if triangle_area(triangle) != 0:
119
+ triangle_areas.append(triangle_area(triangle)) # triangle area
120
+ triangle_centers.append(triangle_center(triangle)) # triangle center
121
+ j += len(facet) - 2
122
+
123
+ x_coord = np.array([point[0] for point in triangle_centers])
124
+ y_coord = np.array([point[1] for point in triangle_centers])
125
+ z_coord = np.array([point[2] for point in triangle_centers])
126
+
127
+ # calculation of the facet center (geometrischer Schwerpunkt)
128
+ xS = np.sum(x_coord * np.array(triangle_areas)) / np.sum(triangle_areas)
129
+ yS = np.sum(y_coord * np.array(triangle_areas)) / np.sum(triangle_areas)
130
+ zS = np.sum(z_coord * np.array(triangle_areas)) / np.sum(triangle_areas)
131
+
132
+ face_centers.append([xS, yS, zS])
133
+
134
+ return face_centers
@@ -0,0 +1,126 @@
1
+ import numpy as np
2
+ import plotly.graph_objects as go
3
+ import plotly.io as pio
4
+ import os
5
+ import kaleido
6
+
7
+
8
+ def build_plotly_figure(fermi_surface_list, brillouin_zone_object, band_index):
9
+ """
10
+ to build the Fermi surface as a 3D interactive plotly figure
11
+ :param fermi_surface_list: list of all surface parts of the Fermi surface
12
+ :param brillouin_zone_object: the brillouin zone object created with the compute_brillouin_zone from FSvisual
13
+ :param band_index: list of band indices corresponding to each surface part
14
+ :return: the plotly figure
15
+ """
16
+ mesh_fermi_surfaces = []
17
+ for index, fermi_surface in enumerate(fermi_surface_list):
18
+ x_mesh, y_mesh, z_mesh = fermi_surface.vertices[:, 0], fermi_surface.vertices[:, 1], fermi_surface.vertices[:,
19
+ 2]
20
+
21
+ # Extract I, J, K indices of faces
22
+ i, j, k = fermi_surface.faces[:, 0], fermi_surface.faces[:, 1], fermi_surface.faces[:, 2]
23
+
24
+ mesh_fermi_surfaces.append(go.Mesh3d(
25
+ x=np.array(x_mesh),
26
+ y=np.array(y_mesh),
27
+ z=np.array(z_mesh),
28
+ i=np.array(i),
29
+ j=np.array(j),
30
+ k=np.array(k),
31
+ name=f"Band {band_index[index]}",
32
+ opacity=1,
33
+ showlegend=True
34
+ ))
35
+
36
+ x = brillouin_zone_object[0][0]
37
+ y = brillouin_zone_object[0][1]
38
+ z = brillouin_zone_object[0][2]
39
+
40
+ # find highest and lowest point of BZ for axis scaling:
41
+
42
+ # filter non values and find maximum on each axis
43
+ max_x = np.max([np.abs(num) for num in x if num is not None])
44
+ max_y = np.max([np.abs(num) for num in y if num is not None])
45
+ max_z = np.max([np.abs(num) for num in z if num is not None])
46
+
47
+ # calculate global maximum
48
+ max_value_axis = np.max([max_x, max_y, max_z])
49
+
50
+
51
+ if max_value_axis > 0:
52
+ max_value_axis += 1/10*max_value_axis
53
+ else:
54
+ max_value_axis -= 1/10*max_value_axis
55
+
56
+
57
+ # Create a 3D scatter plot
58
+ scatter_BZ = go.Scatter3d(
59
+ x=x,
60
+ y=y,
61
+ z=z,
62
+ mode='lines',
63
+ name="1. BZ",
64
+ line=dict(color='black', width=6)
65
+ )
66
+
67
+ # contains all
68
+ fig_data = [scatter_BZ]
69
+ fig_data.extend(mesh_fermi_surfaces)
70
+
71
+ fig = go.Figure(data=fig_data)
72
+
73
+ fig.update_layout(
74
+
75
+ scene=dict(
76
+ xaxis=dict(
77
+ range=[-max_value_axis, max_value_axis], # Fester Bereich für x-Achse
78
+ visible=False
79
+ ),
80
+ yaxis=dict(
81
+ range=[-max_value_axis, max_value_axis], # Fester Bereich für y-Achse
82
+ visible=False
83
+ ),
84
+ zaxis=dict(
85
+ range=[-max_value_axis, max_value_axis], # Fester Bereich für z-Achse
86
+ visible=False
87
+ ),
88
+ annotations=[], # Remove any annotations if present
89
+ aspectmode='cube',
90
+ camera=dict(
91
+ projection=dict(
92
+ type='orthographic'
93
+ # to change the perspective (so that lines don't distort over distance)
94
+ )
95
+ )
96
+ )
97
+ )
98
+ return fig
99
+
100
+
101
+ def write_figure_to_file(fig, filepath, save_figure_directory, create_SVG=True,
102
+ scene_camera_SVG=None):
103
+ """
104
+ Function to take a plotly figure and write it to a file with potentially also creating an SVG file.
105
+ :param fig: plotly figure
106
+ :param filepath: path to .bxsf file/files
107
+ :param save_figure_directory: directory where to save the figure
108
+ :param create_SVG: boolean whether to create SVG file
109
+ :param scene_camera_SVG: alter the cameras position and angle with dictionary (scene_camera parameter in plotly)
110
+ """
111
+
112
+ filename = os.path.basename(filepath)
113
+ filename = filename.split(".")[0]
114
+ pio.write_html(fig, file=f'{save_figure_directory}/{filename}.html', auto_open=False,
115
+ config={'displayModeBar': False})
116
+
117
+ if create_SVG:
118
+ kaleido.get_chrome_sync()
119
+ if scene_camera_SVG is None:
120
+ scene_camera_SVG = dict(eye=dict(x=1, y=1, z=1))
121
+ fig.update_layout(
122
+ showlegend=False,
123
+ scene_camera=scene_camera_SVG, # change camera scene
124
+ margin=dict(l=0, r=0, b=0, t=0) # set the space on the edges to 0 (so that the plot fills out the image)
125
+ )
126
+ fig.write_image(f'{save_figure_directory}/{filename}.svg', format="svg", width=500, height=800)