1from ..core import Basic, Tuple
2from ..core.compatibility import as_int
3from ..sets import FiniteSet
4from ..utilities import flatten, unflatten
5from ..utilities.iterables import minlex
6from .perm_groups import PermutationGroup
7from .permutations import Permutation
8
9
10rmul = Permutation.rmul
11
12
13class Polyhedron(Basic):
14    """
15    Represents the polyhedral symmetry group (PSG).
16
17    The PSG is one of the symmetry groups of the Platonic solids.
18    There are three polyhedral groups: the tetrahedral group
19    of order 12, the octahedral group of order 24, and the
20    icosahedral group of order 60.
21
22    All doctests have been given in the docstring of the
23    constructor of the object.
24
25    References
26    ==========
27
28    * https://mathworld.wolfram.com/PolyhedralGroup.html
29
30    """
31
32    _edges = None
33
34    def __new__(cls, corners, faces=[], pgroup=[]):
35        """
36        The constructor of the Polyhedron group object.
37
38        It takes up to three parameters: the corners, faces, and
39        allowed transformations.
40
41        The corners/vertices are entered as a list of arbitrary
42        expressions that are used to identify each vertex.
43
44        The faces are entered as a list of tuples of indices; a tuple
45        of indices identifies the vertices which define the face. They
46        should be entered in a cw or ccw order; they will be standardized
47        by reversal and rotation to be give the lowest lexical ordering.
48        If no faces are given then no edges will be computed.
49
50            >>> Polyhedron(list('abc'), [(1, 2, 0)]).faces
51            {(0, 1, 2)}
52            >>> Polyhedron(list('abc'), [(1, 0, 2)]).faces
53            {(0, 1, 2)}
54
55        The allowed transformations are entered as allowable permutations
56        of the vertices for the polyhedron. Instance of Permutations
57        (as with faces) should refer to the supplied vertices by index.
58        These permutation are stored as a PermutationGroup.
59
60        Examples
61        ========
62
63        >>> Permutation.print_cyclic = False
64        >>> from diofant.abc import w
65
66        Here we construct the Polyhedron object for a tetrahedron.
67
68        >>> corners = [w, x, y, z]
69        >>> faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)]
70
71        Next, allowed transformations of the polyhedron must be given. This
72        is given as permutations of vertices.
73
74        Although the vertices of a tetrahedron can be numbered in 24 (4!)
75        different ways, there are only 12 different orientations for a
76        physical tetrahedron. The following permutations, applied once or
77        twice, will generate all 12 of the orientations. (The identity
78        permutation, Permutation(range(4)), is not included since it does
79        not change the orientation of the vertices.)
80
81        >>> pgroup = [Permutation([[0, 1, 2], [3]]),
82        ...           Permutation([[0, 1, 3], [2]]),
83        ...           Permutation([[0, 2, 3], [1]]),
84        ...           Permutation([[1, 2, 3], [0]]),
85        ...           Permutation([[0, 1], [2, 3]]),
86        ...           Permutation([[0, 2], [1, 3]]),
87        ...           Permutation([[0, 3], [1, 2]])]
88
89        The Polyhedron is now constructed and demonstrated:
90
91        >>> tetra = Polyhedron(corners, faces, pgroup)
92        >>> tetra.size
93        4
94        >>> tetra.edges
95        {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
96        >>> tetra.corners
97        (w, x, y, z)
98
99        It can be rotated with an arbitrary permutation of vertices, e.g.
100        the following permutation is not in the pgroup:
101
102        >>> tetra.rotate(Permutation([0, 1, 3, 2]))
103        >>> tetra.corners
104        (w, x, z, y)
105
106        An allowed permutation of the vertices can be constructed by
107        repeatedly applying permutations from the pgroup to the vertices.
108        Here is a demonstration that applying p and p**2 for every p in
109        pgroup generates all the orientations of a tetrahedron and no others:
110
111        >>> all = ((w, x, y, z),
112        ...        (x, y, w, z),
113        ...        (y, w, x, z),
114        ...        (w, z, x, y),
115        ...        (z, w, y, x),
116        ...        (w, y, z, x),
117        ...        (y, z, w, x),
118        ...        (x, z, y, w),
119        ...        (z, y, x, w),
120        ...        (y, x, z, w),
121        ...        (x, w, z, y),
122        ...        (z, x, w, y))
123
124        >>> got = []
125        >>> for p in (pgroup + [p**2 for p in pgroup]):
126        ...     h = Polyhedron(corners)
127        ...     h.rotate(p)
128        ...     got.append(h.corners)
129        ...
130        >>> set(got) == set(all)
131        True
132
133        The make_perm method of a PermutationGroup will randomly pick
134        permutations, multiply them together, and return the permutation that
135        can be applied to the polyhedron to give the orientation produced
136        by those individual permutations.
137
138        Here, 3 permutations are used:
139
140        >>> tetra.pgroup.make_perm(3)  # doctest: +SKIP
141        Permutation([0, 3, 1, 2])
142
143        To select the permutations that should be used, supply a list
144        of indices to the permutations in pgroup in the order they should
145        be applied:
146
147        >>> use = [0, 0, 2]
148        >>> p002 = tetra.pgroup.make_perm(3, use)
149        >>> p002
150        Permutation([1, 0, 3, 2])
151
152
153        Apply them one at a time:
154
155        >>> tetra.reset()
156        >>> for i in use:
157        ...     tetra.rotate(pgroup[i])
158        ...
159        >>> tetra.vertices
160        (x, w, z, y)
161        >>> sequentially = tetra.vertices
162
163        Apply the composite permutation:
164
165        >>> tetra.reset()
166        >>> tetra.rotate(p002)
167        >>> tetra.corners
168        (x, w, z, y)
169        >>> tetra.corners in all and tetra.corners == sequentially
170        True
171
172        Notes
173        =====
174
175        Defining permutation groups
176        ---------------------------
177
178        It is not necessary to enter any permutations, nor is necessary to
179        enter a complete set of transformations. In fact, for a polyhedron,
180        all configurations can be constructed from just two permutations.
181        For example, the orientations of a tetrahedron can be generated from
182        an axis passing through a vertex and face and another axis passing
183        through a different vertex or from an axis passing through the
184        midpoints of two edges opposite of each other.
185
186        For simplicity of presentation, consider a square --
187        not a cube -- with vertices 1, 2, 3, and 4:
188
189        1-----2  We could think of axes of rotation being:
190        |     |  1) through the face
191        |     |  2) from midpoint 1-2 to 3-4 or 1-3 to 2-4
192        3-----4  3) lines 1-4 or 2-3
193
194
195        To determine how to write the permutations, imagine 4 cameras,
196        one at each corner, labeled A-D:
197
198        A       B          A       B
199         1-----2            1-----3             vertex index:
200         |     |            |     |                 1   0
201         |     |            |     |                 2   1
202         3-----4            2-----4                 3   2
203        C       D          C       D                4   3
204
205        original           after rotation
206                           along 1-4
207
208        A diagonal and a face axis will be chosen for the "permutation group"
209        from which any orientation can be constructed.
210
211        >>> pgroup = []
212
213        Imagine a clockwise rotation when viewing 1-4 from camera A. The new
214        orientation is (in camera-order): 1, 3, 2, 4 so the permutation is
215        given using the *indices* of the vertices as:
216
217        >>> pgroup.append(Permutation((0, 2, 1, 3)))
218
219        Now imagine rotating clockwise when looking down an axis entering the
220        center of the square as viewed. The new camera-order would be
221        3, 1, 4, 2 so the permutation is (using indices):
222
223        >>> pgroup.append(Permutation((2, 0, 3, 1)))
224
225        The square can now be constructed:
226            ** use real-world labels for the vertices, entering them in
227               camera order
228            ** for the faces we use zero-based indices of the vertices
229               in *edge-order* as the face is traversed; neither the
230               direction nor the starting point matter -- the faces are
231               only used to define edges (if so desired).
232
233        >>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup)
234
235        To rotate the square with a single permutation we can do:
236
237        >>> square.rotate(square.pgroup[0])
238        >>> square.corners
239        (1, 3, 2, 4)
240
241        To use more than one permutation (or to use one permutation more
242        than once) it is more convenient to use the make_perm method:
243
244        >>> p011 = square.pgroup.make_perm([0, 1, 1])  # diag flip + 2 rotations
245        >>> square.reset()  # return to initial orientation
246        >>> square.rotate(p011)
247        >>> square.corners
248        (4, 2, 3, 1)
249
250        Thinking outside the box
251        ------------------------
252
253        Although the Polyhedron object has a direct physical meaning, it
254        actually has broader application. In the most general sense it is
255        just a decorated PermutationGroup, allowing one to connect the
256        permutations to something physical. For example, a Rubik's cube is
257        not a proper polyhedron, but the Polyhedron class can be used to
258        represent it in a way that helps to visualize the Rubik's cube.
259
260        >>> facelets = flatten([symbols(s+'1:5') for s in 'UFRBLD'])
261        >>> def show():
262        ...     pairs = unflatten(r2.corners, 2)
263        ...     print(sstr(pairs[::2]))
264        ...     print(sstr(pairs[1::2]))
265        ...
266        >>> r2 = Polyhedron(facelets, pgroup=RubikGroup(2))
267        >>> show()
268        [(U1, U2), (F1, F2), (R1, R2), (B1, B2), (L1, L2), (D1, D2)]
269        [(U3, U4), (F3, F4), (R3, R4), (B3, B4), (L3, L4), (D3, D4)]
270        >>> r2.rotate(0)  # cw rotation of F
271        >>> show()
272        [(U1, U2), (F3, F1), (U3, R2), (B1, B2), (L1, D1), (R3, R1)]
273        [(L4, L2), (F4, F2), (U4, R4), (B3, B4), (L3, D2), (D3, D4)]
274
275        Predefined Polyhedra
276        ====================
277
278        For convenience, the vertices and faces are defined for the following
279        standard solids along with a permutation group for transformations.
280        When the polyhedron is oriented as indicated below, the vertices in
281        a given horizontal plane are numbered in ccw direction, starting from
282        the vertex that will give the lowest indices in a given face. (In the
283        net of the vertices, indices preceded by "-" indicate replication of
284        the lhs index in the net.)
285
286        tetrahedron, tetrahedron_faces
287        ------------------------------
288
289            4 vertices (vertex up) net:
290
291                 0 0-0
292                1 2 3-1
293
294            4 faces:
295
296            (0,1,2) (0,2,3) (0,3,1) (1,2,3)
297
298        cube, cube_faces
299        ----------------
300
301            8 vertices (face up) net:
302
303                0 1 2 3-0
304                4 5 6 7-4
305
306            6 faces:
307
308            (0,1,2,3)
309            (0,1,5,4) (1,2,6,5) (2,3,7,6) (0,3,7,4)
310            (4,5,6,7)
311
312        octahedron, octahedron_faces
313        ----------------------------
314
315            6 vertices (vertex up) net:
316
317                 0 0 0-0
318                1 2 3 4-1
319                 5 5 5-5
320
321            8 faces:
322
323            (0,1,2) (0,2,3) (0,3,4) (0,1,4)
324            (1,2,5) (2,3,5) (3,4,5) (1,4,5)
325
326        dodecahedron, dodecahedron_faces
327        --------------------------------
328
329            20 vertices (vertex up) net:
330
331                  0  1  2  3  4 -0
332                  5  6  7  8  9 -5
333                14 10 11 12 13-14
334                15 16 17 18 19-15
335
336            12 faces:
337
338            (0,1,2,3,4)
339            (0,1,6,10,5) (1,2,7,11,6) (2,3,8,12,7) (3,4,9,13,8) (0,4,9,14,5)
340            (5,10,16,15,14) (
341                6,10,16,17,11) (7,11,17,18,12) (8,12,18,19,13) (9,13,19,15,14)
342            (15,16,17,18,19)
343
344        icosahedron, icosahedron_faces
345        ------------------------------
346
347            12 vertices (face up) net:
348
349                 0  0  0  0 -0
350                1  2  3  4  5 -1
351                 6  7  8  9  10 -6
352                  11 11 11 11 -11
353
354            20 faces:
355
356            (0,1,2) (0,2,3) (0,3,4) (0,4,5) (0,1,5)
357            (1,2,6) (2,3,7) (3,4,8) (4,5,9) (1,5,10)
358            (2,6,7) (3,7,8) (4,8,9) (5,9,10) (1,6,10)
359            (6,7,11,) (7,8,11) (8,9,11) (9,10,11) (6,10,11)
360
361        >>> cube.edges
362        {(0, 1), (0, 3), (0, 4), ..., (4, 7), (5, 6), (6, 7)}
363
364        If you want to use letters or other names for the corners you
365        can still use the pre-calculated faces:
366
367        >>> corners = list('abcdefgh')
368        >>> Polyhedron(corners, cube.faces).corners
369        (a, b, c, d, e, f, g, h)
370
371        References
372        ==========
373
374        [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf
375
376        """
377        faces = [minlex(f, directed=False, is_set=True) for f in faces]
378        corners, faces, pgroup = args = \
379            [Tuple(*a) for a in (corners, faces, pgroup)]
380        obj = Basic.__new__(cls, *args)
381        obj._corners = tuple(corners)  # in order given
382        obj._faces = FiniteSet(*faces)
383        if pgroup and pgroup[0].size != len(corners):
384            raise ValueError('Permutation size unequal to number of corners.')
385        # use the identity permutation if none are given
386        obj._pgroup = PermutationGroup((pgroup or
387                                        [Permutation(range(len(corners)))]))
388        return obj
389
390    @property
391    def corners(self):
392        """
393        Get the corners of the Polyhedron.
394
395        The method ``vertices`` is an alias for ``corners``.
396
397        Examples
398        ========
399
400        >>> p = Polyhedron(list('abcd'))
401        >>> p.corners == p.vertices == (a, b, c, d)
402        True
403
404        See Also
405        ========
406
407        array_form, cyclic_form
408
409        """
410        return self._corners
411    vertices = corners
412
413    @property
414    def array_form(self):
415        """Return the indices of the corners.
416
417        The indices are given relative to the original position of corners.
418
419        Examples
420        ========
421
422        >>> tetrahedron.array_form
423        [0, 1, 2, 3]
424
425        >>> tetrahedron.rotate(0)
426        >>> tetrahedron.array_form
427        [0, 2, 3, 1]
428        >>> tetrahedron.pgroup[0].array_form
429        [0, 2, 3, 1]
430        >>> tetrahedron.reset()
431
432        See Also
433        ========
434
435        corners, cyclic_form
436
437        """
438        corners = list(self.args[0])
439        return [corners.index(c) for c in self.corners]
440
441    @property
442    def cyclic_form(self):
443        """Return the indices of the corners in cyclic notation.
444
445        The indices are given relative to the original position of corners.
446
447        See Also
448        ========
449
450        corners, array_form
451
452        """
453        return Permutation._af_new(self.array_form).cyclic_form
454
455    @property
456    def size(self):
457        """Get the number of corners of the Polyhedron."""
458        return len(self._corners)
459
460    @property
461    def faces(self):
462        """Get the faces of the Polyhedron."""
463        return self._faces
464
465    @property
466    def pgroup(self):
467        """Get the permutations of the Polyhedron."""
468        return self._pgroup
469
470    @property
471    def edges(self):
472        """
473        Given the faces of the polyhedra we can get the edges.
474
475        Examples
476        ========
477
478        >>> corners = (a, b, c)
479        >>> faces = [(0, 1, 2)]
480        >>> Polyhedron(corners, faces).edges
481        {(0, 1), (0, 2), (1, 2)}
482
483        """
484        if self._edges is None:
485            output = set()
486            for face in self.faces:
487                for i, fi in enumerate(face):
488                    edge = tuple(sorted([fi, face[i - 1]]))
489                    output.add(edge)
490            self._edges = FiniteSet(*output)
491        return self._edges
492
493    def rotate(self, perm):
494        """
495        Apply a permutation to the polyhedron *in place*. The permutation
496        may be given as a Permutation instance or an integer indicating
497        which permutation from pgroup of the Polyhedron should be
498        applied.
499
500        This is an operation that is analogous to rotation about
501        an axis by a fixed increment.
502
503        Notes
504        =====
505
506        When a Permutation is applied, no check is done to see if that
507        is a valid permutation for the Polyhedron. For example, a cube
508        could be given a permutation which effectively swaps only 2
509        vertices. A valid permutation (that rotates the object in a
510        physical way) will be obtained if one only uses
511        permutations from the ``pgroup`` of the Polyhedron. On the other
512        hand, allowing arbitrary rotations (applications of permutations)
513        gives a way to follow named elements rather than indices since
514        Polyhedron allows vertices to be named while Permutation works
515        only with indices.
516
517        Examples
518        ========
519
520        >>> cube.corners
521        (0, 1, 2, 3, 4, 5, 6, 7)
522        >>> cube.rotate(0)
523        >>> cube.corners
524        (1, 2, 3, 0, 5, 6, 7, 4)
525
526        A non-physical "rotation" that is not prohibited by this method:
527
528        >>> cube.reset()
529        >>> cube.rotate(Permutation([[1, 2]], size=8))
530        >>> cube.corners
531        (0, 2, 1, 3, 4, 5, 6, 7)
532
533        Polyhedron can be used to follow elements of set that are
534        identified by letters instead of integers:
535
536        >>> shadow = h5 = Polyhedron(list('abcde'))
537        >>> p = Permutation([3, 0, 1, 2, 4])
538        >>> h5.rotate(p)
539        >>> h5.corners
540        (d, a, b, c, e)
541        >>> _ == shadow.corners
542        True
543        >>> copy = h5.copy()
544        >>> h5.rotate(p)
545        >>> h5.corners == copy.corners
546        False
547
548        """
549        if not isinstance(perm, Permutation):
550            perm = self.pgroup[perm]
551            # and we know it's valid
552        else:
553            if perm.size != self.size:
554                raise ValueError('Polyhedron and Permutation sizes differ.')
555        a = perm.array_form
556        corners = [self.corners[a[i]] for i in range(len(self.corners))]
557        self._corners = tuple(corners)
558
559    def reset(self):
560        """Return corners to their original positions.
561
562        Examples
563        ========
564
565        >>> tetrahedron.corners
566        (0, 1, 2, 3)
567        >>> tetrahedron.rotate(0)
568        >>> tetrahedron.corners
569        (0, 2, 3, 1)
570        >>> tetrahedron.reset()
571        >>> tetrahedron.corners
572        (0, 1, 2, 3)
573
574        """
575        self._corners = self.args[0]
576
577
578def _pgroup_calcs():
579    """Return the permutation groups for each of the polyhedra and the face
580    definitions: tetrahedron, cube, octahedron, dodecahedron, icosahedron,
581    tetrahedron_faces, cube_faces, octahedron_faces, dodecahedron_faces,
582    icosahedron_faces
583
584    (This author didn't find and didn't know of a better way to do it though
585    there likely is such a way.)
586
587    Although only 2 permutations are needed for a polyhedron in order to
588    generate all the possible orientations, a group of permutations is
589    provided instead. A set of permutations is called a "group" if::
590
591    a*b = c (for any pair of permutations in the group, a and b, their
592    product, c, is in the group)
593
594    a*(b*c) = (a*b)*c (for any 3 permutations in the group associativity holds)
595
596    there is an identity permutation, I, such that I*a = a*I for all elements
597    in the group
598
599    a*b = I (the inverse of each permutation is also in the group)
600
601    None of the polyhedron groups defined follow these definitions of a group.
602    Instead, they are selected to contain those permutations whose powers
603    alone will construct all orientations of the polyhedron, i.e. for
604    permutations ``a``, ``b``, etc... in the group, ``a, a**2, ..., a**o_a``,
605    ``b, b**2, ..., b**o_b``, etc... (where ``o_i`` is the order of
606    permutation ``i``) generate all permutations of the polyhedron instead of
607    mixed products like ``a*b``, ``a*b**2``, etc....
608
609    Note that for a polyhedron with n vertices, the valid permutations of the
610    vertices exclude those that do not maintain its faces. e.g. the
611    permutation BCDE of a square's four corners, ABCD, is a valid
612    permutation while CBDE is not (because this would twist the square).
613
614    Examples
615    ========
616
617    The is_group checks for: closure, the presence of the Identity permutation,
618    and the presence of the inverse for each of the elements in the group. This
619    confirms that none of the polyhedra are true groups:
620
621    >>> polyhedra = (tetrahedron, cube, octahedron, dodecahedron, icosahedron)
622    >>> [h.pgroup.is_group for h in polyhedra]
623    ...
624    [True, True, True, True, True]
625
626    Although tests in polyhedron's test suite check that powers of the
627    permutations in the groups generate all permutations of the vertices
628    of the polyhedron, here we also demonstrate the powers of the given
629    permutations create a complete group for the tetrahedron:
630
631    >>> for h in polyhedra[:1]:
632    ...     G = h.pgroup
633    ...     perms = set()
634    ...     for g in G:
635    ...         for e in range(g.order()):
636    ...             p = tuple((g**e).array_form)
637    ...             perms.add(p)
638    ...
639    ...     perms = [Permutation(p) for p in perms]
640    ...     assert PermutationGroup(perms).is_group
641
642    In addition to doing the above, the tests in the suite confirm that the
643    faces are all present after the application of each permutation.
644
645    References
646    ==========
647
648    http://dogschool.tripod.com/trianglegroup.html
649
650    """
651    def _pgroup_of_double(polyh, ordered_faces, pgroup):
652        n = len(ordered_faces[0])
653        # the vertices of the double which sits inside a give polyhedron
654        # can be found by tracking the faces of the outer polyhedron.
655        # A map between face and the vertex of the double is made so that
656        # after rotation the position of the vertices can be located
657        fmap = dict(zip(ordered_faces,
658                        range(len(ordered_faces))))
659        flat_faces = flatten(ordered_faces)
660        new_pgroup = []
661        for p in pgroup:
662            h = polyh.copy()
663            h.rotate(p)
664            c = h.corners
665            # reorder corners in the order they should appear when
666            # enumerating the faces
667            reorder = unflatten([c[j] for j in flat_faces], n)
668            # make them canonical
669            reorder = [tuple(map(as_int,
670                                 minlex(f, directed=False, is_set=True)))
671                       for f in reorder]
672            # map face to vertex: the resulting list of vertices are the
673            # permutation that we seek for the double
674            new_pgroup.append(Permutation([fmap[f] for f in reorder]))
675        return new_pgroup
676
677    tetrahedron_faces = [
678        (0, 1, 2), (0, 2, 3), (0, 3, 1),  # upper 3
679        (1, 2, 3)]  # bottom
680
681    # cw from top
682    #
683    _t_pgroup = [
684        Permutation([[1, 2, 3], [0]]),  # cw from top
685        Permutation([[0, 1, 2], [3]]),  # cw from front face
686        Permutation([[0, 3, 2], [1]]),  # cw from back right face
687        Permutation([[0, 3, 1], [2]]),  # cw from back left face
688        Permutation([[0, 1], [2, 3]]),  # through front left edge
689        Permutation([[0, 2], [1, 3]]),  # through front right edge
690        Permutation([[0, 3], [1, 2]])]  # through back edge
691
692    tetrahedron = Polyhedron(
693        range(4),
694        tetrahedron_faces,
695        _t_pgroup)
696
697    cube_faces = [
698        (0, 1, 2, 3),  # upper
699        (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4),  # middle 4
700        (4, 5, 6, 7)]  # lower
701
702    # U, D, F, B, L, R = up, down, front, back, left, right
703    _c_pgroup = [Permutation(p) for p in
704                 [[1, 2, 3, 0, 5, 6, 7, 4],  # cw from top, U
705                  [4, 0, 3, 7, 5, 1, 2, 6],  # cw from F face
706                  [4, 5, 1, 0, 7, 6, 2, 3],  # cw from R face
707
708                  [1, 0, 4, 5, 2, 3, 7, 6],  # cw through UF edge
709                  [6, 2, 1, 5, 7, 3, 0, 4],  # cw through UR edge
710                  [6, 7, 3, 2, 5, 4, 0, 1],  # cw through UB edge
711                  [3, 7, 4, 0, 2, 6, 5, 1],  # cw through UL edge
712                  [4, 7, 6, 5, 0, 3, 2, 1],  # cw through FL edge
713                  [6, 5, 4, 7, 2, 1, 0, 3],  # cw through FR edge
714
715                  [0, 3, 7, 4, 1, 2, 6, 5],  # cw through UFL vertex
716                  [5, 1, 0, 4, 6, 2, 3, 7],  # cw through UFR vertex
717                  [5, 6, 2, 1, 4, 7, 3, 0],  # cw through UBR vertex
718                  [7, 4, 0, 3, 6, 5, 1, 2]]]  # cw through UBL
719
720    cube = Polyhedron(
721        range(8),
722        cube_faces,
723        _c_pgroup)
724
725    octahedron_faces = [
726        (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4),  # top 4
727        (1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5)]  # bottom 4
728
729    octahedron = Polyhedron(
730        range(6),
731        octahedron_faces,
732        _pgroup_of_double(cube, cube_faces, _c_pgroup))
733
734    dodecahedron_faces = [
735        (0, 1, 2, 3, 4),  # top
736        (0, 1, 6, 10, 5), (1, 2, 7, 11, 6), (2, 3, 8, 12, 7),  # upper 5
737        (3, 4, 9, 13, 8), (0, 4, 9, 14, 5),
738        (5, 10, 16, 15, 14), (6, 10, 16, 17, 11), (7, 11, 17, 18,
739                                                   12),  # lower 5
740        (8, 12, 18, 19, 13), (9, 13, 19, 15, 14),
741        (15, 16, 17, 18, 19)]  # bottom
742
743    def _string_to_perm(s):
744        rv = [Permutation(range(20))]
745        p = None
746        for si in s:
747            if si not in '01':
748                count = int(si) - 1
749            else:
750                count = 1
751                if si == '0':
752                    p = _f0
753                else:
754                    p = _f1
755            rv.extend([p]*count)
756        return Permutation.rmul(*rv)
757
758    # top face cw
759    _f0 = Permutation([
760        1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11,
761        12, 13, 14, 10, 16, 17, 18, 19, 15])
762    # front face cw
763    _f1 = Permutation([
764        5, 0, 4, 9, 14, 10, 1, 3, 13, 15,
765        6, 2, 8, 19, 16, 17, 11, 7, 12, 18])
766    # the strings below, like 0104 are shorthand for F0*F1*F0**4 and are
767    # the remaining 4 face rotations, 15 edge permutations, and the
768    # 10 vertex rotations.
769    _dodeca_pgroup = [_f0, _f1] + [_string_to_perm(s) for s in """
770    0104 140 014 0410
771    010 1403 03104 04103 102
772    120 1304 01303 021302 03130
773    0412041 041204103 04120410 041204104 041204102
774    10 01 1402 0140 04102 0412 1204 1302 0130 03120""".strip().split()]
775
776    dodecahedron = Polyhedron(
777        range(20),
778        dodecahedron_faces,
779        _dodeca_pgroup)
780
781    icosahedron_faces = [
782        [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 1, 5],
783        [1, 6, 7], [1, 2, 7], [2, 7, 8], [2, 3, 8], [3, 8, 9],
784        [3, 4, 9], [4, 9, 10], [4, 5, 10], [5, 6, 10], [1, 5, 6],
785        [6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [6, 10, 11]]
786
787    icosahedron = Polyhedron(
788        range(12),
789        icosahedron_faces,
790        _pgroup_of_double(
791            dodecahedron, dodecahedron_faces, _dodeca_pgroup))
792
793    return (tetrahedron, cube, octahedron, dodecahedron, icosahedron,
794            tetrahedron_faces, cube_faces, octahedron_faces,
795            dodecahedron_faces, icosahedron_faces)
796
797
798(tetrahedron, cube, octahedron, dodecahedron, icosahedron,
799 tetrahedron_faces, cube_faces, octahedron_faces,
800 dodecahedron_faces, icosahedron_faces) = _pgroup_calcs()
801