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.

@@ -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))