diff --git a/hypertiling/CenterContainer.py b/hypertiling/CenterContainer.py index cb5aa3b820bbe36517bd9d02534a5bc998d9bcf1..963d0853d538c3867631b39d3486ce040c07b6af 100644 --- a/hypertiling/CenterContainer.py +++ b/hypertiling/CenterContainer.py @@ -1,6 +1,6 @@ import math -from .util import fund_radius +from util import fund_radius class HTCenter: '''This helper class wraps a complex and enables comparison based on the angle''' diff --git a/hypertiling/core.py b/hypertiling/core.py index 50de28e4ffcc0bd84377f9a3cda32a5478916d05..f16291975a2b2bc4409875e87835beb2241bb163 100644 --- a/hypertiling/core.py +++ b/hypertiling/core.py @@ -1,9 +1,7 @@ # relative imports -from .kernelflo import KernelFlo -from .kernelmanu import KernelManu -from .kerneldunham import KernelDunham - - +from kernelflo import KernelFlo +from kernelmanu import KernelManu +from kerneldunham import KernelDunham # factory pattern allows to select between kernels @@ -31,13 +29,11 @@ def HyperbolicTiling(p, q, n, center="cell", kernel="flo"): if p>20 or q>20 and n>5: print("[hypertiling] Warning: The lattice might become very large with your parameter choice!") - kernels = { "manu": KernelManu, # to-do: we need better names for the kernels ;) "flo": KernelFlo, "dunham": KernelDunham} if kernel not in kernels: - raise KeyError("[hypertiling] Error: No valid kernel specified") + raise KeyError("[hypertiling] Error: No valid kernel specified") if kernel == "dunham": - raise NotImplementedError("[hypertiling] Error: Dunham kernel is currently broken (fixme!)") - return kernels[kernel](p, q, n, center) + return kernels[kernel](p, q, n, center) \ No newline at end of file diff --git a/hypertiling/geodesics.py b/hypertiling/geodesics.py index 4a118fd5b8b5f98933f183ea30b1e7c7b2727044..045bc0caf9c45a24d8936b69a216640bef39b431 100644 --- a/hypertiling/geodesics.py +++ b/hypertiling/geodesics.py @@ -1,5 +1,6 @@ import numpy as np + # returns the "minor" of a matrix def minor(M, i, j): M = np.delete(M, i, 0) diff --git a/hypertiling/hyperpolygon.py b/hypertiling/hyperpolygon.py index f852539a368cccab8846d0848dd5367279c65ceb..77b679fb7875a30d0c60fa4fcff98afc91816e8b 100644 --- a/hypertiling/hyperpolygon.py +++ b/hypertiling/hyperpolygon.py @@ -1,6 +1,6 @@ from math import floor import numpy as np -from .transformation import * +from transformation import * def morigin_py(p, z0, verticesP): for i in range(p + 1): @@ -147,9 +147,6 @@ class HyperPolygon: return True return False - - - # transforms the entire polygon: to the origin, rotate it and back again def tf_full(self, ind, phi): mfull(self.p, phi, ind, self.verticesP) @@ -166,7 +163,6 @@ class HyperPolygon: z = moeb_translate_trafo(self.verticesP[i], s) self.verticesP[i] = z - def moeb_inverse(self, z0): morigin_inv(self.p, z0, self.verticesP) @@ -177,14 +173,11 @@ class HyperPolygon: z = z*rotation self.verticesP[i] = z - - # compute angle between center and the positive x-axis def find_angle(self): self.angle = math.degrees(math.atan2(self.centerP().imag, self.centerP().real)) self.angle += 360 if self.angle < 0 else 0 - def find_sector(self, k, offset=0): """ compute in which sector out of k sectors the polygon resides @@ -209,3 +202,9 @@ class HyperPolygon: def find_orientation(self): self.orientation = np.angle(self.verticesP[0]-self.centerP()) + # ONLY USED BY dunhams tiling class! Transforms all points of the polygon by matrix tmat + def transform(self, tmat): + for i in range(self.p + 1): # +1 to include center + vertex = p2w_py(self.verticesP[i], kernel="dunham") + result = tmat @ vertex + self.verticesP[i] = w2p_py(result, kernel="dunham") diff --git a/hypertiling/io.py b/hypertiling/ion.py similarity index 100% rename from hypertiling/io.py rename to hypertiling/ion.py diff --git a/hypertiling/kernelbase.py b/hypertiling/kernelbase.py index 9fe1ef84f63d244cb28b34f2a14e7d77314ef3ee..c8523f1d25dbb554d1596a0b5929013653b6d124 100644 --- a/hypertiling/kernelbase.py +++ b/hypertiling/kernelbase.py @@ -1,10 +1,12 @@ import numpy as np import math import copy + # relative imports -from .hyperpolygon import HyperPolygon -from .transformation import p2w -from .util import fund_radius +from hyperpolygon import HyperPolygon +from transformation import p2w +from util import fund_radius + # the main object of this library # essentially represents a list of polygons which constitute the hyperbolic lattice @@ -48,15 +50,14 @@ class HyperbolicTilingBase: # technical parameters # do not change, unless you know what you are doing!) - self.degtol = 1 # sector boundary tolerance + self.degtol = 1 # sector boundary tolerance # angular offset, rotates the entire construction by a bit during construction if center == "cell": - self.mangle = self.degphi/math.sqrt(5) + self.mangle = self.degphi/math.sqrt(5) elif center == "vertex": self.mangle = self.degqhi/math.sqrt(5) - # fundamental polygon of the tiling self.fund_poly = self.create_fundamental_polygon(center) @@ -66,17 +67,14 @@ class HyperbolicTilingBase: if center not in ['cell', 'vertex']: raise ValueError('Invalid value for argument "center"!') - def __getitem__(self, idx): return self.polygons[idx] - def __iter__(self): self.iterctr = 0 self.itervar = self.polygons[self.iterctr] return self - def __next__(self): if self.iterctr < len(self.polygons): retval = self.polygons[self.iterctr] @@ -85,11 +83,9 @@ class HyperbolicTilingBase: else: raise StopIteration - def __len__(self): return len(self.polygons) - def create_fundamental_polygon(self, center='cell'): """ Constructs the vertices of the fundamental hyperbolic {p,q} polygon @@ -105,7 +101,6 @@ class HyperbolicTilingBase: """ r = fund_radius(self.p, self.q) polygon = HyperPolygon(self.p) - for i in range(self.p): z = complex(math.cos(i*self.phi), math.sin(i*self.phi)) # = exp(i*phi) z = z/abs(z) @@ -124,24 +119,24 @@ class HyperbolicTilingBase: return polygon + class KernelCommon(HyperbolicTilingBase): """ Commonalities """ - def __init__ (self, p, q, n, center): + def __init__(self, p, q, n, center): super(KernelCommon, self).__init__(p, q, n, center) def replicate(self): """ tessellate the entire disk by replicating the fundamental sector """ - if self.center == 'cell': + if self.center == 'cell': # and self.nlayers > 1: self.angular_replicate(copy.deepcopy(self.polygons), self.p) - elif self.center == 'vertex': + elif self.center == 'vertex': # and self.nlayers > 1: self.angular_replicate(copy.deepcopy(self.polygons), self.q) - def generate(self): """ do full construction @@ -149,8 +144,6 @@ class KernelCommon(HyperbolicTilingBase): self.generate_sector() self.replicate() - - def generate_adj_poly(self, polygon, ind, k): """ finds the next polygon by k-fold rotation of polygon around the vertex number ind @@ -158,7 +151,6 @@ class KernelCommon(HyperbolicTilingBase): polygon.tf_full(ind, k*self.qhi) return polygon - # tessellates the disk by applying a rotation of 2pi/p to the pizza slice def angular_replicate(self, polygons, k): if self.center == 'cell': diff --git a/hypertiling/kerneldunham.py b/hypertiling/kerneldunham.py index 29e643b3a25c13ade9ee8b3ed723f14cc75b0d80..0115c2c5ce6ed3a0e96811697105055dac39ba6a 100644 --- a/hypertiling/kerneldunham.py +++ b/hypertiling/kerneldunham.py @@ -2,10 +2,10 @@ import numpy as np import copy # relative imports -from .kernelbase import HyperbolicTilingBase -from .hyperpolygon import HyperPolygon, mfull_point -from .transformation import p2w -from .util import fund_radius +from kernelbase import HyperbolicTilingBase +from hyperpolygon import HyperPolygon, mfull_point +from transformation import p2w +from util import fund_radius, remove_duplicates class KernelDunham(HyperbolicTilingBase): @@ -17,8 +17,8 @@ class KernelDunham(HyperbolicTilingBase): currently broken! fixme """ - def __init__ (self, p, q, n, center="cell"): - super(KernelDunham, self).__init__(p, q, n, center="cell") + def __init__(self, p, q, n, center="cell"): + super(KernelDunham, self).__init__(p, q, n, center=center) # reflection and rotation matrices self.b = np.arccosh(np.cos(np.pi / q) / np.sin(np.pi / p)) @@ -40,32 +40,51 @@ class KernelDunham(HyperbolicTilingBase): self.RotCenterG = np.eye(3) # G for usage in generate() self.RotCenterR = np.eye(3) # R for usage in replicate(...) + self.fund_poly = self.create_fundamental_polygon(center) + self.polygons = [self.fund_poly] + self.generate() + self.polygons = remove_duplicates(self.polygons) self.fund_poly = self.create_fundamental_polygon() + self.fund_poly.moeb_rotate(2*np.pi/360*(self.mangle+180)) # sloppy: reverts the default rotation in kernelbase self.polygons = [self.fund_poly] - def create_fundamental_polygon(self): # constructs the verticesP of the fundamental hyperbolic {p,q} polygon - r = fund_radius(self.p, self.q) - polygon = HyperPolygon(self.p) - angle = np.pi / self.p - for i in range(self.p): # for every corner of the polygon - z = complex(r * np.cos(angle + 2 * np.pi * i / self.p), r * np.sin(angle + 2 * np.pi * i / self.p)) - polygon.verticesP[i] = z - polygon.verticesW[:, i] = p2w(z) - return polygon - def generate(self): + # if self.nlayers == 0: + # self.polygons = [] + # return + # elif self.nlayers == 1: + # return if self.nlayers == 1: return - for _ in range(self.p): + for i in range(self.p): RotVertex = self.RotCenterG @ self.RotQ self.replicate(self.polygons, RotVertex, self.nlayers - 2, "Edge") - for _ in range(self.q - 3): + for j in range(self.q - 3): RotVertex = RotVertex @ self.RotQ self.replicate(self.polygons, RotVertex, self.nlayers - 2, "Vertex") self.RotCenterG = self.RotCenterG @ self.RotP + # remove duplicates: this kernel produces duplicates during construction per definition + dgts = 10 + centerset = set() + centerset.add(np.round(self.fund_poly.centerP(), dgts)) + polygonlist = [self.fund_poly] + for pgon in self.polygons: + # try adding to centerlist; it is a set() and takes care of duplicates + center = np.round(pgon.centerP(), dgts) # caution: rounding precision chosen arbitrarily + if pgon.layer == 2: + print(pgon.verticesP) + lenA = len(centerset) + centerset.add(center) + lenB = len(centerset) + + if lenB > lenA: + polygonlist.append(pgon) + + self.polygons = polygonlist + def replicate(self, Polygons, InitialTran, LayersToDo, AdjacencyType): poly = copy.deepcopy(self.fund_poly) poly.transform(InitialTran) diff --git a/hypertiling/kernelflo.py b/hypertiling/kernelflo.py index 8fe6c5027617183b1a8de9a54069da524e35858d..dc50793c1fd875a05837f47c659163a8a8a6e9d1 100644 --- a/hypertiling/kernelflo.py +++ b/hypertiling/kernelflo.py @@ -3,17 +3,17 @@ import math import copy # relative imports -from .kernelbase import KernelCommon -from .hyperpolygon import HyperPolygon, mfull_point -from .transformation import moeb_rotate_trafo -from .util import fund_radius -from .CenterContainer import CenterContainer +from kernelbase import KernelCommon +from hyperpolygon import HyperPolygon, mfull_point +from transformation import moeb_rotate_trafo +from util import fund_radius +from CenterContainer import CenterContainer class KernelFlo(KernelCommon): """ High precision kernel written by F. Goth """ - def __init__ (self, p, q, n, center): + def __init__(self, p, q, n, center): super(KernelFlo, self).__init__(p, q, n, center) def generate_sector(self): @@ -30,6 +30,13 @@ class KernelFlo(KernelCommon): # add fundamental polygon to list self.polygons.append(self.fund_poly) + if self.nlayers == 1: + return + # if self.nlayers > 0: + # self.polygons.append(self.fund_poly) + # else: + # return + # angle width of the fundamental sector sect_angle = self.phi sect_angle_deg = self.degphi diff --git a/hypertiling/kernelmanu.py b/hypertiling/kernelmanu.py index c651b558b072b1752d6698e50b7bb354232c81d6..ca36290de2827545e6b6f2ac9db7d788831939b5 100644 --- a/hypertiling/kernelmanu.py +++ b/hypertiling/kernelmanu.py @@ -3,18 +3,18 @@ import math import copy # relative imports -from .kernelbase import KernelCommon -from .hyperpolygon import HyperPolygon, mfull_point -from .transformation import p2w, moeb_rotate_trafo -from .distance import disk_distance +from kernelbase import KernelCommon +from hyperpolygon import HyperPolygon, mfull_point +from transformation import p2w, moeb_rotate_trafo +from distance import disk_distance class KernelManu(KernelCommon): """ Tiling construction algorithm written by M. Schrauth and F. Dusel """ - def __init__ (self, p, q, n, center): + def __init__(self, p, q, n, center): super(KernelManu, self).__init__(p, q, n, center) self.dgts = 8 - self.accuracy = 10**(-self.dgts) # numerical accuracy + self.accuracy = 10**(-self.dgts) # numerical accuracy def generate_sector(self): @@ -31,6 +31,13 @@ class KernelManu(KernelCommon): # add fundamental polygon to list self.polygons.append(self.fund_poly) + # if self.nlayers == 1: + # return + # if self.nlayers > 0: + # self.polygons.append(self.fund_poly) + # else: + # return + # angle width of the fundamental sector sect_angle = self.phi sect_angle_deg = self.degphi diff --git a/hypertiling/neighbours.py b/hypertiling/neighbours.py index 1609c26f7d2fbe271f95176a4a04ece106e0fde6..0c6bef35824035823fc82152e14e9687967be22f 100644 --- a/hypertiling/neighbours.py +++ b/hypertiling/neighbours.py @@ -224,24 +224,24 @@ def find_nn_optimized_slice(tiling, nn_dist, eps=1e-5): # shifts local neighbour list to another sector def shift(lst,times): - lst = sorted(lst) - haszero = (0 in lst) + lst = sorted(lst) + haszero = (0 in lst) # fundamental cell requires a little extra care - if haszero: - lsta = np.array(lst[1:]) + times*pps - lsta[lsta>totalnum-1] -= (totalnum-1) - lsta[lsta<1] += (totalnum-1) - return sorted([0]+list(lsta)) - else: - lsta = np.array(lst) + times*pps - lsta[lsta>totalnum-1] -= (totalnum-1) - lsta[lsta<1] += (totalnum-1) - return sorted(list(lsta)) + if haszero: + lsta = np.array(lst[1:]) + times*pps + lsta[lsta>totalnum-1] -= (totalnum-1) + lsta[lsta<1] += (totalnum-1) + return sorted([0]+list(lsta)) + else: + lsta = np.array(lst) + times*pps + lsta[lsta>totalnum-1] -= (totalnum-1) + lsta[lsta<1] += (totalnum-1) + return sorted(list(lsta)) # increment indices by one def increment(lst): - return list(np.array(lst)+1) + return list(np.array(lst)+1) diff --git a/hypertiling/plot.py b/hypertiling/plot.py index 36c995c3a6f2a4b60d89d4817a28a66e283f166c..441523f2bfb864c98dcc383538a1f1014108d35c 100644 --- a/hypertiling/plot.py +++ b/hypertiling/plot.py @@ -4,7 +4,7 @@ import matplotlib.pyplot as plt import matplotlib.cm as cmap from matplotlib.patches import Polygon from matplotlib.collections import PatchCollection, PolyCollection -from .geodesics import geodesic_arc +from geodesics import geodesic_arc # taken from http://exnumerus.blogspot.com/2011/02/how-to-quickly-plot-polygons-in.html diff --git a/hypertiling/transformation.py b/hypertiling/transformation.py index 476127f90e1b90d46a6a5f9727aaee8bbb104bc4..5c3e7384609c8b33fa86258c5edcc49eefba2541 100644 --- a/hypertiling/transformation.py +++ b/hypertiling/transformation.py @@ -164,17 +164,26 @@ def ddcplxdiv(a, da, b, db): return complex(r, i), complex(dr, di) -def p2w_py(z): + +def p2w_py(z, kernel="manu"): '''Convert Poincare to Weierstraß representation ''' x, y = z.real, z.imag xx = x*x yy = y*y factor = 1 / (1-xx-yy) - return factor*nparray([(1+xx+yy), 2*x, 2*y]) + # return factor * nparray([(1 + xx + yy), 2 * x, 2 * y]) # flo/manu version + if kernel == "dunham": + return factor*nparray([2*x, 2*y, (1+xx+yy)]) # dunham version + else: + return factor*nparray([(1+xx+yy), 2*x, 2*y]) # flo/manu version -def w2p_py(point): +def w2p_py(point, kernel="manu"): '''Convert Weierstraß to Poincare representation ''' - [t, x, y] = point + [t, x, y] = point # flo/manu version + if kernel == "dunham": + [x, y, t] = point # dunham version + else: + [t, x, y] = point # flo/manu version factor = 1 / (1+t) return complex(x*factor, y*factor) diff --git a/hypertiling/util.py b/hypertiling/util.py index a08193a70d439b61a09a3951caafc568d42ee08d..577a6ccc68e68df5a933fe1fcd7c23abe9487000 100644 --- a/hypertiling/util.py +++ b/hypertiling/util.py @@ -10,8 +10,8 @@ from .geodesics import geodesic_midpoint # radius of the fundamental (and every other) polygon def fund_radius(p, q): - num = math.cos(math.pi*(p+q)/p/q) #np.cos(np.pi / p + np.pi / q) - denom = math.cos(math.pi*(q-p)/p/q)#np.cos(np.pi / p - np.pi / q) + num = math.cos(math.pi*(p+q)/p/q) # np.cos(np.pi / p + np.pi / q) + denom = math.cos(math.pi*(q-p)/p/q) # np.cos(np.pi / p - np.pi / q) return np.sqrt(num / denom) @@ -77,8 +77,6 @@ def border_variance(tiling): return var/len(border) - - # formula from Mertens & Moore, PRE 96, 042116 (2017) # note that they use a different convention def n_cell_centered(p,q,n): @@ -87,6 +85,7 @@ def n_cell_centered(p,q,n): retval = retval + n_cell_centered_recursion(q,p,j) # note the exchange p<-->q return retval + def n_cell_centered_recursion(p,q,l): a = (p-2)*(q-2)-2 if l==0: @@ -98,13 +97,14 @@ def n_cell_centered_recursion(p,q,l): # Eq. A4 from Mertens & Moore, PRE 96, 042116 (2017) -def n_vertex_centered(p,q,l): - if l==0: - retval = 0 # no faces in zeroth layer - else: - #retval = ( n_v(p,q,l)+n_v(p,q,l-1) )/(p-2) - retval = ( n_v_vertex_centered(p,q,l)+n_v_vertex_centered(p,q,l-1) )/(p-2) - return retval +def n_vertex_centered(p, q, l): + if l == 0: + retval = 0 # no faces in zeroth layer + else: + #retval = ( n_v(p,q,l)+n_v(p,q,l-1) )/(p-2) + retval = ( n_v_vertex_centered(p,q,l)+n_v_vertex_centered(p,q,l-1) )/(p-2) + return retval + # Eq. A1, A2 from Mertens & Moore, PRE 96, 042116 (2017) def n_v_vertex_centered(p,q,n): @@ -114,7 +114,6 @@ def n_v_vertex_centered(p,q,n): return retval - # the following functions find the total number of polygons for some {p, q} tessellation of l layers # reference: Baek et al., Phys. Rev.E. 79.011124 def find_num_of_pgons_73(l):