# smichr/sympy forked from sympy/sympy

Polyhedron: cyclic clash; polyhedra have pgroups; Basic.copy

```named_groups uses cyclic so the function in permutations was
changed to full_cyclic_form0 and one_based was changed to
cyclic_form1; these are more descriptive and consistent with
the method name (cyclic_form).

All pre-defined polyhedra come with pgroups that are checked
to cover all orientations.

Docstrings have been expanded.

The _canonical function was renamed minlex but it was kept
in the polyhedron file; it is potentially useful for iterables
or geometry.

Unused imports were deleted.

The copy method was added to Basic to allow a copy to be
made of an object that implements in-place changes.```
1 parent 3c49022 commit 052f6fe223aaf5229bfa9e090e8dd0c9cf5a3f6e committed Aug 22, 2012
2 sympy/combinatorics/generators.py
 @@ -1,5 +1,5 @@ from sympy.combinatorics.permutations import Permutation -from sympy.combinatorics.permutations import cyclic as perm_cyclic +from sympy.combinatorics.permutations import full_cyclic_form0 as perm_cyclic from sympy.utilities.iterables import variations, rotate_left def symmetric(n):
42 sympy/combinatorics/permutations.py
 @@ -7,26 +7,28 @@ import random -def cyclic(cyclic_form1, n): - """Convert from 1-based cyclic-form to 0-based cyclic form after - filling in any missing singletons (up to ``n``). +def full_cyclic_form0(cyclic_form1, n): + """Convert from a 1-based permutation in cycle notation + to 0-based cyclic form after filling in any missing + singletons (up to ``n``). If any element in the cycles is greater than n, singletons will - be missing from the cyclic form returned. + be missing from the returned permutation. - >>> from sympy.combinatorics.permutations import cyclic + >>> from sympy.combinatorics.permutations import full_cyclic_form0 >>> a = [(1, 2, 3)] - >>> cyclic(a, 5) + >>> full_cyclic_form0(a, 5) [[0, 1, 2], [3], [4]] >>> a = [(1, 2, 3), (4, 5)] - >>> cyclic(a, 5) + >>> full_cyclic_form0(a, 5) [[0, 1, 2], [3, 4]] - >>> cyclic([[4, 5]], 2) + >>> full_cyclic_form0([[4, 5]], 2) [[3, 4], [0], [1]] See Also ======== - one_based + + cyclic_form1 """ rv = [] @@ -38,34 +40,36 @@ def cyclic(cyclic_form1, n): rv.extend([[i] for i in sorted(need)]) return rv -def one_based(cyclic_form0, singletons=False): +def cyclic_form1(cyclic_form0, singletons=False): """Return a cyclic form in 1-based cyclic form, omitting singletons if ``singleton`` is False (default). Examples ======== - >>> from sympy.combinatorics.permutations import Permutation - >>> from sympy.combinatorics.permutations import one_based, cyclic - >>> one_based([[0, 1], [2]]) + >>> from sympy.combinatorics.permutations import (Permutation, + ... cyclic_form1, full_cyclic_form0) + ... + >>> cyclic_form1([[0, 1], [2]]) [[1, 2]] - >>> one_based([[0, 1], [2]], singletons=True) + >>> cyclic_form1([[0, 1], [2]], singletons=True) [[1, 2], [3]] - >>> p = Permutation(cyclic([(2, 3, 4)], 5)) + >>> p = Permutation(full_cyclic_form0([(2, 3, 4)], 5)) >>> p.reduced_cyclic_form [[1, 2, 3]] >>> p.cyclic_form [[1, 2, 3], [0], [4]] >>> c = _ - >>> one_based(c) + >>> cyclic_form1(c) [[2, 3, 4]] - >>> one_based(c, True) + >>> cyclic_form1(c, True) [[2, 3, 4], [1], [5]] See Also ======== - cyclic + + full_cyclic_form0 """ min = 1 + (not bool(singletons)) @@ -121,7 +125,7 @@ def _af_mul(*a): See Also ======== - Permutation, _af_mul + Permutation """ if not a: raise ValueError("No element")
