def get_faces(self): for i, j in combinations(self.symmetry_gens, 2): f0 = [] # the base face that contains the initial vertex if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) f0.append(self._move(0, (j, ) + (i, j) * k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (i, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self._move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices])
def get_edges(self): """ If the initial vertex `v0` lies on the `i`-th mirror then the reflection about this mirror fixes `v0` and there are no edges of type `i`. Else `v0` and its mirror image about this mirror generates a base edge of type `i`, its stabilizing subgroup is generated by the word `(i,)` and we can again use Todd-Coxeter's procedure to get the word representaions for all edges of type `i`. """ for i, active in enumerate(self.active): if active: # generator for the stabilizing subgroup of the base edge of type `i` egens = [(i,)] # build the coset table etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) # run the table etable.run() # get word representaions for the cosets words = etable.get_words() # store all edges of type `i` in a list elist = [] for word in words: # two ends of this edge v1 = self._move(0, word) v2 = self._move(0, (i,) + word) # avoid duplicates, this is because though the reflection `i` fixes # this edge but it maps the ordered tupe (v1, v2) to (v2, v1) if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices])
def get_edges(self): """ If the initial vertex lies on the i-th mirror then the reflection about this mirror fixes v0 and there are no edges of type i. Else v0 and its virtual image v1 about this mirror generates a base edge e, the stabilizing subgroup of e is generated by a single word (i,), so again we can use Todd-Coxeter's procedure to get a complete list of word representations for the edges of type i and use them to transform e to other edges. """ for i, active in enumerate(self.active): if active: # if there are edges of type i egens = [ (i, ) ] # generators of the stabilizing subgroup that fixes the base edge. etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words( ) # get word representations of the edges elist = [] for word in words: # one end by transform the initial vertex v1 = self.move(0, word) # the other end by transform the virtual image of the initial vertex v2 = self.move(0, (i, ) + word) # avoid duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices])
def get_vertices(self): """ This method computes the following data that will be needed later: 1. Coset table for the total symmetry group. (so for a vertex indexed by `i` and a word `w` we can get the index of the transformed vertex `i·w`) 2. Word representaions for each element in the symmetry group. (for exporting the words to latex format) 3. Coordinates of the vertices. (obviously we will need this) """ # generators for the stabilizing subgroup of the initial vertex. vgens = [(i, ) for i, active in enumerate(self.active) if not active] # build the coset table self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) # run the table self._vtable.run() # get word representaions for the cosets self._vwords = self._vtable.get_words() # number the vertices self.num_vertices = len(self._vwords) # use the word representaions to transform the initial vertex to other vertices self.vertex_coords = tuple( self._transform(self.init_v, word) for word in self._vwords)
def __init__(self, relators, subgens=None, name=None): """ relators: relations between the generators as a 1D list of strings, e.g. ["aaa", "bb", "abab"]. subgens: generators of the subgroup as a 1D list of strings, e.g. ["ab", "Ab"] name: decriptive name of the group, e.g. "S3", "D8", ..., etc. """ if not name: name = self.__class__.__name__ self.name = name self.relators = relators self.generators = get_symbols(relators) if len(self.generators) < 2: raise ValueError("Not enough generators") if not subgens: subgens = [] self.subgens = subgens # initialize the coset table gens = tuple(range(2 * len(self.generators))) relators += tuple(c + c.upper() for c in self.generators) rels = word2int(self.generators, relators) subgens = word2int(self.generators, self.subgens) self.coset_table = CosetTable(gens, rels, subgens, coxeter=False)
class FpGroup(object): """ Finitely presented group by defining relations between its generators. """ def __init__(self, relators, subgens=None, name=None): """ :param relators: relations between the generators as a 1D list of strings, e.g. ["aaa", "bb", "abab"]. :param subgens: generators of the subgroup as a 1D list of strings, e.g. ["ab", "Ab"] :param name: decriptive name of the group, e.g. "S3", "D8", ..., etc. """ if not name: name = self.__class__.__name__ self.name = name self.relators = relators self.generators = get_symbols(relators) if len(self.generators) < 2: raise ValueError("Not enough generators") if not subgens: subgens = [] self.subgens = subgens # initialize the coset table gens = tuple(range(2 * len(self.generators))) relators += tuple(c + c.upper() for c in self.generators) rels = word2int(self.generators, relators) subgens = word2int(self.generators, self.subgens) self.coset_table = CosetTable(gens, rels, subgens, coxeter=False) def __str__(self): s = "\nName: {}\n".format(self.name) s += "Generators: " + ", ".join(self.generators) s += "\nRelators: " + ", ".join(self.relators) s += "\nSubgroup generators: " + ", ".join(self.subgens) return s def compute(self, standard=True): self.coset_table.run(standard) def print_table(self, outfile): """pretty print the table. """ f = sys.stdout if outfile is None else open(outfile, "w") f.write(" ") for x in self.generators: f.write("{:>5s}".format(x)) f.write("{:>5s}".format(x.upper())) f.write("\n" + (2 * len(self.generators) + 2) * 5 * "-" + "\n") for i, row in enumerate(self.coset_table, start=1): f.write("{:>5d}: ".format(i)) for x in row: f.write("{:>5d}".format(x + 1)) f.write("\n") f.close()
def get_vertices(self): self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, coxeter=False) self.vtable.run() self.vwords = self.vtable.get_words() self.num_vertices = len(self.vwords) self.vertex_coords = tuple( self.transform(self.init_v, w) for w in self.vwords)
def get_vertices(self): # generators for the stabilizing subgroup of the initial vertex. vgens = [(i, ) for i, active in enumerate(self.active) if not active] self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self._vtable.run() self._vwords = self._vtable.get_words() self.num_vertices = len(self._vwords) self.vertex_coords = tuple( self._transform(self.init_v, word) for word in self._vwords)
def get_faces(self): """ Basically speaking, for a pair (i, j), the composition of the i-th and the j-th reflection is a rotation which fixes a base face `f0` of type `ij`. But there are some cases need to be considered: 1. The i-th and the j-th mirror are both active: in this case the rotation indeed generates a face `f0` and edges of type `i` and type `j` occur alternatively in it. 2. Exactly one of the i-th and the j-th mirror is active: in this case we should check whether their reflections commute or not: the rotation generates a face `f0` only when these two reflections do not commute, and `f0` contains edges of only one type. 3. Neither of them is active: in this case there are no faces. """ for i, j in combinations(self.symmetry_gens, 2): # the base face that contains the initial vertex f0 = [] if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) f0.append(self._move(0, (j, ) + (i, j) * k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (i, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self._move(v, word) for v in f0) # the rotation fixes this face but it shifts the ordered tuple of vertices # rotationally, so we need to remove the duplicates. if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices])
def get_vertices(self): """ This method computes the following data that will be needed later: 1. a coset table for the symmetry group. 2. a complete list of word representations of the symmetry group. 3. coordinates of the vertices. """ # generators of the stabilizing subgroup that fixes the initial vertex. vgens = [(i,) for i, active in enumerate(self.active) if not active] self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self.vtable.run() self.vwords = self.vtable.get_words() # get word representations of the vertices self.num_vertices = len(self.vwords) # use words in the symmetry group to transform the initial vertex to other vertices. self.vertex_coords = tuple(self.transform(self.init_v, w) for w in self.vwords)
def get_faces(self): """ This method computes the indices and coordinates of all faces. The composition of the i-th and the j-th reflection is a rotation which fixes a base face f. The stabilizing group of f is generated by (i, j) or [(i,), (j,)] depends on whether the two mirrors are both active or exactly one of them is active and the they are not perpendicular to each other. """ for i, j in combinations(self.symmetry_gens, 2): f0 = [] m = self.coxeter_matrix[i][j] if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(m): # rotate the base edge m times to get the base f, # where m = self.coxeter_matrix[i][j] f0.append(self.move(0, (i, j) * k)) f0.append(self.move(0, (j,) + (i, j) * k)) elif self.active[i] and m > 2: fgens = [(i,), (j,)] for k in range(m): f0.append(self.move(0, (i, j) * k)) elif self.active[j] and m > 2: fgens = [(i,), (j,)] for k in range(m): f0.append(self.move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self.move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append([tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices])
def get_edges(self): for i, active in enumerate(self.active): if active: egens = [(i, )] etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words() elist = [] for word in words: # two ends of this edge v1 = self._move(0, word) v2 = self._move(0, (i, ) + word) # remove duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices])
def build(self): gens = list(range(8)) rels = [[0, 2] * self.p, [2, 4] * self.q, [4, 6] * self.r, [0, 4] * 2, [2, 6] * 2, [0, 6] * 2, [0, 0], [2, 2], [4, 4], [6, 6], [0, 1], [2, 3], [4, 5], [6, 7]] # 1. compute the three coset tables self.Vtable = CosetTable(gens, rels, [[2], [4], [6]]) self.Vtable.run() self.Etable = CosetTable(gens, rels, [[0], [4], [6]]) self.Etable.run() self.Ftable = CosetTable(gens, rels, [[0], [2], [6]]) self.Ftable.run() # 2. get the words of these cosets self.Vwords = self.get_words(self.Vtable) self.Ewords = self.get_words(self.Etable) self.Fwords = self.get_words(self.Ftable) # 3. get the matrices of the reflections M = get_mirrors(self.p, self.q, self.r) self.reflectors = [reflection_matrix(v) for v in M] # 4. use Gram-Schimdt to find an initial vertex init_v = gram_schimdt(M[::-1])[-1] init_v /= np.linalg.norm(init_v) # 5. compute the vertex_indices and vertex_coords lists v0 = 0 self.vertex_indices = tuple(range(len(self.Vtable))) self.vertex_coords = [ self.transform(init_v, word) for word in self.Vwords ] # 6. compute the edge_indices and edge_coords lists v1 = self.move(v0, (0, )) # the other end of an edge from v0 e0 = (v0, v1) # an initial edge self.edge_indices = [[self.move(v, word) for v in e0] for word in self.Ewords] self.edge_coords = [] for i, j in self.edge_indices: start = self.vertex_coords[i] end = self.vertex_coords[j] self.edge_coords.append((start, end)) # 7. compute the face_indices and face_coords lists f0 = tuple(self.move(v0, [0, 2] * i) for i in range(self.p)) self.face_indices = [[self.move(v, word) for v in f0] for word in self.Fwords] self.face_coords = [] for face in self.face_indices: self.face_coords.append(tuple(self.vertex_coords[i] for i in face))
class BasePolytope(object): """ Base class for building polyhedron and polychoron using Wythoff's construction. """ def __init__(self, coxeter_diagram, init_dist, extra_relations=()): """ parameters ---------- :coxeter_diagram: Coxeter diagram for this polytope. :init_dist: distances between the initial vertex and the mirrors. :extra_relations: a presentation of a star polytope can be obtained by imposing more relations on the generators. For example "(ρ0ρ1ρ2ρ1)^n = 1" for some integer n, where n is the number of sides of a hole. See Coxeter's article "Regular skew polyhedra in three and four dimensions, and their topological analogues" """ # Coxeter matrix of the symmetry group self.coxeter_matrix = helpers.get_coxeter_matrix(coxeter_diagram) self.mirrors = helpers.get_mirrors(coxeter_diagram) # reflection transformations about the mirrors self.reflections = tuple(helpers.reflection_matrix(v) for v in self.mirrors) # the initial vertex self.init_v = helpers.get_init_point(self.mirrors, init_dist) # a mirror is active if and only if the initial vertex is off it self.active = tuple(bool(x) for x in init_dist) # generators of the symmetry group self.symmetry_gens = tuple(range(len(self.coxeter_matrix))) # relations between the generators self.symmetry_rels = tuple((i, j) * self.coxeter_matrix[i][j] for i, j in combinations(self.symmetry_gens, 2)) self.symmetry_rels += tuple(extra_relations) # to be calculated later self.vtable = None self.num_vertices = None self.vertex_coords = [] self.num_edges = None self.edge_indices = [] self.edge_coords = [] self.num_faces = None self.face_indices = [] self.face_coords = [] def build_geometry(self): self.get_vertices() self.get_edges() self.get_faces() def get_vertices(self): """ This method computes the following data that will be needed later: 1. a coset table for the symmetry group. 2. a complete list of word representations of the symmetry group. 3. coordinates of the vertices. """ # generators of the stabilizing subgroup that fixes the initial vertex. vgens = [(i,) for i, active in enumerate(self.active) if not active] self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self.vtable.run() self.vwords = self.vtable.get_words() # get word representations of the vertices self.num_vertices = len(self.vwords) # use words in the symmetry group to transform the initial vertex to other vertices. self.vertex_coords = tuple(self.transform(self.init_v, w) for w in self.vwords) def get_edges(self): """ This method computes the indices and coordinates of all edges. 1. if the initial vertex lies on the i-th mirror then the reflection about this mirror fixes v0 and there are no edges of type i. 2. else v0 and its virtual image v1 about this mirror generates a base edge e, the stabilizing subgroup of e is generated by a single word (i,), again we use Todd-Coxeter's procedure to get a complete list of word representations for the edges of type i and use them to transform e to other edges. """ for i, active in enumerate(self.active): if active: # if there are edges of type i egens = [(i,)] # generators of the stabilizing subgroup that fixes the base edge. etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words() # get word representations of the edges elist = [] for word in words: v1 = self.move(0, word) v2 = self.move(0, (i,) + word) # avoid duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum(len(elist) for elist in self.edge_indices) def get_faces(self): """ This method computes the indices and coordinates of all faces. The composition of the i-th and the j-th reflection is a rotation which fixes a base face f. The stabilizing group of f is generated by [(i,), (j,)]. """ for i, j in combinations(self.symmetry_gens, 2): f0 = [] m = self.coxeter_matrix[i][j] fgens = [(i,), (j,)] if self.active[i] and self.active[j]: for k in range(m): # rotate the base edge m times to get the base f, # where m = self.coxeter_matrix[i][j] f0.append(self.move(0, (i, j) * k)) f0.append(self.move(0, (j,) + (i, j) * k)) elif (self.active[i] or self.active[j]) and m > 2: for k in range(m): f0.append(self.move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self.move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append([tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum(len(flist) for flist in self.face_indices) def transform(self, vector, word): """Transform a vector by a word in the symmetry group. """ for w in word: vector = np.dot(vector, self.reflections[w]) return vector def move(self, vertex, word): """Transform a vertex by a word in the symmetry group. Return the index of the resulting vertex. """ for w in word: vertex = self.vtable[vertex][w] return vertex def get_latex_format(self, symbol=r"\rho", cols=3, snub=False): """Return the words of the vertices in latex format. `cols` is the number of columns of the output latex array. """ def to_latex(word): if not word: return "e" else: if snub: return "".join(symbol + "_{{{}}}".format(i // 2) for i in word) else: return "".join(symbol + "_{{{}}}".format(i) for i in word) latex = "" for i, word in enumerate(self.vwords): if i > 0 and i % cols == 0: latex += r"\\" latex += to_latex(word) if i % cols != cols - 1: latex += "&" return r"\begin{{array}}{{{}}}{}\end{{array}}".format("l" * cols, latex)
class Snub(Polyhedra): def __init__(self, upper_triangle, weights): if not all([x > 0 for x in weights]): raise ValueError("the weights must all be positive") super().__init__(upper_triangle, weights) self.symmetry_gens = (0, 1, 2, 3) self.symmetry_rels = ((0, ) * self.coxeter_matrix[0][1], (2, ) * self.coxeter_matrix[1][2], (0, 2) * self.coxeter_matrix[0][2], (0, 1), (2, 3)) self.rotations = { (0, ): self.coxeter_matrix[0][1], (2, ): self.coxeter_matrix[1][2], (0, 2): self.coxeter_matrix[0][2] } def get_vertices(self): self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, coxeter=False) self._vtable.run() self._vwords = self._vtable.get_words() self.num_vertices = len(self._vwords) self.vertex_coords = tuple( self._transform(self.init_v, w) for w in self._vwords) def get_edges(self): for rot in self.rotations: elist = [] e0 = (0, self._move(0, rot)) for word in self._vwords: e = tuple(self._move(v, word) for v in e0) if e not in elist and e[::-1] not in elist: elist.append(e) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[i], self.vertex_coords[j]) for i, j in elist]) self.num_edges = sum(len(elist) for elist in self.edge_indices) def get_faces(self): orbits = (tuple( self._move(0, (0, ) * k) for k in range(self.rotations[(0, )])), tuple( self._move(0, (2, ) * k) for k in range(self.rotations[(2, )])), (0, self._move(0, (2, )), self._move(0, (0, 2)))) for f0 in orbits: flist = [] for word in self._vwords: f = tuple(self._move(v, word) for v in f0) if not check_face_in(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[v] for v in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def _transform(self, vertex, word): for g in word: if g == 0: vertex = np.dot(vertex, self._reflections[0]) vertex = np.dot(vertex, self._reflections[1]) else: vertex = np.dot(vertex, self._reflections[1]) vertex = np.dot(vertex, self._reflections[2]) return vertex
class Snub(Polyhedra): """ A snub polyhedra is generated by the subgroup that consists of only rotations in the full symmetry group. This subgroup has presentation <r, s | r^p = s^q = (rs)^2 = 1> where r = ρ0ρ1, s = ρ1ρ2 are two rotations. Again we solve all words in this subgroup and then use them to transform the initial vertex to get all vertices. """ def __init__(self, coxeter_matrix, mirrors, init_dist=(1.0, 1.0, 1.0)): super().__init__(coxeter_matrix, mirrors, init_dist) # the representaion is not in the form of a Coxeter group, # we must overwrite the relations. self.symmetry_gens = (0, 1, 2, 3) self.symmetry_rels = ((0, ) * self.coxeter_matrix[0][1], (2, ) * self.coxeter_matrix[1][2], (0, 2) * self.coxeter_matrix[0][2], (0, 1), (2, 3)) # order of the generator rotations self.rotations = { (0, ): self.coxeter_matrix[0][1], (2, ): self.coxeter_matrix[1][2], (0, 2): self.coxeter_matrix[0][2] } def get_vertices(self): self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, coxeter=False) self.vtable.run() self.vwords = self.vtable.get_words() self.num_vertices = len(self.vwords) self.vertex_coords = tuple( self.transform(self.init_v, w) for w in self.vwords) def get_edges(self): for rot in self.rotations: elist = [] e0 = (0, self.move(0, rot)) for word in self.vwords: e = tuple(self.move(v, word) for v in e0) if e not in elist and e[::-1] not in elist: elist.append(e) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[i], self.vertex_coords[j]) for i, j in elist]) self.num_edges = sum(len(elist) for elist in self.edge_indices) def get_faces(self): orbits = (tuple( self.move(0, (0, ) * k) for k in range(self.rotations[(0, )])), tuple( self.move(0, (2, ) * k) for k in range(self.rotations[(2, )])), (0, self.move(0, (2, )), self.move(0, (0, 2)))) for f0 in orbits: flist = [] for word in self.vwords: f = tuple(self.move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[v] for v in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def transform(self, vertex, word): for g in word: if g == 0: vertex = np.dot(vertex, self.reflections[0]) vertex = np.dot(vertex, self.reflections[1]) else: vertex = np.dot(vertex, self.reflections[1]) vertex = np.dot(vertex, self.reflections[2]) return vertex
class BasePolytope(object): """ Base class for building polyhedron and polychoron using Wythoff's construction. A presentation of the star polyhedra can be obtained by imposing more relations on the generators. For example "(ρ0ρ1ρ2ρ1)^h = 1" for some integer h. """ def __init__(self, coxeter_matrix, mirrors, init_dist, extra_relations): """ params: `coxeter_matrix`: Coxeter matrix of the symmetry group. `mirrors`: normal vectors of the mirrors. `init_dist`: the distances between the initial point and the mirrors. `extra_relations`: extra relations to be imposed on the generators. """ self.coxeter_matrix = coxeter_matrix self.reflections = tuple(helpers.reflection_matrix(v) for v in mirrors) self.init_v = helpers.get_init_point(mirrors, init_dist) self.active = tuple(bool(x) for x in init_dist) # generators of the symmetry group self.symmetry_gens = tuple(range(len(coxeter_matrix))) # relations between the generators self.symmetry_rels = tuple( (i, j) * self.coxeter_matrix[i][j] for i, j in combinations(self.symmetry_gens, 2)) self.symmetry_rels += extra_relations # to be calculated later self.vtable = None self.num_vertices = None self.vertex_coords = [] self.num_edges = None self.edge_indices = [] self.edge_coords = [] self.num_faces = None self.face_indices = [] self.face_coords = [] def build_geometry(self): self.get_vertices() self.get_edges() self.get_faces() def get_vertices(self): """ This method computes the following data that will be needed later: 1. a coset table for the symmetry group. 2. a complete list of word representations of the symmetry group. 3. coordinates of the vertices. """ # generators of the stabilizing subgroup that fixes the initial vertex. vgens = [(i, ) for i, active in enumerate(self.active) if not active] self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self.vtable.run() self.vwords = self.vtable.get_words( ) # get word representations of the vertices self.num_vertices = len(self.vwords) # use words in the symmetry group to transform the initial vertex to other vertices. self.vertex_coords = tuple( self.transform(self.init_v, w) for w in self.vwords) def get_edges(self): """ If the initial vertex lies on the i-th mirror then the reflection about this mirror fixes v0 and there are no edges of type i. Else v0 and its virtual image v1 about this mirror generates a base edge e, the stabilizing subgroup of e is generated by a single word (i,), so again we can use Todd-Coxeter's procedure to get a complete list of word representations for the edges of type i and use them to transform e to other edges. """ for i, active in enumerate(self.active): if active: # if there are edges of type i egens = [ (i, ) ] # generators of the stabilizing subgroup that fixes the base edge. etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words( ) # get word representations of the edges elist = [] for word in words: # one end by transform the initial vertex v1 = self.move(0, word) # the other end by transform the virtual image of the initial vertex v2 = self.move(0, (i, ) + word) # avoid duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices]) def get_faces(self): """ The composition of the i-th and the j-th reflection is a rotation which fixes a base face f of type ij. The stabilizing group of f is generated by (i, j) or [(i,), (j,)] depends on whether the two mirrors are both active or exactly one of them is active and the they are not perpendicular to each other. """ for i, j in combinations(self.symmetry_gens, 2): f0 = [] if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): # rotate the base edge m times to get the base f, # where m = self.coxeter_matrix[i][j] f0.append(self.move(0, (i, j) * k)) f0.append(self.move(0, (j, ) + (i, j) * k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, ), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self.move(0, (i, j) * k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, ), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self.move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self.move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def transform(self, vector, word): """Transform a vector by a word in the symmetry group.""" for w in word: vector = np.dot(vector, self.reflections[w]) return vector def move(self, vertex, word): """ Transform a vertex by a word in the symmetry group. Return the index of the resulting vertex. """ for w in word: vertex = self.vtable[vertex][w] return vertex def export_pov(self, filename): raise NotImplementedError def get_latex_format(self, symbol=r"\rho", cols=3, snub=False): """ Return the words of the vertices in latex format. `cols` is the number of columns in the array. """ def to_latex(word): if not word: return "e" else: if snub: return "".join(symbol + "_{{{}}}".format(i // 2) for i in word) else: return "".join(symbol + "_{{{}}}".format(i) for i in word) latex = "" for i, word in enumerate(self.vwords): if i > 0 and i % cols == 0: latex += r"\\" latex += to_latex(word) if i % cols != cols - 1: latex += "&" return r"\begin{{array}}{{{}}}{}\end{{array}}".format( "l" * cols, latex)
class Polytope(object): """ A 3D polytope has Coxeter diagram * -- p -- * -- q -- *, A 4D polytope has Coxeter diagram * -- p -- * -- q -- * -- r -- *, The first case can be unified into the second one by viewing it as * -- p -- * -- q -- * -- 2 -- *. """ shapes = { "tetrahedron": (3, 3, 2), "cube": (4, 3, 2), "octahedron": (3, 4, 2), "dodecahedron": (5, 3, 2), "icosahedron": (3, 5, 2), "5-cell": (3, 3, 3), "8-cell": (4, 3, 3), "16-cell": (3, 3, 4), "24-cell": (3, 4, 3), "120-cell": (5, 3, 3), "600-cell": (3, 3, 5) } def __init__(self, name): p, q, r = Polytope.shapes[name] self.p = p self.q = q self.r = r def build(self): gens = list(range(8)) rels = [[0, 2] * self.p, [2, 4] * self.q, [4, 6] * self.r, [0, 4] * 2, [2, 6] * 2, [0, 6] * 2, [0, 0], [2, 2], [4, 4], [6, 6], [0, 1], [2, 3], [4, 5], [6, 7]] # 1. compute the three coset tables self.Vtable = CosetTable(gens, rels, [[2], [4], [6]]) self.Vtable.run() self.Etable = CosetTable(gens, rels, [[0], [4], [6]]) self.Etable.run() self.Ftable = CosetTable(gens, rels, [[0], [2], [6]]) self.Ftable.run() # 2. get the words of these cosets self.Vwords = self.get_words(self.Vtable) self.Ewords = self.get_words(self.Etable) self.Fwords = self.get_words(self.Ftable) # 3. get the matrices of the reflections M = get_mirrors(self.p, self.q, self.r) self.reflectors = [reflection_matrix(v) for v in M] # 4. use Gram-Schimdt to find an initial vertex init_v = gram_schimdt(M[::-1])[-1] init_v /= np.linalg.norm(init_v) # 5. compute the vertex_indices and vertex_coords lists v0 = 0 self.vertex_indices = tuple(range(len(self.Vtable))) self.vertex_coords = [ self.transform(init_v, word) for word in self.Vwords ] # 6. compute the edge_indices and edge_coords lists v1 = self.move(v0, (0, )) # the other end of an edge from v0 e0 = (v0, v1) # an initial edge self.edge_indices = [[self.move(v, word) for v in e0] for word in self.Ewords] self.edge_coords = [] for i, j in self.edge_indices: start = self.vertex_coords[i] end = self.vertex_coords[j] self.edge_coords.append((start, end)) # 7. compute the face_indices and face_coords lists f0 = tuple(self.move(v0, [0, 2] * i) for i in range(self.p)) self.face_indices = [[self.move(v, word) for v in f0] for word in self.Fwords] self.face_coords = [] for face in self.face_indices: self.face_coords.append(tuple(self.vertex_coords[i] for i in face)) def transform(self, v, word): """Compute the coordinates of `v` transformed by `word`.""" for w in word: v = np.dot(v, self.reflectors[w // 2]) return v def move(self, coset, word): """Compute the result of `coset` multiplied by `word`.""" for w in word: coset = self.Vtable[coset][w] return coset @staticmethod def get_words(T): """ Return the list of words that represent the cosets in a coset table `T`. """ result = [None] * len(T) result[0] = tuple() q = [0] while len(q) > 0: coset = q.pop() for x in T.A[::2]: new_coset = T[coset][x] if result[new_coset] is None: result[new_coset] = result[coset] + (x, ) q.append(new_coset) return result def export_graph(self): """Export the graph of this polytope.""" with open("polytope-graph.txt", "w") as f: f.write("Vertices: {}\n".format(len(self.Vtable))) f.write("Edges: {}\n".format(len(self.Etable))) f.write("Faces: {}\n".format(len(self.Ftable))) for i, j in self.edge_indices: f.write("({}, {})\n".format(i, j)) for face in self.face_indices: f.write("(" + ", ".join([str(i) for i in face]) + ")\n") def write_to_pov(self): with open("./povray/polytope-data.inc", "w") as f: for v in self.vertex_coords: f.write("Vertex(<{}, {}, {}, {}>)\n".format(*v)) for u, v in self.edge_coords: f.write("Arc(<{}, {}, {}, {}>, <{}, {}, {}, {}>)\n".format( *np.concatenate((u, v))))
class BasePolytope(object): """ Base class for 3d polyhedra and 4d polychora using Wythoff's construction. """ def __init__(self, upper_triangle, init_dist): # the Coxeter matrix self.coxeter_matrix = helpers.get_coxeter_matrix(upper_triangle) # the reflecting mirrors self._mirrors = helpers.get_mirrors(upper_triangle) # reflection transformations about the mirrors self._reflections = tuple( helpers.reflection_matrix(v) for v in self._mirrors) # coordinates of the initial vertex self.init_v = helpers.get_init_point(self._mirrors, init_dist) # a bool list holds if a mirror is active or not. self.active = tuple(bool(x) for x in init_dist) dim = len(self.coxeter_matrix) # generators of the Coxeter group self.symmetry_gens = tuple(range(dim)) # relations between the generators self.symmetry_rels = tuple( (i, j) * self.coxeter_matrix[i][j] for i, j in combinations(self.symmetry_gens, 2)) # to be calculated later self._vtable = None self.num_vertices = None self.vertex_coords = [] self.num_edges = None self.edge_indices = [] self.edge_coords = [] self.num_faces = None self.face_indices = [] self.face_coords = [] def build_geometry(self): self.get_vertices() self.get_edges() self.get_faces() def get_vertices(self): # generators for the stabilizing subgroup of the initial vertex. vgens = [(i, ) for i, active in enumerate(self.active) if not active] self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self._vtable.run() self._vwords = self._vtable.get_words() self.num_vertices = len(self._vwords) self.vertex_coords = tuple( self._transform(self.init_v, word) for word in self._vwords) def get_edges(self): for i, active in enumerate(self.active): if active: egens = [(i, )] etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words() elist = [] for word in words: # two ends of this edge v1 = self._move(0, word) v2 = self._move(0, (i, ) + word) # remove duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices]) def get_faces(self): for i, j in combinations(self.symmetry_gens, 2): f0 = [] # the base face that contains the initial vertex if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) f0.append(self._move(0, (j, ) + (i, j) * k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (i, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self._move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def _transform(self, vector, word): """Transform a vector by a word in the symmetry group.""" for w in word: vector = np.dot(vector, self._reflections[w]) return vector def _move(self, vertex, word): """ Transform a vertex by a word in the symmetry group. Return the index of the resulting vertex. """ for w in word: vertex = self._vtable[vertex][w] return vertex def export_pov(self, filename): raise NotImplementedError def get_word_representations(self, symbol=r"\rho", cols=3, snub=False): """ Return the words corresponding to the vertices in latex format. `cols` is the number of columns in the array. """ def to_latex(word): if not word: return "e" else: if snub: return "".join(symbol + "_{{{}}}".format(i // 2) for i in word) else: return "".join(symbol + "_{{{}}}".format(i) for i in word) latex = "" for i, word in enumerate(self._vwords): if i > 0 and i % cols == 0: latex += r"\\" latex += to_latex(word) if i % cols != cols - 1: latex += "&" return r"\begin{{array}}{{{}}}{}\end{{array}}".format( "l" * cols, latex)
class BasePolytope(object): def __init__(self, upper_triangle, init_dist): # the Coxeter matrix self.coxeter_matrix = helpers.get_coxeter_matrix(upper_triangle) # the reflecting mirrors self._mirrors = helpers.get_mirrors(self.coxeter_matrix) # reflection transformations about the mirrors self._reflections = tuple( helpers.reflection_matrix(v) for v in self._mirrors) # coordinates of the initial vertex self.init_v = helpers.get_init_point(self._mirrors, init_dist) # a bool list holds if a mirror is active or not. self.active = tuple(bool(x) for x in init_dist) dim = len(self.coxeter_matrix) # generators of the Coxeter group self.symmetry_gens = tuple(range(dim)) # relations between the generators self.symmetry_rels = tuple( (i, j) * self.coxeter_matrix[i][j] for i, j in combinations(self.symmetry_gens, 2)) # to be calculated later self._vtable = None self.num_vertices = None self.vertex_coords = [] self.num_edges = None self.edge_indices = [] self.edge_coords = [] self.num_faces = None self.face_indices = [] self.face_coords = [] def build_geometry(self, edge=True, face=True): self.get_vertices() if edge: self.get_edges() if face: self.get_faces() def get_vertices(self): # generators for the stabilizing subgroup of the initial vertex. vgens = [(i, ) for i, active in enumerate(self.active) if not active] self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) self._vtable.run() words = self._vtable.get_words() self.num_vertices = len(words) self.vertex_coords = tuple( self._transform(self.init_v, w) for w in words) def get_edges(self): for i, active in enumerate(self.active): if active: egens = [(i, )] etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) etable.run() words = etable.get_words() elist = [] for word in words: # two ends of this edge v1 = self._move(0, word) v2 = self._move(0, (i, ) + word) # remove duplicates if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices]) def get_faces(self): for i, j in combinations(self.symmetry_gens, 2): f0 = [] # the base face that contains the initial vertex if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) f0.append(self._move(0, (j, ) + (i, j) * k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (i, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (j, )] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j) * k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for w in words: f = tuple(self._move(v, w) for v in f0) if not check_face_in(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def _transform(self, vector, word): """Transform a vector by a word in the symmetry group.""" for w in word: vector = np.dot(vector, self._reflections[w]) return vector def _move(self, vertex, word): """ Transform a vertex by a word in the symmetry group. Return the index of the resulting vertex. """ for w in word: vertex = self._vtable[vertex][w] return vertex def export_pov(self, filename): raise NotImplementedError
class BasePolytope(object): """ Base class for 3d polyhedra and 4d polychora using Wythoff's construction. """ def __init__(self, upper_triangle, init_dist): # the Coxeter matrix self.coxeter_matrix = helpers.get_coxeter_matrix(upper_triangle) # the reflection mirrors self._mirrors = helpers.get_mirrors(upper_triangle) # reflection transformations about the mirrors self._reflections = tuple(helpers.reflection_matrix(v) for v in self._mirrors) # coordinates of the initial vertex self.init_v = helpers.get_init_point(self._mirrors, init_dist) # a bool list holds if a mirror is active or not. self.active = tuple(bool(x) for x in init_dist) dim = len(self.coxeter_matrix) # generators of the Coxeter group self.symmetry_gens = tuple(range(dim)) # relations between the generators self.symmetry_rels = tuple((i, j) * self.coxeter_matrix[i][j] for i, j in combinations(self.symmetry_gens, 2)) # to be calculated later self._vtable = None self.num_vertices = None self.vertex_coords = [] self.num_edges = None self.edge_indices = [] self.edge_coords = [] self.num_faces = None self.face_indices = [] self.face_coords = [] def build_geometry(self): self.get_vertices() self.get_edges() self.get_faces() def get_vertices(self): """ This method computes the following data that will be needed later: 1. Coset table for the total symmetry group. (so for a vertex indexed by `i` and a word `w` we can get the index of the transformed vertex `i·w`) 2. Word representaions for each element in the symmetry group. (for exporting the words to latex format) 3. Coordinates of the vertices. (obviously we will need this) """ # generators for the stabilizing subgroup of the initial vertex. vgens = [(i,) for i, active in enumerate(self.active) if not active] # build the coset table self._vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, vgens) # run the table self._vtable.run() # get word representaions for the cosets self._vwords = self._vtable.get_words() # number the vertices self.num_vertices = len(self._vwords) # use the word representaions to transform the initial vertex to other vertices self.vertex_coords = tuple(self._transform(self.init_v, word) for word in self._vwords) def get_edges(self): """ If the initial vertex `v0` lies on the `i`-th mirror then the reflection about this mirror fixes `v0` and there are no edges of type `i`. Else `v0` and its mirror image about this mirror generates a base edge of type `i`, its stabilizing subgroup is generated by the word `(i,)` and we can again use Todd-Coxeter's procedure to get the word representaions for all edges of type `i`. """ for i, active in enumerate(self.active): if active: # generator for the stabilizing subgroup of the base edge of type `i` egens = [(i,)] # build the coset table etable = CosetTable(self.symmetry_gens, self.symmetry_rels, egens) # run the table etable.run() # get word representaions for the cosets words = etable.get_words() # store all edges of type `i` in a list elist = [] for word in words: # two ends of this edge v1 = self._move(0, word) v2 = self._move(0, (i,) + word) # avoid duplicates, this is because though the reflection `i` fixes # this edge but it maps the ordered tupe (v1, v2) to (v2, v1) if (v1, v2) not in elist and (v2, v1) not in elist: elist.append((v1, v2)) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[x], self.vertex_coords[y]) for x, y in elist]) self.num_edges = sum([len(elist) for elist in self.edge_indices]) def get_faces(self): """ Basically speaking, for a pair (i, j), the composition of the i-th and the j-th reflection is a rotation which fixes a base face `f0` of type `ij`. But there are some cases need to be considered: 1. The i-th and the j-th mirror are both active: in this case the rotation indeed generates a face `f0` and edges of type `i` and type `j` occur alternatively in it. 2. Exactly one of the i-th and the j-th mirror is active: in this case we should check whether their reflections commute or not: the rotation generates a face `f0` only when these two reflections do not commute, and `f0` contains edges of only one type. 3. Neither of them is active: in this case there are no faces. """ for i, j in combinations(self.symmetry_gens, 2): # the base face that contains the initial vertex f0 = [] if self.active[i] and self.active[j]: fgens = [(i, j)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j)*k)) f0.append(self._move(0, (j,) + (i, j)*k)) elif self.active[i] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (i,)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j)*k)) elif self.active[j] and self.coxeter_matrix[i][j] > 2: fgens = [(i, j), (j,)] for k in range(self.coxeter_matrix[i][j]): f0.append(self._move(0, (i, j)*k)) else: continue ftable = CosetTable(self.symmetry_gens, self.symmetry_rels, fgens) ftable.run() words = ftable.get_words() flist = [] for word in words: f = tuple(self._move(v, word) for v in f0) # the rotation fixes this face but it shifts the ordered tuple of vertices # rotationally, so we need to remove the duplicates. if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append([tuple(self.vertex_coords[x] for x in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def _transform(self, vector, word): """Transform a vector by a word in the symmetry group.""" for w in word: vector = np.dot(vector, self._reflections[w]) return vector def _move(self, vertex, word): """ Transform a vertex by a word in the symmetry group. Return the index of the resulting vertex. """ for w in word: vertex = self._vtable[vertex][w] return vertex def export_pov(self, filename): raise NotImplementedError def get_word_representations(self, symbol=r"\rho", cols=3, snub=False): """ Return the words corresponding to the vertices in latex format. `cols` is the number of columns in the array. """ def to_latex(word): if not word: return "e" else: if snub: return "".join(symbol + "_{{{}}}".format(i//2) for i in word) else: return "".join(symbol + "_{{{}}}".format(i) for i in word) latex = "" for i, word in enumerate(self._vwords): if i > 0 and i % cols == 0: latex += r"\\" latex += to_latex(word) if i % cols != cols - 1: latex += "&" return r"\begin{{array}}{{{}}}{}\end{{array}}".format("l"*cols, latex)
class Snub24Cell(Polychora): """The snub 24-cell can be constructed from snub demitesseract [3^(1,1,1)]+, the procedure is similar with snub polyhedron above. Its symmetric group is generated by three rotations {r, s, t}, a presentation is G = <r, s, t | r^3 = s^3 = t^3 = (rs)^2 = (rt)^2 = (s^-1 t)^2 = 1> where r = ρ0ρ1, s = ρ1ρ2, t = ρ1ρ3. """ def __init__(self): coxeter_diagram = (3, 2, 2, 3, 3, 2) coxeter_matrix = helpers.make_symmetry_matrix(coxeter_diagram) mirrors = helpers.get_mirrors(coxeter_diagram) super().__init__(coxeter_matrix, mirrors, (1, 1, 1, 1), extra_relations=()) self.symmetry_gens = tuple(range(6)) self.symmetry_rels = ((0, ) * 3, (2, ) * 3, (4, ) * 3, (0, 2) * 2, (0, 4) * 2, (3, 4) * 2, (0, 1), (2, 3), (4, 5)) self.rotations = ((0, ), (2, ), (4, ), (0, 2), (0, 4), (3, 4)) def get_vertices(self): self.vtable = CosetTable(self.symmetry_gens, self.symmetry_rels, coxeter=False) self.vtable.run() self.vwords = self.vtable.get_words() self.num_vertices = len(self.vwords) self.vertex_coords = tuple( self.transform(self.init_v, w) for w in self.vwords) def get_edges(self): for rot in self.rotations: elist = [] e0 = (0, self.move(0, rot)) for word in self.vwords: e = tuple(self.move(v, word) for v in e0) if e not in elist and e[::-1] not in elist: elist.append(e) self.edge_indices.append(elist) self.edge_coords.append([(self.vertex_coords[i], self.vertex_coords[j]) for i, j in elist]) self.num_edges = sum(len(elist) for elist in self.edge_indices) def get_faces(self): orbits = (tuple(self.move(0, (0, ) * k) for k in range(3)), tuple(self.move(0, (2, ) * k) for k in range(3)), tuple(self.move(0, (4, ) * k) for k in range(3)), (0, self.move(0, (2, )), self.move(0, (0, 2))), (0, self.move(0, (4, )), self.move(0, (0, 4))), (0, self.move(0, (2, )), self.move(0, (5, 2))), (0, self.move(0, (0, 2)), self.move(0, (5, 2)))) for f0 in orbits: flist = [] for word in self.vwords: f = tuple(self.move(v, word) for v in f0) if not helpers.check_duplicate_face(f, flist): flist.append(f) self.face_indices.append(flist) self.face_coords.append( [tuple(self.vertex_coords[v] for v in face) for face in flist]) self.num_faces = sum([len(flist) for flist in self.face_indices]) def transform(self, vertex, word): for g in word: if g == 0: vertex = np.dot(vertex, self.reflections[0]) vertex = np.dot(vertex, self.reflections[1]) elif g == 2: vertex = np.dot(vertex, self.reflections[1]) vertex = np.dot(vertex, self.reflections[2]) else: vertex = np.dot(vertex, self.reflections[1]) vertex = np.dot(vertex, self.reflections[3]) return vertex