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