1+ from __future__ import annotations
2+
3+ import collections .abc
4+ import numbers
15from collections import Counter
2- from collections .abc import Iterable , Sized
36from itertools import chain , combinations
47from math import factorial , sqrt
8+ from typing import Any , Iterable , Iterator , List , Sequence , Tuple , Union
59
610import scipy .spatial
711from numpy import abs as np_abs
1317 dot ,
1418 eye ,
1519 mean ,
20+ ndarray ,
1621 ones ,
1722 square ,
1823 subtract ,
2227from numpy .linalg import det as ndet
2328from numpy .linalg import matrix_rank , norm , slogdet , solve
2429
30+ from adaptive .types import Bool
31+
32+ try :
33+ from typing import TypeAlias
34+ except ImportError :
35+ # Remove this when we drop support for Python 3.9
36+ from typing_extensions import TypeAlias
37+
2538
26- def fast_norm (v ):
39+ SimplexPoints : TypeAlias = Union [List [Tuple [float , ...]], ndarray ]
40+ Simplex : TypeAlias = Union [Sequence [numbers .Integral ], ndarray ]
41+ Point : TypeAlias = Union [Tuple [float , ...], ndarray ]
42+ Points : TypeAlias = Union [Sequence [Tuple [float , ...]], ndarray ]
43+
44+
45+ def fast_norm (v : tuple [float , ...] | ndarray ) -> float :
2746 """Take the vector norm for len 2, 3 vectors.
2847 Defaults to a square root of the dot product for larger vectors.
2948
@@ -41,7 +60,9 @@ def fast_norm(v):
4160 return sqrt (dot (v , v ))
4261
4362
44- def fast_2d_point_in_simplex (point , simplex , eps = 1e-8 ):
63+ def fast_2d_point_in_simplex (
64+ point : Point , simplex : SimplexPoints , eps : float = 1e-8
65+ ) -> Bool :
4566 (p0x , p0y ), (p1x , p1y ), (p2x , p2y ) = simplex
4667 px , py = point
4768
@@ -55,7 +76,7 @@ def fast_2d_point_in_simplex(point, simplex, eps=1e-8):
5576 return (t >= - eps ) and (s + t <= 1 + eps )
5677
5778
58- def point_in_simplex (point , simplex , eps = 1e-8 ):
79+ def point_in_simplex (point : Point , simplex : SimplexPoints , eps : float = 1e-8 ) -> Bool :
5980 if len (point ) == 2 :
6081 return fast_2d_point_in_simplex (point , simplex , eps )
6182
@@ -66,7 +87,7 @@ def point_in_simplex(point, simplex, eps=1e-8):
6687 return all (alpha > - eps ) and sum (alpha ) < 1 + eps
6788
6889
69- def fast_2d_circumcircle (points ) :
90+ def fast_2d_circumcircle (points : Points ) -> tuple [ tuple [ float , float ], float ] :
7091 """Compute the center and radius of the circumscribed circle of a triangle
7192
7293 Parameters
@@ -79,7 +100,7 @@ def fast_2d_circumcircle(points):
79100 tuple
80101 (center point : tuple(float), radius: float)
81102 """
82- points = array (points )
103+ points = array (points , dtype = float )
83104 # transform to relative coordinates
84105 pts = points [1 :] - points [0 ]
85106
@@ -102,7 +123,9 @@ def fast_2d_circumcircle(points):
102123 return (x + points [0 ][0 ], y + points [0 ][1 ]), radius
103124
104125
105- def fast_3d_circumcircle (points ):
126+ def fast_3d_circumcircle (
127+ points : Points ,
128+ ) -> tuple [tuple [float , float , float ], float ]:
106129 """Compute the center and radius of the circumscribed sphere of a simplex.
107130
108131 Parameters
@@ -142,7 +165,7 @@ def fast_3d_circumcircle(points):
142165 return center , radius
143166
144167
145- def fast_det (matrix ) :
168+ def fast_det (matrix : ndarray ) -> float :
146169 matrix = asarray (matrix , dtype = float )
147170 if matrix .shape == (2 , 2 ):
148171 return matrix [0 ][0 ] * matrix [1 ][1 ] - matrix [1 ][0 ] * matrix [0 ][1 ]
@@ -153,7 +176,7 @@ def fast_det(matrix):
153176 return ndet (matrix )
154177
155178
156- def circumsphere (pts ) :
179+ def circumsphere (pts : Simplex ) -> tuple [ tuple [ float , ...], float ] :
157180 """Compute the center and radius of a N dimension sphere which touches each point in pts.
158181
159182 Parameters
@@ -201,7 +224,7 @@ def circumsphere(pts):
201224 return tuple (center ), radius
202225
203226
204- def orientation (face , origin ) :
227+ def orientation (face : tuple | ndarray , origin : tuple | ndarray ) -> int :
205228 """Compute the orientation of the face with respect to a point, origin.
206229
207230 Parameters
@@ -224,14 +247,14 @@ def orientation(face, origin):
224247 sign , logdet = slogdet (vectors - origin )
225248 if logdet < - 50 : # assume it to be zero when it's close to zero
226249 return 0
227- return sign
250+ return int ( sign )
228251
229252
230- def is_iterable_and_sized (obj ) :
231- return isinstance (obj , Iterable ) and isinstance ( obj , Sized )
253+ def is_iterable_and_sized (obj : Any ) -> bool :
254+ return isinstance (obj , collections . abc . Collection )
232255
233256
234- def simplex_volume_in_embedding (vertices ) -> float :
257+ def simplex_volume_in_embedding (vertices : Sequence [ Point ] ) -> float :
235258 """Calculate the volume of a simplex in a higher dimensional embedding.
236259 That is: dim > len(vertices) - 1. For example if you would like to know the
237260 surface area of a triangle in a 3d space.
@@ -312,7 +335,7 @@ class Triangulation:
312335 or more simplices in the
313336 """
314337
315- def __init__ (self , coords ) :
338+ def __init__ (self , coords : Points ) -> None :
316339 if not is_iterable_and_sized (coords ):
317340 raise TypeError ("Please provide a 2-dimensional list of points" )
318341 coords = list (coords )
@@ -340,38 +363,40 @@ def __init__(self, coords):
340363 "(the points are linearly dependent)"
341364 )
342365
343- self .vertices = list (coords )
344- self .simplices = set ()
366+ self .vertices : list [ Point ] = list (coords )
367+ self .simplices : set [ Simplex ] = set ()
345368 # initialise empty set for each vertex
346- self .vertex_to_simplices = [set () for _ in coords ]
369+ self .vertex_to_simplices : list [ set [ Simplex ]] = [set () for _ in coords ]
347370
348371 # find a Delaunay triangulation to start with, then we will throw it
349372 # away and continue with our own algorithm
350373 initial_tri = scipy .spatial .Delaunay (coords )
351374 for simplex in initial_tri .simplices :
352375 self .add_simplex (simplex )
353376
354- def delete_simplex (self , simplex ) :
377+ def delete_simplex (self , simplex : Simplex ) -> None :
355378 simplex = tuple (sorted (simplex ))
356379 self .simplices .remove (simplex )
357380 for vertex in simplex :
358381 self .vertex_to_simplices [vertex ].remove (simplex )
359382
360- def add_simplex (self , simplex ) :
383+ def add_simplex (self , simplex : Simplex ) -> None :
361384 simplex = tuple (sorted (simplex ))
362385 self .simplices .add (simplex )
363386 for vertex in simplex :
364387 self .vertex_to_simplices [vertex ].add (simplex )
365388
366- def get_vertices (self , indices ) :
389+ def get_vertices (self , indices : Iterable [ numbers . Integral ]) -> list [ Point | None ] :
367390 return [self .get_vertex (i ) for i in indices ]
368391
369- def get_vertex (self , index ) :
392+ def get_vertex (self , index : numbers . Integral | None ) -> Point | None :
370393 if index is None :
371394 return None
372395 return self .vertices [index ]
373396
374- def get_reduced_simplex (self , point , simplex , eps = 1e-8 ) -> list :
397+ def get_reduced_simplex (
398+ self , point : Point , simplex : Simplex , eps : float = 1e-8
399+ ) -> list [numbers .Integral ]:
375400 """Check whether vertex lies within a simplex.
376401
377402 Returns
@@ -396,11 +421,13 @@ def get_reduced_simplex(self, point, simplex, eps=1e-8) -> list:
396421
397422 return [simplex [i ] for i in result ]
398423
399- def point_in_simplex (self , point , simplex , eps = 1e-8 ):
424+ def point_in_simplex (
425+ self , point : Point , simplex : Simplex , eps : float = 1e-8
426+ ) -> Bool :
400427 vertices = self .get_vertices (simplex )
401428 return point_in_simplex (point , vertices , eps )
402429
403- def locate_point (self , point ) :
430+ def locate_point (self , point : Point ) -> Simplex :
404431 """Find to which simplex the point belongs.
405432
406433 Return indices of the simplex containing the point.
@@ -412,10 +439,15 @@ def locate_point(self, point):
412439 return ()
413440
414441 @property
415- def dim (self ):
442+ def dim (self ) -> int :
416443 return len (self .vertices [0 ])
417444
418- def faces (self , dim = None , simplices = None , vertices = None ):
445+ def faces (
446+ self ,
447+ dim : int | None = None ,
448+ simplices : Iterable [Simplex ] | None = None ,
449+ vertices : Iterable [int ] | None = None ,
450+ ) -> Iterator [tuple [numbers .Integral , ...]]:
419451 """Iterator over faces of a simplex or vertex sequence."""
420452 if dim is None :
421453 dim = self .dim
@@ -436,11 +468,11 @@ def faces(self, dim=None, simplices=None, vertices=None):
436468 else :
437469 return faces
438470
439- def containing (self , face ) :
471+ def containing (self , face : tuple [ int , ...]) -> set [ Simplex ] :
440472 """Simplices containing a face."""
441473 return set .intersection (* (self .vertex_to_simplices [i ] for i in face ))
442474
443- def _extend_hull (self , new_vertex , eps = 1e-8 ):
475+ def _extend_hull (self , new_vertex : Point , eps : float = 1e-8 ) -> set [ Simplex ] :
444476 # count multiplicities in order to get all hull faces
445477 multiplicities = Counter (face for face in self .faces ())
446478 hull_faces = [face for face , count in multiplicities .items () if count == 1 ]
@@ -480,7 +512,9 @@ def _extend_hull(self, new_vertex, eps=1e-8):
480512
481513 return new_simplices
482514
483- def circumscribed_circle (self , simplex , transform ):
515+ def circumscribed_circle (
516+ self , simplex : Simplex , transform : ndarray
517+ ) -> tuple [tuple [float , ...], float ]:
484518 """Compute the center and radius of the circumscribed circle of a simplex.
485519
486520 Parameters
@@ -496,7 +530,9 @@ def circumscribed_circle(self, simplex, transform):
496530 pts = dot (self .get_vertices (simplex ), transform )
497531 return circumsphere (pts )
498532
499- def point_in_cicumcircle (self , pt_index , simplex , transform ):
533+ def point_in_cicumcircle (
534+ self , pt_index : int , simplex : Simplex , transform : ndarray
535+ ) -> Bool :
500536 # return self.fast_point_in_circumcircle(pt_index, simplex, transform)
501537 eps = 1e-8
502538
@@ -506,10 +542,15 @@ def point_in_cicumcircle(self, pt_index, simplex, transform):
506542 return norm (center - pt ) < (radius * (1 + eps ))
507543
508544 @property
509- def default_transform (self ):
545+ def default_transform (self ) -> ndarray :
510546 return eye (self .dim )
511547
512- def bowyer_watson (self , pt_index , containing_simplex = None , transform = None ):
548+ def bowyer_watson (
549+ self ,
550+ pt_index : int ,
551+ containing_simplex : Simplex | None = None ,
552+ transform : ndarray | None = None ,
553+ ) -> tuple [set [Simplex ], set [Simplex ]]:
513554 """Modified Bowyer-Watson point adding algorithm.
514555
515556 Create a hole in the triangulation around the new point,
@@ -569,10 +610,10 @@ def bowyer_watson(self, pt_index, containing_simplex=None, transform=None):
569610 new_triangles = self .vertex_to_simplices [pt_index ]
570611 return bad_triangles - new_triangles , new_triangles - bad_triangles
571612
572- def _simplex_is_almost_flat (self , simplex ) :
613+ def _simplex_is_almost_flat (self , simplex : Simplex ) -> Bool :
573614 return self ._relative_volume (simplex ) < 1e-8
574615
575- def _relative_volume (self , simplex ) :
616+ def _relative_volume (self , simplex : Simplex ) -> float :
576617 """Compute the volume of a simplex divided by the average (Manhattan)
577618 distance of its vertices. The advantage of this is that the relative
578619 volume is only dependent on the shape of the simplex and not on the
@@ -583,20 +624,25 @@ def _relative_volume(self, simplex):
583624 average_edge_length = mean (np_abs (vectors ))
584625 return self .volume (simplex ) / (average_edge_length ** self .dim )
585626
586- def add_point (self , point , simplex = None , transform = None ):
627+ def add_point (
628+ self ,
629+ point : Point ,
630+ simplex : Simplex | None = None ,
631+ transform : ndarray | None = None ,
632+ ) -> tuple [set [Simplex ], set [Simplex ]]:
587633 """Add a new vertex and create simplices as appropriate.
588634
589635 Parameters
590636 ----------
591637 point : float vector
592638 Coordinates of the point to be added.
593- transform : N*N matrix of floats
594- Multiplication matrix to apply to the point (and neighbouring
595- simplices) when running the Bowyer Watson method.
596639 simplex : tuple of ints, optional
597640 Simplex containing the point. Empty tuple indicates points outside
598641 the hull. If not provided, the algorithm costs O(N), so this should
599642 be used whenever possible.
643+ transform : N*N matrix of floats
644+ Multiplication matrix to apply to the point (and neighbouring
645+ simplices) when running the Bowyer Watson method.
600646 """
601647 point = tuple (point )
602648 if simplex is None :
@@ -632,16 +678,16 @@ def add_point(self, point, simplex=None, transform=None):
632678 self .vertices .append (point )
633679 return self .bowyer_watson (pt_index , actual_simplex , transform )
634680
635- def volume (self , simplex ) :
681+ def volume (self , simplex : Simplex ) -> float :
636682 prefactor = factorial (self .dim )
637683 vertices = array (self .get_vertices (simplex ))
638684 vectors = vertices [1 :] - vertices [0 ]
639685 return float (abs (fast_det (vectors )) / prefactor )
640686
641- def volumes (self ):
687+ def volumes (self ) -> list [ float ] :
642688 return [self .volume (sim ) for sim in self .simplices ]
643689
644- def reference_invariant (self ):
690+ def reference_invariant (self ) -> bool :
645691 """vertex_to_simplices and simplices are compatible."""
646692 for vertex in range (len (self .vertices )):
647693 if any (vertex not in tri for tri in self .vertex_to_simplices [vertex ]):
@@ -655,26 +701,28 @@ def vertex_invariant(self, vertex):
655701 """Simplices originating from a vertex don't overlap."""
656702 raise NotImplementedError
657703
658- def get_neighbors_from_vertices (self , simplex ) :
704+ def get_neighbors_from_vertices (self , simplex : Simplex ) -> set [ Simplex ] :
659705 return set .union (* [self .vertex_to_simplices [p ] for p in simplex ])
660706
661- def get_face_sharing_neighbors (self , neighbors , simplex ):
707+ def get_face_sharing_neighbors (
708+ self , neighbors : set [Simplex ], simplex : Simplex
709+ ) -> set [Simplex ]:
662710 """Keep only the simplices sharing a whole face with simplex."""
663711 return {
664712 simpl for simpl in neighbors if len (set (simpl ) & set (simplex )) == self .dim
665713 } # they share a face
666714
667- def get_simplices_attached_to_points (self , indices ) :
715+ def get_simplices_attached_to_points (self , indices : Simplex ) -> set [ Simplex ] :
668716 # Get all simplices that share at least a point with the simplex
669717 neighbors = self .get_neighbors_from_vertices (indices )
670718 return self .get_face_sharing_neighbors (neighbors , indices )
671719
672- def get_opposing_vertices (self , simplex ) :
720+ def get_opposing_vertices (self , simplex : Simplex ) -> tuple [ int , ...] :
673721 if simplex not in self .simplices :
674722 raise ValueError ("Provided simplex is not part of the triangulation" )
675723 neighbors = self .get_simplices_attached_to_points (simplex )
676724
677- def find_opposing_vertex (vertex ):
725+ def find_opposing_vertex (vertex : int ):
678726 # find the simplex:
679727 simp = next ((x for x in neighbors if vertex not in x ), None )
680728 if simp is None :
@@ -687,7 +735,7 @@ def find_opposing_vertex(vertex):
687735 return result
688736
689737 @property
690- def hull (self ):
738+ def hull (self ) -> set [ numbers . Integral ] :
691739 """Compute hull from triangulation.
692740
693741 Parameters
0 commit comments