Пример #1
0
    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])
Пример #2
0
    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])
Пример #3
0
    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])
Пример #4
0
    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)
Пример #5
0
    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)
Пример #6
0
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()
Пример #7
0
 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)
Пример #8
0
 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)
Пример #9
0
    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])
Пример #10
0
 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)
Пример #11
0
    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])
Пример #12
0
 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])
Пример #13
0
    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))
Пример #14
0
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)
Пример #15
0
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
Пример #16
0
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
Пример #17
0
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)
Пример #18
0
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))))
Пример #19
0
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)
Пример #20
0
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
Пример #21
0
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)
Пример #22
0
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