352 sympy/combinatorics/polyhedron.py
 @@ -2,12 +2,21 @@ from sympy.core.sympify import sympify from sympy.combinatorics import Permutation from sympy.utilities.misc import default_sort_key -from sympy.utilities.iterables import rotate_left, has_variety - -from random import choice - -def _canonical(face): - """Return the face in canonical order.""" +from sympy.utilities.iterables import rotate_left, has_variety, is_sequence +from sympy.utilities.randtest import _randrange + +def minlex(face): + """Return the face in canonical order: smallest first with + direction (perhaps reversed) such that the next smallest comes next. + + Examples + ======== + >>> from sympy.combinatorics.polyhedron import minlex + >>> minlex((1, 0, 2)) + (0, 1, 2) + >>> minlex((1, 2, 0)) + (0, 1, 2) + """ face = list(face) small = min(face) i = face.index(small) @@ -173,19 +182,91 @@ def __new__(cls, corners, faces=[], pgroups=[]): >>> _ in all and _ == sequentially True - Note - ==== + Notes + ===== + + It is not necessary to enter any permutations, nor is necessary to + enter a complete set of transforations. In fact, two permutations + (corresponding to a rotation on an axis through a vertex and face + and another for the rotation through a different vertex or from + one edge to the opposite edge) are sufficient to generate all + orientations. For simplicity of presentation, consider a square -- + not a cube -- with vertices 1, 2, 3, and 4: + + 1-----2 We could think of axes of rotation being: + | | 1) through the face + | | 2) from midpoint 1-2 to 3-4 or 1-3 to 2-4 + 3-----4 3) lines 1-4 or 2-3 + + + To determine how to write the permutations, imagine 4 cameras, one at + each corner: + + A B A B + 1-----2 1-----3 vertex index: + | | | | 1 0 + | | | | 2 1 + 3-----4 2-----4 3 2 + C D C D 4 3 + + original after rotation + along 1-4 + + A diagonal and a face axis will be chosen for the "permutation group" + from which any orientation can be constructed. + + >>> pgroup = [] + + Imagine rotating clockwise when viewing 1-4 from camera A. The new + orientation is (in camera-order): 1, 3, 2, 4 so the permutation is + given using the *indices* of the vertices as: + + >>> pgroup.append(Permutation((0, 2, 1, 3))) + + Now imagine rotating clockwise when looking down an axis entering the + center of the square as viewed. The new camera-order would be + 3, 1, 4, 2 so the permutation is (using indices): + + >>> pgroup.append(Permutation((2, 0, 3, 1))) + + The square can now be constructed: + ** use real-world labels for the vertices, entering them in + camera order + ** for the faces we use zero-based indices of the vertices + in *edge-order* as the face is traversed; neither the + direction nor the starting point matter -- the faces are + only used to define edges (if so desired). + + >>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup) + + To rotate the square a single permutation we can do: + + >>> sq = square.copy() + >>> sq.rotate(square.pgroups[0]); sq.corners + (1, 3, 2, 4) + + To use more than one permutation (or to use one permutation more + than once) it is more convenient to use the make_perm method: + + >>> p011 = square.make_perm([0,1,1]) # diag flip and 2 rotations + >>> sq = square.copy() + >>> sq.rotate(p011); sq.corners + (4, 2, 3, 1) + + + Predefined Polyhedra + ==================== For convenience, the vertices and faces are defined for the following - standard solids (but the allowed transformations are not provided). + standard solids along with a permutation group for transformations. When the polyhedron is oriented as indicated below, the vertices in a given horizontal plane are numbered in ccw direction, starting from the vertex that will give the lowest indices in a given face. (In the net of the vertices, indices preceded by "-" indicate replication of the lhs index in the net.) - tetrahedron - ----------- + tetrahedron, tetrahedron_faces + ------------------------------ 4 vertices (vertex up) net: @@ -196,8 +277,8 @@ def __new__(cls, corners, faces=[], pgroups=[]): (0,1,2) (0,2,3) (0,3,1) (1,2,3) - cube - ---- + cube, cube_faces + ---------------- 8 vertices (face up) net: @@ -210,8 +291,8 @@ def __new__(cls, corners, faces=[], pgroups=[]): (0,1,5,4) (1,2,6,5) (2,3,7,6) (0,3,7,4) (4,5,6,7) - octahedron - ---------- + octahedron, octahedron_faces + ---------------------------- 6 vertices (vertex up) net: @@ -224,8 +305,25 @@ def __new__(cls, corners, faces=[], pgroups=[]): (0,1,2) (0,2,3) (0,3,4) (0,1,4) (1,2,5) (2,3,5) (3,4,5) (1,4,5) - dodecahedron - ------------ + dodecahedron, dodecahedron_faces + -------------------------------- + + 20 vertices (vertex up) net: + + 0 1 2 3 4 -0 + 5 6 7 8 9 -5 + 14 10 11 12 13-14 + 15 16 17 18 19-15 + + 12 faces: + + (0,1,2,3,4) + (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) + (5,10,16,15,14) (6,10,16,17,11) (7,11,17,18,12) (8,12,18,19,13) (9,13,19,15,14) + (15,16,17,18,19) + + icosahedron, icosahedron_faces + ------------------------------ 12 vertices (face up) net: @@ -241,34 +339,15 @@ def __new__(cls, corners, faces=[], pgroups=[]): (2,6,7) (3,7,8) (4,8,9) (5,9,10) (1,6,10) (6,7,11,) (7,8,11) (8,9,11) (9,10,11) (6,10,11) - icosahedron - ----------- - - 20 vertices (vertex up) net: - - 0 1 2 3 4 -0 - 5 6 7 8 9 -5 - 10 11 12 13 14-10 - 15 16 17 18 19-15 - - 12 faces: - - (0,1,2,3,4) - (0,1,6,11,5) (1,2,7,12,6) (2,3,8,13,7) (3,4,9,14,8) (0,4,9,10,5) - (5,10,15,16,11) (6,11,16,17,12) (7,12,17,18,13) (8,13,18,19,14) - (9,10,15,19,14) - (15,16,17,18,19) - >>> from sympy.combinatorics.polyhedron import cube - >>> Polyhedron(*cube).edges + >>> cube.edges {(0, 1), (0, 3), (0, 4), '...', (4, 7), (5, 6), (6, 7)} If you want to use letters or other names for the corners you can still use the pre-calculated faces: - >>> _, faces = cube >>> corners = list('abcdefgh') - >>> Polyhedron(corners, faces).corners + >>> Polyhedron(corners, cube.faces).corners (a, b, c, d, e, f, g, h) References @@ -277,7 +356,7 @@ def __new__(cls, corners, faces=[], pgroups=[]): [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf """ - faces = [_canonical(f) for f in faces] + faces = [minlex(f) for f in faces] corners, faces, pgroups = args = \ [Tuple(*a) for a in (corners, faces, pgroups)] obj = Basic.__new__(cls, *args) @@ -362,12 +441,13 @@ def make_perm(self, n, seed=None): """ Multiply ``n`` randomly selected permutations from pgroups together, starting with the identity - permutation. + permutation. If ``n`` is a list of integers, those + integers will be used to select the permutations. ``seed`` is used to set the seed for the random selection of permutations from pgroups. If this is a list of integers, the corresponding permutations from pgroups will be selected - in the order give. + in the order give. This is mainly used for testing purposes. Examples ======== @@ -379,9 +459,14 @@ def make_perm(self, n, seed=None): Permutation([1, 0, 3, 2]) >>> h.make_perm(3, [0, 1, 0]) Permutation([2, 0, 3, 1]) + >>> h.make_perm([0, 1, 0]) + Permutation([2, 0, 3, 1]) """ - from sympy.utilities.randtest import _randrange + if is_sequence(n): + if is_sequence(seed): + raise ValueError('If n is a sequence, seed should be None') + n, seed = len(n), n randrange = _randrange(seed) # start with the identity permutation @@ -402,11 +487,17 @@ def rotate(self, perm): ======== >>> from sympy.combinatorics import Polyhedron, Permutation - >>> h = Polyhedron(list('abcde')) - >>> h.rotate(Permutation([3, 0, 1, 2, 4])) + >>> shadow = h = Polyhedron(list('abcde')) + >>> p = Permutation([3, 0, 1, 2, 4]) + >>> h.rotate(p) >>> h.corners (d, a, b, c, e) - + >>> _ == shadow.corners + True + >>> copy = h.copy() + >>> h.rotate(p) + >>> h.corners == copy.corners + False """ if perm.size != self.size: raise ValueError("The size of the permutation and polyhedron must match.") @@ -415,24 +506,153 @@ def rotate(self, perm): temp.append(self.corners[perm.array_form[i]]) self._corners = tuple(temp) -tetrahedron = ([0, 1, 2, 3],[[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 2, 3]]) -cube = ([0, 1, 2, 3, 4, 5, 6, 7], [ - [0, 1, 2, 3], - [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [0, 3, 7, 4], - [4, 5, 6, 7]]) -octahedron = ([0, 1, 2, 3, 4, 5], [ - [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 1, 4], - [1, 2, 5], [2, 3, 5], [3, 4, 5], [1, 4, 5]]) -icosahedron = ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [ - [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 1, 5], - [1, 2, 6], [2, 3, 7], [3, 4, 8], [4, 5, 9], [1, 5, 10], - [2, 6, 7], [3, 7, 8], [4, 8, 9], [5, 9, 10], [1, 6, 10], - [6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [6, 10, 11]]) -dodecahedron = ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], [ - [0, 1, 2, 3, 4], - [0, 1, 6, 11, 5], [1, 2, 7, 12, 6], [2, 3, 8, 13, 7], - [3, 4, 9, 14, 8], [0, 4, 9, 10, 5], - [5, 10, 15, 16, 11], [6, 11, 16, 17, 12], [7, 12, 17, 18, 13], - [8, 13, 18, 19, 14], [9, 10, 15, 19, 14], - [15, 16, 17, 18, 19]]) +def _pgroup_calcs(): + """ + Although only 2 permutations are needed for a polyhedron in order to + generate all the possible orientations, it is customary to give a + group of permutations (P0, P1, ...) such that powers of them alone are + able to generate the orientations, e.g. P0, P0**2, P0**3, P1, P1**2, + etc..., instead of mixed permutations (P0*P1**2*P0). The following + work was used to calculate the permutation group of the polyhedra. + """ + def _pgroups_of_double(polyh, ordered_faces, pgroup): + from sympy.utilities import unflatten, flatten + from sympy.ntheory.residue_ntheory import int_tested + n = len(ordered_faces[0]) + # the vertices of the double which sits inside a give polyhedron + # can be found by tracking the faces of the outer polyhedron. + # A map between face and the vertex of the double is made so that + # afater rotation the position of the vertices can be located + fmap = dict(zip(ordered_faces, + range(len(ordered_faces)))) + flat_faces = flatten(ordered_faces) + new_pgroup = [] + for i, p in enumerate(pgroup): + h = polyh.copy() + h.rotate(p) + c = h.corners + # reorder corners in the order they should appear when + # enumerating the faces + reorder = unflatten([c[j] for j in flat_faces], n) + # make them canonical + reorder = [tuple(int_tested(minlex(f))) for f in reorder] + # map face to vertex: the resulting list of vertices are the + # permutation that we seek for the double + new_pgroup.append(Permutation([fmap[f] for f in reorder])) + return new_pgroup + + tetrahedron_faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)] + + _t_pgroups = [ + Permutation([[0,1,2], [3]]),\ + Permutation([[0,1,3], [2]]),\ + Permutation([[0,2,3], [1]]),\ + Permutation([[1,2,3], [0]]),\ + Permutation([[0,1], [2,3]]),\ + Permutation([[0,2], [1,3]]),\ + Permutation([[0,3], [1,2]])] + + tetrahedron = Polyhedron( + range(4), + tetrahedron_faces, + _t_pgroups) + + cube_faces = [ + (0, 1, 2, 3), + (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4), + (4, 5, 6, 7)] + + _c_pgroups = [Permutation(p) for p in + [[1,2,3,0,5,6,7,4], + [4,0,3,7,5,1,2,6], + [4,5,1,0,7,6,2,3], + + [1,0,4,5,2,3,7,6], + [6,2,1,5,7,3,0,4], + [6,7,3,2,5,4,0,1], + [3,7,4,0,2,6,5,1], + [6,5,4,7,2,1,0,3], + [4,7,6,5,0,3,2,1], + + [0,3,7,4,1,2,6,5], + [5,1,0,4,6,2,3,7], + [5,6,2,1,4,7,3,0], + [7,4,0,3,6,5,1,2]]] + + cube = Polyhedron( + range(8), + cube_faces, + _c_pgroups) + + octahedron_faces = [ + (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4), + (1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5)] + + octahedron = Polyhedron( + range(6), + octahedron_faces, + _pgroups_of_double(cube, cube_faces, _c_pgroups)) + + dodecahedron_faces = [ + (0,1,2,3,4), + (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), + (5,10,16,15,14), (6,10,16,17,11), (7,11,17,18,12), + (8,12,18,19,13), (9,13,19,15,14), + (15,16,17,18,19)] + + def _string_to_perm(s): + rv = Permutation(range(20)) + last = None + for si in s: + if si == '0': + rv *= _f0 + last = _f0 + elif si == '1': + rv *= _f1 + last = _f1 + else: + for i in range(int(si) - 1): + rv *= last + return rv + # top face cw + _f0 = Permutation([ + 1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11, + 12, 13, 14, 10, 16, 17, 18, 19, 15]) + # front face cw + _f1 = Permutation([ + 5, 0, 4, 9, 14, 10, 1, 3, 13, 15, + 6, 2, 8, 19, 16, 17, 11, 7, 12, 18]) + # the strings below, like 0104 are shorthand for F0*F1*F0**4 and are + # the remaining 4 face rotations, 15 edge permutations, and the + # 10 vertex rotations. + _dodeca_pgroups = [_f0, _f1] + [_string_to_perm(s) for s in ''' + 0104 140 014 0410 + 010 1403 03104 04103 102 + 120 1304 01303 021302 03130 + 0412041 041204103 04120410 041204104 041204102 + 10 01 1402 0140 04102 0412 1204 1302 0130 03120'''.strip().split()] + + dodecahedron = Polyhedron( + range(20), + dodecahedron_faces, + _dodeca_pgroups) + + icosahedron_faces = [ + [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 1, 5], + [1, 6, 7], [1, 2, 7], [2,7, 8], [2,3,8 ], [3, 8, 9 ], + [3, 4, 9], [4,9,10 ], [4, 5,10], [5, 6, 10], [1, 5, 6 ], + [6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [6, 10, 11]] + + icosahedron = Polyhedron( + range(12), + icosahedron_faces, + _pgroups_of_double(dodecahedron, dodecahedron_faces, _dodeca_pgroups)) + + return (tetrahedron, cube, octahedron, dodecahedron, icosahedron, + tetrahedron_faces, cube_faces, octahedron_faces, + dodecahedron_faces, icosahedron_faces) + +(tetrahedron, cube, octahedron, dodecahedron, icosahedron, +tetrahedron_faces, cube_faces, octahedron_faces, +dodecahedron_faces, icosahedron_faces) = _pgroup_calcs()
