class Tiling2D(object): def __init__(self, coxeter_diagram, init_dist): if len(coxeter_diagram) != 3 or len(init_dist) != 3: raise ValueError("Invalid input dimension") self.diagram = coxeter_diagram # Coxeter matrix and its rank self.cox_mat = helpers.make_symmetry_matrix(coxeter_diagram) self.rank = len(self.cox_mat) # generators of the symmetry group self.gens = tuple(range(self.rank)) # symmetry group of this tiling self.G = CoxeterGroup(self.cox_mat) # a mirror is active iff the initial point is not on it self.active = tuple(bool(x) for x in init_dist) # reflection mirrors self.mirrors = self.get_mirrors(coxeter_diagram) # reflections (possibly affine) about the mirrors self.reflections = self.get_reflections() # coordinates of the initial point self.init_v = self.get_init_point(init_dist) # vertices of the fundamental triangle self.triangle_verts = self.get_fundamental_triangle_verts() # ---------------------- # to be calculated later # ---------------------- # holds the words in the symmetry group up to a given depth self.words = None # holds the coset representatives of the standard parabolic # subgroup of vertex-stabilizing subgroup self.vwords = None self.vertices_coords = [] self.num_vertices = None self.num_edges = None self.num_faces = None self.edge_indices = {} self.face_indices = {} def vertex_at_mirrors(self, i, j): return 2 * (i + j) % 3 def get_init_point(self, init_dist): raise NotImplementedError def get_mirrors(self, coxeter_diagram): raise NotImplementedError def get_fundamental_triangle_verts(self): raise NotImplementedError def build_geometry(self, depth=None, maxcount=20000): """Postpone the actual computations to this method. """ self.G.init() self.words = tuple(self.G.traverse(depth, maxcount)) self.get_vertices() self.get_edges() self.get_faces() return self def get_vertices(self): # generators of the vertex-stabilizing subgroup H = tuple(i for i, x in enumerate(self.active) if not x) # coset representatives of the vertex-stabilizing subgroup reps = set(self.G.get_coset_representative(w, H) for w in self.words) self.vwords = self.G.sort_words(reps) self.vtable = self.G.get_coset_table(self.vwords, H) self.num_vertices = len(self.vwords) self.vertices_coords = [ self.transform(w, self.init_v) for w in self.vwords ] def get_edges(self): for i in self.gens: if self.active[i]: elist = [] H = (i, ) # edge-stabilizing subgroup reps = set( self.G.get_coset_representative(w, H) for w in self.words) reps = self.G.sort_words(reps) for word in reps: v1 = self.G.move(self.vtable, 0, word) v2 = self.G.move(self.vtable, 0, word + (i, )) if v1 is not None and v2 is not None: if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices[i] = elist self.num_edges = sum(len(L) for L in self.edge_indices.values()) def get_faces(self): for i, j in combinations(self.gens, 2): c0 = self.triangle_verts[self.vertex_at_mirrors(i, j)] f0 = [] m = self.cox_mat[i][j] H = (i, j) type = 0 if self.active[i] and self.active[j]: type = 1 for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) f0.append(self.G.move(self.vtable, 0, (i, j) * k + (i, ))) elif self.active[i] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (j, i) * k)) elif self.active[j] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) else: continue reps = set( self.G.get_coset_representative(w, H) for w in self.words) reps = self.G.sort_words(reps) flist = [] for word in reps: f = tuple(self.G.move(self.vtable, v, word) for v in f0) if None not in f and not helpers.check_duplicate_face( f, flist): center = self.transform(word, c0) coords = [self.vertices_coords[k] for k in f] face = DihedralFace(word, f, center, coords, type) flist.append(face) self.face_indices[(i, j)] = flist self.num_faces = sum(len(L) for L in self.face_indices.values()) def get_reflections(self): def reflect(v, normal): return v - 2 * np.dot(v, normal) * normal return [partial(reflect, normal=n) for n in self.mirrors] def transform(self, word, v): for w in reversed(word): v = self.reflections[w](v) return v def get_info(self): """Return some statistics of the tiling. """ pattern = "{}-{}-{}".format(*self.diagram).replace("/", "|") info = "" info += "name: triangle group {}\n".format(pattern) info += "cox_mat: {}\n".format(self.cox_mat) info += "vertices: {}\n".format(self.num_vertices) info += "edges: {}\n".format(self.num_edges) info += "faces: {}\n".format(self.num_faces) info += "states in the automaton: {}\n".format(self.G.dfa.num_states) info += "reflection table:\n{}\n".format(self.G.reftable) info += "the automaton is saved as {}_dfa.png".format(pattern) self.G.dfa.draw(pattern + "_dfa.png") return info def render(self, *arg, **kwargs): raise NotImplementedError
class Tiling2D(object): """ Base class for all three types of tilings. """ def __init__(self, coxeter_diagram, init_dist): if len(coxeter_diagram) != 3 or len(init_dist) != 3: raise ValueError("Invalid input dimension") self.diagram = coxeter_diagram # Coxeter matrix and its rank self.cox_mat = helpers.get_coxeter_matrix(coxeter_diagram) self.rank = len(self.cox_mat) # generators of the symmetry group self.gens = tuple(range(self.rank)) # symmetry group of this tiling self.G = CoxeterGroup(self.cox_mat) # a mirror is active iff the initial point is not on it self.active = tuple(bool(x) for x in init_dist) # reflection mirrors self.mirrors = self.get_mirrors(coxeter_diagram) # reflections (possibly affine) about the mirrors self.reflections = self.get_reflections() # coordinates of the initial point self.init_v = self.get_init_point(init_dist) # vertices of the fundamental triangle self.triangle_verts = self.get_fundamental_triangle_verts() # ---------------------- # to be calculated later # ---------------------- # holds the words in the symmetry group up to a given depth self.words = None # holds the coset representatives of the standard parabolic # subgroup of vertex-stabilizing subgroup self.vwords = None self.vertices_coords = [] self.num_vertices = None self.num_edges = None self.num_faces = None self.edge_indices = {} self.face_indices = {} def vertex_at_mirrors(self, i, j): return 2 * (i + j) % 3 def get_init_point(self, init_dist): raise NotImplementedError def get_mirrors(self, coxeter_diagram): raise NotImplementedError def get_fundamental_triangle_verts(self): raise NotImplementedError def build_geometry(self, depth=None, maxcount=20000): """Postpone the actual computations to this method. """ self.G.init() self.word_generator = partial(self.G.traverse, depth=depth, maxcount=maxcount) self.get_vertices() self.get_edges() self.get_faces() return self def get_vertices(self): # generators of the vertex-stabilizing subgroup H = tuple(i for i, x in enumerate(self.active) if not x) # coset representatives of the vertex-stabilizing subgroup reps = set(self.word_generator(parabolic=H)) # sort the words in shortlex order self.vwords = self.G.sort_words(reps) # build the coset table for these cosets self.vtable = self.G.get_coset_table(self.vwords, H) self.num_vertices = len(self.vwords) # apply each coset representative to the initial vertex self.vertices_coords = [ self.transform(w, self.init_v) for w in self.vwords ] def get_edges(self): """ Compute the indices of the edges. Steps: 1. Use a generator to yield a list L of words in the group. 2. Compute the coset representatives of the edge stabilizing subgroup for words in L and remove duplicates. (So each remaining representative maps to different edges) 3. Apply each coset representative to the ends of an initial edge to get the transformed edge. 4. Find the indices of the resulting edge in L. """ for i in self.gens: if self.active[i]: elist = [] H = (i, ) + self.get_orthogonal_stabilizing_mirrors((i, )) reps = set(self.word_generator(parabolic=H)) reps = self.G.sort_words(reps) for word in reps: v1 = self.G.move(self.vtable, 0, word) v2 = self.G.move(self.vtable, 0, word + (i, )) if v1 is not None and v2 is not None: elist.append((v1, v2)) self.edge_indices[i] = elist self.num_edges = sum(len(L) for L in self.edge_indices.values()) def get_faces(self): """Compute the indices of the faces (and other information we will need). """ for i, j in combinations(self.gens, 2): # this is the center of the initial face, # it's a vertex of the fundamental triangle. c0 = self.triangle_verts[self.vertex_at_mirrors(i, j)] # a list holds the vertices of the initial face. f0 = [] m = self.cox_mat[i][j] # this is the stabilizing subgroup of the initial face f0. H = (i, j) + self.get_orthogonal_stabilizing_mirrors((i, j)) # type indicates if this face is regular (0) or truncated (1). # it's truncated if and only if both mirrors are active. type = 0 # compute the words (may not be in normal form) for the # vertices of the initial face if self.active[i] and self.active[j]: type = 1 for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) f0.append(self.G.move(self.vtable, 0, (i, j) * k + (i, ))) elif self.active[i] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (j, i) * k)) elif self.active[j] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) else: continue # compute coset representatives of the initial face, # each word in the result set maps f0 to a different face. reps = set(self.word_generator(parabolic=H)) # sort the faces in shortlex order. reps = self.G.sort_words(reps) # a set holds faces, we use a set here because though a word w # in H stabilizes f0, it may change cyclically rotate f0 to another # different ordered tuple. flist = [] for word in reps: # compute the indices of the vertices of the transformed face f = tuple(self.G.move(self.vtable, v, word) for v in f0) # check if `None` is in f (in this case f contains some # vertex that is not in the vertices list) or there already has # a rotated version of f in the set. if None not in f: center = self.transform(word, c0) coords = [self.vertices_coords[k] for k in f] face = DihedralFace(word, f, center, coords, type) flist.append(face) self.face_indices[(i, j)] = flist self.num_faces = sum(len(L) for L in self.face_indices.values()) def get_reflections(self): def reflect(v, normal): return v - 2 * np.dot(v, normal) * normal return [partial(reflect, normal=n) for n in self.mirrors] def transform(self, word, v): for w in reversed(word): v = self.reflections[w](v) return v def get_orthogonal_stabilizing_mirrors(self, subgens): """ :param subgens: a list of generators, e.g. [0, 1] Given a list of generators in `subgens`, return the generators that commute with all of those in `subgens` and fix the initial vertex. """ result = [] for s in self.gens: # check commutativity if all(self.cox_mat[x][s] == 2 for x in subgens): # check if it fixes v0 if not self.active[s]: result.append(s) return tuple(result) def get_info(self): """Return some statistics of the tiling. """ pattern = "{}-{}-{}".format(*self.diagram).replace("/", "|") info = "" info += "name: triangle group {}\n".format(pattern) info += "cox_mat: {}\n".format(self.cox_mat) info += "vertices: {}\n".format(self.num_vertices) info += "edges: {}\n".format(self.num_edges) info += "faces: {}\n".format(self.num_faces) info += "states in the automaton: {}\n".format(self.G.dfa.num_states) info += "reflection table:\n{}\n".format(self.G.reftable) info += "the automaton is saved as {}_dfa.png".format(pattern) self.G.dfa.draw(pattern + "_dfa.png") return info def render(self, *arg, **kwargs): raise NotImplementedError
class UniformTiling(object): def __init__(self, coxeter_diagram, init_dist): self.cox_mat = helpers.get_coxeter_matrix(coxeter_diagram) self.G = CoxeterGroup(self.cox_mat) self.active = tuple(bool(x) for x in init_dist) self.words = None self.mirrors = self.get_mirrors(coxeter_diagram) self.init_v = helpers.get_point_from_distance(self.mirrors, init_dist) self.reflections = self.get_reflections(init_dist) # to be calculated later self.vertices_coords = [] self.num_vertices = None self.num_edges = None self.num_faces = None self.edge_indices = {} self.face_indices = {} def build_geometry(self, depth=None, maxcount=20000): self.G.init() self.words = tuple(self.G.traverse(depth, maxcount)) self.get_vertices() self.get_edges() self.get_faces() def get_vertices(self): parabolic = tuple(i for i, x in enumerate(self.active) if not x) coset_reps = set([ self.G.get_coset_representative(w, parabolic, True) for w in self.words ]) self.vwords = self.G.sort_words(coset_reps) self.vtable = self.G.get_coset_table(self.vwords, parabolic) self.num_vertices = len(self.vwords) self.vertices_coords = [ self.transform(word, self.init_v) for word in self.vwords ] def get_edges(self): for i in range(len(self.active)): if self.active[i]: elist = [] coset_reps = set([ self.G.get_coset_representative(w, (i, ), True) for w in self.words ]) for word in self.G.sort_words(coset_reps): v1 = self.G.move(self.vtable, 0, word) v2 = self.G.move(self.vtable, 0, word + (i, )) if v1 is not None and v2 is not None: if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices[i] = elist self.num_edges = sum(len(L) for L in self.edge_indices.values()) def get_faces(self): for i, j in combinations(range(len(self.active)), 2): f0 = [] m = self.cox_mat[i][j] parabolic = (i, j) if self.active[i] and self.active[j]: for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) f0.append(self.G.move(self.vtable, 0, (i, j) * k + (i, ))) elif self.active[i] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (j, i) * k)) elif self.active[j] and m > 2: for k in range(m): f0.append(self.G.move(self.vtable, 0, (i, j) * k)) else: continue coset_reps = set([ self.G.get_coset_representative(w, parabolic, True) for w in self.words ]) flist = [] for word in self.G.sort_words(coset_reps): f = tuple(self.G.move(self.vtable, v, word) for v in f0) if None not in f and not helpers.check_duplicate_face( f, flist): flist.append(f) self.face_indices[(i, j)] = flist self.num_faces = sum(len(L) for L in self.face_indices.values()) def transform(self, word, v): for w in reversed(word): v = self.reflections[w](v) return v def get_reflections(self, init_dist): raise NotImplementedError def get_fundamental_triangle_vertices(self): raise NotImplementedError def project(self, v): raise NotImplementedError def get_mirrors(self, coxeter_diagram): raise NotImplementedError