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,759 @@
|
|
|
1
|
+
#Geometry File
|
|
2
|
+
import numpy as np
|
|
3
|
+
from matplotlib import pyplot as plt
|
|
4
|
+
from crystalbuilder import vectors as vm
|
|
5
|
+
import copy
|
|
6
|
+
import scipy.spatial as scs
|
|
7
|
+
|
|
8
|
+
debug = 'off'
|
|
9
|
+
|
|
10
|
+
class Structure():
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
**kwargs
|
|
14
|
+
):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class SuperCell():
|
|
18
|
+
"""
|
|
19
|
+
Takes other geometry objects and groups them into one supercell object (akin to list) that can be passed to the methods in lattice.py
|
|
20
|
+
Advantage of this over simple list is the addition of rotation and translation options to alter the supercell.
|
|
21
|
+
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
geometries : geo.object or list of geo_objects
|
|
27
|
+
geometries in supercell
|
|
28
|
+
|
|
29
|
+
center : list or ArrayLike
|
|
30
|
+
center of supercell
|
|
31
|
+
|
|
32
|
+
radius : float or list of float
|
|
33
|
+
'radius' or size of all structures
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Methods
|
|
38
|
+
-------
|
|
39
|
+
rotatecell(deg)
|
|
40
|
+
create 360/deg total copies of the unit cell in the supercell
|
|
41
|
+
|
|
42
|
+
add_structure(structure)
|
|
43
|
+
adds a structure to the supercell
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
def __init__(self,
|
|
47
|
+
geometries,
|
|
48
|
+
point='center',
|
|
49
|
+
shift = False,
|
|
50
|
+
**kwargs
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Parameters
|
|
54
|
+
-----------
|
|
55
|
+
geometries (list): geo.geometry objects
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Keyword Arguments
|
|
59
|
+
------------------
|
|
60
|
+
'center' : ArrayLike, default: None unless rotation or translation is defined then (0,0,0)
|
|
61
|
+
defines center for operations.
|
|
62
|
+
|
|
63
|
+
'rotation' : int
|
|
64
|
+
specifies copy+rotation of geometries in degrees. Rotates about center. Creates 360/value number of repetitions.
|
|
65
|
+
|
|
66
|
+
'translation':
|
|
67
|
+
specifies copy+translation by vector. Relative to center.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
self.point_style = point
|
|
72
|
+
self.structures = []
|
|
73
|
+
self._instructures = geometries
|
|
74
|
+
self.input_center = kwargs.get('center', None)
|
|
75
|
+
self.rotation = kwargs.get('rotation', None)
|
|
76
|
+
self.translation = kwargs.get('translation', None)
|
|
77
|
+
self.unit = kwargs.get('unit', 'degrees')
|
|
78
|
+
self.shiftcell = shift
|
|
79
|
+
self.default_center = kwargs.get('relative_center', [0,0,0])
|
|
80
|
+
|
|
81
|
+
if self.input_center == None:
|
|
82
|
+
self.cellcenter = self.default_center
|
|
83
|
+
else:
|
|
84
|
+
self.cellcenter = self.input_center
|
|
85
|
+
|
|
86
|
+
if self.rotation != None:
|
|
87
|
+
if debug == "on": print("geo: center is ", self.cellcenter)
|
|
88
|
+
deg = np.degrees(vm.angle_check(self.rotation, self.unit))
|
|
89
|
+
self.rotatecell(deg)
|
|
90
|
+
else:
|
|
91
|
+
self.structures = self._instructures
|
|
92
|
+
|
|
93
|
+
if self.shiftcell == True:
|
|
94
|
+
self.center = self.cellcenter
|
|
95
|
+
|
|
96
|
+
def __iter__(self):
|
|
97
|
+
return iter(self.structures)
|
|
98
|
+
|
|
99
|
+
def rotatecell(self, deg, copy=True):
|
|
100
|
+
"""
|
|
101
|
+
Rotation method for rotating a unit cell by deg around the supercell center. Unfortunately it only copies the cell for the moment, giving 360/deg numbers of the cell.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
------------
|
|
105
|
+
deg : float
|
|
106
|
+
desired angle of rotation, in degrees
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
if copy == True:
|
|
110
|
+
print("Angle is " , deg, " degrees")
|
|
111
|
+
numrot = round(360/deg)
|
|
112
|
+
for m in self._instructures:
|
|
113
|
+
for n in range(0, numrot):
|
|
114
|
+
if self.point_style == 'center':
|
|
115
|
+
newpoints = vm.rotate([m.center], theta=(n*deg), relative_point=self.cellcenter, unit='degrees')
|
|
116
|
+
#print("Newpoints: ", newpoints)
|
|
117
|
+
self.structures.append(m.copy(center=newpoints))
|
|
118
|
+
elif self.point_style == 'vertices':
|
|
119
|
+
newpoints = vm.rotate(m.vertices, theta=(n*deg), relative_point=self.cellcenter, unit='degrees')
|
|
120
|
+
#print("Newpoints: ", newpoints)
|
|
121
|
+
self.structures.append(m.copy(vertices=newpoints))
|
|
122
|
+
else:
|
|
123
|
+
print("Sorry, this method is only made to rotate and make new copies. Hopefully this will be fixed soon.")
|
|
124
|
+
|
|
125
|
+
def translatecell(self, shiftvec):
|
|
126
|
+
"""
|
|
127
|
+
Translate the entire unit cell by some vector
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
------------
|
|
131
|
+
shiftvec : list
|
|
132
|
+
vector with length/direction determining the shift
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
None
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
if debug == "on": print("geo: translating cell by ", shiftvec, "\n")
|
|
140
|
+
for n in self.structures:
|
|
141
|
+
if debug == "on":print("geo: the structure's original center is ", n.ogcenter)
|
|
142
|
+
newcenter = np.asarray(n.original_center) + shiftvec
|
|
143
|
+
if debug == "on":print("geo: the structure's new center is ", newcenter)
|
|
144
|
+
n.center = newcenter
|
|
145
|
+
|
|
146
|
+
def _shift_center(self, oldcenter, newcenter):
|
|
147
|
+
""" Shift cell to specified center. This creates a shift vector and passes it to translatecell
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
-----------
|
|
151
|
+
oldcenter: list
|
|
152
|
+
current center point of supercell
|
|
153
|
+
newcenter: list
|
|
154
|
+
desired center point of supercell
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
shiftvec = vm.get_shift_vector(oldcenter, newcenter)
|
|
158
|
+
self.translatecell(shiftvec)
|
|
159
|
+
|
|
160
|
+
def copy(self, **kwargs):
|
|
161
|
+
"""
|
|
162
|
+
Makes a deep copy of the supercell.
|
|
163
|
+
|
|
164
|
+
Create a supercell, then call this method to update and copy the parameters.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
memodict = {}
|
|
168
|
+
center = kwargs.get('center', self.cellcenter)
|
|
169
|
+
geos = kwargs.get('structures', self.structures)
|
|
170
|
+
|
|
171
|
+
newcopy = SuperCell(geometries=geos, center=center)
|
|
172
|
+
newcopy.__dict__.update(self.__dict__)
|
|
173
|
+
|
|
174
|
+
newcopy.structures=copy.deepcopy(geos, memodict)
|
|
175
|
+
newcopy.center = copy.deepcopy(center, memodict)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
return newcopy
|
|
179
|
+
|
|
180
|
+
def identify_structures(self):
|
|
181
|
+
"""
|
|
182
|
+
Print the types and positions of all the structures in the cell.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
None
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
stdout
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
k = 0
|
|
194
|
+
for n in self.structures:
|
|
195
|
+
k = k+1
|
|
196
|
+
print("Object %s is %s, with center at %s" % (k, type(n), n.center))
|
|
197
|
+
|
|
198
|
+
def add_structure(self, structure):
|
|
199
|
+
self.structures.append(structure)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def center(self):
|
|
203
|
+
cent = self.cellcenter
|
|
204
|
+
return cent
|
|
205
|
+
|
|
206
|
+
@center.setter
|
|
207
|
+
def center(self, newcent):
|
|
208
|
+
self.cellcenter = newcent
|
|
209
|
+
if debug=='on': print("geo: newcenter ", newcent)
|
|
210
|
+
self._shift_center(self.default_center, newcent)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def radius(self):
|
|
214
|
+
rads = []
|
|
215
|
+
for n in self.structures:
|
|
216
|
+
rads.append(n.radius)
|
|
217
|
+
return rads
|
|
218
|
+
|
|
219
|
+
@radius.setter
|
|
220
|
+
def radius(self, newrad):
|
|
221
|
+
for n in self.structures:
|
|
222
|
+
n.radius= newrad
|
|
223
|
+
|
|
224
|
+
class CylinderVortexCell(SuperCell):
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
lattice,
|
|
229
|
+
center,
|
|
230
|
+
radius_1,
|
|
231
|
+
R_max,
|
|
232
|
+
height = 10,
|
|
233
|
+
vort_center = [0,0,0],
|
|
234
|
+
vort_radius = 1,
|
|
235
|
+
winding_number=1,
|
|
236
|
+
radius_2 = None,
|
|
237
|
+
scale = 0,
|
|
238
|
+
**kwargs
|
|
239
|
+
):
|
|
240
|
+
"""
|
|
241
|
+
Constructs a Dirac Vortex SuperCell based on a hexagonal cell of cylinders (also called Majorana Zero Mode cavity)
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
-----------
|
|
245
|
+
lattice (lattice object): lattice defining basis vectors and such
|
|
246
|
+
center (array-like): center of supercell
|
|
247
|
+
radius_1 (float): radius of cylinders. By default, both sublattices but radius_2 (below) can override this
|
|
248
|
+
R_max(float): maximum value of delta R term
|
|
249
|
+
|
|
250
|
+
vort_center (array-like): determines the vortex center position that is used for calculating phi and r (default zero)
|
|
251
|
+
vort_radius (float): number of unit cells defining the vortex radius
|
|
252
|
+
winding_number (int): w term reflecting topology of arctan function. Positive or Negative.
|
|
253
|
+
|
|
254
|
+
radius_2 (float): radius of cylinders in sublattice B. This is the same radius that is is modulated by the delta R term. If None, will default to radius_1
|
|
255
|
+
scale (float): experimental; scales the position of the cylinders in the unit cell by stretching or shrinking the distance from the cell's origin.
|
|
256
|
+
|
|
257
|
+
Keyword Arguments
|
|
258
|
+
------------------
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
self.lattice = lattice
|
|
262
|
+
self.cellcenter = center
|
|
263
|
+
self.rad1 = radius_1
|
|
264
|
+
if radius_2 == None:
|
|
265
|
+
self.rad2 = radius_1
|
|
266
|
+
else:
|
|
267
|
+
self.rad2 = radius_2
|
|
268
|
+
self.height = height
|
|
269
|
+
self.rmax = R_max
|
|
270
|
+
self.vortcenter = vort_center
|
|
271
|
+
self.vortrad_cells = vort_radius
|
|
272
|
+
self.winding = winding_number
|
|
273
|
+
self.scaling = scale
|
|
274
|
+
self.latt_const = self.lattice.magnitude[0]
|
|
275
|
+
|
|
276
|
+
self.vortrad = self.vortrad_cells * self.latt_const
|
|
277
|
+
|
|
278
|
+
#Building Geometries
|
|
279
|
+
|
|
280
|
+
cyl1_position = self.lattice.lat_to_cart((1-self.scaling)*[1/3, 1/3, 0])
|
|
281
|
+
cyl2_position = self.lattice.lat_to_cart((1+self.scaling)*[2/3, 2/3, 0])
|
|
282
|
+
|
|
283
|
+
cyl1 = Cylinder(cyl1_position, radius=radius_1, height = self.height)
|
|
284
|
+
cyl2 = Cylinder(cyl2_position, radius=radius_2, height = self.height)
|
|
285
|
+
|
|
286
|
+
unitcell = [cyl1, cyl2]
|
|
287
|
+
|
|
288
|
+
super().__init__(unitcell, center=self.cellcenter, rotation=120, unit='degrees', point='center')
|
|
289
|
+
|
|
290
|
+
def calculate_modulation(self):
|
|
291
|
+
self.rho, self.theta = self.calculate_radial_position()
|
|
292
|
+
delR_term = self.rmax*np.tanh(self.rho/self.vortrad)
|
|
293
|
+
KPlus = np.array(4*np.pi/(3*self.latt_const), 0)
|
|
294
|
+
KMinus = np.array(-4*np.pi/(3*self.latt_const), 0)
|
|
295
|
+
Ktot = KPlus - KMinus
|
|
296
|
+
costerm = np.cos(np.dot(Ktot, self.rho) + (self.winding*self.theta))
|
|
297
|
+
RMod = delR_term * costerm
|
|
298
|
+
return RMod
|
|
299
|
+
|
|
300
|
+
def calculate_radial_position(self):
|
|
301
|
+
x, y ,z = self.cellcenter
|
|
302
|
+
theta= np.arctan2(y, x)
|
|
303
|
+
hypotenuse = np.hypot(x, y)
|
|
304
|
+
return [hypotenuse, theta]
|
|
305
|
+
|
|
306
|
+
class HexagonalVortexCell(SuperCell):
|
|
307
|
+
def __init__(
|
|
308
|
+
self,
|
|
309
|
+
lattice,
|
|
310
|
+
center,
|
|
311
|
+
side_length,
|
|
312
|
+
m,
|
|
313
|
+
m_max,
|
|
314
|
+
phi,
|
|
315
|
+
scale = 1,
|
|
316
|
+
**kwargs
|
|
317
|
+
):
|
|
318
|
+
"""
|
|
319
|
+
Constructs a Dirac Vortex SuperCell based on a hexagonal cell of triangles (see Ling Lu work mentioned above)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
-----------
|
|
324
|
+
lattice (lattice object): lattice defining basis vectors and such
|
|
325
|
+
|
|
326
|
+
center (array-like): center of supercell
|
|
327
|
+
|
|
328
|
+
side_length (float): length of triangle sides. sqrt(3)/2 times Ling Lu's "r".
|
|
329
|
+
|
|
330
|
+
m (float): local modulation value, calculated from the tanh potential equation
|
|
331
|
+
|
|
332
|
+
m_max (float): global maximum modulation amplitude. Should be constant for a given system (i.e. don't change when copying cell)
|
|
333
|
+
|
|
334
|
+
phi (float): position dependent phase term, given in radians. Should vary depending on angle of cell relative to center of the vortex structure
|
|
335
|
+
|
|
336
|
+
scale (float): experimental; scales the position of the triangles in the unit cell by stretching or shrinking the distance from the cell's origin.
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
Keyword Arguments
|
|
341
|
+
------------------
|
|
342
|
+
'center' : defines center for operations. default: None, unless rotation or translation is defined, then (0,0,0)
|
|
343
|
+
|
|
344
|
+
'rotation' : specifies copy+rotation of geometries in degrees. Rotates about center. Creates 360/value number of repetitions. default:None
|
|
345
|
+
|
|
346
|
+
'translation': specifies copy+translation by vector. Relative to center. default:None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
self.lattice = lattice
|
|
354
|
+
self.diraccenter=center
|
|
355
|
+
self.m = m
|
|
356
|
+
self.m0 = m_max
|
|
357
|
+
self.phi = phi
|
|
358
|
+
tri1 = lattice.lat_to_cart(scale*[1/3, 1/3, 0])
|
|
359
|
+
tri2 = lattice.lat_to_cart(scale*[2/3, 2/3, 0])
|
|
360
|
+
|
|
361
|
+
self._side_length = side_length
|
|
362
|
+
self._r = (2/np.sqrt(3))*self._side_length
|
|
363
|
+
|
|
364
|
+
tri1new = vm.shift_angle(tri1, self.phi, self.m)
|
|
365
|
+
|
|
366
|
+
tri1 = eqTriangle(1,self._side_length, center=tri1new, theta=-30) #left
|
|
367
|
+
tri2 = eqTriangle(1,self._side_length, center=tri2, theta=30) #right
|
|
368
|
+
#Build unit cell
|
|
369
|
+
unitcell = [tri1, tri2]
|
|
370
|
+
|
|
371
|
+
#initialize parent supercell, specifying the center of the supercell and creating the structures by rotating the unit cell 3 times about the center
|
|
372
|
+
super().__init__(unitcell, center=self.diraccenter, rotation=120, unit='degrees', point='center' )
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def center(self):
|
|
376
|
+
return self.diraccenter
|
|
377
|
+
|
|
378
|
+
@center.setter
|
|
379
|
+
def center(self, center):
|
|
380
|
+
self.diraccenter = center
|
|
381
|
+
|
|
382
|
+
def copy(self, **kwargs):
|
|
383
|
+
"""
|
|
384
|
+
Makes a copy of the supercell. I don't know how center works since it also determines the center of the rotation. I might have to change that and implement a shift function.
|
|
385
|
+
|
|
386
|
+
Create a supercell, then call this method to update and copy the parameters.
|
|
387
|
+
|
|
388
|
+
"""
|
|
389
|
+
center = kwargs.get('center')
|
|
390
|
+
phi = kwargs.get('phi')
|
|
391
|
+
m = kwargs.get('m')
|
|
392
|
+
print("copying: with m = ", m)
|
|
393
|
+
print("copying: with phi = ", phi)
|
|
394
|
+
|
|
395
|
+
newcopy = HexagonalVortexCell(self.lattice, center, self._side_length, m, self.m0, phi)
|
|
396
|
+
|
|
397
|
+
return newcopy
|
|
398
|
+
|
|
399
|
+
class Cylinder(Structure):
|
|
400
|
+
def __init__(
|
|
401
|
+
self,
|
|
402
|
+
center,
|
|
403
|
+
radius,
|
|
404
|
+
height,
|
|
405
|
+
axis=2,
|
|
406
|
+
**kwargs
|
|
407
|
+
):
|
|
408
|
+
super().__init__()
|
|
409
|
+
self.center = center
|
|
410
|
+
self.original_center = kwargs.get("original_center", center)
|
|
411
|
+
self.ogcenter = self.original_center
|
|
412
|
+
self.radius = radius
|
|
413
|
+
self.height = height
|
|
414
|
+
self.inaxis = axis
|
|
415
|
+
self.axis = axis
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
if self.axis==2:
|
|
419
|
+
self.axis=np.array([0, 0, 1])
|
|
420
|
+
elif self.axis==1:
|
|
421
|
+
self.axis=np.array([0, 1, 0])
|
|
422
|
+
elif self.axis==0:
|
|
423
|
+
self.axis=np.array([1, 0, 0])
|
|
424
|
+
else:
|
|
425
|
+
pass
|
|
426
|
+
except ValueError:
|
|
427
|
+
pass
|
|
428
|
+
|
|
429
|
+
@classmethod
|
|
430
|
+
def from_vertices(cls, vertices, radius, height_padding=False):
|
|
431
|
+
"""
|
|
432
|
+
Create a cylinder using the start and end points (vertices) and a specified radius
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
-----------
|
|
437
|
+
|
|
438
|
+
vertices : list of iterables
|
|
439
|
+
starting and ending points. Should be in the form of [ (x,y,z), (x,y,z)]
|
|
440
|
+
|
|
441
|
+
radius : float
|
|
442
|
+
radius of cylinder
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
vert1 = np.asarray(vertices[0])
|
|
446
|
+
vert2 = np.asarray(vertices[1])
|
|
447
|
+
if height_padding == False:
|
|
448
|
+
height = np.linalg.norm((vert2 - vert1))
|
|
449
|
+
else:
|
|
450
|
+
height = np.linalg.norm((vert2 - vert1))+height_padding
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
center = np.mean((vert1, vert2), axis=0)
|
|
454
|
+
axis = vert2 - vert1
|
|
455
|
+
return cls(center=center, radius=radius, height=height, axis=axis)
|
|
456
|
+
|
|
457
|
+
@classmethod
|
|
458
|
+
def towards_point(cls, center, endpoint, radius,height):
|
|
459
|
+
"""Create a cylinder based on its start and end vertices"""
|
|
460
|
+
center = np.asarray(center)
|
|
461
|
+
endpoint = np.asarray(endpoint)
|
|
462
|
+
axis = endpoint-center
|
|
463
|
+
return cls(center=center, radius=radius, height=height, axis=axis)
|
|
464
|
+
|
|
465
|
+
def copy(self, **kwargs):
|
|
466
|
+
"""
|
|
467
|
+
Copy and optionally change the values of the cylinder
|
|
468
|
+
|
|
469
|
+
Keyword Args
|
|
470
|
+
-------------
|
|
471
|
+
'center' : 3-list of new center for copied object
|
|
472
|
+
|
|
473
|
+
'radius' : float of new radius for copied object
|
|
474
|
+
|
|
475
|
+
"""
|
|
476
|
+
cent = kwargs.get('center')
|
|
477
|
+
rad = kwargs.get('radius')
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if 'radius' in kwargs:
|
|
481
|
+
if debug==True:print("Making Structure with radius: ", rad)
|
|
482
|
+
else:
|
|
483
|
+
newrad = self.radius
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
if 'center' in kwargs:
|
|
487
|
+
if debug==True:print("Making Structure with Center: ", cent)
|
|
488
|
+
newcent = cent
|
|
489
|
+
else:
|
|
490
|
+
newcent = self.center
|
|
491
|
+
newcopy = Cylinder(newcent, newrad, self.height, self.inaxis, original_center=self.ogcenter)
|
|
492
|
+
|
|
493
|
+
return newcopy
|
|
494
|
+
|
|
495
|
+
class Sphere(Structure):
|
|
496
|
+
|
|
497
|
+
def __init__(
|
|
498
|
+
self,
|
|
499
|
+
center,
|
|
500
|
+
radius,
|
|
501
|
+
**kwargs
|
|
502
|
+
):
|
|
503
|
+
self.center = center
|
|
504
|
+
self.original_center = kwargs.get("original_center", center)
|
|
505
|
+
self.ogcenter = self.original_center
|
|
506
|
+
self.radius = radius
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def copy(self, **kwargs):
|
|
510
|
+
"""
|
|
511
|
+
Keyword Args
|
|
512
|
+
------------
|
|
513
|
+
center : 3-list of new center for copied object
|
|
514
|
+
radius : float of new radius for copied object
|
|
515
|
+
|
|
516
|
+
"""
|
|
517
|
+
cent = kwargs.get('center')
|
|
518
|
+
rad = kwargs.get('radius')
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if 'radius' in kwargs:
|
|
522
|
+
if debug==True:print("Making Structure with radius: ", rad)
|
|
523
|
+
else:
|
|
524
|
+
newrad = self.radius
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
if 'center' in kwargs:
|
|
528
|
+
if debug==True:print("Making Structure with Center: ", cent)
|
|
529
|
+
newcent = cent
|
|
530
|
+
else:
|
|
531
|
+
newcent = self.center
|
|
532
|
+
newcopy = Sphere(newcent, newrad, original_center=self.ogcenter)
|
|
533
|
+
|
|
534
|
+
return newcopy
|
|
535
|
+
|
|
536
|
+
class Triangle(Structure):
|
|
537
|
+
"""
|
|
538
|
+
Class for triangular structures. Defines vertices, height, and center (centroid).
|
|
539
|
+
Vertices are defined relative to centroid if center != None
|
|
540
|
+
|
|
541
|
+
Note: Centroid is used for the definition instead of circumcenter. This was chosen to guarantee the center is inside
|
|
542
|
+
the triangle. This might change in the future as the positions of the two are different.
|
|
543
|
+
|
|
544
|
+
"""
|
|
545
|
+
|
|
546
|
+
def __init__(
|
|
547
|
+
self,
|
|
548
|
+
vertices,
|
|
549
|
+
height,
|
|
550
|
+
axis=2,
|
|
551
|
+
center=None,
|
|
552
|
+
**kwargs
|
|
553
|
+
):
|
|
554
|
+
"""
|
|
555
|
+
creates triangle
|
|
556
|
+
|
|
557
|
+
Parameters:
|
|
558
|
+
vertices (list of 3-tuples, array_like): the Cartesian x,y,z coordinates of each vertex with zero as origin
|
|
559
|
+
height (float): The height of the triangular prism
|
|
560
|
+
axis (0,1,2): Direction in which triangle is oriented. X = 0, Y = 1, Z = 2
|
|
561
|
+
center (array_like or None): Center of triangle, shifts vertices if not None
|
|
562
|
+
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
self.height = height
|
|
566
|
+
self._ogverts = np.asarray(vertices)
|
|
567
|
+
self._centroid = kwargs.get("original_center", center)
|
|
568
|
+
self.invertices = vertices
|
|
569
|
+
self.inaxis = axis
|
|
570
|
+
if axis==2:
|
|
571
|
+
self.axis=np.array([0, 0, 1])
|
|
572
|
+
elif axis==1:
|
|
573
|
+
self.axis=np.array([0, 1, 0])
|
|
574
|
+
elif axis==0:
|
|
575
|
+
self.axis=np.array([1, 0, 0])
|
|
576
|
+
else:
|
|
577
|
+
print("Error: Axis not Found")
|
|
578
|
+
|
|
579
|
+
self._centroid = np.asarray(np.sum(self.invertices, axis=(0))/np.size(self.invertices, 1))
|
|
580
|
+
|
|
581
|
+
newvertices = self.invertices
|
|
582
|
+
if center is not None and len(vertices):
|
|
583
|
+
self.center = np.asarray(center)
|
|
584
|
+
shift = center-self._centroid
|
|
585
|
+
newvertices = self.invertices + shift
|
|
586
|
+
self.shiftedcentroid = np.asarray(np.sum(newvertices, axis=(0))/np.size(newvertices, 1))
|
|
587
|
+
else:
|
|
588
|
+
self.shiftedcentroid = self._centroid
|
|
589
|
+
|
|
590
|
+
self.vertices = newvertices
|
|
591
|
+
self.vertlist = self.vertices.tolist()
|
|
592
|
+
|
|
593
|
+
## Temporary setting of center to centroid. Probs not good long-term
|
|
594
|
+
self.center = self.centroid
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def original_centroid(self):
|
|
598
|
+
"""Access to centroid before shifting. Not same as center."""
|
|
599
|
+
return self._centroid
|
|
600
|
+
|
|
601
|
+
@property
|
|
602
|
+
def original_center(self):
|
|
603
|
+
"""Someday this will be fixed for center vs centroid, but for now, it's an alias for the original_centroid command"""
|
|
604
|
+
return self._centroid
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def centroid(self):
|
|
609
|
+
return self.shiftedcentroid
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def original_vertices(self):
|
|
613
|
+
"""Access to original vertices before scaling and shifting. """
|
|
614
|
+
return self._ogverts
|
|
615
|
+
|
|
616
|
+
@property
|
|
617
|
+
def verttuple(self):
|
|
618
|
+
newlist = []
|
|
619
|
+
for n in self.vertlist:
|
|
620
|
+
tuppoint = tuple(n)
|
|
621
|
+
newlist.append(tuppoint)
|
|
622
|
+
return newlist
|
|
623
|
+
|
|
624
|
+
@property
|
|
625
|
+
def bounds(self):
|
|
626
|
+
if self.inaxis==0:
|
|
627
|
+
extent = [self.centroid[0]-self.height/2, self.centroid[0]+self.height/2]
|
|
628
|
+
elif self.inaxis==1:
|
|
629
|
+
extent = [self.centroid[1]-self.height/2, self.centroid[1]+self.height/2]
|
|
630
|
+
elif self.inaxis==2:
|
|
631
|
+
extent = [self.centroid[2]-self.height/2, self.centroid[2]+self.height/2]
|
|
632
|
+
return extent
|
|
633
|
+
|
|
634
|
+
def calc_circumcenter(self):
|
|
635
|
+
"""The circumcenter calculations are more involved than the centroid ones
|
|
636
|
+
This function exists in case I need to do those calculations at some point"""
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
def copy(self, **kwargs):
|
|
640
|
+
"""
|
|
641
|
+
create new instance of the structure with the same parameters but different center
|
|
642
|
+
This might be changed in the future to incorporate more kwargs and allow for more control in copying
|
|
643
|
+
|
|
644
|
+
kwargs
|
|
645
|
+
-----------
|
|
646
|
+
'center' : 3-list of new center for copied object
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
"""
|
|
650
|
+
cent = kwargs.get('center')
|
|
651
|
+
verts = kwargs.get('vertices')
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
if 'vertices' in kwargs:
|
|
655
|
+
if debug==True: print("Making Structure with Vertices: ", verts)
|
|
656
|
+
newverts = verts
|
|
657
|
+
newcopy = Triangle(newverts, self.height, self.inaxis)
|
|
658
|
+
else:
|
|
659
|
+
newverts = self.vertices
|
|
660
|
+
|
|
661
|
+
if 'center' in kwargs:
|
|
662
|
+
if debug==True: print("Making Structure with Center: ", cent)
|
|
663
|
+
newcent = cent
|
|
664
|
+
newcopy = Triangle(self.vertices, self.height, self.inaxis, newcent)
|
|
665
|
+
else:
|
|
666
|
+
newcent = self.center
|
|
667
|
+
|
|
668
|
+
if 'center' in kwargs and 'vertices' in kwargs:
|
|
669
|
+
print("Center and Vertices Both Defined. This may not shift like you expect. Please check results.")
|
|
670
|
+
newcopy = Triangle(newverts, self.height, self.inaxis, newcent)
|
|
671
|
+
|
|
672
|
+
return newcopy
|
|
673
|
+
|
|
674
|
+
class eqTriangle(Triangle):
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
this creates an equilateral triangle centered at zero with side length b,
|
|
678
|
+
oriented with 0 degrees being pointing up
|
|
679
|
+
|
|
680
|
+
"""
|
|
681
|
+
def __init__(
|
|
682
|
+
self,
|
|
683
|
+
height,
|
|
684
|
+
b=1,
|
|
685
|
+
axis=2,
|
|
686
|
+
center=None,
|
|
687
|
+
theta = 0,
|
|
688
|
+
**kwargs):
|
|
689
|
+
|
|
690
|
+
"""
|
|
691
|
+
Parameters:
|
|
692
|
+
height (float): height of triangle
|
|
693
|
+
b (float): length of sides
|
|
694
|
+
axis (0,1,2): axis normal to face 0-X, 1-Y, 2-Z
|
|
695
|
+
center (None, array_like): center position that all vertices are shifted to
|
|
696
|
+
theta (degrees): ccw angle of rotation (0 is pointing up)
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
self.b = b
|
|
700
|
+
self.scaled_verts = np.array([[0, np.sqrt(3)/3, 0],
|
|
701
|
+
[1/2, -1/(2*np.sqrt(3)), 0],
|
|
702
|
+
[-1/2, -1/(2*np.sqrt(3)), 0]
|
|
703
|
+
])*self.b
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
self.vertices = vm.rotate(self.scaled_verts, theta, axis=2, unit='degrees', toarray=True)
|
|
707
|
+
|
|
708
|
+
@property
|
|
709
|
+
def vertlist(self):
|
|
710
|
+
return self.vertices.tolist()
|
|
711
|
+
|
|
712
|
+
@property
|
|
713
|
+
def verttuple(self):
|
|
714
|
+
newlist = []
|
|
715
|
+
for n in self.vertlist:
|
|
716
|
+
tuppoint = tuple(n)
|
|
717
|
+
newlist.append(tuppoint)
|
|
718
|
+
return newlist
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
Triangle.__init__(self, vertices=self.vertices, height=height, axis=axis, center=center)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def NearestNeighbors(points, radius, neighborhood_range, a_mag=1.0):
|
|
725
|
+
"""
|
|
726
|
+
Connect nearest neighbors in a list of points with cylinders
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
-----------
|
|
730
|
+
|
|
731
|
+
points : list of (x,y,z) tuples
|
|
732
|
+
Positions to search for neighbors within
|
|
733
|
+
|
|
734
|
+
radius : float
|
|
735
|
+
radius of connecting rods
|
|
736
|
+
|
|
737
|
+
neighborhood : float
|
|
738
|
+
distance to define neighbors
|
|
739
|
+
|
|
740
|
+
"""
|
|
741
|
+
pointarr = np.asarray(points)
|
|
742
|
+
kdtree = scs.KDTree(pointarr, leafsize=15, compact_nodes=True)
|
|
743
|
+
neighbors = kdtree.query_pairs(r=neighborhood_range, p=2)
|
|
744
|
+
structure_list = []
|
|
745
|
+
for pair in neighbors:
|
|
746
|
+
point = pointarr[pair[0]] * a_mag
|
|
747
|
+
neighbor = pointarr[pair[1]] *a_mag
|
|
748
|
+
structure_list.append(Cylinder.from_vertices([point, neighbor], radius=radius))
|
|
749
|
+
return structure_list
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
if __name__ == "__main__":
|
|
755
|
+
rng = np.random.default_rng()
|
|
756
|
+
points = rng.random((15, 3))
|
|
757
|
+
|
|
758
|
+
test = NearestNeighbors(points, radius=.5, neighborhood_range=.3, a_mag=1)
|
|
759
|
+
print(isinstance(test[0], Structure))
|