2 sympy/combinatorics/tests/test_perm_groups.py
 @@ -2,7 +2,7 @@ from sympy.combinatorics.group_constructs import DirectProduct from sympy.combinatorics.named_groups import SymmetricGroup, CyclicGroup,\ DihedralGroup, AlternatingGroup, AbelianGroup -from sympy.combinatorics.permutations import Permutation, _af_mul, cyclic +from sympy.combinatorics.permutations import Permutation, _af_mul from sympy.utilities.pytest import raises, skip, XFAIL from sympy.combinatorics.generators import rubik_cube_generators import random
10 sympy/combinatorics/tests/test_permutations.py
 @@ -1,6 +1,6 @@ from sympy.core import FiniteSet from sympy.combinatorics.permutations import (Permutation, _af_parity, - _af_mul, _af_mul, cyclic, one_based) + _af_mul, _af_mul, full_cyclic_form0, cyclic_form1) from sympy.utilities.pytest import raises @@ -19,10 +19,10 @@ def test_Permutation(): assert _af_mul([2, 5, 1, 6, 3, 0, 4], [3, 1, 4, 5, 0, 6, 2]) == \ [6, 5, 3, 0, 2, 4, 1] - assert cyclic([(2,3,5)], 5) == [[1, 2, 4], [0], [3]] - assert cyclic([(2,3,5)], 3) == [[1, 2, 4], [0]] - assert one_based([[0, 1], [2]], singletons=True) == [[1, 2], [3]] - assert one_based([[0, 1], [2]], singletons=False) == [[1, 2]] + assert full_cyclic_form0([(2,3,5)], 5) == [[1, 2, 4], [0], [3]] + assert full_cyclic_form0([(2,3,5)], 3) == [[1, 2, 4], [0]] + assert cyclic_form1([[0, 1], [2]], singletons=True) == [[1, 2], [3]] + assert cyclic_form1([[0, 1], [2]], singletons=False) == [[1, 2]] assert (Permutation([[1,2,3],[0,4]])*Permutation([[1,2,4],[0],[3]])).cyclic_form == \ [[0, 4, 2], [1, 3]] assert q.array_form == [3, 1, 4, 5, 0, 6, 2]
