Exemple #1
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])
Exemple #2
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])
Exemple #3
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])
Exemple #4
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])
Exemple #5
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])
Exemple #6
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])
Exemple #7
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
Exemple #8
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)
Exemple #9
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)
Exemple #10
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
Exemple #11
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)
Exemple #12
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
Exemple #13
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
Exemple #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)