diff --git a/hypertiling/core.py b/hypertiling/core.py
index 820cfba6bfd2eeaa2a91d2c39dac20ee78cad997..3ccd61872b8d0dee9f895c369c75be40373b6b60 100644
--- a/hypertiling/core.py
+++ b/hypertiling/core.py
@@ -82,6 +82,7 @@ class HyperbolicTiling:
# uses symmetry to fill the disk by rotating the slice
self.angular_replicate(copy.deepcopy(self.polygons), self.p)
+
# finds the next polygon by k-fold rotation of polygon around the vertex number ind
def generate_adj_poly(self, polygon, ind, k):
z0 = polygon.verticesP[ind]
@@ -90,6 +91,7 @@ class HyperbolicTiling:
polygon.moeb_inverse(z0) # map polygon back to former location
return polygon
+
# tessellates the disk by applying a rotation of 2pi/p to the pizza slice
def angular_replicate(self, polygons, k):
polygons.pop(0) # first pgon (partially) lies in every sector and thus need not be replicated
@@ -105,6 +107,21 @@ class HyperbolicTiling:
poly.idx = num + 1
+ # populate the "edges" list of all polygons in the tiling
+ def populate_edge_list(self, digits=12):
+ # note: same neighbour search methods employ the fact that adjacent polygons share an edge
+ # hence these will later be identified via floating point comparison and we need to round
+ # note: this procedure fails for Weierstrass coordinates, as these
+ # are not unique, meaning that the same coordinate can have different representations
+ for poly in self.polygons:
+ poly.edges = []
+ verts = np.round(poly.verticesP, digits)
+
+ # append edges as tuples
+ for i, vert in enumerate(verts[:-1]):
+ poly.edges.append((verts[i], verts[i+1]))
+ poly.edges.append((verts[-1], verts[0]))
+
# After the algorithm by D. Dunham (1982)
diff --git a/hypertiling/hyperpolygon.py b/hypertiling/hyperpolygon.py
index b76ec80fe15bc8654bb7589b21d1cfba8784b174..c52d8c851932fc03f69d1d13a11dae8ee0a1908a 100644
--- a/hypertiling/hyperpolygon.py
+++ b/hypertiling/hyperpolygon.py
@@ -1,6 +1,7 @@
from math import floor
from .transformation import *
+
# defines a hyperbolic polygon
class HyperPolygon:
def __init__(self, p, q):
@@ -23,6 +24,8 @@ class HyperPolygon:
self.angle = 0 # angle between self.centerP and the positive x-axis
self.val = 0 # assign a value (useful in any application)
+ self.edges = [] # compare self.populate_edge_list
+
# checks whether two polygons are equal (within given numerical precision)
# untested!
@@ -129,3 +132,5 @@ class HyperPolygon:
for i in range(self.p):
self.verticesP[i] = complex(self.verticesP[i].real, (-1)*self.verticesP[i].imag)
self.verticesW = p2w(self.verticesP)
+
+
diff --git a/hypertiling/neighbours.py b/hypertiling/neighbours.py
index 97159cbcf11f6a88f1e7d99e042de3b053947256..84fc65f159b024e2820384fb46afbc7629a40b26 100644
--- a/hypertiling/neighbours.py
+++ b/hypertiling/neighbours.py
@@ -17,6 +17,11 @@ def find(tiling, nn_dist=None, which="optimized_slice", index_from_zero=True, ve
retval = find_nn_brute_force(tiling, nn_dist) # use for debug
elif which == "slice":
retval = find_nn_slice(tiling, nn_dist)
+ elif which == "edge_map":
+ retval = find_nn_edge_map_optimized(tiling)
+ elif which == "edge_map_brute_force":
+ retval = find_nn_edge_map_brute_force(tiling)
+
else:
print("[Hypertiling] Error:", which, " is not a valid algorithm!")
@@ -33,6 +38,101 @@ def find(tiling, nn_dist=None, which="optimized_slice", index_from_zero=True, ve
+# find neighbours by identifying corresponding edges among polygons
+# this is a coordinate-free algorithm, it uses only the graph structure
+# can probably be further improved
+def find_nn_edge_map_optimized(tiling):
+
+ tiling.populate_edge_list()
+
+ # we create a kind of "dictionary" where keys are the
+ # edges and values are the corresponding polygon indices
+ edges, vals = [], []
+ for poly in tiling:
+ for edge in poly.edges:
+ edges.append(edge)
+ vals.append(poly.idx)
+
+ # reshape "edges" into its components in order to
+ # make use of numpy vectorization later
+ edge0 = np.zeros(len(edges)).astype(complex)
+ edge1 = np.zeros(len(edges)).astype(complex)
+ for i, edge in enumerate(edges):
+ edge0[i] = edge[0]
+ edge1[i] = edge[1]
+
+ # create empty neighbour array
+ nbrs = []
+ for i in range(len(tiling)):
+ nbrs.append([])
+
+ # an edge that is share by two polygons appears twice
+ # in the "edges" list; we find the corresponding polygon
+ # indices by looping over that list
+ for i, edge in enumerate(edges):
+ # compare against full edges arrays
+ # this avoids a double loop which is slow ...
+ # check edge
+ bool_array1 = (edge[0] == edge0)
+ bool_array2 = (edge[1] == edge1)
+ # check also reverse orientation
+ bool_array3 = (edge[0] == edge1)
+ bool_array4 = (edge[1] == edge0)
+
+ # put everything together; we require
+ # (True and True) or (True and True)
+ b = bool_array1*bool_array2 + bool_array3*bool_array4
+
+ # find indices where resulting boolean array is true
+ w = np.where(b)
+
+ # these indices are neighbours of each other
+ for x in w[0]:
+ if vals[i] is not vals[x]:
+ nbrs[vals[i]-1].append(vals[x])
+
+ return nbrs
+
+
+# find neighbours by identifying corresponding edges among polygons
+# there is an equivalent method available that is much faster: find_nn_edge_map_optimized
+# use this method only for debugging purposes
+def find_nn_edge_map_brute_force(tiling):
+
+ tiling.populate_edge_list()
+
+ # we create a kind of "dictionary" where keys are the
+ # edges and values are the corresponding polygon indices
+ edges, vals = [], []
+ for poly in tiling:
+ for edge in poly.edges:
+ edges.append(edge)
+ vals.append(poly.idx)
+
+ # create empty neighbour array
+ nbrs = []
+ for i in range(len(tiling)):
+ nbrs.append([])
+
+ # an edge that is share by two polygons appears twice
+ # in the "edges" list; we find the corresponding polygon
+ # indices by looping over that list twice
+ for i, k1 in enumerate(edges):
+ for j, k2 in enumerate(edges):
+ if k1[0] == k2[0] and k1[1] == k2[1]:
+ # check edge
+ if vals[i] is not vals[j]:
+ nbrs[vals[i]-1].append(vals[j])
+ # check also reverse orientation
+ elif k1[1] == k2[0] and k1[0] == k2[1]:
+ if vals[i] is not vals[j]:
+ nbrs[vals[i]-1].append(vals[j])
+
+
+ return nbrs
+
+
+
# find nearest neighbours by brute force comparison of all-to-all distances
# scales quadratically in the number of vertices and may thus become prohibitively expensive
# might be used for debugging purposes though