56 sympy/combinatorics/tests/test_polyhedron.py
 @@ -1,11 +1,16 @@ from sympy import symbols, FiniteSet from sympy.combinatorics.polyhedron import (Polyhedron, - tetrahedron, cube as square, octahedron, dodecahedron, icosahedron) + tetrahedron, cube as square, octahedron, dodecahedron, icosahedron, minlex, + cube_faces) from sympy.combinatorics.permutations import Permutation import random -C1, C2, C3, C4, C5, C6, C7, C8, C9 = range(9) +def test_minlex(): + p = Permutation(range(3)) + while p: + assert minlex(p.array_form) == (0, 1, 2) + p = p.next_lex() def test_polyhedron(): pgroup = [Permutation([[0,7,2,5],[6,1,4,3]]),\ @@ -22,17 +27,13 @@ def test_polyhedron(): Permutation([[4,1],[0,5],[6,2],[7,3]]),\ Permutation([[7,2],[3,6],[0,4],[1,5]]),\ Permutation([0,1,2,3,4,5,6,7])] - - faces = ((C1,C8,C3,C6),(C1,C8,C2,C7),(C2,C5,C3,C8), - (C2,C5,C4,C7),(C4,C7,C1,C6),(C3,C5,C4,C6)) - - corners = tuple(symbols('A:H')) + faces = cube_faces cube = Polyhedron(corners, faces, pgroup) - assert cube.size == 8 - assert cube.edges == FiniteSet(*[(C1, C6), (C1, C7), (C1, C8), (C2, C5), (C2, C7), - (C2, C8), (C3, C5), (C3, C6), (C3, C8), (C4, C5), (C4, C6), (C4, C7)]) + assert cube.edges == FiniteSet(*( + (0, 1), (6, 7), (1, 2), (5, 6), (0, 3), (2, 3), + (4, 7), (4, 5), (3, 7), (1, 5), (0, 4), (2, 6))) for i in xrange(3): # add 180 degree face rotations cube.rotate(cube.pgroups[i]**2) @@ -46,6 +47,35 @@ def test_polyhedron(): assert cube.make_perm(5, seed=range(5)) == Permutation([4, 5, 7, 6, 0, 1, 3, 2]) assert cube.make_perm(7, seed=range(7)) == Permutation([5, 4, 6, 7, 1, 0, 2, 3]) - assert all(len(f) + len(v) - len(Polyhedron(v, f).edges) == 2 - for v, f in (tetrahedron, square, octahedron, - dodecahedron, icosahedron)) + + + def check(h, size, rpt, target): + + assert len(h.faces) + len(h.vertices) - len(h.edges) == 2 + assert h.size == size + + got = set() + for p in h.pgroups: + # make sure it restores original + P = h.copy() + hit = P.corners + for i in range(rpt): + P.rotate(p) + if P.corners == hit: + break + else: + print 'error in permutation', p.array_form + for i in range(rpt): + P.rotate(p) + got.add(tuple(P.corners)) + c = P.corners + f = [[c[i] for i in f] for f in P.faces] + assert h.faces == Polyhedron(c, f).faces + assert len(got) == target + + for h, size, rpt, target in zip( + (tetrahedron, square, octahedron, dodecahedron, icosahedron), + (4, 8, 6, 20, 12), + (3, 4, 4, 5, 5), + (12, 24, 24, 60, 60)): + check(h, size, rpt, target)
2 sympy/core/basic.py
 @@ -85,6 +85,8 @@ def __new__(cls, *args): obj._args = args # all items in args must be Basic objects return obj + def copy(self): + return self.func(*self.args) def __reduce_ex__(self, proto): """ Pickling support."""