crystalbuilder 0.5.4__py2.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.
Potentially problematic release.
This version of crystalbuilder might be problematic. Click here for more details.
- crystalbuilder/__init__.py +27 -0
- crystalbuilder/bilbao.py +297 -0
- crystalbuilder/conversions/lumc.py +36 -0
- crystalbuilder/conversions/t3d.py +179 -0
- crystalbuilder/convert.py +379 -0
- crystalbuilder/geometry.py +759 -0
- crystalbuilder/lattice.py +795 -0
- crystalbuilder/utils.py +22 -0
- crystalbuilder/vectors.py +279 -0
- crystalbuilder/viewer.py +82 -0
- crystalbuilder-0.5.4.dist-info/METADATA +12 -0
- crystalbuilder-0.5.4.dist-info/RECORD +14 -0
- crystalbuilder-0.5.4.dist-info/WHEEL +5 -0
- crystalbuilder-0.5.4.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = version("crystalbuilder")
|
|
5
|
+
except PackageNotFoundError:
|
|
6
|
+
__version__ = "unknown"
|
|
7
|
+
|
|
8
|
+
__all__ = ["convert", "geometry", "lattice", "vectors","bilbao", "viewer", "conversions", "conversions"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
CrystalBuilder allows building triangle and cylinder based photonic crystal lattices for MEEP, MPB, and Tidy3D.
|
|
15
|
+
|
|
16
|
+
convert.py: methods for converting CrystalBuilder objects to MEEP/MPB/Tidy3D geometries
|
|
17
|
+
|
|
18
|
+
geometry.py: geometry classes. These are not actually "meshed" structures, but vertices/centers as required by the simulation program. e.g. a "triangle" object is defined by vertices in MEEP and Tidy3D so it is also defined that way in CrystalBuilder.geometry. There are additional methods in the geometry program that allow for defining with a center+size, but under the hood it still just calculates vertices.
|
|
19
|
+
|
|
20
|
+
lattice.py: the lattice class contains the basis vector information, and the methods for tiling the geometry objects. kekule modulation is also here.
|
|
21
|
+
|
|
22
|
+
vectors.py: methods for rotating/shifting/scaling vectors defined as numpy arrays or simple lists. This probably won't need to be called by users, but is integral for all the other packages.
|
|
23
|
+
|
|
24
|
+
bilbao.py: methods for retrieving space group information from Bilbao servers
|
|
25
|
+
|
|
26
|
+
viewer.py: methods for visualizing structures
|
|
27
|
+
"""
|
crystalbuilder/bilbao.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
location = os.path.dirname(os.path.realpath(__file__))
|
|
6
|
+
resources = os.path.join(location, 'resources')
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
import numpy as np
|
|
10
|
+
from bs4 import BeautifulSoup
|
|
11
|
+
import math
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_resource(filename):
|
|
16
|
+
if os.path.exists(filename):
|
|
17
|
+
with open(filename) as f:
|
|
18
|
+
kvec_dict = json.load(f)
|
|
19
|
+
return kvec_dict
|
|
20
|
+
else:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def create_resource(filename, dictionary):
|
|
24
|
+
for key, value in dictionary.items():
|
|
25
|
+
if isinstance(value, np.ndarray):
|
|
26
|
+
dictionary[key] = value.tolist()
|
|
27
|
+
with open(filename, 'w') as f:
|
|
28
|
+
json.dump(dictionary, f, indent=3)
|
|
29
|
+
|
|
30
|
+
def get_kvectors(groupnum, dict_out=False):
|
|
31
|
+
|
|
32
|
+
kvec_array = [] #will convert coordinates to array
|
|
33
|
+
kvec_dictionary = {} ## for converting both symbols and coordinates to formatted dictionary
|
|
34
|
+
|
|
35
|
+
file = os.path.join(resources, f'{groupnum}-kvec.json')
|
|
36
|
+
localkdict = check_resource(file)
|
|
37
|
+
if localkdict:
|
|
38
|
+
for key, k in localkdict.items():
|
|
39
|
+
kvec_array.append(k)
|
|
40
|
+
save_file = False
|
|
41
|
+
kvec_dictionary = localkdict
|
|
42
|
+
else:
|
|
43
|
+
save_file = True
|
|
44
|
+
URL = "https://www.cryst.ehu.es/cgi-bin/cryst/programs/nph-kv-list"
|
|
45
|
+
page = requests.post(URL, data={'gnum': str(groupnum),'standard':'Optimized listing of k-vector types using ITA description'})
|
|
46
|
+
soup = BeautifulSoup(page.content, "html.parser")
|
|
47
|
+
kvec_table = soup.find_all('table')[1]
|
|
48
|
+
rows = kvec_table('tr')[2:]
|
|
49
|
+
raw_kvec_dict = {}
|
|
50
|
+
for row in rows:
|
|
51
|
+
sympoint = row.find_all('td')[0].get_text() #first cell has symbol/letter
|
|
52
|
+
coordstring = row.find_all('td')[1].get_text() #next cell has the coordinates
|
|
53
|
+
coord = coordstring.split(',') #split the kvec into components
|
|
54
|
+
raw_kvec_dict[sympoint] = coord # create dictionary from symbol and coordinate
|
|
55
|
+
|
|
56
|
+
for key, n in raw_kvec_dict.items():
|
|
57
|
+
if len(n) == 3: #Make sure we have (kx,ky,kz)
|
|
58
|
+
point = [] ## container for the 3 coordinates
|
|
59
|
+
for index, k in enumerate(n): ## iterate through all the points
|
|
60
|
+
k= re.split(r'\b\D\b', k) ### remove blanks, letters, and slashes from division signs.
|
|
61
|
+
try:
|
|
62
|
+
coordinate = float(k[0])/float(k[1])
|
|
63
|
+
except IndexError:
|
|
64
|
+
coordinate = float(k[0])
|
|
65
|
+
except ValueError:
|
|
66
|
+
try:
|
|
67
|
+
coordinate = float(k[0])
|
|
68
|
+
except ValueError:
|
|
69
|
+
coordinate = 1
|
|
70
|
+
pass
|
|
71
|
+
pass
|
|
72
|
+
point.append(coordinate)
|
|
73
|
+
kxkykz = np.reshape(np.array(point),(3))
|
|
74
|
+
kvec_array.append(kxkykz)
|
|
75
|
+
|
|
76
|
+
kvec_dictionary[key] = kxkykz
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
kvec_array = np.reshape(np.asarray(kvec_array), (-1,3))
|
|
80
|
+
|
|
81
|
+
if save_file == True:
|
|
82
|
+
create_resource(file, kvec_dictionary)
|
|
83
|
+
|
|
84
|
+
if dict_out == True:
|
|
85
|
+
return kvec_dictionary
|
|
86
|
+
else:
|
|
87
|
+
return kvec_array
|
|
88
|
+
|
|
89
|
+
def get_genmat(groupnum):
|
|
90
|
+
""" Retrieve generator matrices
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
-----------
|
|
94
|
+
groupnum : int
|
|
95
|
+
One of 230 numbered space groups in the IUCr
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
--------
|
|
99
|
+
matrix_list : list
|
|
100
|
+
list of matrices representing general positions
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
matrix_list = [] #will convert coordinates to array
|
|
105
|
+
gen_dictionary = {} ## for converting both symbols and coordinates to formatted dictionary
|
|
106
|
+
|
|
107
|
+
file = os.path.join(resources, f'{groupnum}-generators.json')
|
|
108
|
+
localgendict = check_resource(file)
|
|
109
|
+
if localgendict:
|
|
110
|
+
for key, k in localgendict.items():
|
|
111
|
+
matrix = k
|
|
112
|
+
matrix_list.append(k)
|
|
113
|
+
save_file = False
|
|
114
|
+
else:
|
|
115
|
+
save_file = True
|
|
116
|
+
URL = "https://www.cryst.ehu.es/cgi-bin/cryst/programs/nph-getgen"
|
|
117
|
+
page = requests.post(URL, data={'gnum': str(
|
|
118
|
+
groupnum), 'what': 'gp', 'list': 'Standard/Default+Setting'})
|
|
119
|
+
gen_pos = BeautifulSoup(page.content, "html.parser")
|
|
120
|
+
holder = gen_pos.find_all("pre")
|
|
121
|
+
|
|
122
|
+
matrix_text = []
|
|
123
|
+
for k in holder:
|
|
124
|
+
matrix_text.append(k.get_text()) #get text from table
|
|
125
|
+
|
|
126
|
+
for f in range(0, len(matrix_text)):
|
|
127
|
+
genpos_line = matrix_text[f].split('\n') #separate the matrix text based on newline, giving 3x4 matrix of strings
|
|
128
|
+
|
|
129
|
+
genpos_matrix = np.array([]).reshape(0,4) #create an empty numpy array to "append" each row of the matrix to
|
|
130
|
+
|
|
131
|
+
for row_string in genpos_line:
|
|
132
|
+
row_list = list(filter(None, row_string.split(' '))) # generate a list of strings for each row.
|
|
133
|
+
row_elements = [] ## create list to store the elements as floats
|
|
134
|
+
|
|
135
|
+
for element in row_list: #go through and convert string elements to floating point ones
|
|
136
|
+
try:
|
|
137
|
+
matrix_value = float(element) #try simply converting
|
|
138
|
+
except ValueError:
|
|
139
|
+
elem_split = element.split('/') #if the simple conversion doesn't work, it's likely because it's written as a fraction
|
|
140
|
+
matrix_value = float(elem_split[0])/float(elem_split[1]) #calculate the float from the fraction
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
row_elements.append(matrix_value) #Put the element in a list with the others in the same row
|
|
144
|
+
|
|
145
|
+
row_elements = np.asarray(row_elements) #convert to numpy array
|
|
146
|
+
genpos_matrix = np.vstack([genpos_matrix, row_elements]) #add the below our existing row
|
|
147
|
+
|
|
148
|
+
matrix_list.append(genpos_matrix) #After iterating through the rows of the matrix, put the matrix in the list
|
|
149
|
+
|
|
150
|
+
if save_file == True:
|
|
151
|
+
matdict = {}
|
|
152
|
+
for key, value in enumerate(matrix_list):
|
|
153
|
+
matdict[key] = value
|
|
154
|
+
|
|
155
|
+
create_resource(file, matdict)
|
|
156
|
+
|
|
157
|
+
return matrix_list
|
|
158
|
+
|
|
159
|
+
def get_coordinates(groupnum, origin, output_array=True):
|
|
160
|
+
""" Generates positions from specified origin and generator matrices
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
-----------
|
|
164
|
+
groupnum : int
|
|
165
|
+
One of 230 numbered space groups in the IUCr
|
|
166
|
+
|
|
167
|
+
origin : list
|
|
168
|
+
Any point that should be used as (x,y,z) for symmetry operations from the generator matrices
|
|
169
|
+
|
|
170
|
+
output_array : bool
|
|
171
|
+
Oututs numpy array by default (True), since the result should be an m x 3 matrix with m being the number of generator matrices. If False, the output is a list of lists.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
--------
|
|
175
|
+
coordinates : list, array
|
|
176
|
+
Returns an array if output_array is True (default), returns a list object otherwise.
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
position_vector = np.array([origin[0], origin[1], origin[2]]).reshape(3,1)
|
|
183
|
+
matrix_list = get_genmat(groupnum)
|
|
184
|
+
coordinate_list = []
|
|
185
|
+
coordinate_array = np.array([]).reshape(0,3)
|
|
186
|
+
for n in matrix_list:
|
|
187
|
+
n = np.asarray(n)
|
|
188
|
+
linear_part, translation_part = np.split(n, [3,], axis=1) #Split matrix into linear part and translation part, *after* third element in row
|
|
189
|
+
#linear_part is 3x3, translation_part is 3x1
|
|
190
|
+
linear_product = np.matmul(linear_part, position_vector) #matrix part
|
|
191
|
+
transformation = linear_product + translation_part #affine transformation
|
|
192
|
+
new_point = transformation.reshape(1,3) #make row matrix
|
|
193
|
+
if ((new_point.all() <= 1) and (new_point.all() >= 0)):
|
|
194
|
+
if output_array==True:
|
|
195
|
+
coordinate_array = np.concatenate([coordinate_array, new_point], axis=0)
|
|
196
|
+
else:
|
|
197
|
+
coordinate_list.append(new_point.tolist()) #add point to list
|
|
198
|
+
else:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if output_array == True:
|
|
203
|
+
return coordinate_array
|
|
204
|
+
else:
|
|
205
|
+
return coordinate_list
|
|
206
|
+
|
|
207
|
+
class SpaceGroup():
|
|
208
|
+
"""
|
|
209
|
+
One of 230 space groups
|
|
210
|
+
|
|
211
|
+
This class will have the properties necessary for each space group, as pulled from the Bilbao database.
|
|
212
|
+
|
|
213
|
+
Includes:
|
|
214
|
+
k-vectors with labels
|
|
215
|
+
generator matrices
|
|
216
|
+
generation of equivalent points
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
group_number,
|
|
222
|
+
**kwargs
|
|
223
|
+
) -> None:
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
Create the instance of a Space Group
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
-----------
|
|
230
|
+
group_number : int
|
|
231
|
+
1-230, corresponding to IUCr and Bilbao server notation.
|
|
232
|
+
|
|
233
|
+
kwargs
|
|
234
|
+
-------
|
|
235
|
+
points : list, ndarray
|
|
236
|
+
Initial coordinates that will be operated on by the symmetry operations to create the entire unit cell.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
self.point_list = kwargs.get("points", None)
|
|
241
|
+
self.group_num = group_number
|
|
242
|
+
|
|
243
|
+
self.kvec_dict = get_kvectors(self.group_num, dict_out=True)
|
|
244
|
+
self.kvec_arr = get_kvectors(self.group_num, dict_out=False)
|
|
245
|
+
|
|
246
|
+
self.generator_matrices = get_genmat(self.group_num)
|
|
247
|
+
|
|
248
|
+
self.generated_points = self.calculate_points(self.point_list)
|
|
249
|
+
|
|
250
|
+
def calculate_points(self, point_list):
|
|
251
|
+
"""
|
|
252
|
+
Return a list of coordinates resulting from symmetry operations to each point in `point_list`. This is called once if the `SpaceGroup` is initialized with the `points` kwarg.
|
|
253
|
+
It can be called any number of times to directly return points from new `point_list` inputs.
|
|
254
|
+
|
|
255
|
+
Parameter
|
|
256
|
+
----------
|
|
257
|
+
point_list : tuple, list, ndarray
|
|
258
|
+
point(s) on which to perform symmetry operations
|
|
259
|
+
|
|
260
|
+
Return
|
|
261
|
+
-------
|
|
262
|
+
generated_points : ndarray
|
|
263
|
+
Unique points resulting from the symmetry operations on points in point_list. This includes negative values and values greater than 1 (outside the primitive cell).
|
|
264
|
+
"""
|
|
265
|
+
generated_points = np.array([]).reshape(-1,3)
|
|
266
|
+
if point_list is not None:
|
|
267
|
+
if isinstance(point_list, (list, np.ndarray)):
|
|
268
|
+
for n in point_list:
|
|
269
|
+
newpoint = get_coordinates(self.group_num, origin=n)
|
|
270
|
+
generated_points = np.vstack((generated_points, newpoint))
|
|
271
|
+
generated_points.reshape(-1,3)
|
|
272
|
+
generated_points = np.unique(generated_points, axis=0)
|
|
273
|
+
|
|
274
|
+
else:
|
|
275
|
+
generated_points = get_coordinates(self.group_num, origin=point_list)
|
|
276
|
+
generated_points.reshape(-1,3)
|
|
277
|
+
generated_points = np.unique(generated_points, axis=0)
|
|
278
|
+
|
|
279
|
+
return generated_points
|
|
280
|
+
else:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
from matplotlib import pyplot as plt
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# crystest = SpaceGroup(227)
|
|
289
|
+
# pointlist = crystest.calculate_points([(0,0,0)])
|
|
290
|
+
# print(pointlist)
|
|
291
|
+
# print(pointlist.shape)
|
|
292
|
+
|
|
293
|
+
# fig = plt.figure()
|
|
294
|
+
# ax = fig.add_subplot(projection='3d')
|
|
295
|
+
|
|
296
|
+
# ax.scatter(pointlist[:, 0], pointlist[:, 1], pointlist[:,2])
|
|
297
|
+
# plt.show()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from crystalbuilder import lattice as lat
|
|
3
|
+
from crystalbuilder import geometry as geo
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import lumpy.simobjects as so
|
|
7
|
+
except ModuleNotFoundError:
|
|
8
|
+
print("Error: Lumpy and/or the Lumerical API were not found.")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
debug = 'trace' #trace = 3, debug = 2, info = 1, none = 0
|
|
12
|
+
|
|
13
|
+
def debug_msg(string, level):
|
|
14
|
+
debug_levels = {'trace':3, 'debug':2, 'info': 1, 'none':0}
|
|
15
|
+
req_deb = debug_levels[debug]
|
|
16
|
+
if level <= req_deb:
|
|
17
|
+
print(string)
|
|
18
|
+
|
|
19
|
+
def flatten(list):
|
|
20
|
+
""" Some of these methods can accidentally create nested lists, so this function can be used in try statements to correct those """
|
|
21
|
+
try:
|
|
22
|
+
flat_list = [item for sublist in list for item in sublist]
|
|
23
|
+
except:
|
|
24
|
+
flat_list = list
|
|
25
|
+
return flat_list
|
|
26
|
+
|
|
27
|
+
def convert_cylinder(Cylinder, material='dielectric', index=1.5):
|
|
28
|
+
axis = Cylinder.axis
|
|
29
|
+
lumCyl = so.Cylinder(radius=Cylinder.radius, height=Cylinder.height, center=tuple(flatten(Cylinder.center)), material=material, index=index, orientation=axis)
|
|
30
|
+
debug_msg(lumCyl.out(), 3)
|
|
31
|
+
return lumCyl
|
|
32
|
+
|
|
33
|
+
def convert_prism(Prism, material='dielectric', index=1.5):
|
|
34
|
+
verts = Prism.vertices[:, 0:2]
|
|
35
|
+
lumPrism = so.Prism(vertices=verts, z_span=Prism.height, center=tuple(flatten(Prism.center)), material=material, index=index)
|
|
36
|
+
return lumPrism
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
from tidy3d import Transformed, Structure, GeometryGroup, Cylinder, Medium, Simulation, PointDipole, C_0, GridSpec, GaussianPulse
|
|
4
|
+
try:
|
|
5
|
+
from tidy3d import Transformed, Structure, GeometryGroup, Cylinder, Medium, Simulation, PointDipole, C_0, GridSpec, GaussianPulse
|
|
6
|
+
except ModuleNotFoundError:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
from crystalbuilder import geometry as geo
|
|
10
|
+
debug = "off"
|
|
11
|
+
|
|
12
|
+
def unpack_supercell(supercell):
|
|
13
|
+
"""Turns supercell into a list of geometry objects
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
supercell : gm.SuperCell()
|
|
18
|
+
A SuperCell object from geometry.py
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
[structures]: list
|
|
23
|
+
list of the geometry objects in SuperCell
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
structures = supercell.structures
|
|
28
|
+
return structures
|
|
29
|
+
|
|
30
|
+
def flatten(list):
|
|
31
|
+
""" Some of these methods can accidentally create nested lists, so this function can be used in try statements to correct those """
|
|
32
|
+
try:
|
|
33
|
+
if isinstance(list, list):
|
|
34
|
+
flat_list = [item for sublist in list for item in sublist]
|
|
35
|
+
except:
|
|
36
|
+
flat_list = list
|
|
37
|
+
return flat_list
|
|
38
|
+
|
|
39
|
+
def rotate_to(orientation, v1 = [0,0,1]):
|
|
40
|
+
"""
|
|
41
|
+
Create a quaternion to rotate the original vector to the specified `orientation`. By default, use a Z unit vector
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
orientation = np.asarray(orientation)/np.linalg.norm(orientation)
|
|
45
|
+
v1 = np.asarray(v1)/np.linalg.norm(v1)
|
|
46
|
+
eyemat = np.identity(3)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
rot_axis_unnorm = np.cross(v1, orientation)
|
|
50
|
+
s_angle = np.linalg.norm(rot_axis_unnorm)
|
|
51
|
+
rot_axis = rot_axis_unnorm/s_angle
|
|
52
|
+
c_angle = np.dot(v1, orientation)
|
|
53
|
+
rotmat2 = Transformed.rotation(s_angle, (rot_axis[0], rot_axis[1], rot_axis[2]))
|
|
54
|
+
|
|
55
|
+
# print(s_angle)
|
|
56
|
+
# print(c_angle)
|
|
57
|
+
|
|
58
|
+
skew_mat = np.array(
|
|
59
|
+
((0, -rot_axis[2], rot_axis[1]),
|
|
60
|
+
(rot_axis[2], 0, -rot_axis[0]),
|
|
61
|
+
(-rot_axis[1], rot_axis[0], 0))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
rot_mat = eyemat + (s_angle * skew_mat) + ((1-c_angle)*(skew_mat@skew_mat))
|
|
65
|
+
#Tidy3D takes an affine transformation matrix. So we'll pad it to 4x4 with zeros and a 1 in the bottom corner
|
|
66
|
+
|
|
67
|
+
rot_mat = np.pad(rot_mat, (0,1))
|
|
68
|
+
rot_mat[3,3] = 1
|
|
69
|
+
# rot_mat = rotmat2
|
|
70
|
+
return rot_mat
|
|
71
|
+
|
|
72
|
+
def _convert_cyl(geometry_object, material):
|
|
73
|
+
""" Put the structure at the origin, do the rotation, then shift it to the correct spot."""
|
|
74
|
+
m = geometry_object
|
|
75
|
+
rot_mat = rotate_to(m.axis)
|
|
76
|
+
shift_center = flatten(m.center)
|
|
77
|
+
rot_mat[:3, -1] = shift_center
|
|
78
|
+
tdgeom = Transformed(geometry = Cylinder(radius=m.radius, axis= 2, length=m.height, center=[0,0,0]), transform=rot_mat)
|
|
79
|
+
return tdgeom
|
|
80
|
+
|
|
81
|
+
def _geo_to_tidy3d(geometry_object, material, **kwargs):
|
|
82
|
+
"""Converts geometry object (or supercell) to the Tidy3D equivalent. Note that Tidy3D values always include units (microns by default).
|
|
83
|
+
|
|
84
|
+
Tidy3D geometries are combined with a specified medium to create a Tidy3D structure object, which can be given a unique name. For now, the naming will be systematic. This might be changed in the future via kwargs.
|
|
85
|
+
|
|
86
|
+
The material assignment will occur after all of the geometries have been made. This means a td.GeometryGroup object will be created and made into a structure.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
geom_list = []
|
|
90
|
+
try:
|
|
91
|
+
for m in geometry_object:
|
|
92
|
+
if isinstance(m, geo.SuperCell):
|
|
93
|
+
if debug=="on": print("This is running the iterable Supercell")
|
|
94
|
+
innerlist = _geo_to_tidy3d(m, material)
|
|
95
|
+
geom_list.append(innerlist)
|
|
96
|
+
|
|
97
|
+
elif isinstance(m, geo.Cylinder):
|
|
98
|
+
if debug=="on": print("This is running the iterable cylinder")
|
|
99
|
+
tdgeom = _convert_cyl(m, material)
|
|
100
|
+
geom_list.append(tdgeom)
|
|
101
|
+
|
|
102
|
+
except TypeError:
|
|
103
|
+
if isinstance(geometry_object, geo.SuperCell):
|
|
104
|
+
if debug=="on": print("This is running the single Supercell")
|
|
105
|
+
structs = unpack_supercell(geometry_object)
|
|
106
|
+
m = structs
|
|
107
|
+
newlist = _geo_to_tidy3d(m, material)
|
|
108
|
+
geom_list.append(newlist)
|
|
109
|
+
|
|
110
|
+
elif isinstance(geometry_object, geo.Cylinder):
|
|
111
|
+
m = geometry_object
|
|
112
|
+
if debug=="on": print("This is creating a single cylinder named")
|
|
113
|
+
tdgeom = _convert_cyl(m, material)
|
|
114
|
+
geom_list.append(tdgeom)
|
|
115
|
+
|
|
116
|
+
return geom_list
|
|
117
|
+
|
|
118
|
+
def geo_to_tidy3d(geometry_object, material, name="Structure Group", **kwargs):
|
|
119
|
+
"""Converts CrystalBuilder geometry object(s) to the corresponding Tidy3D object(s) with defined medium.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
`material` can be either a td.Medium() object or a float corresponding to the refractive index of the desired Medium.
|
|
123
|
+
|
|
124
|
+
This is a higher level wrapper of the _geo_to_tidy3d function, which I have yet to document
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
------------
|
|
128
|
+
geometry_object : Geometry or list of Geometry
|
|
129
|
+
an object or list of objects
|
|
130
|
+
material : td.Medium() or float
|
|
131
|
+
Tidy3D Medium or the refractive index that will be assigned to the material.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
------------
|
|
137
|
+
td.Structure()
|
|
138
|
+
a Tidy3D structure group with defined Medium
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
material_name = kwargs.get("material_name", "Dielectric Material")
|
|
142
|
+
geometry_list_raw = _geo_to_tidy3d(geometry_object, material)
|
|
143
|
+
geometry_list =geometry_list_raw
|
|
144
|
+
geometry_group = GeometryGroup(geometries = tuple(geometry_list))
|
|
145
|
+
if isinstance(material, Medium):
|
|
146
|
+
medium = material
|
|
147
|
+
else:
|
|
148
|
+
medium = Medium(permittivity = material**2, name=material_name)
|
|
149
|
+
|
|
150
|
+
return Structure(geometry=geometry_group, medium=medium, name=name)
|
|
151
|
+
|
|
152
|
+
if __name__ == '__main__':
|
|
153
|
+
"""testing code"""
|
|
154
|
+
|
|
155
|
+
cylinder = geo.Cylinder.from_vertices([[0,0,0], [3,0,0]], radius=.1)
|
|
156
|
+
newgeo = geo_to_tidy3d([cylinder, cylinder], material=3)
|
|
157
|
+
|
|
158
|
+
def view_structures(geometry):
|
|
159
|
+
# create source
|
|
160
|
+
lda0 = 0.75 # wavelength of interest (length scales are micrometers in Tidy3D)
|
|
161
|
+
freq0 = C_0 / lda0 # frequency of interest
|
|
162
|
+
source = PointDipole(
|
|
163
|
+
center=(-1.5, 0, 0), # position of the dipole
|
|
164
|
+
source_time=GaussianPulse(freq0=freq0, fwidth=freq0 / 10.0), # time profile of the source
|
|
165
|
+
polarization="Ey", # polarization of the dipole
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
sim = Simulation(
|
|
169
|
+
size=(5, 5, 5), # simulation domain size
|
|
170
|
+
grid_spec=GridSpec.auto(
|
|
171
|
+
min_steps_per_wvl=25
|
|
172
|
+
), # automatic nonuniform FDTD grid with 25 grids per wavelength in the material
|
|
173
|
+
structures=[geometry],
|
|
174
|
+
sources=[source],
|
|
175
|
+
run_time=3e-13, # physical simulation time in second
|
|
176
|
+
)
|
|
177
|
+
sim.plot_3d()
|
|
178
|
+
|
|
179
|
+
view_structures(newgeo)
|