1
+ import collections .abc
1
2
import math
2
3
from collections import Counter
3
- from collections .abc import Iterable , Sized
4
4
from itertools import chain , combinations
5
5
from math import factorial
6
- from typing import Any , Iterator , List , Optional , Sequence , Set , Tuple , Union
6
+ from typing import Any , Iterable , Iterator , List , Optional , Sequence , Set , Tuple , Union
7
7
8
8
import numpy as np
9
9
import scipy .spatial
10
10
11
- Simplex = Tuple [int , ...] # XXX: check if this is correct
11
+ SimplexPoints = Union [
12
+ List [Tuple [float , ...]], np .ndarray
13
+ ] # XXX: check if this is correct
14
+ Simplex = Tuple [int , ...]
15
+ Point = Union [Tuple [float , ...], np .ndarray ] # XXX: check if this is correct
12
16
13
17
14
18
def fast_norm (v : Union [Tuple [float , ...], np .ndarray ]) -> float :
@@ -21,9 +25,7 @@ def fast_norm(v: Union[Tuple[float, ...], np.ndarray]) -> float:
21
25
22
26
23
27
def fast_2d_point_in_simplex (
24
- point : Tuple [float , ...],
25
- simplex : Union [List [Tuple [float , ...]], np .ndarray ],
26
- eps : float = 1e-8 ,
28
+ point : Point , simplex : SimplexPoints , eps : float = 1e-8
27
29
) -> Union [bool , np .bool_ ]:
28
30
(p0x , p0y ), (p1x , p1y ), (p2x , p2y ) = simplex
29
31
px , py = point
@@ -38,9 +40,7 @@ def fast_2d_point_in_simplex(
38
40
return (t >= - eps ) and (s + t <= 1 + eps )
39
41
40
42
41
- def point_in_simplex (
42
- point : Any , simplex : Simplex , eps : float = 1e-8
43
- ) -> Union [bool , np .bool_ ]:
43
+ def point_in_simplex (point : Point , simplex : SimplexPoints , eps : float = 1e-8 ) -> bool :
44
44
if len (point ) == 2 :
45
45
return fast_2d_point_in_simplex (point , simplex , eps )
46
46
@@ -51,7 +51,7 @@ def point_in_simplex(
51
51
return all (alpha > - eps ) and sum (alpha ) < 1 + eps
52
52
53
53
54
- def fast_2d_circumcircle (points : np . ndarray , ) -> Tuple [Tuple [float , float ], float ]:
54
+ def fast_2d_circumcircle (points : Iterable [ Point ] ) -> Tuple [Tuple [float , float ], float ]:
55
55
"""Compute the center and radius of the circumscribed circle of a triangle
56
56
57
57
Parameters
@@ -88,7 +88,7 @@ def fast_2d_circumcircle(points: np.ndarray,) -> Tuple[Tuple[float, float], floa
88
88
89
89
90
90
def fast_3d_circumcircle (
91
- points : np . ndarray ,
91
+ points : Iterable [ Point ] ,
92
92
) -> Tuple [Tuple [float , float , float ], float ]:
93
93
"""Compute the center and radius of the circumscribed shpere of a simplex.
94
94
@@ -140,7 +140,7 @@ def fast_det(matrix: np.ndarray) -> float:
140
140
return np .linalg .det (matrix )
141
141
142
142
143
- def circumsphere (pts : np .ndarray , ) -> Tuple [Tuple [float , ...], float ]:
143
+ def circumsphere (pts : np .ndarray ) -> Tuple [Tuple [float , ...], float ]:
144
144
dim = len (pts ) - 1
145
145
if dim == 2 :
146
146
return fast_2d_circumcircle (pts )
@@ -193,10 +193,12 @@ def orientation(face: np.ndarray, origin: np.ndarray) -> int:
193
193
194
194
195
195
def is_iterable_and_sized (obj : Any ) -> bool :
196
- return isinstance (obj , Iterable ) and isinstance (obj , Sized )
196
+ return isinstance (obj , collections .abc .Iterable ) and isinstance (
197
+ obj , collections .abc .Sized
198
+ )
197
199
198
200
199
- def simplex_volume_in_embedding (vertices : List [ Tuple [ float , ...] ]) -> float :
201
+ def simplex_volume_in_embedding (vertices : Iterable [ Point ]) -> float :
200
202
"""Calculate the volume of a simplex in a higher dimensional embedding.
201
203
That is: dim > len(vertices) - 1. For example if you would like to know the
202
204
surface area of a triangle in a 3d space.
@@ -277,7 +279,7 @@ class Triangulation:
277
279
or more simplices in the
278
280
"""
279
281
280
- def __init__ (self , coords : np . ndarray ) -> None :
282
+ def __init__ (self , coords : Iterable [ Point ] ) -> None :
281
283
if not is_iterable_and_sized (coords ):
282
284
raise TypeError ("Please provide a 2-dimensional list of points" )
283
285
coords = list (coords )
@@ -305,10 +307,10 @@ def __init__(self, coords: np.ndarray) -> None:
305
307
"(the points are linearly dependent)"
306
308
)
307
309
308
- self .vertices = list (coords )
309
- self .simplices = set ()
310
+ self .vertices : List [ Point ] = list (coords )
311
+ self .simplices : Set [ Simplex ] = set ()
310
312
# initialise empty set for each vertex
311
- self .vertex_to_simplices = [set () for _ in coords ]
313
+ self .vertex_to_simplices : List [ Set [ Simplex ]] = [set () for _ in coords ]
312
314
313
315
# find a Delaunay triangulation to start with, then we will throw it
314
316
# away and continue with our own algorithm
@@ -328,16 +330,16 @@ def add_simplex(self, simplex: Simplex) -> None:
328
330
for vertex in simplex :
329
331
self .vertex_to_simplices [vertex ].add (simplex )
330
332
331
- def get_vertices (self , indices : Sequence [int ]) -> Any :
333
+ def get_vertices (self , indices : Sequence [int ]) -> List [ Optional [ Point ]] :
332
334
return [self .get_vertex (i ) for i in indices ]
333
335
334
- def get_vertex (self , index : Optional [int ]) -> Any :
336
+ def get_vertex (self , index : Optional [int ]) -> Optional [ Point ] :
335
337
if index is None :
336
338
return None
337
339
return self .vertices [index ]
338
340
339
341
def get_reduced_simplex (
340
- self , point : Any , simplex : Simplex , eps : float = 1e-8
342
+ self , point : Point , simplex : Simplex , eps : float = 1e-8
341
343
) -> list :
342
344
"""Check whether vertex lies within a simplex.
343
345
@@ -364,12 +366,12 @@ def get_reduced_simplex(
364
366
return [simplex [i ] for i in result ]
365
367
366
368
def point_in_simplex (
367
- self , point : Any , simplex : Simplex , eps : float = 1e-8
368
- ) -> Union [ bool , np . bool_ ] :
369
+ self , point : Point , simplex : Simplex , eps : float = 1e-8
370
+ ) -> bool :
369
371
vertices = self .get_vertices (simplex )
370
372
return point_in_simplex (point , vertices , eps )
371
373
372
- def locate_point (self , point : Any ) -> Any :
374
+ def locate_point (self , point : Point ) -> Simplex :
373
375
"""Find to which simplex the point belongs.
374
376
375
377
Return indices of the simplex containing the point.
@@ -385,8 +387,11 @@ def dim(self) -> int:
385
387
return len (self .vertices [0 ])
386
388
387
389
def faces (
388
- self , dim : None = None , simplices : Optional [Any ] = None , vertices : None = None
389
- ) -> Iterator [Any ]:
390
+ self ,
391
+ dim : Optional [int ] = None ,
392
+ simplices : Optional [Iterable [Simplex ]] = None ,
393
+ vertices : Optional [Iterable [int ]] = None ,
394
+ ) -> Iterator [Tuple [int , ...]]:
390
395
"""Iterator over faces of a simplex or vertex sequence."""
391
396
if dim is None :
392
397
dim = self .dim
@@ -407,11 +412,11 @@ def faces(
407
412
else :
408
413
return faces
409
414
410
- def containing (self , face ) :
415
+ def containing (self , face : Tuple [ int , ...]) -> Set [ Simplex ] :
411
416
"""Simplices containing a face."""
412
417
return set .intersection (* (self .vertex_to_simplices [i ] for i in face ))
413
418
414
- def _extend_hull (self , new_vertex : Any , eps : float = 1e-8 ) -> Any :
419
+ def _extend_hull (self , new_vertex : Point , eps : float = 1e-8 ) -> Set [ Simplex ] :
415
420
# count multiplicities in order to get all hull faces
416
421
multiplicities = Counter (face for face in self .faces ())
417
422
hull_faces = [face for face , count in multiplicities .items () if count == 1 ]
@@ -471,7 +476,7 @@ def circumscribed_circle(
471
476
472
477
def point_in_cicumcircle (
473
478
self , pt_index : int , simplex : Simplex , transform : np .ndarray
474
- ) -> np . bool_ :
479
+ ) -> bool :
475
480
# return self.fast_point_in_circumcircle(pt_index, simplex, transform)
476
481
eps = 1e-8
477
482
@@ -487,9 +492,9 @@ def default_transform(self) -> np.ndarray:
487
492
def bowyer_watson (
488
493
self ,
489
494
pt_index : int ,
490
- containing_simplex : Optional [Any ] = None ,
495
+ containing_simplex : Optional [Simplex ] = None ,
491
496
transform : Optional [np .ndarray ] = None ,
492
- ) -> Any :
497
+ ) -> Tuple [ Set [ Simplex ], Set [ Simplex ]] :
493
498
"""Modified Bowyer-Watson point adding algorithm.
494
499
495
500
Create a hole in the triangulation around the new point,
@@ -549,7 +554,7 @@ def bowyer_watson(
549
554
new_triangles = self .vertex_to_simplices [pt_index ]
550
555
return bad_triangles - new_triangles , new_triangles - bad_triangles
551
556
552
- def _simplex_is_almost_flat (self , simplex : Simplex ) -> np . bool_ :
557
+ def _simplex_is_almost_flat (self , simplex : Simplex ) -> bool :
553
558
return self ._relative_volume (simplex ) < 1e-8
554
559
555
560
def _relative_volume (self , simplex : Simplex ) -> float :
@@ -565,8 +570,8 @@ def _relative_volume(self, simplex: Simplex) -> float:
565
570
566
571
def add_point (
567
572
self ,
568
- point : Any ,
569
- simplex : Optional [Any ] = None ,
573
+ point : Point ,
574
+ simplex : Optional [Simplex ] = None ,
570
575
transform : Optional [np .ndarray ] = None ,
571
576
) -> Any :
572
577
"""Add a new vertex and create simplices as appropriate.
@@ -575,13 +580,13 @@ def add_point(
575
580
----------
576
581
point : float vector
577
582
Coordinates of the point to be added.
578
- transform : N*N matrix of floats
579
- Multiplication matrix to apply to the point (and neighbouring
580
- simplices) when running the Bowyer Watson method.
581
583
simplex : tuple of ints, optional
582
584
Simplex containing the point. Empty tuple indicates points outside
583
585
the hull. If not provided, the algorithm costs O(N), so this should
584
586
be used whenever possible.
587
+ transform : N*N matrix of floats
588
+ Multiplication matrix to apply to the point (and neighbouring
589
+ simplices) when running the Bowyer Watson method.
585
590
"""
586
591
point = tuple (point )
587
592
if simplex is None :
@@ -626,7 +631,7 @@ def volume(self, simplex: Simplex) -> float:
626
631
def volumes (self ) -> List [float ]:
627
632
return [self .volume (sim ) for sim in self .simplices ]
628
633
629
- def reference_invariant (self ):
634
+ def reference_invariant (self ) -> bool :
630
635
"""vertex_to_simplices and simplices are compatible."""
631
636
for vertex in range (len (self .vertices )):
632
637
if any (vertex not in tri for tri in self .vertex_to_simplices [vertex ]):
@@ -640,26 +645,28 @@ def vertex_invariant(self, vertex):
640
645
"""Simplices originating from a vertex don't overlap."""
641
646
raise NotImplementedError
642
647
643
- def get_neighbors_from_vertices (self , simplex : Simplex ) -> Any :
648
+ def get_neighbors_from_vertices (self , simplex : Simplex ) -> Set [ Simplex ] :
644
649
return set .union (* [self .vertex_to_simplices [p ] for p in simplex ])
645
650
646
- def get_face_sharing_neighbors (self , neighbors : Any , simplex : Simplex ) -> Any :
651
+ def get_face_sharing_neighbors (
652
+ self , neighbors : Set [Simplex ], simplex : Simplex
653
+ ) -> Set [Simplex ]:
647
654
"""Keep only the simplices sharing a whole face with simplex."""
648
655
return {
649
656
simpl for simpl in neighbors if len (set (simpl ) & set (simplex )) == self .dim
650
657
} # they share a face
651
658
652
- def get_simplices_attached_to_points (self , indices : Any ) -> Any :
659
+ def get_simplices_attached_to_points (self , indices : Simplex ) -> Set [ Simplex ] :
653
660
# Get all simplices that share at least a point with the simplex
654
661
neighbors = self .get_neighbors_from_vertices (indices )
655
662
return self .get_face_sharing_neighbors (neighbors , indices )
656
663
657
- def get_opposing_vertices (self , simplex : Simplex , ) -> Any :
664
+ def get_opposing_vertices (self , simplex : Simplex ) -> Tuple [ int , ...] :
658
665
if simplex not in self .simplices :
659
666
raise ValueError ("Provided simplex is not part of the triangulation" )
660
667
neighbors = self .get_simplices_attached_to_points (simplex )
661
668
662
- def find_opposing_vertex (vertex ):
669
+ def find_opposing_vertex (vertex : int ):
663
670
# find the simplex:
664
671
simp = next ((x for x in neighbors if vertex not in x ), None )
665
672
if simp is None :
0 commit comments