Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/sympy/combinatorics/__init__.py +43 -0
- lib/python3.12/site-packages/sympy/combinatorics/coset_table.py +1255 -0
- lib/python3.12/site-packages/sympy/combinatorics/fp_groups.py +1354 -0
- lib/python3.12/site-packages/sympy/combinatorics/free_groups.py +1354 -0
- lib/python3.12/site-packages/sympy/combinatorics/galois.py +611 -0
- lib/python3.12/site-packages/sympy/combinatorics/generators.py +302 -0
- lib/python3.12/site-packages/sympy/combinatorics/graycode.py +430 -0
- lib/python3.12/site-packages/sympy/combinatorics/group_constructs.py +61 -0
- lib/python3.12/site-packages/sympy/combinatorics/homomorphisms.py +549 -0
- lib/python3.12/site-packages/sympy/combinatorics/named_groups.py +332 -0
- lib/python3.12/site-packages/sympy/combinatorics/pc_groups.py +709 -0
- lib/python3.12/site-packages/sympy/combinatorics/perm_groups.py +0 -0
- lib/python3.12/site-packages/sympy/combinatorics/permutations.py +3115 -0
- lib/python3.12/site-packages/sympy/combinatorics/polyhedron.py +1019 -0
- lib/python3.12/site-packages/sympy/combinatorics/prufer.py +435 -0
- lib/python3.12/site-packages/sympy/combinatorics/rewritingsystem_fsm.py +60 -0
- lib/python3.12/site-packages/sympy/combinatorics/schur_number.py +160 -0
- lib/python3.12/site-packages/sympy/combinatorics/subsets.py +619 -0
- lib/python3.12/site-packages/sympy/combinatorics/tensor_can.py +1190 -0
- lib/python3.12/site-packages/sympy/combinatorics/testutil.py +357 -0
- lib/python3.12/site-packages/sympy/combinatorics/util.py +532 -0
- lib/python3.12/site-packages/sympy/functions/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/__init__.py +1 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/__pycache__/factorials.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/factorials.py +1133 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/numbers.py +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__init__.py +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_factorials.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/tests/test_comb_factorials.py +653 -0
- lib/python3.12/site-packages/sympy/functions/combinatorial/tests/test_comb_numbers.py +1241 -0
- lib/python3.12/site-packages/sympy/functions/special/__pycache__/elliptic_integrals.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__init__.py +23 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/acceleration.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/approximants.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/aseries.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/formal.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/fourier.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/gruntz.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/kauers.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/limits.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/limitseq.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/order.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/residues.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/sequences.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/series.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/__pycache__/series_class.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/series/acceleration.py +101 -0
lib/python3.12/site-packages/sympy/combinatorics/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.permutations import Permutation, Cycle
|
| 2 |
+
from sympy.combinatorics.prufer import Prufer
|
| 3 |
+
from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral
|
| 4 |
+
from sympy.combinatorics.subsets import Subset
|
| 5 |
+
from sympy.combinatorics.partitions import (Partition, IntegerPartition,
|
| 6 |
+
RGS_rank, RGS_unrank, RGS_enum)
|
| 7 |
+
from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube,
|
| 8 |
+
octahedron, dodecahedron, icosahedron)
|
| 9 |
+
from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup
|
| 10 |
+
from sympy.combinatorics.group_constructs import DirectProduct
|
| 11 |
+
from sympy.combinatorics.graycode import GrayCode
|
| 12 |
+
from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup,
|
| 13 |
+
CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup)
|
| 14 |
+
from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector
|
| 15 |
+
from sympy.combinatorics.free_groups import free_group
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
'Permutation', 'Cycle',
|
| 19 |
+
|
| 20 |
+
'Prufer',
|
| 21 |
+
|
| 22 |
+
'cyclic', 'alternating', 'symmetric', 'dihedral',
|
| 23 |
+
|
| 24 |
+
'Subset',
|
| 25 |
+
|
| 26 |
+
'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum',
|
| 27 |
+
|
| 28 |
+
'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron',
|
| 29 |
+
'icosahedron',
|
| 30 |
+
|
| 31 |
+
'PermutationGroup', 'Coset', 'SymmetricPermutationGroup',
|
| 32 |
+
|
| 33 |
+
'DirectProduct',
|
| 34 |
+
|
| 35 |
+
'GrayCode',
|
| 36 |
+
|
| 37 |
+
'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup',
|
| 38 |
+
'AbelianGroup', 'RubikGroup',
|
| 39 |
+
|
| 40 |
+
'PolycyclicGroup', 'Collector',
|
| 41 |
+
|
| 42 |
+
'free_group',
|
| 43 |
+
]
|
lib/python3.12/site-packages/sympy/combinatorics/coset_table.py
ADDED
|
@@ -0,0 +1,1255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.free_groups import free_group
|
| 2 |
+
from sympy.printing.defaults import DefaultPrinting
|
| 3 |
+
|
| 4 |
+
from itertools import chain, product
|
| 5 |
+
from bisect import bisect_left
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
###############################################################################
|
| 9 |
+
# COSET TABLE #
|
| 10 |
+
###############################################################################
|
| 11 |
+
|
| 12 |
+
class CosetTable(DefaultPrinting):
|
| 13 |
+
# coset_table: Mathematically a coset table
|
| 14 |
+
# represented using a list of lists
|
| 15 |
+
# alpha: Mathematically a coset (precisely, a live coset)
|
| 16 |
+
# represented by an integer between i with 1 <= i <= n
|
| 17 |
+
# alpha in c
|
| 18 |
+
# x: Mathematically an element of "A" (set of generators and
|
| 19 |
+
# their inverses), represented using "FpGroupElement"
|
| 20 |
+
# fp_grp: Finitely Presented Group with < X|R > as presentation.
|
| 21 |
+
# H: subgroup of fp_grp.
|
| 22 |
+
# NOTE: We start with H as being only a list of words in generators
|
| 23 |
+
# of "fp_grp". Since `.subgroup` method has not been implemented.
|
| 24 |
+
|
| 25 |
+
r"""
|
| 26 |
+
|
| 27 |
+
Properties
|
| 28 |
+
==========
|
| 29 |
+
|
| 30 |
+
[1] `0 \in \Omega` and `\tau(1) = \epsilon`
|
| 31 |
+
[2] `\alpha^x = \beta \Leftrightarrow \beta^{x^{-1}} = \alpha`
|
| 32 |
+
[3] If `\alpha^x = \beta`, then `H \tau(\alpha)x = H \tau(\beta)`
|
| 33 |
+
[4] `\forall \alpha \in \Omega, 1^{\tau(\alpha)} = \alpha`
|
| 34 |
+
|
| 35 |
+
References
|
| 36 |
+
==========
|
| 37 |
+
|
| 38 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 39 |
+
"Handbook of Computational Group Theory"
|
| 40 |
+
|
| 41 |
+
.. [2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
|
| 42 |
+
Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
|
| 43 |
+
"Implementation and Analysis of the Todd-Coxeter Algorithm"
|
| 44 |
+
|
| 45 |
+
"""
|
| 46 |
+
# default limit for the number of cosets allowed in a
|
| 47 |
+
# coset enumeration.
|
| 48 |
+
coset_table_max_limit = 4096000
|
| 49 |
+
# limit for the current instance
|
| 50 |
+
coset_table_limit = None
|
| 51 |
+
# maximum size of deduction stack above or equal to
|
| 52 |
+
# which it is emptied
|
| 53 |
+
max_stack_size = 100
|
| 54 |
+
|
| 55 |
+
def __init__(self, fp_grp, subgroup, max_cosets=None):
|
| 56 |
+
if not max_cosets:
|
| 57 |
+
max_cosets = CosetTable.coset_table_max_limit
|
| 58 |
+
self.fp_group = fp_grp
|
| 59 |
+
self.subgroup = subgroup
|
| 60 |
+
self.coset_table_limit = max_cosets
|
| 61 |
+
# "p" is setup independent of Omega and n
|
| 62 |
+
self.p = [0]
|
| 63 |
+
# a list of the form `[gen_1, gen_1^{-1}, ... , gen_k, gen_k^{-1}]`
|
| 64 |
+
self.A = list(chain.from_iterable((gen, gen**-1) \
|
| 65 |
+
for gen in self.fp_group.generators))
|
| 66 |
+
#P[alpha, x] Only defined when alpha^x is defined.
|
| 67 |
+
self.P = [[None]*len(self.A)]
|
| 68 |
+
# the mathematical coset table which is a list of lists
|
| 69 |
+
self.table = [[None]*len(self.A)]
|
| 70 |
+
self.A_dict = {x: self.A.index(x) for x in self.A}
|
| 71 |
+
self.A_dict_inv = {}
|
| 72 |
+
for x, index in self.A_dict.items():
|
| 73 |
+
if index % 2 == 0:
|
| 74 |
+
self.A_dict_inv[x] = self.A_dict[x] + 1
|
| 75 |
+
else:
|
| 76 |
+
self.A_dict_inv[x] = self.A_dict[x] - 1
|
| 77 |
+
# used in the coset-table based method of coset enumeration. Each of
|
| 78 |
+
# the element is called a "deduction" which is the form (alpha, x) whenever
|
| 79 |
+
# a value is assigned to alpha^x during a definition or "deduction process"
|
| 80 |
+
self.deduction_stack = []
|
| 81 |
+
# Attributes for modified methods.
|
| 82 |
+
H = self.subgroup
|
| 83 |
+
self._grp = free_group(', ' .join(["a_%d" % i for i in range(len(H))]))[0]
|
| 84 |
+
self.P = [[None]*len(self.A)]
|
| 85 |
+
self.p_p = {}
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def omega(self):
|
| 89 |
+
"""Set of live cosets. """
|
| 90 |
+
return [coset for coset in range(len(self.p)) if self.p[coset] == coset]
|
| 91 |
+
|
| 92 |
+
def copy(self):
|
| 93 |
+
"""
|
| 94 |
+
Return a shallow copy of Coset Table instance ``self``.
|
| 95 |
+
|
| 96 |
+
"""
|
| 97 |
+
self_copy = self.__class__(self.fp_group, self.subgroup)
|
| 98 |
+
self_copy.table = [list(perm_rep) for perm_rep in self.table]
|
| 99 |
+
self_copy.p = list(self.p)
|
| 100 |
+
self_copy.deduction_stack = list(self.deduction_stack)
|
| 101 |
+
return self_copy
|
| 102 |
+
|
| 103 |
+
def __str__(self):
|
| 104 |
+
return "Coset Table on %s with %s as subgroup generators" \
|
| 105 |
+
% (self.fp_group, self.subgroup)
|
| 106 |
+
|
| 107 |
+
__repr__ = __str__
|
| 108 |
+
|
| 109 |
+
@property
|
| 110 |
+
def n(self):
|
| 111 |
+
"""The number `n` represents the length of the sublist containing the
|
| 112 |
+
live cosets.
|
| 113 |
+
|
| 114 |
+
"""
|
| 115 |
+
if not self.table:
|
| 116 |
+
return 0
|
| 117 |
+
return max(self.omega) + 1
|
| 118 |
+
|
| 119 |
+
# Pg. 152 [1]
|
| 120 |
+
def is_complete(self):
|
| 121 |
+
r"""
|
| 122 |
+
The coset table is called complete if it has no undefined entries
|
| 123 |
+
on the live cosets; that is, `\alpha^x` is defined for all
|
| 124 |
+
`\alpha \in \Omega` and `x \in A`.
|
| 125 |
+
|
| 126 |
+
"""
|
| 127 |
+
return not any(None in self.table[coset] for coset in self.omega)
|
| 128 |
+
|
| 129 |
+
# Pg. 153 [1]
|
| 130 |
+
def define(self, alpha, x, modified=False):
|
| 131 |
+
r"""
|
| 132 |
+
This routine is used in the relator-based strategy of Todd-Coxeter
|
| 133 |
+
algorithm if some `\alpha^x` is undefined. We check whether there is
|
| 134 |
+
space available for defining a new coset. If there is enough space
|
| 135 |
+
then we remedy this by adjoining a new coset `\beta` to `\Omega`
|
| 136 |
+
(i.e to set of live cosets) and put that equal to `\alpha^x`, then
|
| 137 |
+
make an assignment satisfying Property[1]. If there is not enough space
|
| 138 |
+
then we halt the Coset Table creation. The maximum amount of space that
|
| 139 |
+
can be used by Coset Table can be manipulated using the class variable
|
| 140 |
+
``CosetTable.coset_table_max_limit``.
|
| 141 |
+
|
| 142 |
+
See Also
|
| 143 |
+
========
|
| 144 |
+
|
| 145 |
+
define_c
|
| 146 |
+
|
| 147 |
+
"""
|
| 148 |
+
A = self.A
|
| 149 |
+
table = self.table
|
| 150 |
+
len_table = len(table)
|
| 151 |
+
if len_table >= self.coset_table_limit:
|
| 152 |
+
# abort the further generation of cosets
|
| 153 |
+
raise ValueError("the coset enumeration has defined more than "
|
| 154 |
+
"%s cosets. Try with a greater value max number of cosets "
|
| 155 |
+
% self.coset_table_limit)
|
| 156 |
+
table.append([None]*len(A))
|
| 157 |
+
self.P.append([None]*len(self.A))
|
| 158 |
+
# beta is the new coset generated
|
| 159 |
+
beta = len_table
|
| 160 |
+
self.p.append(beta)
|
| 161 |
+
table[alpha][self.A_dict[x]] = beta
|
| 162 |
+
table[beta][self.A_dict_inv[x]] = alpha
|
| 163 |
+
# P[alpha][x] = epsilon, P[beta][x**-1] = epsilon
|
| 164 |
+
if modified:
|
| 165 |
+
self.P[alpha][self.A_dict[x]] = self._grp.identity
|
| 166 |
+
self.P[beta][self.A_dict_inv[x]] = self._grp.identity
|
| 167 |
+
self.p_p[beta] = self._grp.identity
|
| 168 |
+
|
| 169 |
+
def define_c(self, alpha, x):
|
| 170 |
+
r"""
|
| 171 |
+
A variation of ``define`` routine, described on Pg. 165 [1], used in
|
| 172 |
+
the coset table-based strategy of Todd-Coxeter algorithm. It differs
|
| 173 |
+
from ``define`` routine in that for each definition it also adds the
|
| 174 |
+
tuple `(\alpha, x)` to the deduction stack.
|
| 175 |
+
|
| 176 |
+
See Also
|
| 177 |
+
========
|
| 178 |
+
|
| 179 |
+
define
|
| 180 |
+
|
| 181 |
+
"""
|
| 182 |
+
A = self.A
|
| 183 |
+
table = self.table
|
| 184 |
+
len_table = len(table)
|
| 185 |
+
if len_table >= self.coset_table_limit:
|
| 186 |
+
# abort the further generation of cosets
|
| 187 |
+
raise ValueError("the coset enumeration has defined more than "
|
| 188 |
+
"%s cosets. Try with a greater value max number of cosets "
|
| 189 |
+
% self.coset_table_limit)
|
| 190 |
+
table.append([None]*len(A))
|
| 191 |
+
# beta is the new coset generated
|
| 192 |
+
beta = len_table
|
| 193 |
+
self.p.append(beta)
|
| 194 |
+
table[alpha][self.A_dict[x]] = beta
|
| 195 |
+
table[beta][self.A_dict_inv[x]] = alpha
|
| 196 |
+
# append to deduction stack
|
| 197 |
+
self.deduction_stack.append((alpha, x))
|
| 198 |
+
|
| 199 |
+
def scan_c(self, alpha, word):
|
| 200 |
+
"""
|
| 201 |
+
A variation of ``scan`` routine, described on pg. 165 of [1], which
|
| 202 |
+
puts at tuple, whenever a deduction occurs, to deduction stack.
|
| 203 |
+
|
| 204 |
+
See Also
|
| 205 |
+
========
|
| 206 |
+
|
| 207 |
+
scan, scan_check, scan_and_fill, scan_and_fill_c
|
| 208 |
+
|
| 209 |
+
"""
|
| 210 |
+
# alpha is an integer representing a "coset"
|
| 211 |
+
# since scanning can be in two cases
|
| 212 |
+
# 1. for alpha=0 and w in Y (i.e generating set of H)
|
| 213 |
+
# 2. alpha in Omega (set of live cosets), w in R (relators)
|
| 214 |
+
A_dict = self.A_dict
|
| 215 |
+
A_dict_inv = self.A_dict_inv
|
| 216 |
+
table = self.table
|
| 217 |
+
f = alpha
|
| 218 |
+
i = 0
|
| 219 |
+
r = len(word)
|
| 220 |
+
b = alpha
|
| 221 |
+
j = r - 1
|
| 222 |
+
# list of union of generators and their inverses
|
| 223 |
+
while i <= j and table[f][A_dict[word[i]]] is not None:
|
| 224 |
+
f = table[f][A_dict[word[i]]]
|
| 225 |
+
i += 1
|
| 226 |
+
if i > j:
|
| 227 |
+
if f != b:
|
| 228 |
+
self.coincidence_c(f, b)
|
| 229 |
+
return
|
| 230 |
+
while j >= i and table[b][A_dict_inv[word[j]]] is not None:
|
| 231 |
+
b = table[b][A_dict_inv[word[j]]]
|
| 232 |
+
j -= 1
|
| 233 |
+
if j < i:
|
| 234 |
+
# we have an incorrect completed scan with coincidence f ~ b
|
| 235 |
+
# run the "coincidence" routine
|
| 236 |
+
self.coincidence_c(f, b)
|
| 237 |
+
elif j == i:
|
| 238 |
+
# deduction process
|
| 239 |
+
table[f][A_dict[word[i]]] = b
|
| 240 |
+
table[b][A_dict_inv[word[i]]] = f
|
| 241 |
+
self.deduction_stack.append((f, word[i]))
|
| 242 |
+
# otherwise scan is incomplete and yields no information
|
| 243 |
+
|
| 244 |
+
# alpha, beta coincide, i.e. alpha, beta represent the pair of cosets where
|
| 245 |
+
# coincidence occurs
|
| 246 |
+
def coincidence_c(self, alpha, beta):
|
| 247 |
+
"""
|
| 248 |
+
A variation of ``coincidence`` routine used in the coset-table based
|
| 249 |
+
method of coset enumeration. The only difference being on addition of
|
| 250 |
+
a new coset in coset table(i.e new coset introduction), then it is
|
| 251 |
+
appended to ``deduction_stack``.
|
| 252 |
+
|
| 253 |
+
See Also
|
| 254 |
+
========
|
| 255 |
+
|
| 256 |
+
coincidence
|
| 257 |
+
|
| 258 |
+
"""
|
| 259 |
+
A_dict = self.A_dict
|
| 260 |
+
A_dict_inv = self.A_dict_inv
|
| 261 |
+
table = self.table
|
| 262 |
+
# behaves as a queue
|
| 263 |
+
q = []
|
| 264 |
+
self.merge(alpha, beta, q)
|
| 265 |
+
while len(q) > 0:
|
| 266 |
+
gamma = q.pop(0)
|
| 267 |
+
for x in A_dict:
|
| 268 |
+
delta = table[gamma][A_dict[x]]
|
| 269 |
+
if delta is not None:
|
| 270 |
+
table[delta][A_dict_inv[x]] = None
|
| 271 |
+
# only line of difference from ``coincidence`` routine
|
| 272 |
+
self.deduction_stack.append((delta, x**-1))
|
| 273 |
+
mu = self.rep(gamma)
|
| 274 |
+
nu = self.rep(delta)
|
| 275 |
+
if table[mu][A_dict[x]] is not None:
|
| 276 |
+
self.merge(nu, table[mu][A_dict[x]], q)
|
| 277 |
+
elif table[nu][A_dict_inv[x]] is not None:
|
| 278 |
+
self.merge(mu, table[nu][A_dict_inv[x]], q)
|
| 279 |
+
else:
|
| 280 |
+
table[mu][A_dict[x]] = nu
|
| 281 |
+
table[nu][A_dict_inv[x]] = mu
|
| 282 |
+
|
| 283 |
+
def scan(self, alpha, word, y=None, fill=False, modified=False):
|
| 284 |
+
r"""
|
| 285 |
+
``scan`` performs a scanning process on the input ``word``.
|
| 286 |
+
It first locates the largest prefix ``s`` of ``word`` for which
|
| 287 |
+
`\alpha^s` is defined (i.e is not ``None``), ``s`` may be empty. Let
|
| 288 |
+
``word=sv``, let ``t`` be the longest suffix of ``v`` for which
|
| 289 |
+
`\alpha^{t^{-1}}` is defined, and let ``v=ut``. Then three
|
| 290 |
+
possibilities are there:
|
| 291 |
+
|
| 292 |
+
1. If ``t=v``, then we say that the scan completes, and if, in addition
|
| 293 |
+
`\alpha^s = \alpha^{t^{-1}}`, then we say that the scan completes
|
| 294 |
+
correctly.
|
| 295 |
+
|
| 296 |
+
2. It can also happen that scan does not complete, but `|u|=1`; that
|
| 297 |
+
is, the word ``u`` consists of a single generator `x \in A`. In that
|
| 298 |
+
case, if `\alpha^s = \beta` and `\alpha^{t^{-1}} = \gamma`, then we can
|
| 299 |
+
set `\beta^x = \gamma` and `\gamma^{x^{-1}} = \beta`. These assignments
|
| 300 |
+
are known as deductions and enable the scan to complete correctly.
|
| 301 |
+
|
| 302 |
+
3. See ``coicidence`` routine for explanation of third condition.
|
| 303 |
+
|
| 304 |
+
Notes
|
| 305 |
+
=====
|
| 306 |
+
|
| 307 |
+
The code for the procedure of scanning `\alpha \in \Omega`
|
| 308 |
+
under `w \in A*` is defined on pg. 155 [1]
|
| 309 |
+
|
| 310 |
+
See Also
|
| 311 |
+
========
|
| 312 |
+
|
| 313 |
+
scan_c, scan_check, scan_and_fill, scan_and_fill_c
|
| 314 |
+
|
| 315 |
+
Scan and Fill
|
| 316 |
+
=============
|
| 317 |
+
|
| 318 |
+
Performed when the default argument fill=True.
|
| 319 |
+
|
| 320 |
+
Modified Scan
|
| 321 |
+
=============
|
| 322 |
+
|
| 323 |
+
Performed when the default argument modified=True
|
| 324 |
+
|
| 325 |
+
"""
|
| 326 |
+
# alpha is an integer representing a "coset"
|
| 327 |
+
# since scanning can be in two cases
|
| 328 |
+
# 1. for alpha=0 and w in Y (i.e generating set of H)
|
| 329 |
+
# 2. alpha in Omega (set of live cosets), w in R (relators)
|
| 330 |
+
A_dict = self.A_dict
|
| 331 |
+
A_dict_inv = self.A_dict_inv
|
| 332 |
+
table = self.table
|
| 333 |
+
f = alpha
|
| 334 |
+
i = 0
|
| 335 |
+
r = len(word)
|
| 336 |
+
b = alpha
|
| 337 |
+
j = r - 1
|
| 338 |
+
b_p = y
|
| 339 |
+
if modified:
|
| 340 |
+
f_p = self._grp.identity
|
| 341 |
+
flag = 0
|
| 342 |
+
while fill or flag == 0:
|
| 343 |
+
flag = 1
|
| 344 |
+
while i <= j and table[f][A_dict[word[i]]] is not None:
|
| 345 |
+
if modified:
|
| 346 |
+
f_p = f_p*self.P[f][A_dict[word[i]]]
|
| 347 |
+
f = table[f][A_dict[word[i]]]
|
| 348 |
+
i += 1
|
| 349 |
+
if i > j:
|
| 350 |
+
if f != b:
|
| 351 |
+
if modified:
|
| 352 |
+
self.modified_coincidence(f, b, f_p**-1*y)
|
| 353 |
+
else:
|
| 354 |
+
self.coincidence(f, b)
|
| 355 |
+
return
|
| 356 |
+
while j >= i and table[b][A_dict_inv[word[j]]] is not None:
|
| 357 |
+
if modified:
|
| 358 |
+
b_p = b_p*self.P[b][self.A_dict_inv[word[j]]]
|
| 359 |
+
b = table[b][A_dict_inv[word[j]]]
|
| 360 |
+
j -= 1
|
| 361 |
+
if j < i:
|
| 362 |
+
# we have an incorrect completed scan with coincidence f ~ b
|
| 363 |
+
# run the "coincidence" routine
|
| 364 |
+
if modified:
|
| 365 |
+
self.modified_coincidence(f, b, f_p**-1*b_p)
|
| 366 |
+
else:
|
| 367 |
+
self.coincidence(f, b)
|
| 368 |
+
elif j == i:
|
| 369 |
+
# deduction process
|
| 370 |
+
table[f][A_dict[word[i]]] = b
|
| 371 |
+
table[b][A_dict_inv[word[i]]] = f
|
| 372 |
+
if modified:
|
| 373 |
+
self.P[f][self.A_dict[word[i]]] = f_p**-1*b_p
|
| 374 |
+
self.P[b][self.A_dict_inv[word[i]]] = b_p**-1*f_p
|
| 375 |
+
return
|
| 376 |
+
elif fill:
|
| 377 |
+
self.define(f, word[i], modified=modified)
|
| 378 |
+
# otherwise scan is incomplete and yields no information
|
| 379 |
+
|
| 380 |
+
# used in the low-index subgroups algorithm
|
| 381 |
+
def scan_check(self, alpha, word):
|
| 382 |
+
r"""
|
| 383 |
+
Another version of ``scan`` routine, described on, it checks whether
|
| 384 |
+
`\alpha` scans correctly under `word`, it is a straightforward
|
| 385 |
+
modification of ``scan``. ``scan_check`` returns ``False`` (rather than
|
| 386 |
+
calling ``coincidence``) if the scan completes incorrectly; otherwise
|
| 387 |
+
it returns ``True``.
|
| 388 |
+
|
| 389 |
+
See Also
|
| 390 |
+
========
|
| 391 |
+
|
| 392 |
+
scan, scan_c, scan_and_fill, scan_and_fill_c
|
| 393 |
+
|
| 394 |
+
"""
|
| 395 |
+
# alpha is an integer representing a "coset"
|
| 396 |
+
# since scanning can be in two cases
|
| 397 |
+
# 1. for alpha=0 and w in Y (i.e generating set of H)
|
| 398 |
+
# 2. alpha in Omega (set of live cosets), w in R (relators)
|
| 399 |
+
A_dict = self.A_dict
|
| 400 |
+
A_dict_inv = self.A_dict_inv
|
| 401 |
+
table = self.table
|
| 402 |
+
f = alpha
|
| 403 |
+
i = 0
|
| 404 |
+
r = len(word)
|
| 405 |
+
b = alpha
|
| 406 |
+
j = r - 1
|
| 407 |
+
while i <= j and table[f][A_dict[word[i]]] is not None:
|
| 408 |
+
f = table[f][A_dict[word[i]]]
|
| 409 |
+
i += 1
|
| 410 |
+
if i > j:
|
| 411 |
+
return f == b
|
| 412 |
+
while j >= i and table[b][A_dict_inv[word[j]]] is not None:
|
| 413 |
+
b = table[b][A_dict_inv[word[j]]]
|
| 414 |
+
j -= 1
|
| 415 |
+
if j < i:
|
| 416 |
+
# we have an incorrect completed scan with coincidence f ~ b
|
| 417 |
+
# return False, instead of calling coincidence routine
|
| 418 |
+
return False
|
| 419 |
+
elif j == i:
|
| 420 |
+
# deduction process
|
| 421 |
+
table[f][A_dict[word[i]]] = b
|
| 422 |
+
table[b][A_dict_inv[word[i]]] = f
|
| 423 |
+
return True
|
| 424 |
+
|
| 425 |
+
def merge(self, k, lamda, q, w=None, modified=False):
|
| 426 |
+
"""
|
| 427 |
+
Merge two classes with representatives ``k`` and ``lamda``, described
|
| 428 |
+
on Pg. 157 [1] (for pseudocode), start by putting ``p[k] = lamda``.
|
| 429 |
+
It is more efficient to choose the new representative from the larger
|
| 430 |
+
of the two classes being merged, i.e larger among ``k`` and ``lamda``.
|
| 431 |
+
procedure ``merge`` performs the merging operation, adds the deleted
|
| 432 |
+
class representative to the queue ``q``.
|
| 433 |
+
|
| 434 |
+
Parameters
|
| 435 |
+
==========
|
| 436 |
+
|
| 437 |
+
'k', 'lamda' being the two class representatives to be merged.
|
| 438 |
+
|
| 439 |
+
Notes
|
| 440 |
+
=====
|
| 441 |
+
|
| 442 |
+
Pg. 86-87 [1] contains a description of this method.
|
| 443 |
+
|
| 444 |
+
See Also
|
| 445 |
+
========
|
| 446 |
+
|
| 447 |
+
coincidence, rep
|
| 448 |
+
|
| 449 |
+
"""
|
| 450 |
+
p = self.p
|
| 451 |
+
rep = self.rep
|
| 452 |
+
phi = rep(k, modified=modified)
|
| 453 |
+
psi = rep(lamda, modified=modified)
|
| 454 |
+
if phi != psi:
|
| 455 |
+
mu = min(phi, psi)
|
| 456 |
+
v = max(phi, psi)
|
| 457 |
+
p[v] = mu
|
| 458 |
+
if modified:
|
| 459 |
+
if v == phi:
|
| 460 |
+
self.p_p[phi] = self.p_p[k]**-1*w*self.p_p[lamda]
|
| 461 |
+
else:
|
| 462 |
+
self.p_p[psi] = self.p_p[lamda]**-1*w**-1*self.p_p[k]
|
| 463 |
+
q.append(v)
|
| 464 |
+
|
| 465 |
+
def rep(self, k, modified=False):
|
| 466 |
+
r"""
|
| 467 |
+
Parameters
|
| 468 |
+
==========
|
| 469 |
+
|
| 470 |
+
`k \in [0 \ldots n-1]`, as for ``self`` only array ``p`` is used
|
| 471 |
+
|
| 472 |
+
Returns
|
| 473 |
+
=======
|
| 474 |
+
|
| 475 |
+
Representative of the class containing ``k``.
|
| 476 |
+
|
| 477 |
+
Returns the representative of `\sim` class containing ``k``, it also
|
| 478 |
+
makes some modification to array ``p`` of ``self`` to ease further
|
| 479 |
+
computations, described on Pg. 157 [1].
|
| 480 |
+
|
| 481 |
+
The information on classes under `\sim` is stored in array `p` of
|
| 482 |
+
``self`` argument, which will always satisfy the property:
|
| 483 |
+
|
| 484 |
+
`p[\alpha] \sim \alpha` and `p[\alpha]=\alpha \iff \alpha=rep(\alpha)`
|
| 485 |
+
`\forall \in [0 \ldots n-1]`.
|
| 486 |
+
|
| 487 |
+
So, for `\alpha \in [0 \ldots n-1]`, we find `rep(self, \alpha)` by
|
| 488 |
+
continually replacing `\alpha` by `p[\alpha]` until it becomes
|
| 489 |
+
constant (i.e satisfies `p[\alpha] = \alpha`):w
|
| 490 |
+
|
| 491 |
+
To increase the efficiency of later ``rep`` calculations, whenever we
|
| 492 |
+
find `rep(self, \alpha)=\beta`, we set
|
| 493 |
+
`p[\gamma] = \beta \forall \gamma \in p-chain` from `\alpha` to `\beta`
|
| 494 |
+
|
| 495 |
+
Notes
|
| 496 |
+
=====
|
| 497 |
+
|
| 498 |
+
``rep`` routine is also described on Pg. 85-87 [1] in Atkinson's
|
| 499 |
+
algorithm, this results from the fact that ``coincidence`` routine
|
| 500 |
+
introduces functionality similar to that introduced by the
|
| 501 |
+
``minimal_block`` routine on Pg. 85-87 [1].
|
| 502 |
+
|
| 503 |
+
See Also
|
| 504 |
+
========
|
| 505 |
+
|
| 506 |
+
coincidence, merge
|
| 507 |
+
|
| 508 |
+
"""
|
| 509 |
+
p = self.p
|
| 510 |
+
lamda = k
|
| 511 |
+
rho = p[lamda]
|
| 512 |
+
if modified:
|
| 513 |
+
s = p[:]
|
| 514 |
+
while rho != lamda:
|
| 515 |
+
if modified:
|
| 516 |
+
s[rho] = lamda
|
| 517 |
+
lamda = rho
|
| 518 |
+
rho = p[lamda]
|
| 519 |
+
if modified:
|
| 520 |
+
rho = s[lamda]
|
| 521 |
+
while rho != k:
|
| 522 |
+
mu = rho
|
| 523 |
+
rho = s[mu]
|
| 524 |
+
p[rho] = lamda
|
| 525 |
+
self.p_p[rho] = self.p_p[rho]*self.p_p[mu]
|
| 526 |
+
else:
|
| 527 |
+
mu = k
|
| 528 |
+
rho = p[mu]
|
| 529 |
+
while rho != lamda:
|
| 530 |
+
p[mu] = lamda
|
| 531 |
+
mu = rho
|
| 532 |
+
rho = p[mu]
|
| 533 |
+
return lamda
|
| 534 |
+
|
| 535 |
+
# alpha, beta coincide, i.e. alpha, beta represent the pair of cosets
|
| 536 |
+
# where coincidence occurs
|
| 537 |
+
def coincidence(self, alpha, beta, w=None, modified=False):
|
| 538 |
+
r"""
|
| 539 |
+
The third situation described in ``scan`` routine is handled by this
|
| 540 |
+
routine, described on Pg. 156-161 [1].
|
| 541 |
+
|
| 542 |
+
The unfortunate situation when the scan completes but not correctly,
|
| 543 |
+
then ``coincidence`` routine is run. i.e when for some `i` with
|
| 544 |
+
`1 \le i \le r+1`, we have `w=st` with `s = x_1 x_2 \dots x_{i-1}`,
|
| 545 |
+
`t = x_i x_{i+1} \dots x_r`, and `\beta = \alpha^s` and
|
| 546 |
+
`\gamma = \alpha^{t-1}` are defined but unequal. This means that
|
| 547 |
+
`\beta` and `\gamma` represent the same coset of `H` in `G`. Described
|
| 548 |
+
on Pg. 156 [1]. ``rep``
|
| 549 |
+
|
| 550 |
+
See Also
|
| 551 |
+
========
|
| 552 |
+
|
| 553 |
+
scan
|
| 554 |
+
|
| 555 |
+
"""
|
| 556 |
+
A_dict = self.A_dict
|
| 557 |
+
A_dict_inv = self.A_dict_inv
|
| 558 |
+
table = self.table
|
| 559 |
+
# behaves as a queue
|
| 560 |
+
q = []
|
| 561 |
+
if modified:
|
| 562 |
+
self.modified_merge(alpha, beta, w, q)
|
| 563 |
+
else:
|
| 564 |
+
self.merge(alpha, beta, q)
|
| 565 |
+
while len(q) > 0:
|
| 566 |
+
gamma = q.pop(0)
|
| 567 |
+
for x in A_dict:
|
| 568 |
+
delta = table[gamma][A_dict[x]]
|
| 569 |
+
if delta is not None:
|
| 570 |
+
table[delta][A_dict_inv[x]] = None
|
| 571 |
+
mu = self.rep(gamma, modified=modified)
|
| 572 |
+
nu = self.rep(delta, modified=modified)
|
| 573 |
+
if table[mu][A_dict[x]] is not None:
|
| 574 |
+
if modified:
|
| 575 |
+
v = self.p_p[delta]**-1*self.P[gamma][self.A_dict[x]]**-1
|
| 576 |
+
v = v*self.p_p[gamma]*self.P[mu][self.A_dict[x]]
|
| 577 |
+
self.modified_merge(nu, table[mu][self.A_dict[x]], v, q)
|
| 578 |
+
else:
|
| 579 |
+
self.merge(nu, table[mu][A_dict[x]], q)
|
| 580 |
+
elif table[nu][A_dict_inv[x]] is not None:
|
| 581 |
+
if modified:
|
| 582 |
+
v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]
|
| 583 |
+
v = v*self.p_p[delta]*self.P[mu][self.A_dict_inv[x]]
|
| 584 |
+
self.modified_merge(mu, table[nu][self.A_dict_inv[x]], v, q)
|
| 585 |
+
else:
|
| 586 |
+
self.merge(mu, table[nu][A_dict_inv[x]], q)
|
| 587 |
+
else:
|
| 588 |
+
table[mu][A_dict[x]] = nu
|
| 589 |
+
table[nu][A_dict_inv[x]] = mu
|
| 590 |
+
if modified:
|
| 591 |
+
v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]*self.p_p[delta]
|
| 592 |
+
self.P[mu][self.A_dict[x]] = v
|
| 593 |
+
self.P[nu][self.A_dict_inv[x]] = v**-1
|
| 594 |
+
|
| 595 |
+
# method used in the HLT strategy
|
| 596 |
+
def scan_and_fill(self, alpha, word):
|
| 597 |
+
"""
|
| 598 |
+
A modified version of ``scan`` routine used in the relator-based
|
| 599 |
+
method of coset enumeration, described on pg. 162-163 [1], which
|
| 600 |
+
follows the idea that whenever the procedure is called and the scan
|
| 601 |
+
is incomplete then it makes new definitions to enable the scan to
|
| 602 |
+
complete; i.e it fills in the gaps in the scan of the relator or
|
| 603 |
+
subgroup generator.
|
| 604 |
+
|
| 605 |
+
"""
|
| 606 |
+
self.scan(alpha, word, fill=True)
|
| 607 |
+
|
| 608 |
+
def scan_and_fill_c(self, alpha, word):
|
| 609 |
+
"""
|
| 610 |
+
A modified version of ``scan`` routine, described on Pg. 165 second
|
| 611 |
+
para. [1], with modification similar to that of ``scan_anf_fill`` the
|
| 612 |
+
only difference being it calls the coincidence procedure used in the
|
| 613 |
+
coset-table based method i.e. the routine ``coincidence_c`` is used.
|
| 614 |
+
|
| 615 |
+
See Also
|
| 616 |
+
========
|
| 617 |
+
|
| 618 |
+
scan, scan_and_fill
|
| 619 |
+
|
| 620 |
+
"""
|
| 621 |
+
A_dict = self.A_dict
|
| 622 |
+
A_dict_inv = self.A_dict_inv
|
| 623 |
+
table = self.table
|
| 624 |
+
r = len(word)
|
| 625 |
+
f = alpha
|
| 626 |
+
i = 0
|
| 627 |
+
b = alpha
|
| 628 |
+
j = r - 1
|
| 629 |
+
# loop until it has filled the alpha row in the table.
|
| 630 |
+
while True:
|
| 631 |
+
# do the forward scanning
|
| 632 |
+
while i <= j and table[f][A_dict[word[i]]] is not None:
|
| 633 |
+
f = table[f][A_dict[word[i]]]
|
| 634 |
+
i += 1
|
| 635 |
+
if i > j:
|
| 636 |
+
if f != b:
|
| 637 |
+
self.coincidence_c(f, b)
|
| 638 |
+
return
|
| 639 |
+
# forward scan was incomplete, scan backwards
|
| 640 |
+
while j >= i and table[b][A_dict_inv[word[j]]] is not None:
|
| 641 |
+
b = table[b][A_dict_inv[word[j]]]
|
| 642 |
+
j -= 1
|
| 643 |
+
if j < i:
|
| 644 |
+
self.coincidence_c(f, b)
|
| 645 |
+
elif j == i:
|
| 646 |
+
table[f][A_dict[word[i]]] = b
|
| 647 |
+
table[b][A_dict_inv[word[i]]] = f
|
| 648 |
+
self.deduction_stack.append((f, word[i]))
|
| 649 |
+
else:
|
| 650 |
+
self.define_c(f, word[i])
|
| 651 |
+
|
| 652 |
+
# method used in the HLT strategy
|
| 653 |
+
def look_ahead(self):
|
| 654 |
+
"""
|
| 655 |
+
When combined with the HLT method this is known as HLT+Lookahead
|
| 656 |
+
method of coset enumeration, described on pg. 164 [1]. Whenever
|
| 657 |
+
``define`` aborts due to lack of space available this procedure is
|
| 658 |
+
executed. This routine helps in recovering space resulting from
|
| 659 |
+
"coincidence" of cosets.
|
| 660 |
+
|
| 661 |
+
"""
|
| 662 |
+
R = self.fp_group.relators
|
| 663 |
+
p = self.p
|
| 664 |
+
# complete scan all relators under all cosets(obviously live)
|
| 665 |
+
# without making new definitions
|
| 666 |
+
for beta in self.omega:
|
| 667 |
+
for w in R:
|
| 668 |
+
self.scan(beta, w)
|
| 669 |
+
if p[beta] < beta:
|
| 670 |
+
break
|
| 671 |
+
|
| 672 |
+
# Pg. 166
|
| 673 |
+
def process_deductions(self, R_c_x, R_c_x_inv):
|
| 674 |
+
"""
|
| 675 |
+
Processes the deductions that have been pushed onto ``deduction_stack``,
|
| 676 |
+
described on Pg. 166 [1] and is used in coset-table based enumeration.
|
| 677 |
+
|
| 678 |
+
See Also
|
| 679 |
+
========
|
| 680 |
+
|
| 681 |
+
deduction_stack
|
| 682 |
+
|
| 683 |
+
"""
|
| 684 |
+
p = self.p
|
| 685 |
+
table = self.table
|
| 686 |
+
while len(self.deduction_stack) > 0:
|
| 687 |
+
if len(self.deduction_stack) >= CosetTable.max_stack_size:
|
| 688 |
+
self.look_ahead()
|
| 689 |
+
del self.deduction_stack[:]
|
| 690 |
+
continue
|
| 691 |
+
else:
|
| 692 |
+
alpha, x = self.deduction_stack.pop()
|
| 693 |
+
if p[alpha] == alpha:
|
| 694 |
+
for w in R_c_x:
|
| 695 |
+
self.scan_c(alpha, w)
|
| 696 |
+
if p[alpha] < alpha:
|
| 697 |
+
break
|
| 698 |
+
beta = table[alpha][self.A_dict[x]]
|
| 699 |
+
if beta is not None and p[beta] == beta:
|
| 700 |
+
for w in R_c_x_inv:
|
| 701 |
+
self.scan_c(beta, w)
|
| 702 |
+
if p[beta] < beta:
|
| 703 |
+
break
|
| 704 |
+
|
| 705 |
+
def process_deductions_check(self, R_c_x, R_c_x_inv):
|
| 706 |
+
"""
|
| 707 |
+
A variation of ``process_deductions``, this calls ``scan_check``
|
| 708 |
+
wherever ``process_deductions`` calls ``scan``, described on Pg. [1].
|
| 709 |
+
|
| 710 |
+
See Also
|
| 711 |
+
========
|
| 712 |
+
|
| 713 |
+
process_deductions
|
| 714 |
+
|
| 715 |
+
"""
|
| 716 |
+
table = self.table
|
| 717 |
+
while len(self.deduction_stack) > 0:
|
| 718 |
+
alpha, x = self.deduction_stack.pop()
|
| 719 |
+
for w in R_c_x:
|
| 720 |
+
if not self.scan_check(alpha, w):
|
| 721 |
+
return False
|
| 722 |
+
beta = table[alpha][self.A_dict[x]]
|
| 723 |
+
if beta is not None:
|
| 724 |
+
for w in R_c_x_inv:
|
| 725 |
+
if not self.scan_check(beta, w):
|
| 726 |
+
return False
|
| 727 |
+
return True
|
| 728 |
+
|
| 729 |
+
def switch(self, beta, gamma):
|
| 730 |
+
r"""Switch the elements `\beta, \gamma \in \Omega` of ``self``, used
|
| 731 |
+
by the ``standardize`` procedure, described on Pg. 167 [1].
|
| 732 |
+
|
| 733 |
+
See Also
|
| 734 |
+
========
|
| 735 |
+
|
| 736 |
+
standardize
|
| 737 |
+
|
| 738 |
+
"""
|
| 739 |
+
A = self.A
|
| 740 |
+
A_dict = self.A_dict
|
| 741 |
+
table = self.table
|
| 742 |
+
for x in A:
|
| 743 |
+
z = table[gamma][A_dict[x]]
|
| 744 |
+
table[gamma][A_dict[x]] = table[beta][A_dict[x]]
|
| 745 |
+
table[beta][A_dict[x]] = z
|
| 746 |
+
for alpha in range(len(self.p)):
|
| 747 |
+
if self.p[alpha] == alpha:
|
| 748 |
+
if table[alpha][A_dict[x]] == beta:
|
| 749 |
+
table[alpha][A_dict[x]] = gamma
|
| 750 |
+
elif table[alpha][A_dict[x]] == gamma:
|
| 751 |
+
table[alpha][A_dict[x]] = beta
|
| 752 |
+
|
| 753 |
+
def standardize(self):
|
| 754 |
+
r"""
|
| 755 |
+
A coset table is standardized if when running through the cosets and
|
| 756 |
+
within each coset through the generator images (ignoring generator
|
| 757 |
+
inverses), the cosets appear in order of the integers
|
| 758 |
+
`0, 1, \dots, n`. "Standardize" reorders the elements of `\Omega`
|
| 759 |
+
such that, if we scan the coset table first by elements of `\Omega`
|
| 760 |
+
and then by elements of A, then the cosets occur in ascending order.
|
| 761 |
+
``standardize()`` is used at the end of an enumeration to permute the
|
| 762 |
+
cosets so that they occur in some sort of standard order.
|
| 763 |
+
|
| 764 |
+
Notes
|
| 765 |
+
=====
|
| 766 |
+
|
| 767 |
+
procedure is described on pg. 167-168 [1], it also makes use of the
|
| 768 |
+
``switch`` routine to replace by smaller integer value.
|
| 769 |
+
|
| 770 |
+
Examples
|
| 771 |
+
========
|
| 772 |
+
|
| 773 |
+
>>> from sympy.combinatorics import free_group
|
| 774 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
|
| 775 |
+
>>> F, x, y = free_group("x, y")
|
| 776 |
+
|
| 777 |
+
# Example 5.3 from [1]
|
| 778 |
+
>>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
|
| 779 |
+
>>> C = coset_enumeration_r(f, [])
|
| 780 |
+
>>> C.compress()
|
| 781 |
+
>>> C.table
|
| 782 |
+
[[1, 3, 1, 3], [2, 0, 2, 0], [3, 1, 3, 1], [0, 2, 0, 2]]
|
| 783 |
+
>>> C.standardize()
|
| 784 |
+
>>> C.table
|
| 785 |
+
[[1, 2, 1, 2], [3, 0, 3, 0], [0, 3, 0, 3], [2, 1, 2, 1]]
|
| 786 |
+
|
| 787 |
+
"""
|
| 788 |
+
A = self.A
|
| 789 |
+
A_dict = self.A_dict
|
| 790 |
+
gamma = 1
|
| 791 |
+
for alpha, x in product(range(self.n), A):
|
| 792 |
+
beta = self.table[alpha][A_dict[x]]
|
| 793 |
+
if beta >= gamma:
|
| 794 |
+
if beta > gamma:
|
| 795 |
+
self.switch(gamma, beta)
|
| 796 |
+
gamma += 1
|
| 797 |
+
if gamma == self.n:
|
| 798 |
+
return
|
| 799 |
+
|
| 800 |
+
# Compression of a Coset Table
|
| 801 |
+
def compress(self):
|
| 802 |
+
"""Removes the non-live cosets from the coset table, described on
|
| 803 |
+
pg. 167 [1].
|
| 804 |
+
|
| 805 |
+
"""
|
| 806 |
+
gamma = -1
|
| 807 |
+
A = self.A
|
| 808 |
+
A_dict = self.A_dict
|
| 809 |
+
A_dict_inv = self.A_dict_inv
|
| 810 |
+
table = self.table
|
| 811 |
+
chi = tuple([i for i in range(len(self.p)) if self.p[i] != i])
|
| 812 |
+
for alpha in self.omega:
|
| 813 |
+
gamma += 1
|
| 814 |
+
if gamma != alpha:
|
| 815 |
+
# replace alpha by gamma in coset table
|
| 816 |
+
for x in A:
|
| 817 |
+
beta = table[alpha][A_dict[x]]
|
| 818 |
+
table[gamma][A_dict[x]] = beta
|
| 819 |
+
table[beta][A_dict_inv[x]] == gamma
|
| 820 |
+
# all the cosets in the table are live cosets
|
| 821 |
+
self.p = list(range(gamma + 1))
|
| 822 |
+
# delete the useless columns
|
| 823 |
+
del table[len(self.p):]
|
| 824 |
+
# re-define values
|
| 825 |
+
for row in table:
|
| 826 |
+
for j in range(len(self.A)):
|
| 827 |
+
row[j] -= bisect_left(chi, row[j])
|
| 828 |
+
|
| 829 |
+
def conjugates(self, R):
|
| 830 |
+
R_c = list(chain.from_iterable((rel.cyclic_conjugates(), \
|
| 831 |
+
(rel**-1).cyclic_conjugates()) for rel in R))
|
| 832 |
+
R_set = set()
|
| 833 |
+
for conjugate in R_c:
|
| 834 |
+
R_set = R_set.union(conjugate)
|
| 835 |
+
R_c_list = []
|
| 836 |
+
for x in self.A:
|
| 837 |
+
r = {word for word in R_set if word[0] == x}
|
| 838 |
+
R_c_list.append(r)
|
| 839 |
+
R_set.difference_update(r)
|
| 840 |
+
return R_c_list
|
| 841 |
+
|
| 842 |
+
def coset_representative(self, coset):
|
| 843 |
+
'''
|
| 844 |
+
Compute the coset representative of a given coset.
|
| 845 |
+
|
| 846 |
+
Examples
|
| 847 |
+
========
|
| 848 |
+
|
| 849 |
+
>>> from sympy.combinatorics import free_group
|
| 850 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
|
| 851 |
+
>>> F, x, y = free_group("x, y")
|
| 852 |
+
>>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
| 853 |
+
>>> C = coset_enumeration_r(f, [x])
|
| 854 |
+
>>> C.compress()
|
| 855 |
+
>>> C.table
|
| 856 |
+
[[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
|
| 857 |
+
>>> C.coset_representative(0)
|
| 858 |
+
<identity>
|
| 859 |
+
>>> C.coset_representative(1)
|
| 860 |
+
y
|
| 861 |
+
>>> C.coset_representative(2)
|
| 862 |
+
y**-1
|
| 863 |
+
|
| 864 |
+
'''
|
| 865 |
+
for x in self.A:
|
| 866 |
+
gamma = self.table[coset][self.A_dict[x]]
|
| 867 |
+
if coset == 0:
|
| 868 |
+
return self.fp_group.identity
|
| 869 |
+
if gamma < coset:
|
| 870 |
+
return self.coset_representative(gamma)*x**-1
|
| 871 |
+
|
| 872 |
+
##############################
|
| 873 |
+
# Modified Methods #
|
| 874 |
+
##############################
|
| 875 |
+
|
| 876 |
+
def modified_define(self, alpha, x):
|
| 877 |
+
r"""
|
| 878 |
+
Define a function p_p from from [1..n] to A* as
|
| 879 |
+
an additional component of the modified coset table.
|
| 880 |
+
|
| 881 |
+
Parameters
|
| 882 |
+
==========
|
| 883 |
+
|
| 884 |
+
\alpha \in \Omega
|
| 885 |
+
x \in A*
|
| 886 |
+
|
| 887 |
+
See Also
|
| 888 |
+
========
|
| 889 |
+
|
| 890 |
+
define
|
| 891 |
+
|
| 892 |
+
"""
|
| 893 |
+
self.define(alpha, x, modified=True)
|
| 894 |
+
|
| 895 |
+
def modified_scan(self, alpha, w, y, fill=False):
|
| 896 |
+
r"""
|
| 897 |
+
Parameters
|
| 898 |
+
==========
|
| 899 |
+
\alpha \in \Omega
|
| 900 |
+
w \in A*
|
| 901 |
+
y \in (YUY^-1)
|
| 902 |
+
fill -- `modified_scan_and_fill` when set to True.
|
| 903 |
+
|
| 904 |
+
See Also
|
| 905 |
+
========
|
| 906 |
+
|
| 907 |
+
scan
|
| 908 |
+
"""
|
| 909 |
+
self.scan(alpha, w, y=y, fill=fill, modified=True)
|
| 910 |
+
|
| 911 |
+
def modified_scan_and_fill(self, alpha, w, y):
|
| 912 |
+
self.modified_scan(alpha, w, y, fill=True)
|
| 913 |
+
|
| 914 |
+
def modified_merge(self, k, lamda, w, q):
|
| 915 |
+
r"""
|
| 916 |
+
Parameters
|
| 917 |
+
==========
|
| 918 |
+
|
| 919 |
+
'k', 'lamda' -- the two class representatives to be merged.
|
| 920 |
+
q -- queue of length l of elements to be deleted from `\Omega` *.
|
| 921 |
+
w -- Word in (YUY^-1)
|
| 922 |
+
|
| 923 |
+
See Also
|
| 924 |
+
========
|
| 925 |
+
|
| 926 |
+
merge
|
| 927 |
+
"""
|
| 928 |
+
self.merge(k, lamda, q, w=w, modified=True)
|
| 929 |
+
|
| 930 |
+
def modified_rep(self, k):
|
| 931 |
+
r"""
|
| 932 |
+
Parameters
|
| 933 |
+
==========
|
| 934 |
+
|
| 935 |
+
`k \in [0 \ldots n-1]`
|
| 936 |
+
|
| 937 |
+
See Also
|
| 938 |
+
========
|
| 939 |
+
|
| 940 |
+
rep
|
| 941 |
+
"""
|
| 942 |
+
self.rep(k, modified=True)
|
| 943 |
+
|
| 944 |
+
def modified_coincidence(self, alpha, beta, w):
|
| 945 |
+
r"""
|
| 946 |
+
Parameters
|
| 947 |
+
==========
|
| 948 |
+
|
| 949 |
+
A coincident pair `\alpha, \beta \in \Omega, w \in Y \cup Y^{-1}`
|
| 950 |
+
|
| 951 |
+
See Also
|
| 952 |
+
========
|
| 953 |
+
|
| 954 |
+
coincidence
|
| 955 |
+
|
| 956 |
+
"""
|
| 957 |
+
self.coincidence(alpha, beta, w=w, modified=True)
|
| 958 |
+
|
| 959 |
+
###############################################################################
|
| 960 |
+
# COSET ENUMERATION #
|
| 961 |
+
###############################################################################
|
| 962 |
+
|
| 963 |
+
# relator-based method
|
| 964 |
+
def coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
|
| 965 |
+
incomplete=False, modified=False):
|
| 966 |
+
"""
|
| 967 |
+
This is easier of the two implemented methods of coset enumeration.
|
| 968 |
+
and is often called the HLT method, after Hazelgrove, Leech, Trotter
|
| 969 |
+
The idea is that we make use of ``scan_and_fill`` makes new definitions
|
| 970 |
+
whenever the scan is incomplete to enable the scan to complete; this way
|
| 971 |
+
we fill in the gaps in the scan of the relator or subgroup generator,
|
| 972 |
+
that's why the name relator-based method.
|
| 973 |
+
|
| 974 |
+
An instance of `CosetTable` for `fp_grp` can be passed as the keyword
|
| 975 |
+
argument `draft` in which case the coset enumeration will start with
|
| 976 |
+
that instance and attempt to complete it.
|
| 977 |
+
|
| 978 |
+
When `incomplete` is `True` and the function is unable to complete for
|
| 979 |
+
some reason, the partially complete table will be returned.
|
| 980 |
+
|
| 981 |
+
# TODO: complete the docstring
|
| 982 |
+
|
| 983 |
+
See Also
|
| 984 |
+
========
|
| 985 |
+
|
| 986 |
+
scan_and_fill,
|
| 987 |
+
|
| 988 |
+
Examples
|
| 989 |
+
========
|
| 990 |
+
|
| 991 |
+
>>> from sympy.combinatorics.free_groups import free_group
|
| 992 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
|
| 993 |
+
>>> F, x, y = free_group("x, y")
|
| 994 |
+
|
| 995 |
+
# Example 5.1 from [1]
|
| 996 |
+
>>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
| 997 |
+
>>> C = coset_enumeration_r(f, [x])
|
| 998 |
+
>>> for i in range(len(C.p)):
|
| 999 |
+
... if C.p[i] == i:
|
| 1000 |
+
... print(C.table[i])
|
| 1001 |
+
[0, 0, 1, 2]
|
| 1002 |
+
[1, 1, 2, 0]
|
| 1003 |
+
[2, 2, 0, 1]
|
| 1004 |
+
>>> C.p
|
| 1005 |
+
[0, 1, 2, 1, 1]
|
| 1006 |
+
|
| 1007 |
+
# Example from exercises Q2 [1]
|
| 1008 |
+
>>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
|
| 1009 |
+
>>> C = coset_enumeration_r(f, [])
|
| 1010 |
+
>>> C.compress(); C.standardize()
|
| 1011 |
+
>>> C.table
|
| 1012 |
+
[[1, 2, 3, 4],
|
| 1013 |
+
[5, 0, 6, 7],
|
| 1014 |
+
[0, 5, 7, 6],
|
| 1015 |
+
[7, 6, 5, 0],
|
| 1016 |
+
[6, 7, 0, 5],
|
| 1017 |
+
[2, 1, 4, 3],
|
| 1018 |
+
[3, 4, 2, 1],
|
| 1019 |
+
[4, 3, 1, 2]]
|
| 1020 |
+
|
| 1021 |
+
# Example 5.2
|
| 1022 |
+
>>> f = FpGroup(F, [x**2, y**3, (x*y)**3])
|
| 1023 |
+
>>> Y = [x*y]
|
| 1024 |
+
>>> C = coset_enumeration_r(f, Y)
|
| 1025 |
+
>>> for i in range(len(C.p)):
|
| 1026 |
+
... if C.p[i] == i:
|
| 1027 |
+
... print(C.table[i])
|
| 1028 |
+
[1, 1, 2, 1]
|
| 1029 |
+
[0, 0, 0, 2]
|
| 1030 |
+
[3, 3, 1, 0]
|
| 1031 |
+
[2, 2, 3, 3]
|
| 1032 |
+
|
| 1033 |
+
# Example 5.3
|
| 1034 |
+
>>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
|
| 1035 |
+
>>> Y = []
|
| 1036 |
+
>>> C = coset_enumeration_r(f, Y)
|
| 1037 |
+
>>> for i in range(len(C.p)):
|
| 1038 |
+
... if C.p[i] == i:
|
| 1039 |
+
... print(C.table[i])
|
| 1040 |
+
[1, 3, 1, 3]
|
| 1041 |
+
[2, 0, 2, 0]
|
| 1042 |
+
[3, 1, 3, 1]
|
| 1043 |
+
[0, 2, 0, 2]
|
| 1044 |
+
|
| 1045 |
+
# Example 5.4
|
| 1046 |
+
>>> F, a, b, c, d, e = free_group("a, b, c, d, e")
|
| 1047 |
+
>>> f = FpGroup(F, [a*b*c**-1, b*c*d**-1, c*d*e**-1, d*e*a**-1, e*a*b**-1])
|
| 1048 |
+
>>> Y = [a]
|
| 1049 |
+
>>> C = coset_enumeration_r(f, Y)
|
| 1050 |
+
>>> for i in range(len(C.p)):
|
| 1051 |
+
... if C.p[i] == i:
|
| 1052 |
+
... print(C.table[i])
|
| 1053 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
| 1054 |
+
|
| 1055 |
+
# example of "compress" method
|
| 1056 |
+
>>> C.compress()
|
| 1057 |
+
>>> C.table
|
| 1058 |
+
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
|
| 1059 |
+
|
| 1060 |
+
# Exercises Pg. 161, Q2.
|
| 1061 |
+
>>> F, x, y = free_group("x, y")
|
| 1062 |
+
>>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
|
| 1063 |
+
>>> Y = []
|
| 1064 |
+
>>> C = coset_enumeration_r(f, Y)
|
| 1065 |
+
>>> C.compress()
|
| 1066 |
+
>>> C.standardize()
|
| 1067 |
+
>>> C.table
|
| 1068 |
+
[[1, 2, 3, 4],
|
| 1069 |
+
[5, 0, 6, 7],
|
| 1070 |
+
[0, 5, 7, 6],
|
| 1071 |
+
[7, 6, 5, 0],
|
| 1072 |
+
[6, 7, 0, 5],
|
| 1073 |
+
[2, 1, 4, 3],
|
| 1074 |
+
[3, 4, 2, 1],
|
| 1075 |
+
[4, 3, 1, 2]]
|
| 1076 |
+
|
| 1077 |
+
# John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
|
| 1078 |
+
# Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490
|
| 1079 |
+
# from 1973chwd.pdf
|
| 1080 |
+
# Table 1. Ex. 1
|
| 1081 |
+
>>> F, r, s, t = free_group("r, s, t")
|
| 1082 |
+
>>> E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2])
|
| 1083 |
+
>>> C = coset_enumeration_r(E1, [r])
|
| 1084 |
+
>>> for i in range(len(C.p)):
|
| 1085 |
+
... if C.p[i] == i:
|
| 1086 |
+
... print(C.table[i])
|
| 1087 |
+
[0, 0, 0, 0, 0, 0]
|
| 1088 |
+
|
| 1089 |
+
Ex. 2
|
| 1090 |
+
>>> F, a, b = free_group("a, b")
|
| 1091 |
+
>>> Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5])
|
| 1092 |
+
>>> C = coset_enumeration_r(Cox, [a])
|
| 1093 |
+
>>> index = 0
|
| 1094 |
+
>>> for i in range(len(C.p)):
|
| 1095 |
+
... if C.p[i] == i:
|
| 1096 |
+
... index += 1
|
| 1097 |
+
>>> index
|
| 1098 |
+
500
|
| 1099 |
+
|
| 1100 |
+
# Ex. 3
|
| 1101 |
+
>>> F, a, b = free_group("a, b")
|
| 1102 |
+
>>> B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \
|
| 1103 |
+
(a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4])
|
| 1104 |
+
>>> C = coset_enumeration_r(B_2_4, [a])
|
| 1105 |
+
>>> index = 0
|
| 1106 |
+
>>> for i in range(len(C.p)):
|
| 1107 |
+
... if C.p[i] == i:
|
| 1108 |
+
... index += 1
|
| 1109 |
+
>>> index
|
| 1110 |
+
1024
|
| 1111 |
+
|
| 1112 |
+
References
|
| 1113 |
+
==========
|
| 1114 |
+
|
| 1115 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 1116 |
+
"Handbook of computational group theory"
|
| 1117 |
+
|
| 1118 |
+
"""
|
| 1119 |
+
# 1. Initialize a coset table C for < X|R >
|
| 1120 |
+
C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
|
| 1121 |
+
# Define coset table methods.
|
| 1122 |
+
if modified:
|
| 1123 |
+
_scan_and_fill = C.modified_scan_and_fill
|
| 1124 |
+
_define = C.modified_define
|
| 1125 |
+
else:
|
| 1126 |
+
_scan_and_fill = C.scan_and_fill
|
| 1127 |
+
_define = C.define
|
| 1128 |
+
if draft:
|
| 1129 |
+
C.table = draft.table[:]
|
| 1130 |
+
C.p = draft.p[:]
|
| 1131 |
+
R = fp_grp.relators
|
| 1132 |
+
A_dict = C.A_dict
|
| 1133 |
+
p = C.p
|
| 1134 |
+
for i in range(len(Y)):
|
| 1135 |
+
if modified:
|
| 1136 |
+
_scan_and_fill(0, Y[i], C._grp.generators[i])
|
| 1137 |
+
else:
|
| 1138 |
+
_scan_and_fill(0, Y[i])
|
| 1139 |
+
alpha = 0
|
| 1140 |
+
while alpha < C.n:
|
| 1141 |
+
if p[alpha] == alpha:
|
| 1142 |
+
try:
|
| 1143 |
+
for w in R:
|
| 1144 |
+
if modified:
|
| 1145 |
+
_scan_and_fill(alpha, w, C._grp.identity)
|
| 1146 |
+
else:
|
| 1147 |
+
_scan_and_fill(alpha, w)
|
| 1148 |
+
# if alpha was eliminated during the scan then break
|
| 1149 |
+
if p[alpha] < alpha:
|
| 1150 |
+
break
|
| 1151 |
+
if p[alpha] == alpha:
|
| 1152 |
+
for x in A_dict:
|
| 1153 |
+
if C.table[alpha][A_dict[x]] is None:
|
| 1154 |
+
_define(alpha, x)
|
| 1155 |
+
except ValueError as e:
|
| 1156 |
+
if incomplete:
|
| 1157 |
+
return C
|
| 1158 |
+
raise e
|
| 1159 |
+
alpha += 1
|
| 1160 |
+
return C
|
| 1161 |
+
|
| 1162 |
+
def modified_coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
|
| 1163 |
+
incomplete=False):
|
| 1164 |
+
r"""
|
| 1165 |
+
Introduce a new set of symbols y \in Y that correspond to the
|
| 1166 |
+
generators of the subgroup. Store the elements of Y as a
|
| 1167 |
+
word P[\alpha, x] and compute the coset table similar to that of
|
| 1168 |
+
the regular coset enumeration methods.
|
| 1169 |
+
|
| 1170 |
+
Examples
|
| 1171 |
+
========
|
| 1172 |
+
|
| 1173 |
+
>>> from sympy.combinatorics.free_groups import free_group
|
| 1174 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup
|
| 1175 |
+
>>> from sympy.combinatorics.coset_table import modified_coset_enumeration_r
|
| 1176 |
+
>>> F, x, y = free_group("x, y")
|
| 1177 |
+
>>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
| 1178 |
+
>>> C = modified_coset_enumeration_r(f, [x])
|
| 1179 |
+
>>> C.table
|
| 1180 |
+
[[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], [None, 1, None, None], [1, 3, None, None]]
|
| 1181 |
+
|
| 1182 |
+
See Also
|
| 1183 |
+
========
|
| 1184 |
+
|
| 1185 |
+
coset_enumertation_r
|
| 1186 |
+
|
| 1187 |
+
References
|
| 1188 |
+
==========
|
| 1189 |
+
|
| 1190 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.,
|
| 1191 |
+
"Handbook of Computational Group Theory",
|
| 1192 |
+
Section 5.3.2
|
| 1193 |
+
"""
|
| 1194 |
+
return coset_enumeration_r(fp_grp, Y, max_cosets=max_cosets, draft=draft,
|
| 1195 |
+
incomplete=incomplete, modified=True)
|
| 1196 |
+
|
| 1197 |
+
# Pg. 166
|
| 1198 |
+
# coset-table based method
|
| 1199 |
+
def coset_enumeration_c(fp_grp, Y, max_cosets=None, draft=None,
|
| 1200 |
+
incomplete=False):
|
| 1201 |
+
"""
|
| 1202 |
+
>>> from sympy.combinatorics.free_groups import free_group
|
| 1203 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_c
|
| 1204 |
+
>>> F, x, y = free_group("x, y")
|
| 1205 |
+
>>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
| 1206 |
+
>>> C = coset_enumeration_c(f, [x])
|
| 1207 |
+
>>> C.table
|
| 1208 |
+
[[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
|
| 1209 |
+
|
| 1210 |
+
"""
|
| 1211 |
+
# Initialize a coset table C for < X|R >
|
| 1212 |
+
X = fp_grp.generators
|
| 1213 |
+
R = fp_grp.relators
|
| 1214 |
+
C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
|
| 1215 |
+
if draft:
|
| 1216 |
+
C.table = draft.table[:]
|
| 1217 |
+
C.p = draft.p[:]
|
| 1218 |
+
C.deduction_stack = draft.deduction_stack
|
| 1219 |
+
for alpha, x in product(range(len(C.table)), X):
|
| 1220 |
+
if C.table[alpha][C.A_dict[x]] is not None:
|
| 1221 |
+
C.deduction_stack.append((alpha, x))
|
| 1222 |
+
A = C.A
|
| 1223 |
+
# replace all the elements by cyclic reductions
|
| 1224 |
+
R_cyc_red = [rel.identity_cyclic_reduction() for rel in R]
|
| 1225 |
+
R_c = list(chain.from_iterable((rel.cyclic_conjugates(), (rel**-1).cyclic_conjugates()) \
|
| 1226 |
+
for rel in R_cyc_red))
|
| 1227 |
+
R_set = set()
|
| 1228 |
+
for conjugate in R_c:
|
| 1229 |
+
R_set = R_set.union(conjugate)
|
| 1230 |
+
# a list of subsets of R_c whose words start with "x".
|
| 1231 |
+
R_c_list = []
|
| 1232 |
+
for x in C.A:
|
| 1233 |
+
r = {word for word in R_set if word[0] == x}
|
| 1234 |
+
R_c_list.append(r)
|
| 1235 |
+
R_set.difference_update(r)
|
| 1236 |
+
for w in Y:
|
| 1237 |
+
C.scan_and_fill_c(0, w)
|
| 1238 |
+
for x in A:
|
| 1239 |
+
C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
|
| 1240 |
+
alpha = 0
|
| 1241 |
+
while alpha < len(C.table):
|
| 1242 |
+
if C.p[alpha] == alpha:
|
| 1243 |
+
try:
|
| 1244 |
+
for x in C.A:
|
| 1245 |
+
if C.p[alpha] != alpha:
|
| 1246 |
+
break
|
| 1247 |
+
if C.table[alpha][C.A_dict[x]] is None:
|
| 1248 |
+
C.define_c(alpha, x)
|
| 1249 |
+
C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
|
| 1250 |
+
except ValueError as e:
|
| 1251 |
+
if incomplete:
|
| 1252 |
+
return C
|
| 1253 |
+
raise e
|
| 1254 |
+
alpha += 1
|
| 1255 |
+
return C
|
lib/python3.12/site-packages/sympy/combinatorics/fp_groups.py
ADDED
|
@@ -0,0 +1,1354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Finitely Presented Groups and its algorithms. """
|
| 2 |
+
|
| 3 |
+
from sympy.core.singleton import S
|
| 4 |
+
from sympy.core.symbol import symbols
|
| 5 |
+
from sympy.combinatorics.free_groups import (FreeGroup, FreeGroupElement,
|
| 6 |
+
free_group)
|
| 7 |
+
from sympy.combinatorics.rewritingsystem import RewritingSystem
|
| 8 |
+
from sympy.combinatorics.coset_table import (CosetTable,
|
| 9 |
+
coset_enumeration_r,
|
| 10 |
+
coset_enumeration_c)
|
| 11 |
+
from sympy.combinatorics import PermutationGroup
|
| 12 |
+
from sympy.matrices.normalforms import invariant_factors
|
| 13 |
+
from sympy.matrices import Matrix
|
| 14 |
+
from sympy.polys.polytools import gcd
|
| 15 |
+
from sympy.printing.defaults import DefaultPrinting
|
| 16 |
+
from sympy.utilities import public
|
| 17 |
+
from sympy.utilities.magic import pollute
|
| 18 |
+
|
| 19 |
+
from itertools import product
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@public
|
| 23 |
+
def fp_group(fr_grp, relators=()):
|
| 24 |
+
_fp_group = FpGroup(fr_grp, relators)
|
| 25 |
+
return (_fp_group,) + tuple(_fp_group._generators)
|
| 26 |
+
|
| 27 |
+
@public
|
| 28 |
+
def xfp_group(fr_grp, relators=()):
|
| 29 |
+
_fp_group = FpGroup(fr_grp, relators)
|
| 30 |
+
return (_fp_group, _fp_group._generators)
|
| 31 |
+
|
| 32 |
+
# Does not work. Both symbols and pollute are undefined. Never tested.
|
| 33 |
+
@public
|
| 34 |
+
def vfp_group(fr_grpm, relators):
|
| 35 |
+
_fp_group = FpGroup(symbols, relators)
|
| 36 |
+
pollute([sym.name for sym in _fp_group.symbols], _fp_group.generators)
|
| 37 |
+
return _fp_group
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _parse_relators(rels):
|
| 41 |
+
"""Parse the passed relators."""
|
| 42 |
+
return rels
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
###############################################################################
|
| 46 |
+
# FINITELY PRESENTED GROUPS #
|
| 47 |
+
###############################################################################
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class FpGroup(DefaultPrinting):
|
| 51 |
+
"""
|
| 52 |
+
The FpGroup would take a FreeGroup and a list/tuple of relators, the
|
| 53 |
+
relators would be specified in such a way that each of them be equal to the
|
| 54 |
+
identity of the provided free group.
|
| 55 |
+
|
| 56 |
+
"""
|
| 57 |
+
is_group = True
|
| 58 |
+
is_FpGroup = True
|
| 59 |
+
is_PermutationGroup = False
|
| 60 |
+
|
| 61 |
+
def __init__(self, fr_grp, relators):
|
| 62 |
+
relators = _parse_relators(relators)
|
| 63 |
+
self.free_group = fr_grp
|
| 64 |
+
self.relators = relators
|
| 65 |
+
self.generators = self._generators()
|
| 66 |
+
self.dtype = type("FpGroupElement", (FpGroupElement,), {"group": self})
|
| 67 |
+
|
| 68 |
+
# CosetTable instance on identity subgroup
|
| 69 |
+
self._coset_table = None
|
| 70 |
+
# returns whether coset table on identity subgroup
|
| 71 |
+
# has been standardized
|
| 72 |
+
self._is_standardized = False
|
| 73 |
+
|
| 74 |
+
self._order = None
|
| 75 |
+
self._center = None
|
| 76 |
+
|
| 77 |
+
self._rewriting_system = RewritingSystem(self)
|
| 78 |
+
self._perm_isomorphism = None
|
| 79 |
+
return
|
| 80 |
+
|
| 81 |
+
def _generators(self):
|
| 82 |
+
return self.free_group.generators
|
| 83 |
+
|
| 84 |
+
def make_confluent(self):
|
| 85 |
+
'''
|
| 86 |
+
Try to make the group's rewriting system confluent
|
| 87 |
+
|
| 88 |
+
'''
|
| 89 |
+
self._rewriting_system.make_confluent()
|
| 90 |
+
return
|
| 91 |
+
|
| 92 |
+
def reduce(self, word):
|
| 93 |
+
'''
|
| 94 |
+
Return the reduced form of `word` in `self` according to the group's
|
| 95 |
+
rewriting system. If it's confluent, the reduced form is the unique normal
|
| 96 |
+
form of the word in the group.
|
| 97 |
+
|
| 98 |
+
'''
|
| 99 |
+
return self._rewriting_system.reduce(word)
|
| 100 |
+
|
| 101 |
+
def equals(self, word1, word2):
|
| 102 |
+
'''
|
| 103 |
+
Compare `word1` and `word2` for equality in the group
|
| 104 |
+
using the group's rewriting system. If the system is
|
| 105 |
+
confluent, the returned answer is necessarily correct.
|
| 106 |
+
(If it is not, `False` could be returned in some cases
|
| 107 |
+
where in fact `word1 == word2`)
|
| 108 |
+
|
| 109 |
+
'''
|
| 110 |
+
if self.reduce(word1*word2**-1) == self.identity:
|
| 111 |
+
return True
|
| 112 |
+
elif self._rewriting_system.is_confluent:
|
| 113 |
+
return False
|
| 114 |
+
return None
|
| 115 |
+
|
| 116 |
+
@property
|
| 117 |
+
def identity(self):
|
| 118 |
+
return self.free_group.identity
|
| 119 |
+
|
| 120 |
+
def __contains__(self, g):
|
| 121 |
+
return g in self.free_group
|
| 122 |
+
|
| 123 |
+
def subgroup(self, gens, C=None, homomorphism=False):
|
| 124 |
+
'''
|
| 125 |
+
Return the subgroup generated by `gens` using the
|
| 126 |
+
Reidemeister-Schreier algorithm
|
| 127 |
+
homomorphism -- When set to True, return a dictionary containing the images
|
| 128 |
+
of the presentation generators in the original group.
|
| 129 |
+
|
| 130 |
+
Examples
|
| 131 |
+
========
|
| 132 |
+
|
| 133 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup
|
| 134 |
+
>>> from sympy.combinatorics import free_group
|
| 135 |
+
>>> F, x, y = free_group("x, y")
|
| 136 |
+
>>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
|
| 137 |
+
>>> H = [x*y, x**-1*y**-1*x*y*x]
|
| 138 |
+
>>> K, T = f.subgroup(H, homomorphism=True)
|
| 139 |
+
>>> T(K.generators)
|
| 140 |
+
[x*y, x**-1*y**2*x**-1]
|
| 141 |
+
|
| 142 |
+
'''
|
| 143 |
+
|
| 144 |
+
if not all(isinstance(g, FreeGroupElement) for g in gens):
|
| 145 |
+
raise ValueError("Generators must be `FreeGroupElement`s")
|
| 146 |
+
if not all(g.group == self.free_group for g in gens):
|
| 147 |
+
raise ValueError("Given generators are not members of the group")
|
| 148 |
+
if homomorphism:
|
| 149 |
+
g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True)
|
| 150 |
+
else:
|
| 151 |
+
g, rels = reidemeister_presentation(self, gens, C=C)
|
| 152 |
+
if g:
|
| 153 |
+
g = FpGroup(g[0].group, rels)
|
| 154 |
+
else:
|
| 155 |
+
g = FpGroup(free_group('')[0], [])
|
| 156 |
+
if homomorphism:
|
| 157 |
+
from sympy.combinatorics.homomorphisms import homomorphism
|
| 158 |
+
return g, homomorphism(g, self, g.generators, _gens, check=False)
|
| 159 |
+
return g
|
| 160 |
+
|
| 161 |
+
def coset_enumeration(self, H, strategy="relator_based", max_cosets=None,
|
| 162 |
+
draft=None, incomplete=False):
|
| 163 |
+
"""
|
| 164 |
+
Return an instance of ``coset table``, when Todd-Coxeter algorithm is
|
| 165 |
+
run over the ``self`` with ``H`` as subgroup, using ``strategy``
|
| 166 |
+
argument as strategy. The returned coset table is compressed but not
|
| 167 |
+
standardized.
|
| 168 |
+
|
| 169 |
+
An instance of `CosetTable` for `fp_grp` can be passed as the keyword
|
| 170 |
+
argument `draft` in which case the coset enumeration will start with
|
| 171 |
+
that instance and attempt to complete it.
|
| 172 |
+
|
| 173 |
+
When `incomplete` is `True` and the function is unable to complete for
|
| 174 |
+
some reason, the partially complete table will be returned.
|
| 175 |
+
|
| 176 |
+
"""
|
| 177 |
+
if not max_cosets:
|
| 178 |
+
max_cosets = CosetTable.coset_table_max_limit
|
| 179 |
+
if strategy == 'relator_based':
|
| 180 |
+
C = coset_enumeration_r(self, H, max_cosets=max_cosets,
|
| 181 |
+
draft=draft, incomplete=incomplete)
|
| 182 |
+
else:
|
| 183 |
+
C = coset_enumeration_c(self, H, max_cosets=max_cosets,
|
| 184 |
+
draft=draft, incomplete=incomplete)
|
| 185 |
+
if C.is_complete():
|
| 186 |
+
C.compress()
|
| 187 |
+
return C
|
| 188 |
+
|
| 189 |
+
def standardize_coset_table(self):
|
| 190 |
+
"""
|
| 191 |
+
Standardized the coset table ``self`` and makes the internal variable
|
| 192 |
+
``_is_standardized`` equal to ``True``.
|
| 193 |
+
|
| 194 |
+
"""
|
| 195 |
+
self._coset_table.standardize()
|
| 196 |
+
self._is_standardized = True
|
| 197 |
+
|
| 198 |
+
def coset_table(self, H, strategy="relator_based", max_cosets=None,
|
| 199 |
+
draft=None, incomplete=False):
|
| 200 |
+
"""
|
| 201 |
+
Return the mathematical coset table of ``self`` in ``H``.
|
| 202 |
+
|
| 203 |
+
"""
|
| 204 |
+
if not H:
|
| 205 |
+
if self._coset_table is not None:
|
| 206 |
+
if not self._is_standardized:
|
| 207 |
+
self.standardize_coset_table()
|
| 208 |
+
else:
|
| 209 |
+
C = self.coset_enumeration([], strategy, max_cosets=max_cosets,
|
| 210 |
+
draft=draft, incomplete=incomplete)
|
| 211 |
+
self._coset_table = C
|
| 212 |
+
self.standardize_coset_table()
|
| 213 |
+
return self._coset_table.table
|
| 214 |
+
else:
|
| 215 |
+
C = self.coset_enumeration(H, strategy, max_cosets=max_cosets,
|
| 216 |
+
draft=draft, incomplete=incomplete)
|
| 217 |
+
C.standardize()
|
| 218 |
+
return C.table
|
| 219 |
+
|
| 220 |
+
def order(self, strategy="relator_based"):
|
| 221 |
+
"""
|
| 222 |
+
Returns the order of the finitely presented group ``self``. It uses
|
| 223 |
+
the coset enumeration with identity group as subgroup, i.e ``H=[]``.
|
| 224 |
+
|
| 225 |
+
Examples
|
| 226 |
+
========
|
| 227 |
+
|
| 228 |
+
>>> from sympy.combinatorics import free_group
|
| 229 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup
|
| 230 |
+
>>> F, x, y = free_group("x, y")
|
| 231 |
+
>>> f = FpGroup(F, [x, y**2])
|
| 232 |
+
>>> f.order(strategy="coset_table_based")
|
| 233 |
+
2
|
| 234 |
+
|
| 235 |
+
"""
|
| 236 |
+
if self._order is not None:
|
| 237 |
+
return self._order
|
| 238 |
+
if self._coset_table is not None:
|
| 239 |
+
self._order = len(self._coset_table.table)
|
| 240 |
+
elif len(self.relators) == 0:
|
| 241 |
+
self._order = self.free_group.order()
|
| 242 |
+
elif len(self.generators) == 1:
|
| 243 |
+
self._order = abs(gcd([r.array_form[0][1] for r in self.relators]))
|
| 244 |
+
elif self._is_infinite():
|
| 245 |
+
self._order = S.Infinity
|
| 246 |
+
else:
|
| 247 |
+
gens, C = self._finite_index_subgroup()
|
| 248 |
+
if C:
|
| 249 |
+
ind = len(C.table)
|
| 250 |
+
self._order = ind*self.subgroup(gens, C=C).order()
|
| 251 |
+
else:
|
| 252 |
+
self._order = self.index([])
|
| 253 |
+
return self._order
|
| 254 |
+
|
| 255 |
+
def _is_infinite(self):
|
| 256 |
+
'''
|
| 257 |
+
Test if the group is infinite. Return `True` if the test succeeds
|
| 258 |
+
and `None` otherwise
|
| 259 |
+
|
| 260 |
+
'''
|
| 261 |
+
used_gens = set()
|
| 262 |
+
for r in self.relators:
|
| 263 |
+
used_gens.update(r.contains_generators())
|
| 264 |
+
if not set(self.generators) <= used_gens:
|
| 265 |
+
return True
|
| 266 |
+
# Abelianisation test: check is the abelianisation is infinite
|
| 267 |
+
abelian_rels = []
|
| 268 |
+
for rel in self.relators:
|
| 269 |
+
abelian_rels.append([rel.exponent_sum(g) for g in self.generators])
|
| 270 |
+
m = Matrix(Matrix(abelian_rels))
|
| 271 |
+
if 0 in invariant_factors(m):
|
| 272 |
+
return True
|
| 273 |
+
else:
|
| 274 |
+
return None
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _finite_index_subgroup(self, s=None):
|
| 278 |
+
'''
|
| 279 |
+
Find the elements of `self` that generate a finite index subgroup
|
| 280 |
+
and, if found, return the list of elements and the coset table of `self` by
|
| 281 |
+
the subgroup, otherwise return `(None, None)`
|
| 282 |
+
|
| 283 |
+
'''
|
| 284 |
+
gen = self.most_frequent_generator()
|
| 285 |
+
rels = list(self.generators)
|
| 286 |
+
rels.extend(self.relators)
|
| 287 |
+
if not s:
|
| 288 |
+
if len(self.generators) == 2:
|
| 289 |
+
s = [gen] + [g for g in self.generators if g != gen]
|
| 290 |
+
else:
|
| 291 |
+
rand = self.free_group.identity
|
| 292 |
+
i = 0
|
| 293 |
+
while ((rand in rels or rand**-1 in rels or rand.is_identity)
|
| 294 |
+
and i<10):
|
| 295 |
+
rand = self.random()
|
| 296 |
+
i += 1
|
| 297 |
+
s = [gen, rand] + [g for g in self.generators if g != gen]
|
| 298 |
+
mid = (len(s)+1)//2
|
| 299 |
+
half1 = s[:mid]
|
| 300 |
+
half2 = s[mid:]
|
| 301 |
+
draft1 = None
|
| 302 |
+
draft2 = None
|
| 303 |
+
m = 200
|
| 304 |
+
C = None
|
| 305 |
+
while not C and (m/2 < CosetTable.coset_table_max_limit):
|
| 306 |
+
m = min(m, CosetTable.coset_table_max_limit)
|
| 307 |
+
draft1 = self.coset_enumeration(half1, max_cosets=m,
|
| 308 |
+
draft=draft1, incomplete=True)
|
| 309 |
+
if draft1.is_complete():
|
| 310 |
+
C = draft1
|
| 311 |
+
half = half1
|
| 312 |
+
else:
|
| 313 |
+
draft2 = self.coset_enumeration(half2, max_cosets=m,
|
| 314 |
+
draft=draft2, incomplete=True)
|
| 315 |
+
if draft2.is_complete():
|
| 316 |
+
C = draft2
|
| 317 |
+
half = half2
|
| 318 |
+
if not C:
|
| 319 |
+
m *= 2
|
| 320 |
+
if not C:
|
| 321 |
+
return None, None
|
| 322 |
+
C.compress()
|
| 323 |
+
return half, C
|
| 324 |
+
|
| 325 |
+
def most_frequent_generator(self):
|
| 326 |
+
gens = self.generators
|
| 327 |
+
rels = self.relators
|
| 328 |
+
freqs = [sum(r.generator_count(g) for r in rels) for g in gens]
|
| 329 |
+
return gens[freqs.index(max(freqs))]
|
| 330 |
+
|
| 331 |
+
def random(self):
|
| 332 |
+
import random
|
| 333 |
+
r = self.free_group.identity
|
| 334 |
+
for i in range(random.randint(2,3)):
|
| 335 |
+
r = r*random.choice(self.generators)**random.choice([1,-1])
|
| 336 |
+
return r
|
| 337 |
+
|
| 338 |
+
def index(self, H, strategy="relator_based"):
|
| 339 |
+
"""
|
| 340 |
+
Return the index of subgroup ``H`` in group ``self``.
|
| 341 |
+
|
| 342 |
+
Examples
|
| 343 |
+
========
|
| 344 |
+
|
| 345 |
+
>>> from sympy.combinatorics import free_group
|
| 346 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup
|
| 347 |
+
>>> F, x, y = free_group("x, y")
|
| 348 |
+
>>> f = FpGroup(F, [x**5, y**4, y*x*y**3*x**3])
|
| 349 |
+
>>> f.index([x])
|
| 350 |
+
4
|
| 351 |
+
|
| 352 |
+
"""
|
| 353 |
+
# TODO: use |G:H| = |G|/|H| (currently H can't be made into a group)
|
| 354 |
+
# when we know |G| and |H|
|
| 355 |
+
|
| 356 |
+
if H == []:
|
| 357 |
+
return self.order()
|
| 358 |
+
else:
|
| 359 |
+
C = self.coset_enumeration(H, strategy)
|
| 360 |
+
return len(C.table)
|
| 361 |
+
|
| 362 |
+
def __str__(self):
|
| 363 |
+
if self.free_group.rank > 30:
|
| 364 |
+
str_form = "<fp group with %s generators>" % self.free_group.rank
|
| 365 |
+
else:
|
| 366 |
+
str_form = "<fp group on the generators %s>" % str(self.generators)
|
| 367 |
+
return str_form
|
| 368 |
+
|
| 369 |
+
__repr__ = __str__
|
| 370 |
+
|
| 371 |
+
#==============================================================================
|
| 372 |
+
# PERMUTATION GROUP METHODS
|
| 373 |
+
#==============================================================================
|
| 374 |
+
|
| 375 |
+
def _to_perm_group(self):
|
| 376 |
+
'''
|
| 377 |
+
Return an isomorphic permutation group and the isomorphism.
|
| 378 |
+
The implementation is dependent on coset enumeration so
|
| 379 |
+
will only terminate for finite groups.
|
| 380 |
+
|
| 381 |
+
'''
|
| 382 |
+
from sympy.combinatorics import Permutation
|
| 383 |
+
from sympy.combinatorics.homomorphisms import homomorphism
|
| 384 |
+
if self.order() is S.Infinity:
|
| 385 |
+
raise NotImplementedError("Permutation presentation of infinite "
|
| 386 |
+
"groups is not implemented")
|
| 387 |
+
if self._perm_isomorphism:
|
| 388 |
+
T = self._perm_isomorphism
|
| 389 |
+
P = T.image()
|
| 390 |
+
else:
|
| 391 |
+
C = self.coset_table([])
|
| 392 |
+
gens = self.generators
|
| 393 |
+
images = [[C[i][2*gens.index(g)] for i in range(len(C))] for g in gens]
|
| 394 |
+
images = [Permutation(i) for i in images]
|
| 395 |
+
P = PermutationGroup(images)
|
| 396 |
+
T = homomorphism(self, P, gens, images, check=False)
|
| 397 |
+
self._perm_isomorphism = T
|
| 398 |
+
return P, T
|
| 399 |
+
|
| 400 |
+
def _perm_group_list(self, method_name, *args):
|
| 401 |
+
'''
|
| 402 |
+
Given the name of a `PermutationGroup` method (returning a subgroup
|
| 403 |
+
or a list of subgroups) and (optionally) additional arguments it takes,
|
| 404 |
+
return a list or a list of lists containing the generators of this (or
|
| 405 |
+
these) subgroups in terms of the generators of `self`.
|
| 406 |
+
|
| 407 |
+
'''
|
| 408 |
+
P, T = self._to_perm_group()
|
| 409 |
+
perm_result = getattr(P, method_name)(*args)
|
| 410 |
+
single = False
|
| 411 |
+
if isinstance(perm_result, PermutationGroup):
|
| 412 |
+
perm_result, single = [perm_result], True
|
| 413 |
+
result = []
|
| 414 |
+
for group in perm_result:
|
| 415 |
+
gens = group.generators
|
| 416 |
+
result.append(T.invert(gens))
|
| 417 |
+
return result[0] if single else result
|
| 418 |
+
|
| 419 |
+
def derived_series(self):
|
| 420 |
+
'''
|
| 421 |
+
Return the list of lists containing the generators
|
| 422 |
+
of the subgroups in the derived series of `self`.
|
| 423 |
+
|
| 424 |
+
'''
|
| 425 |
+
return self._perm_group_list('derived_series')
|
| 426 |
+
|
| 427 |
+
def lower_central_series(self):
|
| 428 |
+
'''
|
| 429 |
+
Return the list of lists containing the generators
|
| 430 |
+
of the subgroups in the lower central series of `self`.
|
| 431 |
+
|
| 432 |
+
'''
|
| 433 |
+
return self._perm_group_list('lower_central_series')
|
| 434 |
+
|
| 435 |
+
def center(self):
|
| 436 |
+
'''
|
| 437 |
+
Return the list of generators of the center of `self`.
|
| 438 |
+
|
| 439 |
+
'''
|
| 440 |
+
return self._perm_group_list('center')
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def derived_subgroup(self):
|
| 444 |
+
'''
|
| 445 |
+
Return the list of generators of the derived subgroup of `self`.
|
| 446 |
+
|
| 447 |
+
'''
|
| 448 |
+
return self._perm_group_list('derived_subgroup')
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
def centralizer(self, other):
|
| 452 |
+
'''
|
| 453 |
+
Return the list of generators of the centralizer of `other`
|
| 454 |
+
(a list of elements of `self`) in `self`.
|
| 455 |
+
|
| 456 |
+
'''
|
| 457 |
+
T = self._to_perm_group()[1]
|
| 458 |
+
other = T(other)
|
| 459 |
+
return self._perm_group_list('centralizer', other)
|
| 460 |
+
|
| 461 |
+
def normal_closure(self, other):
|
| 462 |
+
'''
|
| 463 |
+
Return the list of generators of the normal closure of `other`
|
| 464 |
+
(a list of elements of `self`) in `self`.
|
| 465 |
+
|
| 466 |
+
'''
|
| 467 |
+
T = self._to_perm_group()[1]
|
| 468 |
+
other = T(other)
|
| 469 |
+
return self._perm_group_list('normal_closure', other)
|
| 470 |
+
|
| 471 |
+
def _perm_property(self, attr):
|
| 472 |
+
'''
|
| 473 |
+
Given an attribute of a `PermutationGroup`, return
|
| 474 |
+
its value for a permutation group isomorphic to `self`.
|
| 475 |
+
|
| 476 |
+
'''
|
| 477 |
+
P = self._to_perm_group()[0]
|
| 478 |
+
return getattr(P, attr)
|
| 479 |
+
|
| 480 |
+
@property
|
| 481 |
+
def is_abelian(self):
|
| 482 |
+
'''
|
| 483 |
+
Check if `self` is abelian.
|
| 484 |
+
|
| 485 |
+
'''
|
| 486 |
+
return self._perm_property("is_abelian")
|
| 487 |
+
|
| 488 |
+
@property
|
| 489 |
+
def is_nilpotent(self):
|
| 490 |
+
'''
|
| 491 |
+
Check if `self` is nilpotent.
|
| 492 |
+
|
| 493 |
+
'''
|
| 494 |
+
return self._perm_property("is_nilpotent")
|
| 495 |
+
|
| 496 |
+
@property
|
| 497 |
+
def is_solvable(self):
|
| 498 |
+
'''
|
| 499 |
+
Check if `self` is solvable.
|
| 500 |
+
|
| 501 |
+
'''
|
| 502 |
+
return self._perm_property("is_solvable")
|
| 503 |
+
|
| 504 |
+
@property
|
| 505 |
+
def elements(self):
|
| 506 |
+
'''
|
| 507 |
+
List the elements of `self`.
|
| 508 |
+
|
| 509 |
+
'''
|
| 510 |
+
P, T = self._to_perm_group()
|
| 511 |
+
return T.invert(P.elements)
|
| 512 |
+
|
| 513 |
+
@property
|
| 514 |
+
def is_cyclic(self):
|
| 515 |
+
"""
|
| 516 |
+
Return ``True`` if group is Cyclic.
|
| 517 |
+
|
| 518 |
+
"""
|
| 519 |
+
if len(self.generators) <= 1:
|
| 520 |
+
return True
|
| 521 |
+
try:
|
| 522 |
+
P, T = self._to_perm_group()
|
| 523 |
+
except NotImplementedError:
|
| 524 |
+
raise NotImplementedError("Check for infinite Cyclic group "
|
| 525 |
+
"is not implemented")
|
| 526 |
+
return P.is_cyclic
|
| 527 |
+
|
| 528 |
+
def abelian_invariants(self):
|
| 529 |
+
"""
|
| 530 |
+
Return Abelian Invariants of a group.
|
| 531 |
+
"""
|
| 532 |
+
try:
|
| 533 |
+
P, T = self._to_perm_group()
|
| 534 |
+
except NotImplementedError:
|
| 535 |
+
raise NotImplementedError("abelian invariants is not implemented"
|
| 536 |
+
"for infinite group")
|
| 537 |
+
return P.abelian_invariants()
|
| 538 |
+
|
| 539 |
+
def composition_series(self):
|
| 540 |
+
"""
|
| 541 |
+
Return subnormal series of maximum length for a group.
|
| 542 |
+
"""
|
| 543 |
+
try:
|
| 544 |
+
P, T = self._to_perm_group()
|
| 545 |
+
except NotImplementedError:
|
| 546 |
+
raise NotImplementedError("composition series is not implemented"
|
| 547 |
+
"for infinite group")
|
| 548 |
+
return P.composition_series()
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
class FpSubgroup(DefaultPrinting):
|
| 552 |
+
'''
|
| 553 |
+
The class implementing a subgroup of an FpGroup or a FreeGroup
|
| 554 |
+
(only finite index subgroups are supported at this point). This
|
| 555 |
+
is to be used if one wishes to check if an element of the original
|
| 556 |
+
group belongs to the subgroup
|
| 557 |
+
|
| 558 |
+
'''
|
| 559 |
+
def __init__(self, G, gens, normal=False):
|
| 560 |
+
super().__init__()
|
| 561 |
+
self.parent = G
|
| 562 |
+
self.generators = list({g for g in gens if g != G.identity})
|
| 563 |
+
self._min_words = None #for use in __contains__
|
| 564 |
+
self.C = None
|
| 565 |
+
self.normal = normal
|
| 566 |
+
|
| 567 |
+
def __contains__(self, g):
|
| 568 |
+
|
| 569 |
+
if isinstance(self.parent, FreeGroup):
|
| 570 |
+
if self._min_words is None:
|
| 571 |
+
# make _min_words - a list of subwords such that
|
| 572 |
+
# g is in the subgroup if and only if it can be
|
| 573 |
+
# partitioned into these subwords. Infinite families of
|
| 574 |
+
# subwords are presented by tuples, e.g. (r, w)
|
| 575 |
+
# stands for the family of subwords r*w**n*r**-1
|
| 576 |
+
|
| 577 |
+
def _process(w):
|
| 578 |
+
# this is to be used before adding new words
|
| 579 |
+
# into _min_words; if the word w is not cyclically
|
| 580 |
+
# reduced, it will generate an infinite family of
|
| 581 |
+
# subwords so should be written as a tuple;
|
| 582 |
+
# if it is, w**-1 should be added to the list
|
| 583 |
+
# as well
|
| 584 |
+
p, r = w.cyclic_reduction(removed=True)
|
| 585 |
+
if not r.is_identity:
|
| 586 |
+
return [(r, p)]
|
| 587 |
+
else:
|
| 588 |
+
return [w, w**-1]
|
| 589 |
+
|
| 590 |
+
# make the initial list
|
| 591 |
+
gens = []
|
| 592 |
+
for w in self.generators:
|
| 593 |
+
if self.normal:
|
| 594 |
+
w = w.cyclic_reduction()
|
| 595 |
+
gens.extend(_process(w))
|
| 596 |
+
|
| 597 |
+
for w1 in gens:
|
| 598 |
+
for w2 in gens:
|
| 599 |
+
# if w1 and w2 are equal or are inverses, continue
|
| 600 |
+
if w1 == w2 or (not isinstance(w1, tuple)
|
| 601 |
+
and w1**-1 == w2):
|
| 602 |
+
continue
|
| 603 |
+
|
| 604 |
+
# if the start of one word is the inverse of the
|
| 605 |
+
# end of the other, their multiple should be added
|
| 606 |
+
# to _min_words because of cancellation
|
| 607 |
+
if isinstance(w1, tuple):
|
| 608 |
+
# start, end
|
| 609 |
+
s1, s2 = w1[0][0], w1[0][0]**-1
|
| 610 |
+
else:
|
| 611 |
+
s1, s2 = w1[0], w1[len(w1)-1]
|
| 612 |
+
|
| 613 |
+
if isinstance(w2, tuple):
|
| 614 |
+
# start, end
|
| 615 |
+
r1, r2 = w2[0][0], w2[0][0]**-1
|
| 616 |
+
else:
|
| 617 |
+
r1, r2 = w2[0], w2[len(w1)-1]
|
| 618 |
+
|
| 619 |
+
# p1 and p2 are w1 and w2 or, in case when
|
| 620 |
+
# w1 or w2 is an infinite family, a representative
|
| 621 |
+
p1, p2 = w1, w2
|
| 622 |
+
if isinstance(w1, tuple):
|
| 623 |
+
p1 = w1[0]*w1[1]*w1[0]**-1
|
| 624 |
+
if isinstance(w2, tuple):
|
| 625 |
+
p2 = w2[0]*w2[1]*w2[0]**-1
|
| 626 |
+
|
| 627 |
+
# add the product of the words to the list is necessary
|
| 628 |
+
if r1**-1 == s2 and not (p1*p2).is_identity:
|
| 629 |
+
new = _process(p1*p2)
|
| 630 |
+
if new not in gens:
|
| 631 |
+
gens.extend(new)
|
| 632 |
+
|
| 633 |
+
if r2**-1 == s1 and not (p2*p1).is_identity:
|
| 634 |
+
new = _process(p2*p1)
|
| 635 |
+
if new not in gens:
|
| 636 |
+
gens.extend(new)
|
| 637 |
+
|
| 638 |
+
self._min_words = gens
|
| 639 |
+
|
| 640 |
+
min_words = self._min_words
|
| 641 |
+
|
| 642 |
+
def _is_subword(w):
|
| 643 |
+
# check if w is a word in _min_words or one of
|
| 644 |
+
# the infinite families in it
|
| 645 |
+
w, r = w.cyclic_reduction(removed=True)
|
| 646 |
+
if r.is_identity or self.normal:
|
| 647 |
+
return w in min_words
|
| 648 |
+
else:
|
| 649 |
+
t = [s[1] for s in min_words if isinstance(s, tuple)
|
| 650 |
+
and s[0] == r]
|
| 651 |
+
return [s for s in t if w.power_of(s)] != []
|
| 652 |
+
|
| 653 |
+
# store the solution of words for which the result of
|
| 654 |
+
# _word_break (below) is known
|
| 655 |
+
known = {}
|
| 656 |
+
|
| 657 |
+
def _word_break(w):
|
| 658 |
+
# check if w can be written as a product of words
|
| 659 |
+
# in min_words
|
| 660 |
+
if len(w) == 0:
|
| 661 |
+
return True
|
| 662 |
+
i = 0
|
| 663 |
+
while i < len(w):
|
| 664 |
+
i += 1
|
| 665 |
+
prefix = w.subword(0, i)
|
| 666 |
+
if not _is_subword(prefix):
|
| 667 |
+
continue
|
| 668 |
+
rest = w.subword(i, len(w))
|
| 669 |
+
if rest not in known:
|
| 670 |
+
known[rest] = _word_break(rest)
|
| 671 |
+
if known[rest]:
|
| 672 |
+
return True
|
| 673 |
+
return False
|
| 674 |
+
|
| 675 |
+
if self.normal:
|
| 676 |
+
g = g.cyclic_reduction()
|
| 677 |
+
return _word_break(g)
|
| 678 |
+
else:
|
| 679 |
+
if self.C is None:
|
| 680 |
+
C = self.parent.coset_enumeration(self.generators)
|
| 681 |
+
self.C = C
|
| 682 |
+
i = 0
|
| 683 |
+
C = self.C
|
| 684 |
+
for j in range(len(g)):
|
| 685 |
+
i = C.table[i][C.A_dict[g[j]]]
|
| 686 |
+
return i == 0
|
| 687 |
+
|
| 688 |
+
def order(self):
|
| 689 |
+
if not self.generators:
|
| 690 |
+
return S.One
|
| 691 |
+
if isinstance(self.parent, FreeGroup):
|
| 692 |
+
return S.Infinity
|
| 693 |
+
if self.C is None:
|
| 694 |
+
C = self.parent.coset_enumeration(self.generators)
|
| 695 |
+
self.C = C
|
| 696 |
+
# This is valid because `len(self.C.table)` (the index of the subgroup)
|
| 697 |
+
# will always be finite - otherwise coset enumeration doesn't terminate
|
| 698 |
+
return self.parent.order()/len(self.C.table)
|
| 699 |
+
|
| 700 |
+
def to_FpGroup(self):
|
| 701 |
+
if isinstance(self.parent, FreeGroup):
|
| 702 |
+
gen_syms = [('x_%d'%i) for i in range(len(self.generators))]
|
| 703 |
+
return free_group(', '.join(gen_syms))[0]
|
| 704 |
+
return self.parent.subgroup(C=self.C)
|
| 705 |
+
|
| 706 |
+
def __str__(self):
|
| 707 |
+
if len(self.generators) > 30:
|
| 708 |
+
str_form = "<fp subgroup with %s generators>" % len(self.generators)
|
| 709 |
+
else:
|
| 710 |
+
str_form = "<fp subgroup on the generators %s>" % str(self.generators)
|
| 711 |
+
return str_form
|
| 712 |
+
|
| 713 |
+
__repr__ = __str__
|
| 714 |
+
|
| 715 |
+
|
| 716 |
+
###############################################################################
|
| 717 |
+
# LOW INDEX SUBGROUPS #
|
| 718 |
+
###############################################################################
|
| 719 |
+
|
| 720 |
+
def low_index_subgroups(G, N, Y=()):
|
| 721 |
+
"""
|
| 722 |
+
Implements the Low Index Subgroups algorithm, i.e find all subgroups of
|
| 723 |
+
``G`` upto a given index ``N``. This implements the method described in
|
| 724 |
+
[Sim94]. This procedure involves a backtrack search over incomplete Coset
|
| 725 |
+
Tables, rather than over forced coincidences.
|
| 726 |
+
|
| 727 |
+
Parameters
|
| 728 |
+
==========
|
| 729 |
+
|
| 730 |
+
G: An FpGroup < X|R >
|
| 731 |
+
N: positive integer, representing the maximum index value for subgroups
|
| 732 |
+
Y: (an optional argument) specifying a list of subgroup generators, such
|
| 733 |
+
that each of the resulting subgroup contains the subgroup generated by Y.
|
| 734 |
+
|
| 735 |
+
Examples
|
| 736 |
+
========
|
| 737 |
+
|
| 738 |
+
>>> from sympy.combinatorics import free_group
|
| 739 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, low_index_subgroups
|
| 740 |
+
>>> F, x, y = free_group("x, y")
|
| 741 |
+
>>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
|
| 742 |
+
>>> L = low_index_subgroups(f, 4)
|
| 743 |
+
>>> for coset_table in L:
|
| 744 |
+
... print(coset_table.table)
|
| 745 |
+
[[0, 0, 0, 0]]
|
| 746 |
+
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]]
|
| 747 |
+
[[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]]
|
| 748 |
+
[[1, 1, 0, 0], [0, 0, 1, 1]]
|
| 749 |
+
|
| 750 |
+
References
|
| 751 |
+
==========
|
| 752 |
+
|
| 753 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 754 |
+
"Handbook of Computational Group Theory"
|
| 755 |
+
Section 5.4
|
| 756 |
+
|
| 757 |
+
.. [2] Marston Conder and Peter Dobcsanyi
|
| 758 |
+
"Applications and Adaptions of the Low Index Subgroups Procedure"
|
| 759 |
+
|
| 760 |
+
"""
|
| 761 |
+
C = CosetTable(G, [])
|
| 762 |
+
R = G.relators
|
| 763 |
+
# length chosen for the length of the short relators
|
| 764 |
+
len_short_rel = 5
|
| 765 |
+
# elements of R2 only checked at the last step for complete
|
| 766 |
+
# coset tables
|
| 767 |
+
R2 = {rel for rel in R if len(rel) > len_short_rel}
|
| 768 |
+
# elements of R1 are used in inner parts of the process to prune
|
| 769 |
+
# branches of the search tree,
|
| 770 |
+
R1 = {rel.identity_cyclic_reduction() for rel in set(R) - R2}
|
| 771 |
+
R1_c_list = C.conjugates(R1)
|
| 772 |
+
S = []
|
| 773 |
+
descendant_subgroups(S, C, R1_c_list, C.A[0], R2, N, Y)
|
| 774 |
+
return S
|
| 775 |
+
|
| 776 |
+
|
| 777 |
+
def descendant_subgroups(S, C, R1_c_list, x, R2, N, Y):
|
| 778 |
+
A_dict = C.A_dict
|
| 779 |
+
A_dict_inv = C.A_dict_inv
|
| 780 |
+
if C.is_complete():
|
| 781 |
+
# if C is complete then it only needs to test
|
| 782 |
+
# whether the relators in R2 are satisfied
|
| 783 |
+
for w, alpha in product(R2, C.omega):
|
| 784 |
+
if not C.scan_check(alpha, w):
|
| 785 |
+
return
|
| 786 |
+
# relators in R2 are satisfied, append the table to list
|
| 787 |
+
S.append(C)
|
| 788 |
+
else:
|
| 789 |
+
# find the first undefined entry in Coset Table
|
| 790 |
+
for alpha, x in product(range(len(C.table)), C.A):
|
| 791 |
+
if C.table[alpha][A_dict[x]] is None:
|
| 792 |
+
# this is "x" in pseudo-code (using "y" makes it clear)
|
| 793 |
+
undefined_coset, undefined_gen = alpha, x
|
| 794 |
+
break
|
| 795 |
+
# for filling up the undefine entry we try all possible values
|
| 796 |
+
# of beta in Omega or beta = n where beta^(undefined_gen^-1) is undefined
|
| 797 |
+
reach = C.omega + [C.n]
|
| 798 |
+
for beta in reach:
|
| 799 |
+
if beta < N:
|
| 800 |
+
if beta == C.n or C.table[beta][A_dict_inv[undefined_gen]] is None:
|
| 801 |
+
try_descendant(S, C, R1_c_list, R2, N, undefined_coset, \
|
| 802 |
+
undefined_gen, beta, Y)
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
def try_descendant(S, C, R1_c_list, R2, N, alpha, x, beta, Y):
|
| 806 |
+
r"""
|
| 807 |
+
Solves the problem of trying out each individual possibility
|
| 808 |
+
for `\alpha^x.
|
| 809 |
+
|
| 810 |
+
"""
|
| 811 |
+
D = C.copy()
|
| 812 |
+
if beta == D.n and beta < N:
|
| 813 |
+
D.table.append([None]*len(D.A))
|
| 814 |
+
D.p.append(beta)
|
| 815 |
+
D.table[alpha][D.A_dict[x]] = beta
|
| 816 |
+
D.table[beta][D.A_dict_inv[x]] = alpha
|
| 817 |
+
D.deduction_stack.append((alpha, x))
|
| 818 |
+
if not D.process_deductions_check(R1_c_list[D.A_dict[x]], \
|
| 819 |
+
R1_c_list[D.A_dict_inv[x]]):
|
| 820 |
+
return
|
| 821 |
+
for w in Y:
|
| 822 |
+
if not D.scan_check(0, w):
|
| 823 |
+
return
|
| 824 |
+
if first_in_class(D, Y):
|
| 825 |
+
descendant_subgroups(S, D, R1_c_list, x, R2, N, Y)
|
| 826 |
+
|
| 827 |
+
|
| 828 |
+
def first_in_class(C, Y=()):
|
| 829 |
+
"""
|
| 830 |
+
Checks whether the subgroup ``H=G1`` corresponding to the Coset Table
|
| 831 |
+
could possibly be the canonical representative of its conjugacy class.
|
| 832 |
+
|
| 833 |
+
Parameters
|
| 834 |
+
==========
|
| 835 |
+
|
| 836 |
+
C: CosetTable
|
| 837 |
+
|
| 838 |
+
Returns
|
| 839 |
+
=======
|
| 840 |
+
|
| 841 |
+
bool: True/False
|
| 842 |
+
|
| 843 |
+
If this returns False, then no descendant of C can have that property, and
|
| 844 |
+
so we can abandon C. If it returns True, then we need to process further
|
| 845 |
+
the node of the search tree corresponding to C, and so we call
|
| 846 |
+
``descendant_subgroups`` recursively on C.
|
| 847 |
+
|
| 848 |
+
Examples
|
| 849 |
+
========
|
| 850 |
+
|
| 851 |
+
>>> from sympy.combinatorics import free_group
|
| 852 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, first_in_class
|
| 853 |
+
>>> F, x, y = free_group("x, y")
|
| 854 |
+
>>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
|
| 855 |
+
>>> C = CosetTable(f, [])
|
| 856 |
+
>>> C.table = [[0, 0, None, None]]
|
| 857 |
+
>>> first_in_class(C)
|
| 858 |
+
True
|
| 859 |
+
>>> C.table = [[1, 1, 1, None], [0, 0, None, 1]]; C.p = [0, 1]
|
| 860 |
+
>>> first_in_class(C)
|
| 861 |
+
True
|
| 862 |
+
>>> C.table = [[1, 1, 2, 1], [0, 0, 0, None], [None, None, None, 0]]
|
| 863 |
+
>>> C.p = [0, 1, 2]
|
| 864 |
+
>>> first_in_class(C)
|
| 865 |
+
False
|
| 866 |
+
>>> C.table = [[1, 1, 1, 2], [0, 0, 2, 0], [2, None, 0, 1]]
|
| 867 |
+
>>> first_in_class(C)
|
| 868 |
+
False
|
| 869 |
+
|
| 870 |
+
# TODO:: Sims points out in [Sim94] that performance can be improved by
|
| 871 |
+
# remembering some of the information computed by ``first_in_class``. If
|
| 872 |
+
# the ``continue alpha`` statement is executed at line 14, then the same thing
|
| 873 |
+
# will happen for that value of alpha in any descendant of the table C, and so
|
| 874 |
+
# the values the values of alpha for which this occurs could profitably be
|
| 875 |
+
# stored and passed through to the descendants of C. Of course this would
|
| 876 |
+
# make the code more complicated.
|
| 877 |
+
|
| 878 |
+
# The code below is taken directly from the function on page 208 of [Sim94]
|
| 879 |
+
# nu[alpha]
|
| 880 |
+
|
| 881 |
+
"""
|
| 882 |
+
n = C.n
|
| 883 |
+
# lamda is the largest numbered point in Omega_c_alpha which is currently defined
|
| 884 |
+
lamda = -1
|
| 885 |
+
# for alpha in Omega_c, nu[alpha] is the point in Omega_c_alpha corresponding to alpha
|
| 886 |
+
nu = [None]*n
|
| 887 |
+
# for alpha in Omega_c_alpha, mu[alpha] is the point in Omega_c corresponding to alpha
|
| 888 |
+
mu = [None]*n
|
| 889 |
+
# mutually nu and mu are the mutually-inverse equivalence maps between
|
| 890 |
+
# Omega_c_alpha and Omega_c
|
| 891 |
+
next_alpha = False
|
| 892 |
+
# For each 0!=alpha in [0 .. nc-1], we start by constructing the equivalent
|
| 893 |
+
# standardized coset table C_alpha corresponding to H_alpha
|
| 894 |
+
for alpha in range(1, n):
|
| 895 |
+
# reset nu to "None" after previous value of alpha
|
| 896 |
+
for beta in range(lamda+1):
|
| 897 |
+
nu[mu[beta]] = None
|
| 898 |
+
# we only want to reject our current table in favour of a preceding
|
| 899 |
+
# table in the ordering in which 1 is replaced by alpha, if the subgroup
|
| 900 |
+
# G_alpha corresponding to this preceding table definitely contains the
|
| 901 |
+
# given subgroup
|
| 902 |
+
for w in Y:
|
| 903 |
+
# TODO: this should support input of a list of general words
|
| 904 |
+
# not just the words which are in "A" (i.e gen and gen^-1)
|
| 905 |
+
if C.table[alpha][C.A_dict[w]] != alpha:
|
| 906 |
+
# continue with alpha
|
| 907 |
+
next_alpha = True
|
| 908 |
+
break
|
| 909 |
+
if next_alpha:
|
| 910 |
+
next_alpha = False
|
| 911 |
+
continue
|
| 912 |
+
# try alpha as the new point 0 in Omega_C_alpha
|
| 913 |
+
mu[0] = alpha
|
| 914 |
+
nu[alpha] = 0
|
| 915 |
+
# compare corresponding entries in C and C_alpha
|
| 916 |
+
lamda = 0
|
| 917 |
+
for beta in range(n):
|
| 918 |
+
for x in C.A:
|
| 919 |
+
gamma = C.table[beta][C.A_dict[x]]
|
| 920 |
+
delta = C.table[mu[beta]][C.A_dict[x]]
|
| 921 |
+
# if either of the entries is undefined,
|
| 922 |
+
# we move with next alpha
|
| 923 |
+
if gamma is None or delta is None:
|
| 924 |
+
# continue with alpha
|
| 925 |
+
next_alpha = True
|
| 926 |
+
break
|
| 927 |
+
if nu[delta] is None:
|
| 928 |
+
# delta becomes the next point in Omega_C_alpha
|
| 929 |
+
lamda += 1
|
| 930 |
+
nu[delta] = lamda
|
| 931 |
+
mu[lamda] = delta
|
| 932 |
+
if nu[delta] < gamma:
|
| 933 |
+
return False
|
| 934 |
+
if nu[delta] > gamma:
|
| 935 |
+
# continue with alpha
|
| 936 |
+
next_alpha = True
|
| 937 |
+
break
|
| 938 |
+
if next_alpha:
|
| 939 |
+
next_alpha = False
|
| 940 |
+
break
|
| 941 |
+
return True
|
| 942 |
+
|
| 943 |
+
#========================================================================
|
| 944 |
+
# Simplifying Presentation
|
| 945 |
+
#========================================================================
|
| 946 |
+
|
| 947 |
+
def simplify_presentation(*args, change_gens=False):
|
| 948 |
+
'''
|
| 949 |
+
For an instance of `FpGroup`, return a simplified isomorphic copy of
|
| 950 |
+
the group (e.g. remove redundant generators or relators). Alternatively,
|
| 951 |
+
a list of generators and relators can be passed in which case the
|
| 952 |
+
simplified lists will be returned.
|
| 953 |
+
|
| 954 |
+
By default, the generators of the group are unchanged. If you would
|
| 955 |
+
like to remove redundant generators, set the keyword argument
|
| 956 |
+
`change_gens = True`.
|
| 957 |
+
|
| 958 |
+
'''
|
| 959 |
+
if len(args) == 1:
|
| 960 |
+
if not isinstance(args[0], FpGroup):
|
| 961 |
+
raise TypeError("The argument must be an instance of FpGroup")
|
| 962 |
+
G = args[0]
|
| 963 |
+
gens, rels = simplify_presentation(G.generators, G.relators,
|
| 964 |
+
change_gens=change_gens)
|
| 965 |
+
if gens:
|
| 966 |
+
return FpGroup(gens[0].group, rels)
|
| 967 |
+
return FpGroup(FreeGroup([]), [])
|
| 968 |
+
elif len(args) == 2:
|
| 969 |
+
gens, rels = args[0][:], args[1][:]
|
| 970 |
+
if not gens:
|
| 971 |
+
return gens, rels
|
| 972 |
+
identity = gens[0].group.identity
|
| 973 |
+
else:
|
| 974 |
+
if len(args) == 0:
|
| 975 |
+
m = "Not enough arguments"
|
| 976 |
+
else:
|
| 977 |
+
m = "Too many arguments"
|
| 978 |
+
raise RuntimeError(m)
|
| 979 |
+
|
| 980 |
+
prev_gens = []
|
| 981 |
+
prev_rels = []
|
| 982 |
+
while not set(prev_rels) == set(rels):
|
| 983 |
+
prev_rels = rels
|
| 984 |
+
while change_gens and not set(prev_gens) == set(gens):
|
| 985 |
+
prev_gens = gens
|
| 986 |
+
gens, rels = elimination_technique_1(gens, rels, identity)
|
| 987 |
+
rels = _simplify_relators(rels)
|
| 988 |
+
|
| 989 |
+
if change_gens:
|
| 990 |
+
syms = [g.array_form[0][0] for g in gens]
|
| 991 |
+
F = free_group(syms)[0]
|
| 992 |
+
identity = F.identity
|
| 993 |
+
gens = F.generators
|
| 994 |
+
subs = dict(zip(syms, gens))
|
| 995 |
+
for j, r in enumerate(rels):
|
| 996 |
+
a = r.array_form
|
| 997 |
+
rel = identity
|
| 998 |
+
for sym, p in a:
|
| 999 |
+
rel = rel*subs[sym]**p
|
| 1000 |
+
rels[j] = rel
|
| 1001 |
+
return gens, rels
|
| 1002 |
+
|
| 1003 |
+
def _simplify_relators(rels):
|
| 1004 |
+
"""
|
| 1005 |
+
Simplifies a set of relators. All relators are checked to see if they are
|
| 1006 |
+
of the form `gen^n`. If any such relators are found then all other relators
|
| 1007 |
+
are processed for strings in the `gen` known order.
|
| 1008 |
+
|
| 1009 |
+
Examples
|
| 1010 |
+
========
|
| 1011 |
+
|
| 1012 |
+
>>> from sympy.combinatorics import free_group
|
| 1013 |
+
>>> from sympy.combinatorics.fp_groups import _simplify_relators
|
| 1014 |
+
>>> F, x, y = free_group("x, y")
|
| 1015 |
+
>>> w1 = [x**2*y**4, x**3]
|
| 1016 |
+
>>> _simplify_relators(w1)
|
| 1017 |
+
[x**3, x**-1*y**4]
|
| 1018 |
+
|
| 1019 |
+
>>> w2 = [x**2*y**-4*x**5, x**3, x**2*y**8, y**5]
|
| 1020 |
+
>>> _simplify_relators(w2)
|
| 1021 |
+
[x**-1*y**-2, x**-1*y*x**-1, x**3, y**5]
|
| 1022 |
+
|
| 1023 |
+
>>> w3 = [x**6*y**4, x**4]
|
| 1024 |
+
>>> _simplify_relators(w3)
|
| 1025 |
+
[x**4, x**2*y**4]
|
| 1026 |
+
|
| 1027 |
+
>>> w4 = [x**2, x**5, y**3]
|
| 1028 |
+
>>> _simplify_relators(w4)
|
| 1029 |
+
[x, y**3]
|
| 1030 |
+
|
| 1031 |
+
"""
|
| 1032 |
+
rels = rels[:]
|
| 1033 |
+
|
| 1034 |
+
if not rels:
|
| 1035 |
+
return []
|
| 1036 |
+
|
| 1037 |
+
identity = rels[0].group.identity
|
| 1038 |
+
|
| 1039 |
+
# build dictionary with "gen: n" where gen^n is one of the relators
|
| 1040 |
+
exps = {}
|
| 1041 |
+
for i in range(len(rels)):
|
| 1042 |
+
rel = rels[i]
|
| 1043 |
+
if rel.number_syllables() == 1:
|
| 1044 |
+
g = rel[0]
|
| 1045 |
+
exp = abs(rel.array_form[0][1])
|
| 1046 |
+
if rel.array_form[0][1] < 0:
|
| 1047 |
+
rels[i] = rels[i]**-1
|
| 1048 |
+
g = g**-1
|
| 1049 |
+
if g in exps:
|
| 1050 |
+
exp = gcd(exp, exps[g].array_form[0][1])
|
| 1051 |
+
exps[g] = g**exp
|
| 1052 |
+
|
| 1053 |
+
one_syllables_words = list(exps.values())
|
| 1054 |
+
# decrease some of the exponents in relators, making use of the single
|
| 1055 |
+
# syllable relators
|
| 1056 |
+
for i, rel in enumerate(rels):
|
| 1057 |
+
if rel in one_syllables_words:
|
| 1058 |
+
continue
|
| 1059 |
+
rel = rel.eliminate_words(one_syllables_words, _all = True)
|
| 1060 |
+
# if rels[i] contains g**n where abs(n) is greater than half of the power p
|
| 1061 |
+
# of g in exps, g**n can be replaced by g**(n-p) (or g**(p-n) if n<0)
|
| 1062 |
+
for g in rel.contains_generators():
|
| 1063 |
+
if g in exps:
|
| 1064 |
+
exp = exps[g].array_form[0][1]
|
| 1065 |
+
max_exp = (exp + 1)//2
|
| 1066 |
+
rel = rel.eliminate_word(g**(max_exp), g**(max_exp-exp), _all = True)
|
| 1067 |
+
rel = rel.eliminate_word(g**(-max_exp), g**(-(max_exp-exp)), _all = True)
|
| 1068 |
+
rels[i] = rel
|
| 1069 |
+
|
| 1070 |
+
rels = [r.identity_cyclic_reduction() for r in rels]
|
| 1071 |
+
|
| 1072 |
+
rels += one_syllables_words # include one_syllable_words in the list of relators
|
| 1073 |
+
rels = list(set(rels)) # get unique values in rels
|
| 1074 |
+
rels.sort()
|
| 1075 |
+
|
| 1076 |
+
# remove <identity> entries in rels
|
| 1077 |
+
try:
|
| 1078 |
+
rels.remove(identity)
|
| 1079 |
+
except ValueError:
|
| 1080 |
+
pass
|
| 1081 |
+
return rels
|
| 1082 |
+
|
| 1083 |
+
# Pg 350, section 2.5.1 from [2]
|
| 1084 |
+
def elimination_technique_1(gens, rels, identity):
|
| 1085 |
+
rels = rels[:]
|
| 1086 |
+
# the shorter relators are examined first so that generators selected for
|
| 1087 |
+
# elimination will have shorter strings as equivalent
|
| 1088 |
+
rels.sort()
|
| 1089 |
+
gens = gens[:]
|
| 1090 |
+
redundant_gens = {}
|
| 1091 |
+
redundant_rels = []
|
| 1092 |
+
used_gens = set()
|
| 1093 |
+
# examine each relator in relator list for any generator occurring exactly
|
| 1094 |
+
# once
|
| 1095 |
+
for rel in rels:
|
| 1096 |
+
# don't look for a redundant generator in a relator which
|
| 1097 |
+
# depends on previously found ones
|
| 1098 |
+
contained_gens = rel.contains_generators()
|
| 1099 |
+
if any(g in contained_gens for g in redundant_gens):
|
| 1100 |
+
continue
|
| 1101 |
+
contained_gens = list(contained_gens)
|
| 1102 |
+
contained_gens.sort(reverse = True)
|
| 1103 |
+
for gen in contained_gens:
|
| 1104 |
+
if rel.generator_count(gen) == 1 and gen not in used_gens:
|
| 1105 |
+
k = rel.exponent_sum(gen)
|
| 1106 |
+
gen_index = rel.index(gen**k)
|
| 1107 |
+
bk = rel.subword(gen_index + 1, len(rel))
|
| 1108 |
+
fw = rel.subword(0, gen_index)
|
| 1109 |
+
chi = bk*fw
|
| 1110 |
+
redundant_gens[gen] = chi**(-1*k)
|
| 1111 |
+
used_gens.update(chi.contains_generators())
|
| 1112 |
+
redundant_rels.append(rel)
|
| 1113 |
+
break
|
| 1114 |
+
rels = [r for r in rels if r not in redundant_rels]
|
| 1115 |
+
# eliminate the redundant generators from remaining relators
|
| 1116 |
+
rels = [r.eliminate_words(redundant_gens, _all = True).identity_cyclic_reduction() for r in rels]
|
| 1117 |
+
rels = list(set(rels))
|
| 1118 |
+
try:
|
| 1119 |
+
rels.remove(identity)
|
| 1120 |
+
except ValueError:
|
| 1121 |
+
pass
|
| 1122 |
+
gens = [g for g in gens if g not in redundant_gens]
|
| 1123 |
+
return gens, rels
|
| 1124 |
+
|
| 1125 |
+
###############################################################################
|
| 1126 |
+
# SUBGROUP PRESENTATIONS #
|
| 1127 |
+
###############################################################################
|
| 1128 |
+
|
| 1129 |
+
# Pg 175 [1]
|
| 1130 |
+
def define_schreier_generators(C, homomorphism=False):
|
| 1131 |
+
'''
|
| 1132 |
+
Parameters
|
| 1133 |
+
==========
|
| 1134 |
+
|
| 1135 |
+
C -- Coset table.
|
| 1136 |
+
homomorphism -- When set to True, return a dictionary containing the images
|
| 1137 |
+
of the presentation generators in the original group.
|
| 1138 |
+
'''
|
| 1139 |
+
y = []
|
| 1140 |
+
gamma = 1
|
| 1141 |
+
f = C.fp_group
|
| 1142 |
+
X = f.generators
|
| 1143 |
+
if homomorphism:
|
| 1144 |
+
# `_gens` stores the elements of the parent group to
|
| 1145 |
+
# to which the schreier generators correspond to.
|
| 1146 |
+
_gens = {}
|
| 1147 |
+
# compute the schreier Traversal
|
| 1148 |
+
tau = {}
|
| 1149 |
+
tau[0] = f.identity
|
| 1150 |
+
C.P = [[None]*len(C.A) for i in range(C.n)]
|
| 1151 |
+
for alpha, x in product(C.omega, C.A):
|
| 1152 |
+
beta = C.table[alpha][C.A_dict[x]]
|
| 1153 |
+
if beta == gamma:
|
| 1154 |
+
C.P[alpha][C.A_dict[x]] = "<identity>"
|
| 1155 |
+
C.P[beta][C.A_dict_inv[x]] = "<identity>"
|
| 1156 |
+
gamma += 1
|
| 1157 |
+
if homomorphism:
|
| 1158 |
+
tau[beta] = tau[alpha]*x
|
| 1159 |
+
elif x in X and C.P[alpha][C.A_dict[x]] is None:
|
| 1160 |
+
y_alpha_x = '%s_%s' % (x, alpha)
|
| 1161 |
+
y.append(y_alpha_x)
|
| 1162 |
+
C.P[alpha][C.A_dict[x]] = y_alpha_x
|
| 1163 |
+
if homomorphism:
|
| 1164 |
+
_gens[y_alpha_x] = tau[alpha]*x*tau[beta]**-1
|
| 1165 |
+
grp_gens = list(free_group(', '.join(y)))
|
| 1166 |
+
C._schreier_free_group = grp_gens.pop(0)
|
| 1167 |
+
C._schreier_generators = grp_gens
|
| 1168 |
+
if homomorphism:
|
| 1169 |
+
C._schreier_gen_elem = _gens
|
| 1170 |
+
# replace all elements of P by, free group elements
|
| 1171 |
+
for i, j in product(range(len(C.P)), range(len(C.A))):
|
| 1172 |
+
# if equals "<identity>", replace by identity element
|
| 1173 |
+
if C.P[i][j] == "<identity>":
|
| 1174 |
+
C.P[i][j] = C._schreier_free_group.identity
|
| 1175 |
+
elif isinstance(C.P[i][j], str):
|
| 1176 |
+
r = C._schreier_generators[y.index(C.P[i][j])]
|
| 1177 |
+
C.P[i][j] = r
|
| 1178 |
+
beta = C.table[i][j]
|
| 1179 |
+
C.P[beta][j + 1] = r**-1
|
| 1180 |
+
|
| 1181 |
+
def reidemeister_relators(C):
|
| 1182 |
+
R = C.fp_group.relators
|
| 1183 |
+
rels = [rewrite(C, coset, word) for word in R for coset in range(C.n)]
|
| 1184 |
+
order_1_gens = {i for i in rels if len(i) == 1}
|
| 1185 |
+
|
| 1186 |
+
# remove all the order 1 generators from relators
|
| 1187 |
+
rels = list(filter(lambda rel: rel not in order_1_gens, rels))
|
| 1188 |
+
|
| 1189 |
+
# replace order 1 generators by identity element in reidemeister relators
|
| 1190 |
+
for i in range(len(rels)):
|
| 1191 |
+
w = rels[i]
|
| 1192 |
+
w = w.eliminate_words(order_1_gens, _all=True)
|
| 1193 |
+
rels[i] = w
|
| 1194 |
+
|
| 1195 |
+
C._schreier_generators = [i for i in C._schreier_generators
|
| 1196 |
+
if not (i in order_1_gens or i**-1 in order_1_gens)]
|
| 1197 |
+
|
| 1198 |
+
# Tietze transformation 1 i.e TT_1
|
| 1199 |
+
# remove cyclic conjugate elements from relators
|
| 1200 |
+
i = 0
|
| 1201 |
+
while i < len(rels):
|
| 1202 |
+
w = rels[i]
|
| 1203 |
+
j = i + 1
|
| 1204 |
+
while j < len(rels):
|
| 1205 |
+
if w.is_cyclic_conjugate(rels[j]):
|
| 1206 |
+
del rels[j]
|
| 1207 |
+
else:
|
| 1208 |
+
j += 1
|
| 1209 |
+
i += 1
|
| 1210 |
+
|
| 1211 |
+
C._reidemeister_relators = rels
|
| 1212 |
+
|
| 1213 |
+
|
| 1214 |
+
def rewrite(C, alpha, w):
|
| 1215 |
+
"""
|
| 1216 |
+
Parameters
|
| 1217 |
+
==========
|
| 1218 |
+
|
| 1219 |
+
C: CosetTable
|
| 1220 |
+
alpha: A live coset
|
| 1221 |
+
w: A word in `A*`
|
| 1222 |
+
|
| 1223 |
+
Returns
|
| 1224 |
+
=======
|
| 1225 |
+
|
| 1226 |
+
rho(tau(alpha), w)
|
| 1227 |
+
|
| 1228 |
+
Examples
|
| 1229 |
+
========
|
| 1230 |
+
|
| 1231 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, define_schreier_generators, rewrite
|
| 1232 |
+
>>> from sympy.combinatorics import free_group
|
| 1233 |
+
>>> F, x, y = free_group("x, y")
|
| 1234 |
+
>>> f = FpGroup(F, [x**2, y**3, (x*y)**6])
|
| 1235 |
+
>>> C = CosetTable(f, [])
|
| 1236 |
+
>>> C.table = [[1, 1, 2, 3], [0, 0, 4, 5], [4, 4, 3, 0], [5, 5, 0, 2], [2, 2, 5, 1], [3, 3, 1, 4]]
|
| 1237 |
+
>>> C.p = [0, 1, 2, 3, 4, 5]
|
| 1238 |
+
>>> define_schreier_generators(C)
|
| 1239 |
+
>>> rewrite(C, 0, (x*y)**6)
|
| 1240 |
+
x_4*y_2*x_3*x_1*x_2*y_4*x_5
|
| 1241 |
+
|
| 1242 |
+
"""
|
| 1243 |
+
v = C._schreier_free_group.identity
|
| 1244 |
+
for i in range(len(w)):
|
| 1245 |
+
x_i = w[i]
|
| 1246 |
+
v = v*C.P[alpha][C.A_dict[x_i]]
|
| 1247 |
+
alpha = C.table[alpha][C.A_dict[x_i]]
|
| 1248 |
+
return v
|
| 1249 |
+
|
| 1250 |
+
# Pg 350, section 2.5.2 from [2]
|
| 1251 |
+
def elimination_technique_2(C):
|
| 1252 |
+
"""
|
| 1253 |
+
This technique eliminates one generator at a time. Heuristically this
|
| 1254 |
+
seems superior in that we may select for elimination the generator with
|
| 1255 |
+
shortest equivalent string at each stage.
|
| 1256 |
+
|
| 1257 |
+
>>> from sympy.combinatorics import free_group
|
| 1258 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, \
|
| 1259 |
+
reidemeister_relators, define_schreier_generators, elimination_technique_2
|
| 1260 |
+
>>> F, x, y = free_group("x, y")
|
| 1261 |
+
>>> f = FpGroup(F, [x**3, y**5, (x*y)**2]); H = [x*y, x**-1*y**-1*x*y*x]
|
| 1262 |
+
>>> C = coset_enumeration_r(f, H)
|
| 1263 |
+
>>> C.compress(); C.standardize()
|
| 1264 |
+
>>> define_schreier_generators(C)
|
| 1265 |
+
>>> reidemeister_relators(C)
|
| 1266 |
+
>>> elimination_technique_2(C)
|
| 1267 |
+
([y_1, y_2], [y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1, y_1**2])
|
| 1268 |
+
|
| 1269 |
+
"""
|
| 1270 |
+
rels = C._reidemeister_relators
|
| 1271 |
+
rels.sort(reverse=True)
|
| 1272 |
+
gens = C._schreier_generators
|
| 1273 |
+
for i in range(len(gens) - 1, -1, -1):
|
| 1274 |
+
rel = rels[i]
|
| 1275 |
+
for j in range(len(gens) - 1, -1, -1):
|
| 1276 |
+
gen = gens[j]
|
| 1277 |
+
if rel.generator_count(gen) == 1:
|
| 1278 |
+
k = rel.exponent_sum(gen)
|
| 1279 |
+
gen_index = rel.index(gen**k)
|
| 1280 |
+
bk = rel.subword(gen_index + 1, len(rel))
|
| 1281 |
+
fw = rel.subword(0, gen_index)
|
| 1282 |
+
rep_by = (bk*fw)**(-1*k)
|
| 1283 |
+
del rels[i]; del gens[j]
|
| 1284 |
+
for l in range(len(rels)):
|
| 1285 |
+
rels[l] = rels[l].eliminate_word(gen, rep_by)
|
| 1286 |
+
break
|
| 1287 |
+
C._reidemeister_relators = rels
|
| 1288 |
+
C._schreier_generators = gens
|
| 1289 |
+
return C._schreier_generators, C._reidemeister_relators
|
| 1290 |
+
|
| 1291 |
+
def reidemeister_presentation(fp_grp, H, C=None, homomorphism=False):
|
| 1292 |
+
"""
|
| 1293 |
+
Parameters
|
| 1294 |
+
==========
|
| 1295 |
+
|
| 1296 |
+
fp_group: A finitely presented group, an instance of FpGroup
|
| 1297 |
+
H: A subgroup whose presentation is to be found, given as a list
|
| 1298 |
+
of words in generators of `fp_grp`
|
| 1299 |
+
homomorphism: When set to True, return a homomorphism from the subgroup
|
| 1300 |
+
to the parent group
|
| 1301 |
+
|
| 1302 |
+
Examples
|
| 1303 |
+
========
|
| 1304 |
+
|
| 1305 |
+
>>> from sympy.combinatorics import free_group
|
| 1306 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup, reidemeister_presentation
|
| 1307 |
+
>>> F, x, y = free_group("x, y")
|
| 1308 |
+
|
| 1309 |
+
Example 5.6 Pg. 177 from [1]
|
| 1310 |
+
>>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
|
| 1311 |
+
>>> H = [x*y, x**-1*y**-1*x*y*x]
|
| 1312 |
+
>>> reidemeister_presentation(f, H)
|
| 1313 |
+
((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1))
|
| 1314 |
+
|
| 1315 |
+
Example 5.8 Pg. 183 from [1]
|
| 1316 |
+
>>> f = FpGroup(F, [x**3, y**3, (x*y)**3])
|
| 1317 |
+
>>> H = [x*y, x*y**-1]
|
| 1318 |
+
>>> reidemeister_presentation(f, H)
|
| 1319 |
+
((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0))
|
| 1320 |
+
|
| 1321 |
+
Exercises Q2. Pg 187 from [1]
|
| 1322 |
+
>>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
|
| 1323 |
+
>>> H = [x]
|
| 1324 |
+
>>> reidemeister_presentation(f, H)
|
| 1325 |
+
((x_0,), (x_0**4,))
|
| 1326 |
+
|
| 1327 |
+
Example 5.9 Pg. 183 from [1]
|
| 1328 |
+
>>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
|
| 1329 |
+
>>> H = [x]
|
| 1330 |
+
>>> reidemeister_presentation(f, H)
|
| 1331 |
+
((x_0,), (x_0**6,))
|
| 1332 |
+
|
| 1333 |
+
"""
|
| 1334 |
+
if not C:
|
| 1335 |
+
C = coset_enumeration_r(fp_grp, H)
|
| 1336 |
+
C.compress(); C.standardize()
|
| 1337 |
+
define_schreier_generators(C, homomorphism=homomorphism)
|
| 1338 |
+
reidemeister_relators(C)
|
| 1339 |
+
gens, rels = C._schreier_generators, C._reidemeister_relators
|
| 1340 |
+
gens, rels = simplify_presentation(gens, rels, change_gens=True)
|
| 1341 |
+
|
| 1342 |
+
C.schreier_generators = tuple(gens)
|
| 1343 |
+
C.reidemeister_relators = tuple(rels)
|
| 1344 |
+
|
| 1345 |
+
if homomorphism:
|
| 1346 |
+
_gens = []
|
| 1347 |
+
for gen in gens:
|
| 1348 |
+
_gens.append(C._schreier_gen_elem[str(gen)])
|
| 1349 |
+
return C.schreier_generators, C.reidemeister_relators, _gens
|
| 1350 |
+
|
| 1351 |
+
return C.schreier_generators, C.reidemeister_relators
|
| 1352 |
+
|
| 1353 |
+
|
| 1354 |
+
FpGroupElement = FreeGroupElement
|
lib/python3.12/site-packages/sympy/combinatorics/free_groups.py
ADDED
|
@@ -0,0 +1,1354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from sympy.core import S
|
| 4 |
+
from sympy.core.expr import Expr
|
| 5 |
+
from sympy.core.symbol import Symbol, symbols as _symbols
|
| 6 |
+
from sympy.core.sympify import CantSympify
|
| 7 |
+
from sympy.printing.defaults import DefaultPrinting
|
| 8 |
+
from sympy.utilities import public
|
| 9 |
+
from sympy.utilities.iterables import flatten, is_sequence
|
| 10 |
+
from sympy.utilities.magic import pollute
|
| 11 |
+
from sympy.utilities.misc import as_int
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@public
|
| 15 |
+
def free_group(symbols):
|
| 16 |
+
"""Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``.
|
| 17 |
+
|
| 18 |
+
Parameters
|
| 19 |
+
==========
|
| 20 |
+
|
| 21 |
+
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
| 22 |
+
|
| 23 |
+
Examples
|
| 24 |
+
========
|
| 25 |
+
|
| 26 |
+
>>> from sympy.combinatorics import free_group
|
| 27 |
+
>>> F, x, y, z = free_group("x, y, z")
|
| 28 |
+
>>> F
|
| 29 |
+
<free group on the generators (x, y, z)>
|
| 30 |
+
>>> x**2*y**-1
|
| 31 |
+
x**2*y**-1
|
| 32 |
+
>>> type(_)
|
| 33 |
+
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
| 34 |
+
|
| 35 |
+
"""
|
| 36 |
+
_free_group = FreeGroup(symbols)
|
| 37 |
+
return (_free_group,) + tuple(_free_group.generators)
|
| 38 |
+
|
| 39 |
+
@public
|
| 40 |
+
def xfree_group(symbols):
|
| 41 |
+
"""Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``.
|
| 42 |
+
|
| 43 |
+
Parameters
|
| 44 |
+
==========
|
| 45 |
+
|
| 46 |
+
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
| 47 |
+
|
| 48 |
+
Examples
|
| 49 |
+
========
|
| 50 |
+
|
| 51 |
+
>>> from sympy.combinatorics.free_groups import xfree_group
|
| 52 |
+
>>> F, (x, y, z) = xfree_group("x, y, z")
|
| 53 |
+
>>> F
|
| 54 |
+
<free group on the generators (x, y, z)>
|
| 55 |
+
>>> y**2*x**-2*z**-1
|
| 56 |
+
y**2*x**-2*z**-1
|
| 57 |
+
>>> type(_)
|
| 58 |
+
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
| 59 |
+
|
| 60 |
+
"""
|
| 61 |
+
_free_group = FreeGroup(symbols)
|
| 62 |
+
return (_free_group, _free_group.generators)
|
| 63 |
+
|
| 64 |
+
@public
|
| 65 |
+
def vfree_group(symbols):
|
| 66 |
+
"""Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols
|
| 67 |
+
into the global namespace.
|
| 68 |
+
|
| 69 |
+
Parameters
|
| 70 |
+
==========
|
| 71 |
+
|
| 72 |
+
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
| 73 |
+
|
| 74 |
+
Examples
|
| 75 |
+
========
|
| 76 |
+
|
| 77 |
+
>>> from sympy.combinatorics.free_groups import vfree_group
|
| 78 |
+
>>> vfree_group("x, y, z")
|
| 79 |
+
<free group on the generators (x, y, z)>
|
| 80 |
+
>>> x**2*y**-2*z # noqa: F821
|
| 81 |
+
x**2*y**-2*z
|
| 82 |
+
>>> type(_)
|
| 83 |
+
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
_free_group = FreeGroup(symbols)
|
| 87 |
+
pollute([sym.name for sym in _free_group.symbols], _free_group.generators)
|
| 88 |
+
return _free_group
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _parse_symbols(symbols):
|
| 92 |
+
if not symbols:
|
| 93 |
+
return ()
|
| 94 |
+
if isinstance(symbols, str):
|
| 95 |
+
return _symbols(symbols, seq=True)
|
| 96 |
+
elif isinstance(symbols, (Expr, FreeGroupElement)):
|
| 97 |
+
return (symbols,)
|
| 98 |
+
elif is_sequence(symbols):
|
| 99 |
+
if all(isinstance(s, str) for s in symbols):
|
| 100 |
+
return _symbols(symbols)
|
| 101 |
+
elif all(isinstance(s, Expr) for s in symbols):
|
| 102 |
+
return symbols
|
| 103 |
+
raise ValueError("The type of `symbols` must be one of the following: "
|
| 104 |
+
"a str, Symbol/Expr or a sequence of "
|
| 105 |
+
"one of these types")
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
##############################################################################
|
| 109 |
+
# FREE GROUP #
|
| 110 |
+
##############################################################################
|
| 111 |
+
|
| 112 |
+
_free_group_cache: dict[int, FreeGroup] = {}
|
| 113 |
+
|
| 114 |
+
class FreeGroup(DefaultPrinting):
|
| 115 |
+
"""
|
| 116 |
+
Free group with finite or infinite number of generators. Its input API
|
| 117 |
+
is that of a str, Symbol/Expr or a sequence of one of
|
| 118 |
+
these types (which may be empty)
|
| 119 |
+
|
| 120 |
+
See Also
|
| 121 |
+
========
|
| 122 |
+
|
| 123 |
+
sympy.polys.rings.PolyRing
|
| 124 |
+
|
| 125 |
+
References
|
| 126 |
+
==========
|
| 127 |
+
|
| 128 |
+
.. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html
|
| 129 |
+
|
| 130 |
+
.. [2] https://en.wikipedia.org/wiki/Free_group
|
| 131 |
+
|
| 132 |
+
"""
|
| 133 |
+
is_associative = True
|
| 134 |
+
is_group = True
|
| 135 |
+
is_FreeGroup = True
|
| 136 |
+
is_PermutationGroup = False
|
| 137 |
+
relators: list[Expr] = []
|
| 138 |
+
|
| 139 |
+
def __new__(cls, symbols):
|
| 140 |
+
symbols = tuple(_parse_symbols(symbols))
|
| 141 |
+
rank = len(symbols)
|
| 142 |
+
_hash = hash((cls.__name__, symbols, rank))
|
| 143 |
+
obj = _free_group_cache.get(_hash)
|
| 144 |
+
|
| 145 |
+
if obj is None:
|
| 146 |
+
obj = object.__new__(cls)
|
| 147 |
+
obj._hash = _hash
|
| 148 |
+
obj._rank = rank
|
| 149 |
+
# dtype method is used to create new instances of FreeGroupElement
|
| 150 |
+
obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj})
|
| 151 |
+
obj.symbols = symbols
|
| 152 |
+
obj.generators = obj._generators()
|
| 153 |
+
obj._gens_set = set(obj.generators)
|
| 154 |
+
for symbol, generator in zip(obj.symbols, obj.generators):
|
| 155 |
+
if isinstance(symbol, Symbol):
|
| 156 |
+
name = symbol.name
|
| 157 |
+
if hasattr(obj, name):
|
| 158 |
+
setattr(obj, name, generator)
|
| 159 |
+
|
| 160 |
+
_free_group_cache[_hash] = obj
|
| 161 |
+
|
| 162 |
+
return obj
|
| 163 |
+
|
| 164 |
+
def _generators(group):
|
| 165 |
+
"""Returns the generators of the FreeGroup.
|
| 166 |
+
|
| 167 |
+
Examples
|
| 168 |
+
========
|
| 169 |
+
|
| 170 |
+
>>> from sympy.combinatorics import free_group
|
| 171 |
+
>>> F, x, y, z = free_group("x, y, z")
|
| 172 |
+
>>> F.generators
|
| 173 |
+
(x, y, z)
|
| 174 |
+
|
| 175 |
+
"""
|
| 176 |
+
gens = []
|
| 177 |
+
for sym in group.symbols:
|
| 178 |
+
elm = ((sym, 1),)
|
| 179 |
+
gens.append(group.dtype(elm))
|
| 180 |
+
return tuple(gens)
|
| 181 |
+
|
| 182 |
+
def clone(self, symbols=None):
|
| 183 |
+
return self.__class__(symbols or self.symbols)
|
| 184 |
+
|
| 185 |
+
def __contains__(self, i):
|
| 186 |
+
"""Return True if ``i`` is contained in FreeGroup."""
|
| 187 |
+
if not isinstance(i, FreeGroupElement):
|
| 188 |
+
return False
|
| 189 |
+
group = i.group
|
| 190 |
+
return self == group
|
| 191 |
+
|
| 192 |
+
def __hash__(self):
|
| 193 |
+
return self._hash
|
| 194 |
+
|
| 195 |
+
def __len__(self):
|
| 196 |
+
return self.rank
|
| 197 |
+
|
| 198 |
+
def __str__(self):
|
| 199 |
+
if self.rank > 30:
|
| 200 |
+
str_form = "<free group with %s generators>" % self.rank
|
| 201 |
+
else:
|
| 202 |
+
str_form = "<free group on the generators "
|
| 203 |
+
gens = self.generators
|
| 204 |
+
str_form += str(gens) + ">"
|
| 205 |
+
return str_form
|
| 206 |
+
|
| 207 |
+
__repr__ = __str__
|
| 208 |
+
|
| 209 |
+
def __getitem__(self, index):
|
| 210 |
+
symbols = self.symbols[index]
|
| 211 |
+
return self.clone(symbols=symbols)
|
| 212 |
+
|
| 213 |
+
def __eq__(self, other):
|
| 214 |
+
"""No ``FreeGroup`` is equal to any "other" ``FreeGroup``.
|
| 215 |
+
"""
|
| 216 |
+
return self is other
|
| 217 |
+
|
| 218 |
+
def index(self, gen):
|
| 219 |
+
"""Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``.
|
| 220 |
+
|
| 221 |
+
Examples
|
| 222 |
+
========
|
| 223 |
+
|
| 224 |
+
>>> from sympy.combinatorics import free_group
|
| 225 |
+
>>> F, x, y = free_group("x, y")
|
| 226 |
+
>>> F.index(y)
|
| 227 |
+
1
|
| 228 |
+
>>> F.index(x)
|
| 229 |
+
0
|
| 230 |
+
|
| 231 |
+
"""
|
| 232 |
+
if isinstance(gen, self.dtype):
|
| 233 |
+
return self.generators.index(gen)
|
| 234 |
+
else:
|
| 235 |
+
raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen))
|
| 236 |
+
|
| 237 |
+
def order(self):
|
| 238 |
+
"""Return the order of the free group.
|
| 239 |
+
|
| 240 |
+
Examples
|
| 241 |
+
========
|
| 242 |
+
|
| 243 |
+
>>> from sympy.combinatorics import free_group
|
| 244 |
+
>>> F, x, y = free_group("x, y")
|
| 245 |
+
>>> F.order()
|
| 246 |
+
oo
|
| 247 |
+
|
| 248 |
+
>>> free_group("")[0].order()
|
| 249 |
+
1
|
| 250 |
+
|
| 251 |
+
"""
|
| 252 |
+
if self.rank == 0:
|
| 253 |
+
return S.One
|
| 254 |
+
else:
|
| 255 |
+
return S.Infinity
|
| 256 |
+
|
| 257 |
+
@property
|
| 258 |
+
def elements(self):
|
| 259 |
+
"""
|
| 260 |
+
Return the elements of the free group.
|
| 261 |
+
|
| 262 |
+
Examples
|
| 263 |
+
========
|
| 264 |
+
|
| 265 |
+
>>> from sympy.combinatorics import free_group
|
| 266 |
+
>>> (z,) = free_group("")
|
| 267 |
+
>>> z.elements
|
| 268 |
+
{<identity>}
|
| 269 |
+
|
| 270 |
+
"""
|
| 271 |
+
if self.rank == 0:
|
| 272 |
+
# A set containing Identity element of `FreeGroup` self is returned
|
| 273 |
+
return {self.identity}
|
| 274 |
+
else:
|
| 275 |
+
raise ValueError("Group contains infinitely many elements"
|
| 276 |
+
", hence cannot be represented")
|
| 277 |
+
|
| 278 |
+
@property
|
| 279 |
+
def rank(self):
|
| 280 |
+
r"""
|
| 281 |
+
In group theory, the `rank` of a group `G`, denoted `G.rank`,
|
| 282 |
+
can refer to the smallest cardinality of a generating set
|
| 283 |
+
for G, that is
|
| 284 |
+
|
| 285 |
+
\operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}.
|
| 286 |
+
|
| 287 |
+
"""
|
| 288 |
+
return self._rank
|
| 289 |
+
|
| 290 |
+
@property
|
| 291 |
+
def is_abelian(self):
|
| 292 |
+
"""Returns if the group is Abelian.
|
| 293 |
+
|
| 294 |
+
Examples
|
| 295 |
+
========
|
| 296 |
+
|
| 297 |
+
>>> from sympy.combinatorics import free_group
|
| 298 |
+
>>> f, x, y, z = free_group("x y z")
|
| 299 |
+
>>> f.is_abelian
|
| 300 |
+
False
|
| 301 |
+
|
| 302 |
+
"""
|
| 303 |
+
return self.rank in (0, 1)
|
| 304 |
+
|
| 305 |
+
@property
|
| 306 |
+
def identity(self):
|
| 307 |
+
"""Returns the identity element of free group."""
|
| 308 |
+
return self.dtype()
|
| 309 |
+
|
| 310 |
+
def contains(self, g):
|
| 311 |
+
"""Tests if Free Group element ``g`` belong to self, ``G``.
|
| 312 |
+
|
| 313 |
+
In mathematical terms any linear combination of generators
|
| 314 |
+
of a Free Group is contained in it.
|
| 315 |
+
|
| 316 |
+
Examples
|
| 317 |
+
========
|
| 318 |
+
|
| 319 |
+
>>> from sympy.combinatorics import free_group
|
| 320 |
+
>>> f, x, y, z = free_group("x y z")
|
| 321 |
+
>>> f.contains(x**3*y**2)
|
| 322 |
+
True
|
| 323 |
+
|
| 324 |
+
"""
|
| 325 |
+
if not isinstance(g, FreeGroupElement):
|
| 326 |
+
return False
|
| 327 |
+
elif self != g.group:
|
| 328 |
+
return False
|
| 329 |
+
else:
|
| 330 |
+
return True
|
| 331 |
+
|
| 332 |
+
def center(self):
|
| 333 |
+
"""Returns the center of the free group `self`."""
|
| 334 |
+
return {self.identity}
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
############################################################################
|
| 338 |
+
# FreeGroupElement #
|
| 339 |
+
############################################################################
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
class FreeGroupElement(CantSympify, DefaultPrinting, tuple):
|
| 343 |
+
"""Used to create elements of FreeGroup. It cannot be used directly to
|
| 344 |
+
create a free group element. It is called by the `dtype` method of the
|
| 345 |
+
`FreeGroup` class.
|
| 346 |
+
|
| 347 |
+
"""
|
| 348 |
+
is_assoc_word = True
|
| 349 |
+
|
| 350 |
+
def new(self, init):
|
| 351 |
+
return self.__class__(init)
|
| 352 |
+
|
| 353 |
+
_hash = None
|
| 354 |
+
|
| 355 |
+
def __hash__(self):
|
| 356 |
+
_hash = self._hash
|
| 357 |
+
if _hash is None:
|
| 358 |
+
self._hash = _hash = hash((self.group, frozenset(tuple(self))))
|
| 359 |
+
return _hash
|
| 360 |
+
|
| 361 |
+
def copy(self):
|
| 362 |
+
return self.new(self)
|
| 363 |
+
|
| 364 |
+
@property
|
| 365 |
+
def is_identity(self):
|
| 366 |
+
if self.array_form == ():
|
| 367 |
+
return True
|
| 368 |
+
else:
|
| 369 |
+
return False
|
| 370 |
+
|
| 371 |
+
@property
|
| 372 |
+
def array_form(self):
|
| 373 |
+
"""
|
| 374 |
+
SymPy provides two different internal kinds of representation
|
| 375 |
+
of associative words. The first one is called the `array_form`
|
| 376 |
+
which is a tuple containing `tuples` as its elements, where the
|
| 377 |
+
size of each tuple is two. At the first position the tuple
|
| 378 |
+
contains the `symbol-generator`, while at the second position
|
| 379 |
+
of tuple contains the exponent of that generator at the position.
|
| 380 |
+
Since elements (i.e. words) do not commute, the indexing of tuple
|
| 381 |
+
makes that property to stay.
|
| 382 |
+
|
| 383 |
+
The structure in ``array_form`` of ``FreeGroupElement`` is of form:
|
| 384 |
+
|
| 385 |
+
``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )``
|
| 386 |
+
|
| 387 |
+
Examples
|
| 388 |
+
========
|
| 389 |
+
|
| 390 |
+
>>> from sympy.combinatorics import free_group
|
| 391 |
+
>>> f, x, y, z = free_group("x y z")
|
| 392 |
+
>>> (x*z).array_form
|
| 393 |
+
((x, 1), (z, 1))
|
| 394 |
+
>>> (x**2*z*y*x**2).array_form
|
| 395 |
+
((x, 2), (z, 1), (y, 1), (x, 2))
|
| 396 |
+
|
| 397 |
+
See Also
|
| 398 |
+
========
|
| 399 |
+
|
| 400 |
+
letter_repr
|
| 401 |
+
|
| 402 |
+
"""
|
| 403 |
+
return tuple(self)
|
| 404 |
+
|
| 405 |
+
@property
|
| 406 |
+
def letter_form(self):
|
| 407 |
+
"""
|
| 408 |
+
The letter representation of a ``FreeGroupElement`` is a tuple
|
| 409 |
+
of generator symbols, with each entry corresponding to a group
|
| 410 |
+
generator. Inverses of the generators are represented by
|
| 411 |
+
negative generator symbols.
|
| 412 |
+
|
| 413 |
+
Examples
|
| 414 |
+
========
|
| 415 |
+
|
| 416 |
+
>>> from sympy.combinatorics import free_group
|
| 417 |
+
>>> f, a, b, c, d = free_group("a b c d")
|
| 418 |
+
>>> (a**3).letter_form
|
| 419 |
+
(a, a, a)
|
| 420 |
+
>>> (a**2*d**-2*a*b**-4).letter_form
|
| 421 |
+
(a, a, -d, -d, a, -b, -b, -b, -b)
|
| 422 |
+
>>> (a**-2*b**3*d).letter_form
|
| 423 |
+
(-a, -a, b, b, b, d)
|
| 424 |
+
|
| 425 |
+
See Also
|
| 426 |
+
========
|
| 427 |
+
|
| 428 |
+
array_form
|
| 429 |
+
|
| 430 |
+
"""
|
| 431 |
+
return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j)
|
| 432 |
+
for i, j in self.array_form]))
|
| 433 |
+
|
| 434 |
+
def __getitem__(self, i):
|
| 435 |
+
group = self.group
|
| 436 |
+
r = self.letter_form[i]
|
| 437 |
+
if r.is_Symbol:
|
| 438 |
+
return group.dtype(((r, 1),))
|
| 439 |
+
else:
|
| 440 |
+
return group.dtype(((-r, -1),))
|
| 441 |
+
|
| 442 |
+
def index(self, gen):
|
| 443 |
+
if len(gen) != 1:
|
| 444 |
+
raise ValueError()
|
| 445 |
+
return (self.letter_form).index(gen.letter_form[0])
|
| 446 |
+
|
| 447 |
+
@property
|
| 448 |
+
def letter_form_elm(self):
|
| 449 |
+
"""
|
| 450 |
+
"""
|
| 451 |
+
group = self.group
|
| 452 |
+
r = self.letter_form
|
| 453 |
+
return [group.dtype(((elm,1),)) if elm.is_Symbol \
|
| 454 |
+
else group.dtype(((-elm,-1),)) for elm in r]
|
| 455 |
+
|
| 456 |
+
@property
|
| 457 |
+
def ext_rep(self):
|
| 458 |
+
"""This is called the External Representation of ``FreeGroupElement``
|
| 459 |
+
"""
|
| 460 |
+
return tuple(flatten(self.array_form))
|
| 461 |
+
|
| 462 |
+
def __contains__(self, gen):
|
| 463 |
+
return gen.array_form[0][0] in tuple([r[0] for r in self.array_form])
|
| 464 |
+
|
| 465 |
+
def __str__(self):
|
| 466 |
+
if self.is_identity:
|
| 467 |
+
return "<identity>"
|
| 468 |
+
|
| 469 |
+
str_form = ""
|
| 470 |
+
array_form = self.array_form
|
| 471 |
+
for i in range(len(array_form)):
|
| 472 |
+
if i == len(array_form) - 1:
|
| 473 |
+
if array_form[i][1] == 1:
|
| 474 |
+
str_form += str(array_form[i][0])
|
| 475 |
+
else:
|
| 476 |
+
str_form += str(array_form[i][0]) + \
|
| 477 |
+
"**" + str(array_form[i][1])
|
| 478 |
+
else:
|
| 479 |
+
if array_form[i][1] == 1:
|
| 480 |
+
str_form += str(array_form[i][0]) + "*"
|
| 481 |
+
else:
|
| 482 |
+
str_form += str(array_form[i][0]) + \
|
| 483 |
+
"**" + str(array_form[i][1]) + "*"
|
| 484 |
+
return str_form
|
| 485 |
+
|
| 486 |
+
__repr__ = __str__
|
| 487 |
+
|
| 488 |
+
def __pow__(self, n):
|
| 489 |
+
n = as_int(n)
|
| 490 |
+
result = self.group.identity
|
| 491 |
+
if n == 0:
|
| 492 |
+
return result
|
| 493 |
+
if n < 0:
|
| 494 |
+
n = -n
|
| 495 |
+
x = self.inverse()
|
| 496 |
+
else:
|
| 497 |
+
x = self
|
| 498 |
+
while True:
|
| 499 |
+
if n % 2:
|
| 500 |
+
result *= x
|
| 501 |
+
n >>= 1
|
| 502 |
+
if not n:
|
| 503 |
+
break
|
| 504 |
+
x *= x
|
| 505 |
+
return result
|
| 506 |
+
|
| 507 |
+
def __mul__(self, other):
|
| 508 |
+
"""Returns the product of elements belonging to the same ``FreeGroup``.
|
| 509 |
+
|
| 510 |
+
Examples
|
| 511 |
+
========
|
| 512 |
+
|
| 513 |
+
>>> from sympy.combinatorics import free_group
|
| 514 |
+
>>> f, x, y, z = free_group("x y z")
|
| 515 |
+
>>> x*y**2*y**-4
|
| 516 |
+
x*y**-2
|
| 517 |
+
>>> z*y**-2
|
| 518 |
+
z*y**-2
|
| 519 |
+
>>> x**2*y*y**-1*x**-2
|
| 520 |
+
<identity>
|
| 521 |
+
|
| 522 |
+
"""
|
| 523 |
+
group = self.group
|
| 524 |
+
if not isinstance(other, group.dtype):
|
| 525 |
+
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
| 526 |
+
"be multiplied")
|
| 527 |
+
if self.is_identity:
|
| 528 |
+
return other
|
| 529 |
+
if other.is_identity:
|
| 530 |
+
return self
|
| 531 |
+
r = list(self.array_form + other.array_form)
|
| 532 |
+
zero_mul_simp(r, len(self.array_form) - 1)
|
| 533 |
+
return group.dtype(tuple(r))
|
| 534 |
+
|
| 535 |
+
def __truediv__(self, other):
|
| 536 |
+
group = self.group
|
| 537 |
+
if not isinstance(other, group.dtype):
|
| 538 |
+
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
| 539 |
+
"be multiplied")
|
| 540 |
+
return self*(other.inverse())
|
| 541 |
+
|
| 542 |
+
def __rtruediv__(self, other):
|
| 543 |
+
group = self.group
|
| 544 |
+
if not isinstance(other, group.dtype):
|
| 545 |
+
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
| 546 |
+
"be multiplied")
|
| 547 |
+
return other*(self.inverse())
|
| 548 |
+
|
| 549 |
+
def __add__(self, other):
|
| 550 |
+
return NotImplemented
|
| 551 |
+
|
| 552 |
+
def inverse(self):
|
| 553 |
+
"""
|
| 554 |
+
Returns the inverse of a ``FreeGroupElement`` element
|
| 555 |
+
|
| 556 |
+
Examples
|
| 557 |
+
========
|
| 558 |
+
|
| 559 |
+
>>> from sympy.combinatorics import free_group
|
| 560 |
+
>>> f, x, y, z = free_group("x y z")
|
| 561 |
+
>>> x.inverse()
|
| 562 |
+
x**-1
|
| 563 |
+
>>> (x*y).inverse()
|
| 564 |
+
y**-1*x**-1
|
| 565 |
+
|
| 566 |
+
"""
|
| 567 |
+
group = self.group
|
| 568 |
+
r = tuple([(i, -j) for i, j in self.array_form[::-1]])
|
| 569 |
+
return group.dtype(r)
|
| 570 |
+
|
| 571 |
+
def order(self):
|
| 572 |
+
"""Find the order of a ``FreeGroupElement``.
|
| 573 |
+
|
| 574 |
+
Examples
|
| 575 |
+
========
|
| 576 |
+
|
| 577 |
+
>>> from sympy.combinatorics import free_group
|
| 578 |
+
>>> f, x, y = free_group("x y")
|
| 579 |
+
>>> (x**2*y*y**-1*x**-2).order()
|
| 580 |
+
1
|
| 581 |
+
|
| 582 |
+
"""
|
| 583 |
+
if self.is_identity:
|
| 584 |
+
return S.One
|
| 585 |
+
else:
|
| 586 |
+
return S.Infinity
|
| 587 |
+
|
| 588 |
+
def commutator(self, other):
|
| 589 |
+
"""
|
| 590 |
+
Return the commutator of `self` and `x`: ``~x*~self*x*self``
|
| 591 |
+
|
| 592 |
+
"""
|
| 593 |
+
group = self.group
|
| 594 |
+
if not isinstance(other, group.dtype):
|
| 595 |
+
raise ValueError("commutator of only FreeGroupElement of the same "
|
| 596 |
+
"FreeGroup exists")
|
| 597 |
+
else:
|
| 598 |
+
return self.inverse()*other.inverse()*self*other
|
| 599 |
+
|
| 600 |
+
def eliminate_words(self, words, _all=False, inverse=True):
|
| 601 |
+
'''
|
| 602 |
+
Replace each subword from the dictionary `words` by words[subword].
|
| 603 |
+
If words is a list, replace the words by the identity.
|
| 604 |
+
|
| 605 |
+
'''
|
| 606 |
+
again = True
|
| 607 |
+
new = self
|
| 608 |
+
if isinstance(words, dict):
|
| 609 |
+
while again:
|
| 610 |
+
again = False
|
| 611 |
+
for sub in words:
|
| 612 |
+
prev = new
|
| 613 |
+
new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse)
|
| 614 |
+
if new != prev:
|
| 615 |
+
again = True
|
| 616 |
+
else:
|
| 617 |
+
while again:
|
| 618 |
+
again = False
|
| 619 |
+
for sub in words:
|
| 620 |
+
prev = new
|
| 621 |
+
new = new.eliminate_word(sub, _all=_all, inverse=inverse)
|
| 622 |
+
if new != prev:
|
| 623 |
+
again = True
|
| 624 |
+
return new
|
| 625 |
+
|
| 626 |
+
def eliminate_word(self, gen, by=None, _all=False, inverse=True):
|
| 627 |
+
"""
|
| 628 |
+
For an associative word `self`, a subword `gen`, and an associative
|
| 629 |
+
word `by` (identity by default), return the associative word obtained by
|
| 630 |
+
replacing each occurrence of `gen` in `self` by `by`. If `_all = True`,
|
| 631 |
+
the occurrences of `gen` that may appear after the first substitution will
|
| 632 |
+
also be replaced and so on until no occurrences are found. This might not
|
| 633 |
+
always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`).
|
| 634 |
+
|
| 635 |
+
Examples
|
| 636 |
+
========
|
| 637 |
+
|
| 638 |
+
>>> from sympy.combinatorics import free_group
|
| 639 |
+
>>> f, x, y = free_group("x y")
|
| 640 |
+
>>> w = x**5*y*x**2*y**-4*x
|
| 641 |
+
>>> w.eliminate_word( x, x**2 )
|
| 642 |
+
x**10*y*x**4*y**-4*x**2
|
| 643 |
+
>>> w.eliminate_word( x, y**-1 )
|
| 644 |
+
y**-11
|
| 645 |
+
>>> w.eliminate_word(x**5)
|
| 646 |
+
y*x**2*y**-4*x
|
| 647 |
+
>>> w.eliminate_word(x*y, y)
|
| 648 |
+
x**4*y*x**2*y**-4*x
|
| 649 |
+
|
| 650 |
+
See Also
|
| 651 |
+
========
|
| 652 |
+
substituted_word
|
| 653 |
+
|
| 654 |
+
"""
|
| 655 |
+
if by is None:
|
| 656 |
+
by = self.group.identity
|
| 657 |
+
if self.is_independent(gen) or gen == by:
|
| 658 |
+
return self
|
| 659 |
+
if gen == self:
|
| 660 |
+
return by
|
| 661 |
+
if gen**-1 == by:
|
| 662 |
+
_all = False
|
| 663 |
+
word = self
|
| 664 |
+
l = len(gen)
|
| 665 |
+
|
| 666 |
+
try:
|
| 667 |
+
i = word.subword_index(gen)
|
| 668 |
+
k = 1
|
| 669 |
+
except ValueError:
|
| 670 |
+
if not inverse:
|
| 671 |
+
return word
|
| 672 |
+
try:
|
| 673 |
+
i = word.subword_index(gen**-1)
|
| 674 |
+
k = -1
|
| 675 |
+
except ValueError:
|
| 676 |
+
return word
|
| 677 |
+
|
| 678 |
+
word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)
|
| 679 |
+
|
| 680 |
+
if _all:
|
| 681 |
+
return word.eliminate_word(gen, by, _all=True, inverse=inverse)
|
| 682 |
+
else:
|
| 683 |
+
return word
|
| 684 |
+
|
| 685 |
+
def __len__(self):
|
| 686 |
+
"""
|
| 687 |
+
For an associative word `self`, returns the number of letters in it.
|
| 688 |
+
|
| 689 |
+
Examples
|
| 690 |
+
========
|
| 691 |
+
|
| 692 |
+
>>> from sympy.combinatorics import free_group
|
| 693 |
+
>>> f, a, b = free_group("a b")
|
| 694 |
+
>>> w = a**5*b*a**2*b**-4*a
|
| 695 |
+
>>> len(w)
|
| 696 |
+
13
|
| 697 |
+
>>> len(a**17)
|
| 698 |
+
17
|
| 699 |
+
>>> len(w**0)
|
| 700 |
+
0
|
| 701 |
+
|
| 702 |
+
"""
|
| 703 |
+
return sum(abs(j) for (i, j) in self)
|
| 704 |
+
|
| 705 |
+
def __eq__(self, other):
|
| 706 |
+
"""
|
| 707 |
+
Two associative words are equal if they are words over the
|
| 708 |
+
same alphabet and if they are sequences of the same letters.
|
| 709 |
+
This is equivalent to saying that the external representations
|
| 710 |
+
of the words are equal.
|
| 711 |
+
There is no "universal" empty word, every alphabet has its own
|
| 712 |
+
empty word.
|
| 713 |
+
|
| 714 |
+
Examples
|
| 715 |
+
========
|
| 716 |
+
|
| 717 |
+
>>> from sympy.combinatorics import free_group
|
| 718 |
+
>>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
|
| 719 |
+
>>> f
|
| 720 |
+
<free group on the generators (swapnil0, swapnil1)>
|
| 721 |
+
>>> g, swap0, swap1 = free_group("swap0 swap1")
|
| 722 |
+
>>> g
|
| 723 |
+
<free group on the generators (swap0, swap1)>
|
| 724 |
+
|
| 725 |
+
>>> swapnil0 == swapnil1
|
| 726 |
+
False
|
| 727 |
+
>>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1
|
| 728 |
+
True
|
| 729 |
+
>>> swapnil0*swapnil1 == swapnil1*swapnil0
|
| 730 |
+
False
|
| 731 |
+
>>> swapnil1**0 == swap0**0
|
| 732 |
+
False
|
| 733 |
+
|
| 734 |
+
"""
|
| 735 |
+
group = self.group
|
| 736 |
+
if not isinstance(other, group.dtype):
|
| 737 |
+
return False
|
| 738 |
+
return tuple.__eq__(self, other)
|
| 739 |
+
|
| 740 |
+
def __lt__(self, other):
|
| 741 |
+
"""
|
| 742 |
+
The ordering of associative words is defined by length and
|
| 743 |
+
lexicography (this ordering is called short-lex ordering), that
|
| 744 |
+
is, shorter words are smaller than longer words, and words of the
|
| 745 |
+
same length are compared w.r.t. the lexicographical ordering induced
|
| 746 |
+
by the ordering of generators. Generators are sorted according
|
| 747 |
+
to the order in which they were created. If the generators are
|
| 748 |
+
invertible then each generator `g` is larger than its inverse `g^{-1}`,
|
| 749 |
+
and `g^{-1}` is larger than every generator that is smaller than `g`.
|
| 750 |
+
|
| 751 |
+
Examples
|
| 752 |
+
========
|
| 753 |
+
|
| 754 |
+
>>> from sympy.combinatorics import free_group
|
| 755 |
+
>>> f, a, b = free_group("a b")
|
| 756 |
+
>>> b < a
|
| 757 |
+
False
|
| 758 |
+
>>> a < a.inverse()
|
| 759 |
+
False
|
| 760 |
+
|
| 761 |
+
"""
|
| 762 |
+
group = self.group
|
| 763 |
+
if not isinstance(other, group.dtype):
|
| 764 |
+
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
| 765 |
+
"be compared")
|
| 766 |
+
l = len(self)
|
| 767 |
+
m = len(other)
|
| 768 |
+
# implement lenlex order
|
| 769 |
+
if l < m:
|
| 770 |
+
return True
|
| 771 |
+
elif l > m:
|
| 772 |
+
return False
|
| 773 |
+
for i in range(l):
|
| 774 |
+
a = self[i].array_form[0]
|
| 775 |
+
b = other[i].array_form[0]
|
| 776 |
+
p = group.symbols.index(a[0])
|
| 777 |
+
q = group.symbols.index(b[0])
|
| 778 |
+
if p < q:
|
| 779 |
+
return True
|
| 780 |
+
elif p > q:
|
| 781 |
+
return False
|
| 782 |
+
elif a[1] < b[1]:
|
| 783 |
+
return True
|
| 784 |
+
elif a[1] > b[1]:
|
| 785 |
+
return False
|
| 786 |
+
return False
|
| 787 |
+
|
| 788 |
+
def __le__(self, other):
|
| 789 |
+
return (self == other or self < other)
|
| 790 |
+
|
| 791 |
+
def __gt__(self, other):
|
| 792 |
+
"""
|
| 793 |
+
|
| 794 |
+
Examples
|
| 795 |
+
========
|
| 796 |
+
|
| 797 |
+
>>> from sympy.combinatorics import free_group
|
| 798 |
+
>>> f, x, y, z = free_group("x y z")
|
| 799 |
+
>>> y**2 > x**2
|
| 800 |
+
True
|
| 801 |
+
>>> y*z > z*y
|
| 802 |
+
False
|
| 803 |
+
>>> x > x.inverse()
|
| 804 |
+
True
|
| 805 |
+
|
| 806 |
+
"""
|
| 807 |
+
group = self.group
|
| 808 |
+
if not isinstance(other, group.dtype):
|
| 809 |
+
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
| 810 |
+
"be compared")
|
| 811 |
+
return not self <= other
|
| 812 |
+
|
| 813 |
+
def __ge__(self, other):
|
| 814 |
+
return not self < other
|
| 815 |
+
|
| 816 |
+
def exponent_sum(self, gen):
|
| 817 |
+
"""
|
| 818 |
+
For an associative word `self` and a generator or inverse of generator
|
| 819 |
+
`gen`, ``exponent_sum`` returns the number of times `gen` appears in
|
| 820 |
+
`self` minus the number of times its inverse appears in `self`. If
|
| 821 |
+
neither `gen` nor its inverse occur in `self` then 0 is returned.
|
| 822 |
+
|
| 823 |
+
Examples
|
| 824 |
+
========
|
| 825 |
+
|
| 826 |
+
>>> from sympy.combinatorics import free_group
|
| 827 |
+
>>> F, x, y = free_group("x, y")
|
| 828 |
+
>>> w = x**2*y**3
|
| 829 |
+
>>> w.exponent_sum(x)
|
| 830 |
+
2
|
| 831 |
+
>>> w.exponent_sum(x**-1)
|
| 832 |
+
-2
|
| 833 |
+
>>> w = x**2*y**4*x**-3
|
| 834 |
+
>>> w.exponent_sum(x)
|
| 835 |
+
-1
|
| 836 |
+
|
| 837 |
+
See Also
|
| 838 |
+
========
|
| 839 |
+
|
| 840 |
+
generator_count
|
| 841 |
+
|
| 842 |
+
"""
|
| 843 |
+
if len(gen) != 1:
|
| 844 |
+
raise ValueError("gen must be a generator or inverse of a generator")
|
| 845 |
+
s = gen.array_form[0]
|
| 846 |
+
return s[1]*sum(i[1] for i in self.array_form if i[0] == s[0])
|
| 847 |
+
|
| 848 |
+
def generator_count(self, gen):
|
| 849 |
+
"""
|
| 850 |
+
For an associative word `self` and a generator `gen`,
|
| 851 |
+
``generator_count`` returns the multiplicity of generator
|
| 852 |
+
`gen` in `self`.
|
| 853 |
+
|
| 854 |
+
Examples
|
| 855 |
+
========
|
| 856 |
+
|
| 857 |
+
>>> from sympy.combinatorics import free_group
|
| 858 |
+
>>> F, x, y = free_group("x, y")
|
| 859 |
+
>>> w = x**2*y**3
|
| 860 |
+
>>> w.generator_count(x)
|
| 861 |
+
2
|
| 862 |
+
>>> w = x**2*y**4*x**-3
|
| 863 |
+
>>> w.generator_count(x)
|
| 864 |
+
5
|
| 865 |
+
|
| 866 |
+
See Also
|
| 867 |
+
========
|
| 868 |
+
|
| 869 |
+
exponent_sum
|
| 870 |
+
|
| 871 |
+
"""
|
| 872 |
+
if len(gen) != 1 or gen.array_form[0][1] < 0:
|
| 873 |
+
raise ValueError("gen must be a generator")
|
| 874 |
+
s = gen.array_form[0]
|
| 875 |
+
return s[1]*sum(abs(i[1]) for i in self.array_form if i[0] == s[0])
|
| 876 |
+
|
| 877 |
+
def subword(self, from_i, to_j, strict=True):
|
| 878 |
+
"""
|
| 879 |
+
For an associative word `self` and two positive integers `from_i` and
|
| 880 |
+
`to_j`, `subword` returns the subword of `self` that begins at position
|
| 881 |
+
`from_i` and ends at `to_j - 1`, indexing is done with origin 0.
|
| 882 |
+
|
| 883 |
+
Examples
|
| 884 |
+
========
|
| 885 |
+
|
| 886 |
+
>>> from sympy.combinatorics import free_group
|
| 887 |
+
>>> f, a, b = free_group("a b")
|
| 888 |
+
>>> w = a**5*b*a**2*b**-4*a
|
| 889 |
+
>>> w.subword(2, 6)
|
| 890 |
+
a**3*b
|
| 891 |
+
|
| 892 |
+
"""
|
| 893 |
+
group = self.group
|
| 894 |
+
if not strict:
|
| 895 |
+
from_i = max(from_i, 0)
|
| 896 |
+
to_j = min(len(self), to_j)
|
| 897 |
+
if from_i < 0 or to_j > len(self):
|
| 898 |
+
raise ValueError("`from_i`, `to_j` must be positive and no greater than "
|
| 899 |
+
"the length of associative word")
|
| 900 |
+
if to_j <= from_i:
|
| 901 |
+
return group.identity
|
| 902 |
+
else:
|
| 903 |
+
letter_form = self.letter_form[from_i: to_j]
|
| 904 |
+
array_form = letter_form_to_array_form(letter_form, group)
|
| 905 |
+
return group.dtype(array_form)
|
| 906 |
+
|
| 907 |
+
def subword_index(self, word, start = 0):
|
| 908 |
+
'''
|
| 909 |
+
Find the index of `word` in `self`.
|
| 910 |
+
|
| 911 |
+
Examples
|
| 912 |
+
========
|
| 913 |
+
|
| 914 |
+
>>> from sympy.combinatorics import free_group
|
| 915 |
+
>>> f, a, b = free_group("a b")
|
| 916 |
+
>>> w = a**2*b*a*b**3
|
| 917 |
+
>>> w.subword_index(a*b*a*b)
|
| 918 |
+
1
|
| 919 |
+
|
| 920 |
+
'''
|
| 921 |
+
l = len(word)
|
| 922 |
+
self_lf = self.letter_form
|
| 923 |
+
word_lf = word.letter_form
|
| 924 |
+
index = None
|
| 925 |
+
for i in range(start,len(self_lf)-l+1):
|
| 926 |
+
if self_lf[i:i+l] == word_lf:
|
| 927 |
+
index = i
|
| 928 |
+
break
|
| 929 |
+
if index is not None:
|
| 930 |
+
return index
|
| 931 |
+
else:
|
| 932 |
+
raise ValueError("The given word is not a subword of self")
|
| 933 |
+
|
| 934 |
+
def is_dependent(self, word):
|
| 935 |
+
"""
|
| 936 |
+
Examples
|
| 937 |
+
========
|
| 938 |
+
|
| 939 |
+
>>> from sympy.combinatorics import free_group
|
| 940 |
+
>>> F, x, y = free_group("x, y")
|
| 941 |
+
>>> (x**4*y**-3).is_dependent(x**4*y**-2)
|
| 942 |
+
True
|
| 943 |
+
>>> (x**2*y**-1).is_dependent(x*y)
|
| 944 |
+
False
|
| 945 |
+
>>> (x*y**2*x*y**2).is_dependent(x*y**2)
|
| 946 |
+
True
|
| 947 |
+
>>> (x**12).is_dependent(x**-4)
|
| 948 |
+
True
|
| 949 |
+
|
| 950 |
+
See Also
|
| 951 |
+
========
|
| 952 |
+
|
| 953 |
+
is_independent
|
| 954 |
+
|
| 955 |
+
"""
|
| 956 |
+
try:
|
| 957 |
+
return self.subword_index(word) is not None
|
| 958 |
+
except ValueError:
|
| 959 |
+
pass
|
| 960 |
+
try:
|
| 961 |
+
return self.subword_index(word**-1) is not None
|
| 962 |
+
except ValueError:
|
| 963 |
+
return False
|
| 964 |
+
|
| 965 |
+
def is_independent(self, word):
|
| 966 |
+
"""
|
| 967 |
+
|
| 968 |
+
See Also
|
| 969 |
+
========
|
| 970 |
+
|
| 971 |
+
is_dependent
|
| 972 |
+
|
| 973 |
+
"""
|
| 974 |
+
return not self.is_dependent(word)
|
| 975 |
+
|
| 976 |
+
def contains_generators(self):
|
| 977 |
+
"""
|
| 978 |
+
Examples
|
| 979 |
+
========
|
| 980 |
+
|
| 981 |
+
>>> from sympy.combinatorics import free_group
|
| 982 |
+
>>> F, x, y, z = free_group("x, y, z")
|
| 983 |
+
>>> (x**2*y**-1).contains_generators()
|
| 984 |
+
{x, y}
|
| 985 |
+
>>> (x**3*z).contains_generators()
|
| 986 |
+
{x, z}
|
| 987 |
+
|
| 988 |
+
"""
|
| 989 |
+
group = self.group
|
| 990 |
+
gens = {group.dtype(((syllable[0], 1),)) for syllable in self.array_form}
|
| 991 |
+
return gens
|
| 992 |
+
|
| 993 |
+
def cyclic_subword(self, from_i, to_j):
|
| 994 |
+
group = self.group
|
| 995 |
+
l = len(self)
|
| 996 |
+
letter_form = self.letter_form
|
| 997 |
+
period1 = int(from_i/l)
|
| 998 |
+
if from_i >= l:
|
| 999 |
+
from_i -= l*period1
|
| 1000 |
+
to_j -= l*period1
|
| 1001 |
+
diff = to_j - from_i
|
| 1002 |
+
word = letter_form[from_i: to_j]
|
| 1003 |
+
period2 = int(to_j/l) - 1
|
| 1004 |
+
word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2]
|
| 1005 |
+
word = letter_form_to_array_form(word, group)
|
| 1006 |
+
return group.dtype(word)
|
| 1007 |
+
|
| 1008 |
+
def cyclic_conjugates(self):
|
| 1009 |
+
"""Returns a words which are cyclic to the word `self`.
|
| 1010 |
+
|
| 1011 |
+
Examples
|
| 1012 |
+
========
|
| 1013 |
+
|
| 1014 |
+
>>> from sympy.combinatorics import free_group
|
| 1015 |
+
>>> F, x, y = free_group("x, y")
|
| 1016 |
+
>>> w = x*y*x*y*x
|
| 1017 |
+
>>> w.cyclic_conjugates()
|
| 1018 |
+
{x*y*x**2*y, x**2*y*x*y, y*x*y*x**2, y*x**2*y*x, x*y*x*y*x}
|
| 1019 |
+
>>> s = x*y*x**2*y*x
|
| 1020 |
+
>>> s.cyclic_conjugates()
|
| 1021 |
+
{x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x}
|
| 1022 |
+
|
| 1023 |
+
References
|
| 1024 |
+
==========
|
| 1025 |
+
|
| 1026 |
+
.. [1] https://planetmath.org/cyclicpermutation
|
| 1027 |
+
|
| 1028 |
+
"""
|
| 1029 |
+
return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))}
|
| 1030 |
+
|
| 1031 |
+
def is_cyclic_conjugate(self, w):
|
| 1032 |
+
"""
|
| 1033 |
+
Checks whether words ``self``, ``w`` are cyclic conjugates.
|
| 1034 |
+
|
| 1035 |
+
Examples
|
| 1036 |
+
========
|
| 1037 |
+
|
| 1038 |
+
>>> from sympy.combinatorics import free_group
|
| 1039 |
+
>>> F, x, y = free_group("x, y")
|
| 1040 |
+
>>> w1 = x**2*y**5
|
| 1041 |
+
>>> w2 = x*y**5*x
|
| 1042 |
+
>>> w1.is_cyclic_conjugate(w2)
|
| 1043 |
+
True
|
| 1044 |
+
>>> w3 = x**-1*y**5*x**-1
|
| 1045 |
+
>>> w3.is_cyclic_conjugate(w2)
|
| 1046 |
+
False
|
| 1047 |
+
|
| 1048 |
+
"""
|
| 1049 |
+
l1 = len(self)
|
| 1050 |
+
l2 = len(w)
|
| 1051 |
+
if l1 != l2:
|
| 1052 |
+
return False
|
| 1053 |
+
w1 = self.identity_cyclic_reduction()
|
| 1054 |
+
w2 = w.identity_cyclic_reduction()
|
| 1055 |
+
letter1 = w1.letter_form
|
| 1056 |
+
letter2 = w2.letter_form
|
| 1057 |
+
str1 = ' '.join(map(str, letter1))
|
| 1058 |
+
str2 = ' '.join(map(str, letter2))
|
| 1059 |
+
if len(str1) != len(str2):
|
| 1060 |
+
return False
|
| 1061 |
+
|
| 1062 |
+
return str1 in str2 + ' ' + str2
|
| 1063 |
+
|
| 1064 |
+
def number_syllables(self):
|
| 1065 |
+
"""Returns the number of syllables of the associative word `self`.
|
| 1066 |
+
|
| 1067 |
+
Examples
|
| 1068 |
+
========
|
| 1069 |
+
|
| 1070 |
+
>>> from sympy.combinatorics import free_group
|
| 1071 |
+
>>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
|
| 1072 |
+
>>> (swapnil1**3*swapnil0*swapnil1**-1).number_syllables()
|
| 1073 |
+
3
|
| 1074 |
+
|
| 1075 |
+
"""
|
| 1076 |
+
return len(self.array_form)
|
| 1077 |
+
|
| 1078 |
+
def exponent_syllable(self, i):
|
| 1079 |
+
"""
|
| 1080 |
+
Returns the exponent of the `i`-th syllable of the associative word
|
| 1081 |
+
`self`.
|
| 1082 |
+
|
| 1083 |
+
Examples
|
| 1084 |
+
========
|
| 1085 |
+
|
| 1086 |
+
>>> from sympy.combinatorics import free_group
|
| 1087 |
+
>>> f, a, b = free_group("a b")
|
| 1088 |
+
>>> w = a**5*b*a**2*b**-4*a
|
| 1089 |
+
>>> w.exponent_syllable( 2 )
|
| 1090 |
+
2
|
| 1091 |
+
|
| 1092 |
+
"""
|
| 1093 |
+
return self.array_form[i][1]
|
| 1094 |
+
|
| 1095 |
+
def generator_syllable(self, i):
|
| 1096 |
+
"""
|
| 1097 |
+
Returns the symbol of the generator that is involved in the
|
| 1098 |
+
i-th syllable of the associative word `self`.
|
| 1099 |
+
|
| 1100 |
+
Examples
|
| 1101 |
+
========
|
| 1102 |
+
|
| 1103 |
+
>>> from sympy.combinatorics import free_group
|
| 1104 |
+
>>> f, a, b = free_group("a b")
|
| 1105 |
+
>>> w = a**5*b*a**2*b**-4*a
|
| 1106 |
+
>>> w.generator_syllable( 3 )
|
| 1107 |
+
b
|
| 1108 |
+
|
| 1109 |
+
"""
|
| 1110 |
+
return self.array_form[i][0]
|
| 1111 |
+
|
| 1112 |
+
def sub_syllables(self, from_i, to_j):
|
| 1113 |
+
"""
|
| 1114 |
+
`sub_syllables` returns the subword of the associative word `self` that
|
| 1115 |
+
consists of syllables from positions `from_to` to `to_j`, where
|
| 1116 |
+
`from_to` and `to_j` must be positive integers and indexing is done
|
| 1117 |
+
with origin 0.
|
| 1118 |
+
|
| 1119 |
+
Examples
|
| 1120 |
+
========
|
| 1121 |
+
|
| 1122 |
+
>>> from sympy.combinatorics import free_group
|
| 1123 |
+
>>> f, a, b = free_group("a, b")
|
| 1124 |
+
>>> w = a**5*b*a**2*b**-4*a
|
| 1125 |
+
>>> w.sub_syllables(1, 2)
|
| 1126 |
+
b
|
| 1127 |
+
>>> w.sub_syllables(3, 3)
|
| 1128 |
+
<identity>
|
| 1129 |
+
|
| 1130 |
+
"""
|
| 1131 |
+
if not isinstance(from_i, int) or not isinstance(to_j, int):
|
| 1132 |
+
raise ValueError("both arguments should be integers")
|
| 1133 |
+
group = self.group
|
| 1134 |
+
if to_j <= from_i:
|
| 1135 |
+
return group.identity
|
| 1136 |
+
else:
|
| 1137 |
+
r = tuple(self.array_form[from_i: to_j])
|
| 1138 |
+
return group.dtype(r)
|
| 1139 |
+
|
| 1140 |
+
def substituted_word(self, from_i, to_j, by):
|
| 1141 |
+
"""
|
| 1142 |
+
Returns the associative word obtained by replacing the subword of
|
| 1143 |
+
`self` that begins at position `from_i` and ends at position `to_j - 1`
|
| 1144 |
+
by the associative word `by`. `from_i` and `to_j` must be positive
|
| 1145 |
+
integers, indexing is done with origin 0. In other words,
|
| 1146 |
+
`w.substituted_word(w, from_i, to_j, by)` is the product of the three
|
| 1147 |
+
words: `w.subword(0, from_i)`, `by`, and
|
| 1148 |
+
`w.subword(to_j len(w))`.
|
| 1149 |
+
|
| 1150 |
+
See Also
|
| 1151 |
+
========
|
| 1152 |
+
|
| 1153 |
+
eliminate_word
|
| 1154 |
+
|
| 1155 |
+
"""
|
| 1156 |
+
lw = len(self)
|
| 1157 |
+
if from_i >= to_j or from_i > lw or to_j > lw:
|
| 1158 |
+
raise ValueError("values should be within bounds")
|
| 1159 |
+
|
| 1160 |
+
# otherwise there are four possibilities
|
| 1161 |
+
|
| 1162 |
+
# first if from=1 and to=lw then
|
| 1163 |
+
if from_i == 0 and to_j == lw:
|
| 1164 |
+
return by
|
| 1165 |
+
elif from_i == 0: # second if from_i=1 (and to_j < lw) then
|
| 1166 |
+
return by*self.subword(to_j, lw)
|
| 1167 |
+
elif to_j == lw: # third if to_j=1 (and from_i > 1) then
|
| 1168 |
+
return self.subword(0, from_i)*by
|
| 1169 |
+
else: # finally
|
| 1170 |
+
return self.subword(0, from_i)*by*self.subword(to_j, lw)
|
| 1171 |
+
|
| 1172 |
+
def is_cyclically_reduced(self):
|
| 1173 |
+
r"""Returns whether the word is cyclically reduced or not.
|
| 1174 |
+
A word is cyclically reduced if by forming the cycle of the
|
| 1175 |
+
word, the word is not reduced, i.e a word w = `a_1 ... a_n`
|
| 1176 |
+
is called cyclically reduced if `a_1 \ne a_n^{-1}`.
|
| 1177 |
+
|
| 1178 |
+
Examples
|
| 1179 |
+
========
|
| 1180 |
+
|
| 1181 |
+
>>> from sympy.combinatorics import free_group
|
| 1182 |
+
>>> F, x, y = free_group("x, y")
|
| 1183 |
+
>>> (x**2*y**-1*x**-1).is_cyclically_reduced()
|
| 1184 |
+
False
|
| 1185 |
+
>>> (y*x**2*y**2).is_cyclically_reduced()
|
| 1186 |
+
True
|
| 1187 |
+
|
| 1188 |
+
"""
|
| 1189 |
+
if not self:
|
| 1190 |
+
return True
|
| 1191 |
+
return self[0] != self[-1]**-1
|
| 1192 |
+
|
| 1193 |
+
def identity_cyclic_reduction(self):
|
| 1194 |
+
"""Return a unique cyclically reduced version of the word.
|
| 1195 |
+
|
| 1196 |
+
Examples
|
| 1197 |
+
========
|
| 1198 |
+
|
| 1199 |
+
>>> from sympy.combinatorics import free_group
|
| 1200 |
+
>>> F, x, y = free_group("x, y")
|
| 1201 |
+
>>> (x**2*y**2*x**-1).identity_cyclic_reduction()
|
| 1202 |
+
x*y**2
|
| 1203 |
+
>>> (x**-3*y**-1*x**5).identity_cyclic_reduction()
|
| 1204 |
+
x**2*y**-1
|
| 1205 |
+
|
| 1206 |
+
References
|
| 1207 |
+
==========
|
| 1208 |
+
|
| 1209 |
+
.. [1] https://planetmath.org/cyclicallyreduced
|
| 1210 |
+
|
| 1211 |
+
"""
|
| 1212 |
+
word = self.copy()
|
| 1213 |
+
group = self.group
|
| 1214 |
+
while not word.is_cyclically_reduced():
|
| 1215 |
+
exp1 = word.exponent_syllable(0)
|
| 1216 |
+
exp2 = word.exponent_syllable(-1)
|
| 1217 |
+
r = exp1 + exp2
|
| 1218 |
+
if r == 0:
|
| 1219 |
+
rep = word.array_form[1: word.number_syllables() - 1]
|
| 1220 |
+
else:
|
| 1221 |
+
rep = ((word.generator_syllable(0), exp1 + exp2),) + \
|
| 1222 |
+
word.array_form[1: word.number_syllables() - 1]
|
| 1223 |
+
word = group.dtype(rep)
|
| 1224 |
+
return word
|
| 1225 |
+
|
| 1226 |
+
def cyclic_reduction(self, removed=False):
|
| 1227 |
+
"""Return a cyclically reduced version of the word. Unlike
|
| 1228 |
+
`identity_cyclic_reduction`, this will not cyclically permute
|
| 1229 |
+
the reduced word - just remove the "unreduced" bits on either
|
| 1230 |
+
side of it. Compare the examples with those of
|
| 1231 |
+
`identity_cyclic_reduction`.
|
| 1232 |
+
|
| 1233 |
+
When `removed` is `True`, return a tuple `(word, r)` where
|
| 1234 |
+
self `r` is such that before the reduction the word was either
|
| 1235 |
+
`r*word*r**-1`.
|
| 1236 |
+
|
| 1237 |
+
Examples
|
| 1238 |
+
========
|
| 1239 |
+
|
| 1240 |
+
>>> from sympy.combinatorics import free_group
|
| 1241 |
+
>>> F, x, y = free_group("x, y")
|
| 1242 |
+
>>> (x**2*y**2*x**-1).cyclic_reduction()
|
| 1243 |
+
x*y**2
|
| 1244 |
+
>>> (x**-3*y**-1*x**5).cyclic_reduction()
|
| 1245 |
+
y**-1*x**2
|
| 1246 |
+
>>> (x**-3*y**-1*x**5).cyclic_reduction(removed=True)
|
| 1247 |
+
(y**-1*x**2, x**-3)
|
| 1248 |
+
|
| 1249 |
+
"""
|
| 1250 |
+
word = self.copy()
|
| 1251 |
+
g = self.group.identity
|
| 1252 |
+
while not word.is_cyclically_reduced():
|
| 1253 |
+
exp1 = abs(word.exponent_syllable(0))
|
| 1254 |
+
exp2 = abs(word.exponent_syllable(-1))
|
| 1255 |
+
exp = min(exp1, exp2)
|
| 1256 |
+
start = word[0]**abs(exp)
|
| 1257 |
+
end = word[-1]**abs(exp)
|
| 1258 |
+
word = start**-1*word*end**-1
|
| 1259 |
+
g = g*start
|
| 1260 |
+
if removed:
|
| 1261 |
+
return word, g
|
| 1262 |
+
return word
|
| 1263 |
+
|
| 1264 |
+
def power_of(self, other):
|
| 1265 |
+
'''
|
| 1266 |
+
Check if `self == other**n` for some integer n.
|
| 1267 |
+
|
| 1268 |
+
Examples
|
| 1269 |
+
========
|
| 1270 |
+
|
| 1271 |
+
>>> from sympy.combinatorics import free_group
|
| 1272 |
+
>>> F, x, y = free_group("x, y")
|
| 1273 |
+
>>> ((x*y)**2).power_of(x*y)
|
| 1274 |
+
True
|
| 1275 |
+
>>> (x**-3*y**-2*x**3).power_of(x**-3*y*x**3)
|
| 1276 |
+
True
|
| 1277 |
+
|
| 1278 |
+
'''
|
| 1279 |
+
if self.is_identity:
|
| 1280 |
+
return True
|
| 1281 |
+
|
| 1282 |
+
l = len(other)
|
| 1283 |
+
if l == 1:
|
| 1284 |
+
# self has to be a power of one generator
|
| 1285 |
+
gens = self.contains_generators()
|
| 1286 |
+
s = other in gens or other**-1 in gens
|
| 1287 |
+
return len(gens) == 1 and s
|
| 1288 |
+
|
| 1289 |
+
# if self is not cyclically reduced and it is a power of other,
|
| 1290 |
+
# other isn't cyclically reduced and the parts removed during
|
| 1291 |
+
# their reduction must be equal
|
| 1292 |
+
reduced, r1 = self.cyclic_reduction(removed=True)
|
| 1293 |
+
if not r1.is_identity:
|
| 1294 |
+
other, r2 = other.cyclic_reduction(removed=True)
|
| 1295 |
+
if r1 == r2:
|
| 1296 |
+
return reduced.power_of(other)
|
| 1297 |
+
return False
|
| 1298 |
+
|
| 1299 |
+
if len(self) < l or len(self) % l:
|
| 1300 |
+
return False
|
| 1301 |
+
|
| 1302 |
+
prefix = self.subword(0, l)
|
| 1303 |
+
if prefix == other or prefix**-1 == other:
|
| 1304 |
+
rest = self.subword(l, len(self))
|
| 1305 |
+
return rest.power_of(other)
|
| 1306 |
+
return False
|
| 1307 |
+
|
| 1308 |
+
|
| 1309 |
+
def letter_form_to_array_form(array_form, group):
|
| 1310 |
+
"""
|
| 1311 |
+
This method converts a list given with possible repetitions of elements in
|
| 1312 |
+
it. It returns a new list such that repetitions of consecutive elements is
|
| 1313 |
+
removed and replace with a tuple element of size two such that the first
|
| 1314 |
+
index contains `value` and the second index contains the number of
|
| 1315 |
+
consecutive repetitions of `value`.
|
| 1316 |
+
|
| 1317 |
+
"""
|
| 1318 |
+
a = list(array_form[:])
|
| 1319 |
+
new_array = []
|
| 1320 |
+
n = 1
|
| 1321 |
+
symbols = group.symbols
|
| 1322 |
+
for i in range(len(a)):
|
| 1323 |
+
if i == len(a) - 1:
|
| 1324 |
+
if a[i] == a[i - 1]:
|
| 1325 |
+
if (-a[i]) in symbols:
|
| 1326 |
+
new_array.append((-a[i], -n))
|
| 1327 |
+
else:
|
| 1328 |
+
new_array.append((a[i], n))
|
| 1329 |
+
else:
|
| 1330 |
+
if (-a[i]) in symbols:
|
| 1331 |
+
new_array.append((-a[i], -1))
|
| 1332 |
+
else:
|
| 1333 |
+
new_array.append((a[i], 1))
|
| 1334 |
+
return new_array
|
| 1335 |
+
elif a[i] == a[i + 1]:
|
| 1336 |
+
n += 1
|
| 1337 |
+
else:
|
| 1338 |
+
if (-a[i]) in symbols:
|
| 1339 |
+
new_array.append((-a[i], -n))
|
| 1340 |
+
else:
|
| 1341 |
+
new_array.append((a[i], n))
|
| 1342 |
+
n = 1
|
| 1343 |
+
|
| 1344 |
+
|
| 1345 |
+
def zero_mul_simp(l, index):
|
| 1346 |
+
"""Used to combine two reduced words."""
|
| 1347 |
+
while index >=0 and index < len(l) - 1 and l[index][0] == l[index + 1][0]:
|
| 1348 |
+
exp = l[index][1] + l[index + 1][1]
|
| 1349 |
+
base = l[index][0]
|
| 1350 |
+
l[index] = (base, exp)
|
| 1351 |
+
del l[index + 1]
|
| 1352 |
+
if l[index][1] == 0:
|
| 1353 |
+
del l[index]
|
| 1354 |
+
index -= 1
|
lib/python3.12/site-packages/sympy/combinatorics/galois.py
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""
|
| 2 |
+
Construct transitive subgroups of symmetric groups, useful in Galois theory.
|
| 3 |
+
|
| 4 |
+
Besides constructing instances of the :py:class:`~.PermutationGroup` class to
|
| 5 |
+
represent the transitive subgroups of $S_n$ for small $n$, this module provides
|
| 6 |
+
*names* for these groups.
|
| 7 |
+
|
| 8 |
+
In some applications, it may be preferable to know the name of a group,
|
| 9 |
+
rather than receive an instance of the :py:class:`~.PermutationGroup`
|
| 10 |
+
class, and then have to do extra work to determine which group it is, by
|
| 11 |
+
checking various properties.
|
| 12 |
+
|
| 13 |
+
Names are instances of ``Enum`` classes defined in this module. With a name in
|
| 14 |
+
hand, the name's ``get_perm_group`` method can then be used to retrieve a
|
| 15 |
+
:py:class:`~.PermutationGroup`.
|
| 16 |
+
|
| 17 |
+
The names used for groups in this module are taken from [1].
|
| 18 |
+
|
| 19 |
+
References
|
| 20 |
+
==========
|
| 21 |
+
|
| 22 |
+
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
|
| 23 |
+
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
from collections import defaultdict
|
| 27 |
+
from enum import Enum
|
| 28 |
+
import itertools
|
| 29 |
+
|
| 30 |
+
from sympy.combinatorics.named_groups import (
|
| 31 |
+
SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup,
|
| 32 |
+
set_symmetric_group_properties, set_alternating_group_properties,
|
| 33 |
+
)
|
| 34 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 35 |
+
from sympy.combinatorics.permutations import Permutation
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class S1TransitiveSubgroups(Enum):
|
| 39 |
+
"""
|
| 40 |
+
Names for the transitive subgroups of S1.
|
| 41 |
+
"""
|
| 42 |
+
S1 = "S1"
|
| 43 |
+
|
| 44 |
+
def get_perm_group(self):
|
| 45 |
+
return SymmetricGroup(1)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class S2TransitiveSubgroups(Enum):
|
| 49 |
+
"""
|
| 50 |
+
Names for the transitive subgroups of S2.
|
| 51 |
+
"""
|
| 52 |
+
S2 = "S2"
|
| 53 |
+
|
| 54 |
+
def get_perm_group(self):
|
| 55 |
+
return SymmetricGroup(2)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class S3TransitiveSubgroups(Enum):
|
| 59 |
+
"""
|
| 60 |
+
Names for the transitive subgroups of S3.
|
| 61 |
+
"""
|
| 62 |
+
A3 = "A3"
|
| 63 |
+
S3 = "S3"
|
| 64 |
+
|
| 65 |
+
def get_perm_group(self):
|
| 66 |
+
if self == S3TransitiveSubgroups.A3:
|
| 67 |
+
return AlternatingGroup(3)
|
| 68 |
+
elif self == S3TransitiveSubgroups.S3:
|
| 69 |
+
return SymmetricGroup(3)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class S4TransitiveSubgroups(Enum):
|
| 73 |
+
"""
|
| 74 |
+
Names for the transitive subgroups of S4.
|
| 75 |
+
"""
|
| 76 |
+
C4 = "C4"
|
| 77 |
+
V = "V"
|
| 78 |
+
D4 = "D4"
|
| 79 |
+
A4 = "A4"
|
| 80 |
+
S4 = "S4"
|
| 81 |
+
|
| 82 |
+
def get_perm_group(self):
|
| 83 |
+
if self == S4TransitiveSubgroups.C4:
|
| 84 |
+
return CyclicGroup(4)
|
| 85 |
+
elif self == S4TransitiveSubgroups.V:
|
| 86 |
+
return four_group()
|
| 87 |
+
elif self == S4TransitiveSubgroups.D4:
|
| 88 |
+
return DihedralGroup(4)
|
| 89 |
+
elif self == S4TransitiveSubgroups.A4:
|
| 90 |
+
return AlternatingGroup(4)
|
| 91 |
+
elif self == S4TransitiveSubgroups.S4:
|
| 92 |
+
return SymmetricGroup(4)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class S5TransitiveSubgroups(Enum):
|
| 96 |
+
"""
|
| 97 |
+
Names for the transitive subgroups of S5.
|
| 98 |
+
"""
|
| 99 |
+
C5 = "C5"
|
| 100 |
+
D5 = "D5"
|
| 101 |
+
M20 = "M20"
|
| 102 |
+
A5 = "A5"
|
| 103 |
+
S5 = "S5"
|
| 104 |
+
|
| 105 |
+
def get_perm_group(self):
|
| 106 |
+
if self == S5TransitiveSubgroups.C5:
|
| 107 |
+
return CyclicGroup(5)
|
| 108 |
+
elif self == S5TransitiveSubgroups.D5:
|
| 109 |
+
return DihedralGroup(5)
|
| 110 |
+
elif self == S5TransitiveSubgroups.M20:
|
| 111 |
+
return M20()
|
| 112 |
+
elif self == S5TransitiveSubgroups.A5:
|
| 113 |
+
return AlternatingGroup(5)
|
| 114 |
+
elif self == S5TransitiveSubgroups.S5:
|
| 115 |
+
return SymmetricGroup(5)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class S6TransitiveSubgroups(Enum):
|
| 119 |
+
"""
|
| 120 |
+
Names for the transitive subgroups of S6.
|
| 121 |
+
"""
|
| 122 |
+
C6 = "C6"
|
| 123 |
+
S3 = "S3"
|
| 124 |
+
D6 = "D6"
|
| 125 |
+
A4 = "A4"
|
| 126 |
+
G18 = "G18"
|
| 127 |
+
A4xC2 = "A4 x C2"
|
| 128 |
+
S4m = "S4-"
|
| 129 |
+
S4p = "S4+"
|
| 130 |
+
G36m = "G36-"
|
| 131 |
+
G36p = "G36+"
|
| 132 |
+
S4xC2 = "S4 x C2"
|
| 133 |
+
PSL2F5 = "PSL2(F5)"
|
| 134 |
+
G72 = "G72"
|
| 135 |
+
PGL2F5 = "PGL2(F5)"
|
| 136 |
+
A6 = "A6"
|
| 137 |
+
S6 = "S6"
|
| 138 |
+
|
| 139 |
+
def get_perm_group(self):
|
| 140 |
+
if self == S6TransitiveSubgroups.C6:
|
| 141 |
+
return CyclicGroup(6)
|
| 142 |
+
elif self == S6TransitiveSubgroups.S3:
|
| 143 |
+
return S3_in_S6()
|
| 144 |
+
elif self == S6TransitiveSubgroups.D6:
|
| 145 |
+
return DihedralGroup(6)
|
| 146 |
+
elif self == S6TransitiveSubgroups.A4:
|
| 147 |
+
return A4_in_S6()
|
| 148 |
+
elif self == S6TransitiveSubgroups.G18:
|
| 149 |
+
return G18()
|
| 150 |
+
elif self == S6TransitiveSubgroups.A4xC2:
|
| 151 |
+
return A4xC2()
|
| 152 |
+
elif self == S6TransitiveSubgroups.S4m:
|
| 153 |
+
return S4m()
|
| 154 |
+
elif self == S6TransitiveSubgroups.S4p:
|
| 155 |
+
return S4p()
|
| 156 |
+
elif self == S6TransitiveSubgroups.G36m:
|
| 157 |
+
return G36m()
|
| 158 |
+
elif self == S6TransitiveSubgroups.G36p:
|
| 159 |
+
return G36p()
|
| 160 |
+
elif self == S6TransitiveSubgroups.S4xC2:
|
| 161 |
+
return S4xC2()
|
| 162 |
+
elif self == S6TransitiveSubgroups.PSL2F5:
|
| 163 |
+
return PSL2F5()
|
| 164 |
+
elif self == S6TransitiveSubgroups.G72:
|
| 165 |
+
return G72()
|
| 166 |
+
elif self == S6TransitiveSubgroups.PGL2F5:
|
| 167 |
+
return PGL2F5()
|
| 168 |
+
elif self == S6TransitiveSubgroups.A6:
|
| 169 |
+
return AlternatingGroup(6)
|
| 170 |
+
elif self == S6TransitiveSubgroups.S6:
|
| 171 |
+
return SymmetricGroup(6)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def four_group():
|
| 175 |
+
"""
|
| 176 |
+
Return a representation of the Klein four-group as a transitive subgroup
|
| 177 |
+
of S4.
|
| 178 |
+
"""
|
| 179 |
+
return PermutationGroup(
|
| 180 |
+
Permutation(0, 1)(2, 3),
|
| 181 |
+
Permutation(0, 2)(1, 3)
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def M20():
|
| 186 |
+
"""
|
| 187 |
+
Return a representation of the metacyclic group M20, a transitive subgroup
|
| 188 |
+
of S5 that is one of the possible Galois groups for polys of degree 5.
|
| 189 |
+
|
| 190 |
+
Notes
|
| 191 |
+
=====
|
| 192 |
+
|
| 193 |
+
See [1], Page 323.
|
| 194 |
+
|
| 195 |
+
"""
|
| 196 |
+
G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3))
|
| 197 |
+
G._degree = 5
|
| 198 |
+
G._order = 20
|
| 199 |
+
G._is_transitive = True
|
| 200 |
+
G._is_sym = False
|
| 201 |
+
G._is_alt = False
|
| 202 |
+
G._is_cyclic = False
|
| 203 |
+
G._is_dihedral = False
|
| 204 |
+
return G
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def S3_in_S6():
|
| 208 |
+
"""
|
| 209 |
+
Return a representation of S3 as a transitive subgroup of S6.
|
| 210 |
+
|
| 211 |
+
Notes
|
| 212 |
+
=====
|
| 213 |
+
|
| 214 |
+
The representation is found by viewing the group as the symmetries of a
|
| 215 |
+
triangular prism.
|
| 216 |
+
|
| 217 |
+
"""
|
| 218 |
+
G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5))
|
| 219 |
+
set_symmetric_group_properties(G, 3, 6)
|
| 220 |
+
return G
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def A4_in_S6():
|
| 224 |
+
"""
|
| 225 |
+
Return a representation of A4 as a transitive subgroup of S6.
|
| 226 |
+
|
| 227 |
+
Notes
|
| 228 |
+
=====
|
| 229 |
+
|
| 230 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 231 |
+
|
| 232 |
+
"""
|
| 233 |
+
G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4))
|
| 234 |
+
set_alternating_group_properties(G, 4, 6)
|
| 235 |
+
return G
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def S4m():
|
| 239 |
+
"""
|
| 240 |
+
Return a representation of the S4- transitive subgroup of S6.
|
| 241 |
+
|
| 242 |
+
Notes
|
| 243 |
+
=====
|
| 244 |
+
|
| 245 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 246 |
+
|
| 247 |
+
"""
|
| 248 |
+
G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3))
|
| 249 |
+
set_symmetric_group_properties(G, 4, 6)
|
| 250 |
+
return G
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def S4p():
|
| 254 |
+
"""
|
| 255 |
+
Return a representation of the S4+ transitive subgroup of S6.
|
| 256 |
+
|
| 257 |
+
Notes
|
| 258 |
+
=====
|
| 259 |
+
|
| 260 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 261 |
+
|
| 262 |
+
"""
|
| 263 |
+
G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5))
|
| 264 |
+
set_symmetric_group_properties(G, 4, 6)
|
| 265 |
+
return G
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def A4xC2():
|
| 269 |
+
"""
|
| 270 |
+
Return a representation of the (A4 x C2) transitive subgroup of S6.
|
| 271 |
+
|
| 272 |
+
Notes
|
| 273 |
+
=====
|
| 274 |
+
|
| 275 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 276 |
+
|
| 277 |
+
"""
|
| 278 |
+
return PermutationGroup(
|
| 279 |
+
Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4),
|
| 280 |
+
Permutation(5)(2, 4))
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def S4xC2():
|
| 284 |
+
"""
|
| 285 |
+
Return a representation of the (S4 x C2) transitive subgroup of S6.
|
| 286 |
+
|
| 287 |
+
Notes
|
| 288 |
+
=====
|
| 289 |
+
|
| 290 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 291 |
+
|
| 292 |
+
"""
|
| 293 |
+
return PermutationGroup(
|
| 294 |
+
Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3),
|
| 295 |
+
Permutation(1, 4)(3, 5))
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def G18():
|
| 299 |
+
"""
|
| 300 |
+
Return a representation of the group G18, a transitive subgroup of S6
|
| 301 |
+
isomorphic to the semidirect product of C3^2 with C2.
|
| 302 |
+
|
| 303 |
+
Notes
|
| 304 |
+
=====
|
| 305 |
+
|
| 306 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 307 |
+
|
| 308 |
+
"""
|
| 309 |
+
return PermutationGroup(
|
| 310 |
+
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
| 311 |
+
Permutation(0, 4)(1, 5)(2, 3))
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def G36m():
|
| 315 |
+
"""
|
| 316 |
+
Return a representation of the group G36-, a transitive subgroup of S6
|
| 317 |
+
isomorphic to the semidirect product of C3^2 with C2^2.
|
| 318 |
+
|
| 319 |
+
Notes
|
| 320 |
+
=====
|
| 321 |
+
|
| 322 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 323 |
+
|
| 324 |
+
"""
|
| 325 |
+
return PermutationGroup(
|
| 326 |
+
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
| 327 |
+
Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3))
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def G36p():
|
| 331 |
+
"""
|
| 332 |
+
Return a representation of the group G36+, a transitive subgroup of S6
|
| 333 |
+
isomorphic to the semidirect product of C3^2 with C4.
|
| 334 |
+
|
| 335 |
+
Notes
|
| 336 |
+
=====
|
| 337 |
+
|
| 338 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 339 |
+
|
| 340 |
+
"""
|
| 341 |
+
return PermutationGroup(
|
| 342 |
+
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
| 343 |
+
Permutation(0, 5, 2, 3)(1, 4))
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def G72():
|
| 347 |
+
"""
|
| 348 |
+
Return a representation of the group G72, a transitive subgroup of S6
|
| 349 |
+
isomorphic to the semidirect product of C3^2 with D4.
|
| 350 |
+
|
| 351 |
+
Notes
|
| 352 |
+
=====
|
| 353 |
+
|
| 354 |
+
See [1], Page 325.
|
| 355 |
+
|
| 356 |
+
"""
|
| 357 |
+
return PermutationGroup(
|
| 358 |
+
Permutation(5)(0, 1, 2),
|
| 359 |
+
Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5))
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def PSL2F5():
|
| 363 |
+
r"""
|
| 364 |
+
Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive
|
| 365 |
+
subgroup of S6, isomorphic to $A_5$.
|
| 366 |
+
|
| 367 |
+
Notes
|
| 368 |
+
=====
|
| 369 |
+
|
| 370 |
+
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
| 371 |
+
|
| 372 |
+
"""
|
| 373 |
+
G = PermutationGroup(
|
| 374 |
+
Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5))
|
| 375 |
+
set_alternating_group_properties(G, 5, 6)
|
| 376 |
+
return G
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def PGL2F5():
|
| 380 |
+
r"""
|
| 381 |
+
Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive
|
| 382 |
+
subgroup of S6, isomorphic to $S_5$.
|
| 383 |
+
|
| 384 |
+
Notes
|
| 385 |
+
=====
|
| 386 |
+
|
| 387 |
+
See [1], Page 325.
|
| 388 |
+
|
| 389 |
+
"""
|
| 390 |
+
G = PermutationGroup(
|
| 391 |
+
Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4))
|
| 392 |
+
set_symmetric_group_properties(G, 5, 6)
|
| 393 |
+
return G
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
def find_transitive_subgroups_of_S6(*targets, print_report=False):
|
| 397 |
+
r"""
|
| 398 |
+
Search for certain transitive subgroups of $S_6$.
|
| 399 |
+
|
| 400 |
+
The symmetric group $S_6$ has 16 different transitive subgroups, up to
|
| 401 |
+
conjugacy. Some are more easily constructed than others. For example, the
|
| 402 |
+
dihedral group $D_6$ is immediately found, but it is not at all obvious how
|
| 403 |
+
to realize $S_4$ or $S_5$ *transitively* within $S_6$.
|
| 404 |
+
|
| 405 |
+
In some cases there are well-known constructions that can be used. For
|
| 406 |
+
example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a
|
| 407 |
+
natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6.
|
| 408 |
+
|
| 409 |
+
In absence of such special constructions however, we can simply search for
|
| 410 |
+
generators. For example, transitive instances of $A_4$ and $S_4$ can be
|
| 411 |
+
found within $S_6$ in this way.
|
| 412 |
+
|
| 413 |
+
Once we are engaged in such searches, it may then be easier (if less
|
| 414 |
+
elegant) to find even those groups like $S_5$ that do have special
|
| 415 |
+
constructions, by mere search.
|
| 416 |
+
|
| 417 |
+
This function locates generators for transitive instances in $S_6$ of the
|
| 418 |
+
following subgroups:
|
| 419 |
+
|
| 420 |
+
* $A_4$
|
| 421 |
+
* $S_4^-$ ($S_4$ not contained within $A_6$)
|
| 422 |
+
* $S_4^+$ ($S_4$ contained within $A_6$)
|
| 423 |
+
* $A_4 \times C_2$
|
| 424 |
+
* $S_4 \times C_2$
|
| 425 |
+
* $G_{18} = C_3^2 \rtimes C_2$
|
| 426 |
+
* $G_{36}^- = C_3^2 \rtimes C_2^2$
|
| 427 |
+
* $G_{36}^+ = C_3^2 \rtimes C_4$
|
| 428 |
+
* $G_{72} = C_3^2 \rtimes D_4$
|
| 429 |
+
* $A_5$
|
| 430 |
+
* $S_5$
|
| 431 |
+
|
| 432 |
+
Note: Each of these groups also has a dedicated function in this module
|
| 433 |
+
that returns the group immediately, using generators that were found by
|
| 434 |
+
this search procedure.
|
| 435 |
+
|
| 436 |
+
The search procedure serves as a record of how these generators were
|
| 437 |
+
found. Also, due to randomness in the generation of the elements of
|
| 438 |
+
permutation groups, it can be called again, in order to (probably) get
|
| 439 |
+
different generators for the same groups.
|
| 440 |
+
|
| 441 |
+
Parameters
|
| 442 |
+
==========
|
| 443 |
+
|
| 444 |
+
targets : list of :py:class:`~.S6TransitiveSubgroups` values
|
| 445 |
+
The groups you want to find.
|
| 446 |
+
|
| 447 |
+
print_report : bool (default False)
|
| 448 |
+
If True, print to stdout the generators found for each group.
|
| 449 |
+
|
| 450 |
+
Returns
|
| 451 |
+
=======
|
| 452 |
+
|
| 453 |
+
dict
|
| 454 |
+
mapping each name in *targets* to the :py:class:`~.PermutationGroup`
|
| 455 |
+
that was found
|
| 456 |
+
|
| 457 |
+
References
|
| 458 |
+
==========
|
| 459 |
+
|
| 460 |
+
.. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms
|
| 461 |
+
.. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL%282,5%29
|
| 462 |
+
|
| 463 |
+
"""
|
| 464 |
+
def elts_by_order(G):
|
| 465 |
+
"""Sort the elements of a group by their order. """
|
| 466 |
+
elts = defaultdict(list)
|
| 467 |
+
for g in G.elements:
|
| 468 |
+
elts[g.order()].append(g)
|
| 469 |
+
return elts
|
| 470 |
+
|
| 471 |
+
def order_profile(G, name=None):
|
| 472 |
+
"""Determine how many elements a group has, of each order. """
|
| 473 |
+
elts = elts_by_order(G)
|
| 474 |
+
profile = {o:len(e) for o, e in elts.items()}
|
| 475 |
+
if name:
|
| 476 |
+
print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys())))
|
| 477 |
+
return profile
|
| 478 |
+
|
| 479 |
+
S6 = SymmetricGroup(6)
|
| 480 |
+
A6 = AlternatingGroup(6)
|
| 481 |
+
S6_by_order = elts_by_order(S6)
|
| 482 |
+
|
| 483 |
+
def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None):
|
| 484 |
+
"""
|
| 485 |
+
Find a transitive subgroup of S6.
|
| 486 |
+
|
| 487 |
+
Parameters
|
| 488 |
+
==========
|
| 489 |
+
|
| 490 |
+
existing_gens : list of Permutation
|
| 491 |
+
Optionally empty list of generators that must be in the group.
|
| 492 |
+
|
| 493 |
+
needed_gen_orders : list of positive int
|
| 494 |
+
Nonempty list of the orders of the additional generators that are
|
| 495 |
+
to be found.
|
| 496 |
+
|
| 497 |
+
order: int
|
| 498 |
+
The order of the group being sought.
|
| 499 |
+
|
| 500 |
+
alt: bool, None
|
| 501 |
+
If True, require the group to be contained in A6.
|
| 502 |
+
If False, require the group not to be contained in A6.
|
| 503 |
+
|
| 504 |
+
profile : dict
|
| 505 |
+
If given, the group's order profile must equal this.
|
| 506 |
+
|
| 507 |
+
anti_profile : dict
|
| 508 |
+
If given, the group's order profile must *not* equal this.
|
| 509 |
+
|
| 510 |
+
"""
|
| 511 |
+
for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]):
|
| 512 |
+
if len(set(gens)) < len(gens):
|
| 513 |
+
continue
|
| 514 |
+
G = PermutationGroup(existing_gens + list(gens))
|
| 515 |
+
if G.order() == order and G.is_transitive():
|
| 516 |
+
if alt is not None and G.is_subgroup(A6) != alt:
|
| 517 |
+
continue
|
| 518 |
+
if profile and order_profile(G) != profile:
|
| 519 |
+
continue
|
| 520 |
+
if anti_profile and order_profile(G) == anti_profile:
|
| 521 |
+
continue
|
| 522 |
+
return G
|
| 523 |
+
|
| 524 |
+
def match_known_group(G, alt=None):
|
| 525 |
+
needed = [g.order() for g in G.generators]
|
| 526 |
+
return search([], needed, G.order(), alt=alt, profile=order_profile(G))
|
| 527 |
+
|
| 528 |
+
found = {}
|
| 529 |
+
|
| 530 |
+
def finish_up(name, G):
|
| 531 |
+
found[name] = G
|
| 532 |
+
if print_report:
|
| 533 |
+
print("=" * 40)
|
| 534 |
+
print(f"{name}:")
|
| 535 |
+
print(G.generators)
|
| 536 |
+
|
| 537 |
+
if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets:
|
| 538 |
+
A4_in_S6 = match_known_group(AlternatingGroup(4))
|
| 539 |
+
finish_up(S6TransitiveSubgroups.A4, A4_in_S6)
|
| 540 |
+
|
| 541 |
+
if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets:
|
| 542 |
+
S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False)
|
| 543 |
+
finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6)
|
| 544 |
+
|
| 545 |
+
if S6TransitiveSubgroups.S4p in targets:
|
| 546 |
+
S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True)
|
| 547 |
+
finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6)
|
| 548 |
+
|
| 549 |
+
if S6TransitiveSubgroups.A4xC2 in targets:
|
| 550 |
+
A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4)))
|
| 551 |
+
finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6)
|
| 552 |
+
|
| 553 |
+
if S6TransitiveSubgroups.S4xC2 in targets:
|
| 554 |
+
S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48)
|
| 555 |
+
finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6)
|
| 556 |
+
|
| 557 |
+
# For the normal factor N = C3^2 in any of the G_n subgroups, we take one
|
| 558 |
+
# obvious instance of C3^2 in S6:
|
| 559 |
+
N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)]
|
| 560 |
+
|
| 561 |
+
if S6TransitiveSubgroups.G18 in targets:
|
| 562 |
+
G18_in_S6 = search(N_gens, [2], 18)
|
| 563 |
+
finish_up(S6TransitiveSubgroups.G18, G18_in_S6)
|
| 564 |
+
|
| 565 |
+
if S6TransitiveSubgroups.G36m in targets:
|
| 566 |
+
G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False)
|
| 567 |
+
finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6)
|
| 568 |
+
|
| 569 |
+
if S6TransitiveSubgroups.G36p in targets:
|
| 570 |
+
G36p_in_S6 = search(N_gens, [4], 36, alt=True)
|
| 571 |
+
finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6)
|
| 572 |
+
|
| 573 |
+
if S6TransitiveSubgroups.G72 in targets:
|
| 574 |
+
G72_in_S6 = search(N_gens, [4, 2], 72)
|
| 575 |
+
finish_up(S6TransitiveSubgroups.G72, G72_in_S6)
|
| 576 |
+
|
| 577 |
+
# The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp.
|
| 578 |
+
|
| 579 |
+
if S6TransitiveSubgroups.PSL2F5 in targets:
|
| 580 |
+
PSL2F5_in_S6 = match_known_group(AlternatingGroup(5))
|
| 581 |
+
finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6)
|
| 582 |
+
|
| 583 |
+
if S6TransitiveSubgroups.PGL2F5 in targets:
|
| 584 |
+
PGL2F5_in_S6 = match_known_group(SymmetricGroup(5))
|
| 585 |
+
finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6)
|
| 586 |
+
|
| 587 |
+
# There is little need to "search" for any of the groups C6, S3, D6, A6,
|
| 588 |
+
# or S6, since they all have obvious realizations within S6. However, we
|
| 589 |
+
# support them here just in case a random representation is desired.
|
| 590 |
+
|
| 591 |
+
if S6TransitiveSubgroups.C6 in targets:
|
| 592 |
+
C6 = match_known_group(CyclicGroup(6))
|
| 593 |
+
finish_up(S6TransitiveSubgroups.C6, C6)
|
| 594 |
+
|
| 595 |
+
if S6TransitiveSubgroups.S3 in targets:
|
| 596 |
+
S3 = match_known_group(SymmetricGroup(3))
|
| 597 |
+
finish_up(S6TransitiveSubgroups.S3, S3)
|
| 598 |
+
|
| 599 |
+
if S6TransitiveSubgroups.D6 in targets:
|
| 600 |
+
D6 = match_known_group(DihedralGroup(6))
|
| 601 |
+
finish_up(S6TransitiveSubgroups.D6, D6)
|
| 602 |
+
|
| 603 |
+
if S6TransitiveSubgroups.A6 in targets:
|
| 604 |
+
A6 = match_known_group(A6)
|
| 605 |
+
finish_up(S6TransitiveSubgroups.A6, A6)
|
| 606 |
+
|
| 607 |
+
if S6TransitiveSubgroups.S6 in targets:
|
| 608 |
+
S6 = match_known_group(S6)
|
| 609 |
+
finish_up(S6TransitiveSubgroups.S6, S6)
|
| 610 |
+
|
| 611 |
+
return found
|
lib/python3.12/site-packages/sympy/combinatorics/generators.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.permutations import Permutation
|
| 2 |
+
from sympy.core.symbol import symbols
|
| 3 |
+
from sympy.matrices import Matrix
|
| 4 |
+
from sympy.utilities.iterables import variations, rotate_left
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def symmetric(n):
|
| 8 |
+
"""
|
| 9 |
+
Generates the symmetric group of order n, Sn.
|
| 10 |
+
|
| 11 |
+
Examples
|
| 12 |
+
========
|
| 13 |
+
|
| 14 |
+
>>> from sympy.combinatorics.generators import symmetric
|
| 15 |
+
>>> list(symmetric(3))
|
| 16 |
+
[(2), (1 2), (2)(0 1), (0 1 2), (0 2 1), (0 2)]
|
| 17 |
+
"""
|
| 18 |
+
for perm in variations(range(n), n):
|
| 19 |
+
yield Permutation(perm)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def cyclic(n):
|
| 23 |
+
"""
|
| 24 |
+
Generates the cyclic group of order n, Cn.
|
| 25 |
+
|
| 26 |
+
Examples
|
| 27 |
+
========
|
| 28 |
+
|
| 29 |
+
>>> from sympy.combinatorics.generators import cyclic
|
| 30 |
+
>>> list(cyclic(5))
|
| 31 |
+
[(4), (0 1 2 3 4), (0 2 4 1 3),
|
| 32 |
+
(0 3 1 4 2), (0 4 3 2 1)]
|
| 33 |
+
|
| 34 |
+
See Also
|
| 35 |
+
========
|
| 36 |
+
|
| 37 |
+
dihedral
|
| 38 |
+
"""
|
| 39 |
+
gen = list(range(n))
|
| 40 |
+
for i in range(n):
|
| 41 |
+
yield Permutation(gen)
|
| 42 |
+
gen = rotate_left(gen, 1)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def alternating(n):
|
| 46 |
+
"""
|
| 47 |
+
Generates the alternating group of order n, An.
|
| 48 |
+
|
| 49 |
+
Examples
|
| 50 |
+
========
|
| 51 |
+
|
| 52 |
+
>>> from sympy.combinatorics.generators import alternating
|
| 53 |
+
>>> list(alternating(3))
|
| 54 |
+
[(2), (0 1 2), (0 2 1)]
|
| 55 |
+
"""
|
| 56 |
+
for perm in variations(range(n), n):
|
| 57 |
+
p = Permutation(perm)
|
| 58 |
+
if p.is_even:
|
| 59 |
+
yield p
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def dihedral(n):
|
| 63 |
+
"""
|
| 64 |
+
Generates the dihedral group of order 2n, Dn.
|
| 65 |
+
|
| 66 |
+
The result is given as a subgroup of Sn, except for the special cases n=1
|
| 67 |
+
(the group S2) and n=2 (the Klein 4-group) where that's not possible
|
| 68 |
+
and embeddings in S2 and S4 respectively are given.
|
| 69 |
+
|
| 70 |
+
Examples
|
| 71 |
+
========
|
| 72 |
+
|
| 73 |
+
>>> from sympy.combinatorics.generators import dihedral
|
| 74 |
+
>>> list(dihedral(3))
|
| 75 |
+
[(2), (0 2), (0 1 2), (1 2), (0 2 1), (2)(0 1)]
|
| 76 |
+
|
| 77 |
+
See Also
|
| 78 |
+
========
|
| 79 |
+
|
| 80 |
+
cyclic
|
| 81 |
+
"""
|
| 82 |
+
if n == 1:
|
| 83 |
+
yield Permutation([0, 1])
|
| 84 |
+
yield Permutation([1, 0])
|
| 85 |
+
elif n == 2:
|
| 86 |
+
yield Permutation([0, 1, 2, 3])
|
| 87 |
+
yield Permutation([1, 0, 3, 2])
|
| 88 |
+
yield Permutation([2, 3, 0, 1])
|
| 89 |
+
yield Permutation([3, 2, 1, 0])
|
| 90 |
+
else:
|
| 91 |
+
gen = list(range(n))
|
| 92 |
+
for i in range(n):
|
| 93 |
+
yield Permutation(gen)
|
| 94 |
+
yield Permutation(gen[::-1])
|
| 95 |
+
gen = rotate_left(gen, 1)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def rubik_cube_generators():
|
| 99 |
+
"""Return the permutations of the 3x3 Rubik's cube, see
|
| 100 |
+
https://www.gap-system.org/Doc/Examples/rubik.html
|
| 101 |
+
"""
|
| 102 |
+
a = [
|
| 103 |
+
[(1, 3, 8, 6), (2, 5, 7, 4), (9, 33, 25, 17), (10, 34, 26, 18),
|
| 104 |
+
(11, 35, 27, 19)],
|
| 105 |
+
[(9, 11, 16, 14), (10, 13, 15, 12), (1, 17, 41, 40), (4, 20, 44, 37),
|
| 106 |
+
(6, 22, 46, 35)],
|
| 107 |
+
[(17, 19, 24, 22), (18, 21, 23, 20), (6, 25, 43, 16), (7, 28, 42, 13),
|
| 108 |
+
(8, 30, 41, 11)],
|
| 109 |
+
[(25, 27, 32, 30), (26, 29, 31, 28), (3, 38, 43, 19), (5, 36, 45, 21),
|
| 110 |
+
(8, 33, 48, 24)],
|
| 111 |
+
[(33, 35, 40, 38), (34, 37, 39, 36), (3, 9, 46, 32), (2, 12, 47, 29),
|
| 112 |
+
(1, 14, 48, 27)],
|
| 113 |
+
[(41, 43, 48, 46), (42, 45, 47, 44), (14, 22, 30, 38),
|
| 114 |
+
(15, 23, 31, 39), (16, 24, 32, 40)]
|
| 115 |
+
]
|
| 116 |
+
return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a]
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def rubik(n):
|
| 120 |
+
"""Return permutations for an nxn Rubik's cube.
|
| 121 |
+
|
| 122 |
+
Permutations returned are for rotation of each of the slice
|
| 123 |
+
from the face up to the last face for each of the 3 sides (in this order):
|
| 124 |
+
front, right and bottom. Hence, the first n - 1 permutations are for the
|
| 125 |
+
slices from the front.
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
if n < 2:
|
| 129 |
+
raise ValueError('dimension of cube must be > 1')
|
| 130 |
+
|
| 131 |
+
# 1-based reference to rows and columns in Matrix
|
| 132 |
+
def getr(f, i):
|
| 133 |
+
return faces[f].col(n - i)
|
| 134 |
+
|
| 135 |
+
def getl(f, i):
|
| 136 |
+
return faces[f].col(i - 1)
|
| 137 |
+
|
| 138 |
+
def getu(f, i):
|
| 139 |
+
return faces[f].row(i - 1)
|
| 140 |
+
|
| 141 |
+
def getd(f, i):
|
| 142 |
+
return faces[f].row(n - i)
|
| 143 |
+
|
| 144 |
+
def setr(f, i, s):
|
| 145 |
+
faces[f][:, n - i] = Matrix(n, 1, s)
|
| 146 |
+
|
| 147 |
+
def setl(f, i, s):
|
| 148 |
+
faces[f][:, i - 1] = Matrix(n, 1, s)
|
| 149 |
+
|
| 150 |
+
def setu(f, i, s):
|
| 151 |
+
faces[f][i - 1, :] = Matrix(1, n, s)
|
| 152 |
+
|
| 153 |
+
def setd(f, i, s):
|
| 154 |
+
faces[f][n - i, :] = Matrix(1, n, s)
|
| 155 |
+
|
| 156 |
+
# motion of a single face
|
| 157 |
+
def cw(F, r=1):
|
| 158 |
+
for _ in range(r):
|
| 159 |
+
face = faces[F]
|
| 160 |
+
rv = []
|
| 161 |
+
for c in range(n):
|
| 162 |
+
for r in range(n - 1, -1, -1):
|
| 163 |
+
rv.append(face[r, c])
|
| 164 |
+
faces[F] = Matrix(n, n, rv)
|
| 165 |
+
|
| 166 |
+
def ccw(F):
|
| 167 |
+
cw(F, 3)
|
| 168 |
+
|
| 169 |
+
# motion of plane i from the F side;
|
| 170 |
+
# fcw(0) moves the F face, fcw(1) moves the plane
|
| 171 |
+
# just behind the front face, etc...
|
| 172 |
+
def fcw(i, r=1):
|
| 173 |
+
for _ in range(r):
|
| 174 |
+
if i == 0:
|
| 175 |
+
cw(F)
|
| 176 |
+
i += 1
|
| 177 |
+
temp = getr(L, i)
|
| 178 |
+
setr(L, i, list(getu(D, i)))
|
| 179 |
+
setu(D, i, list(reversed(getl(R, i))))
|
| 180 |
+
setl(R, i, list(getd(U, i)))
|
| 181 |
+
setd(U, i, list(reversed(temp)))
|
| 182 |
+
i -= 1
|
| 183 |
+
|
| 184 |
+
def fccw(i):
|
| 185 |
+
fcw(i, 3)
|
| 186 |
+
|
| 187 |
+
# motion of the entire cube from the F side
|
| 188 |
+
def FCW(r=1):
|
| 189 |
+
for _ in range(r):
|
| 190 |
+
cw(F)
|
| 191 |
+
ccw(B)
|
| 192 |
+
cw(U)
|
| 193 |
+
t = faces[U]
|
| 194 |
+
cw(L)
|
| 195 |
+
faces[U] = faces[L]
|
| 196 |
+
cw(D)
|
| 197 |
+
faces[L] = faces[D]
|
| 198 |
+
cw(R)
|
| 199 |
+
faces[D] = faces[R]
|
| 200 |
+
faces[R] = t
|
| 201 |
+
|
| 202 |
+
def FCCW():
|
| 203 |
+
FCW(3)
|
| 204 |
+
|
| 205 |
+
# motion of the entire cube from the U side
|
| 206 |
+
def UCW(r=1):
|
| 207 |
+
for _ in range(r):
|
| 208 |
+
cw(U)
|
| 209 |
+
ccw(D)
|
| 210 |
+
t = faces[F]
|
| 211 |
+
faces[F] = faces[R]
|
| 212 |
+
faces[R] = faces[B]
|
| 213 |
+
faces[B] = faces[L]
|
| 214 |
+
faces[L] = t
|
| 215 |
+
|
| 216 |
+
def UCCW():
|
| 217 |
+
UCW(3)
|
| 218 |
+
|
| 219 |
+
# defining the permutations for the cube
|
| 220 |
+
|
| 221 |
+
U, F, R, B, L, D = names = symbols('U, F, R, B, L, D')
|
| 222 |
+
|
| 223 |
+
# the faces are represented by nxn matrices
|
| 224 |
+
faces = {}
|
| 225 |
+
count = 0
|
| 226 |
+
for fi in range(6):
|
| 227 |
+
f = []
|
| 228 |
+
for a in range(n**2):
|
| 229 |
+
f.append(count)
|
| 230 |
+
count += 1
|
| 231 |
+
faces[names[fi]] = Matrix(n, n, f)
|
| 232 |
+
|
| 233 |
+
# this will either return the value of the current permutation
|
| 234 |
+
# (show != 1) or else append the permutation to the group, g
|
| 235 |
+
def perm(show=0):
|
| 236 |
+
# add perm to the list of perms
|
| 237 |
+
p = []
|
| 238 |
+
for f in names:
|
| 239 |
+
p.extend(faces[f])
|
| 240 |
+
if show:
|
| 241 |
+
return p
|
| 242 |
+
g.append(Permutation(p))
|
| 243 |
+
|
| 244 |
+
g = [] # container for the group's permutations
|
| 245 |
+
I = list(range(6*n**2)) # the identity permutation used for checking
|
| 246 |
+
|
| 247 |
+
# define permutations corresponding to cw rotations of the planes
|
| 248 |
+
# up TO the last plane from that direction; by not including the
|
| 249 |
+
# last plane, the orientation of the cube is maintained.
|
| 250 |
+
|
| 251 |
+
# F slices
|
| 252 |
+
for i in range(n - 1):
|
| 253 |
+
fcw(i)
|
| 254 |
+
perm()
|
| 255 |
+
fccw(i) # restore
|
| 256 |
+
assert perm(1) == I
|
| 257 |
+
|
| 258 |
+
# R slices
|
| 259 |
+
# bring R to front
|
| 260 |
+
UCW()
|
| 261 |
+
for i in range(n - 1):
|
| 262 |
+
fcw(i)
|
| 263 |
+
# put it back in place
|
| 264 |
+
UCCW()
|
| 265 |
+
# record
|
| 266 |
+
perm()
|
| 267 |
+
# restore
|
| 268 |
+
# bring face to front
|
| 269 |
+
UCW()
|
| 270 |
+
fccw(i)
|
| 271 |
+
# restore
|
| 272 |
+
UCCW()
|
| 273 |
+
assert perm(1) == I
|
| 274 |
+
|
| 275 |
+
# D slices
|
| 276 |
+
# bring up bottom
|
| 277 |
+
FCW()
|
| 278 |
+
UCCW()
|
| 279 |
+
FCCW()
|
| 280 |
+
for i in range(n - 1):
|
| 281 |
+
# turn strip
|
| 282 |
+
fcw(i)
|
| 283 |
+
# put bottom back on the bottom
|
| 284 |
+
FCW()
|
| 285 |
+
UCW()
|
| 286 |
+
FCCW()
|
| 287 |
+
# record
|
| 288 |
+
perm()
|
| 289 |
+
# restore
|
| 290 |
+
# bring up bottom
|
| 291 |
+
FCW()
|
| 292 |
+
UCCW()
|
| 293 |
+
FCCW()
|
| 294 |
+
# turn strip
|
| 295 |
+
fccw(i)
|
| 296 |
+
# put bottom back on the bottom
|
| 297 |
+
FCW()
|
| 298 |
+
UCW()
|
| 299 |
+
FCCW()
|
| 300 |
+
assert perm(1) == I
|
| 301 |
+
|
| 302 |
+
return g
|
lib/python3.12/site-packages/sympy/combinatorics/graycode.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core import Basic, Integer
|
| 2 |
+
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class GrayCode(Basic):
|
| 7 |
+
"""
|
| 8 |
+
A Gray code is essentially a Hamiltonian walk on
|
| 9 |
+
a n-dimensional cube with edge length of one.
|
| 10 |
+
The vertices of the cube are represented by vectors
|
| 11 |
+
whose values are binary. The Hamilton walk visits
|
| 12 |
+
each vertex exactly once. The Gray code for a 3d
|
| 13 |
+
cube is ['000','100','110','010','011','111','101',
|
| 14 |
+
'001'].
|
| 15 |
+
|
| 16 |
+
A Gray code solves the problem of sequentially
|
| 17 |
+
generating all possible subsets of n objects in such
|
| 18 |
+
a way that each subset is obtained from the previous
|
| 19 |
+
one by either deleting or adding a single object.
|
| 20 |
+
In the above example, 1 indicates that the object is
|
| 21 |
+
present, and 0 indicates that its absent.
|
| 22 |
+
|
| 23 |
+
Gray codes have applications in statistics as well when
|
| 24 |
+
we want to compute various statistics related to subsets
|
| 25 |
+
in an efficient manner.
|
| 26 |
+
|
| 27 |
+
Examples
|
| 28 |
+
========
|
| 29 |
+
|
| 30 |
+
>>> from sympy.combinatorics import GrayCode
|
| 31 |
+
>>> a = GrayCode(3)
|
| 32 |
+
>>> list(a.generate_gray())
|
| 33 |
+
['000', '001', '011', '010', '110', '111', '101', '100']
|
| 34 |
+
>>> a = GrayCode(4)
|
| 35 |
+
>>> list(a.generate_gray())
|
| 36 |
+
['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \
|
| 37 |
+
'1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000']
|
| 38 |
+
|
| 39 |
+
References
|
| 40 |
+
==========
|
| 41 |
+
|
| 42 |
+
.. [1] Nijenhuis,A. and Wilf,H.S.(1978).
|
| 43 |
+
Combinatorial Algorithms. Academic Press.
|
| 44 |
+
.. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4
|
| 45 |
+
Addison Wesley
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
_skip = False
|
| 51 |
+
_current = 0
|
| 52 |
+
_rank = None
|
| 53 |
+
|
| 54 |
+
def __new__(cls, n, *args, **kw_args):
|
| 55 |
+
"""
|
| 56 |
+
Default constructor.
|
| 57 |
+
|
| 58 |
+
It takes a single argument ``n`` which gives the dimension of the Gray
|
| 59 |
+
code. The starting Gray code string (``start``) or the starting ``rank``
|
| 60 |
+
may also be given; the default is to start at rank = 0 ('0...0').
|
| 61 |
+
|
| 62 |
+
Examples
|
| 63 |
+
========
|
| 64 |
+
|
| 65 |
+
>>> from sympy.combinatorics import GrayCode
|
| 66 |
+
>>> a = GrayCode(3)
|
| 67 |
+
>>> a
|
| 68 |
+
GrayCode(3)
|
| 69 |
+
>>> a.n
|
| 70 |
+
3
|
| 71 |
+
|
| 72 |
+
>>> a = GrayCode(3, start='100')
|
| 73 |
+
>>> a.current
|
| 74 |
+
'100'
|
| 75 |
+
|
| 76 |
+
>>> a = GrayCode(4, rank=4)
|
| 77 |
+
>>> a.current
|
| 78 |
+
'0110'
|
| 79 |
+
>>> a.rank
|
| 80 |
+
4
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
if n < 1 or int(n) != n:
|
| 84 |
+
raise ValueError(
|
| 85 |
+
'Gray code dimension must be a positive integer, not %i' % n)
|
| 86 |
+
n = Integer(n)
|
| 87 |
+
args = (n,) + args
|
| 88 |
+
obj = Basic.__new__(cls, *args)
|
| 89 |
+
if 'start' in kw_args:
|
| 90 |
+
obj._current = kw_args["start"]
|
| 91 |
+
if len(obj._current) > n:
|
| 92 |
+
raise ValueError('Gray code start has length %i but '
|
| 93 |
+
'should not be greater than %i' % (len(obj._current), n))
|
| 94 |
+
elif 'rank' in kw_args:
|
| 95 |
+
if int(kw_args["rank"]) != kw_args["rank"]:
|
| 96 |
+
raise ValueError('Gray code rank must be a positive integer, '
|
| 97 |
+
'not %i' % kw_args["rank"])
|
| 98 |
+
obj._rank = int(kw_args["rank"]) % obj.selections
|
| 99 |
+
obj._current = obj.unrank(n, obj._rank)
|
| 100 |
+
return obj
|
| 101 |
+
|
| 102 |
+
def next(self, delta=1):
|
| 103 |
+
"""
|
| 104 |
+
Returns the Gray code a distance ``delta`` (default = 1) from the
|
| 105 |
+
current value in canonical order.
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
Examples
|
| 109 |
+
========
|
| 110 |
+
|
| 111 |
+
>>> from sympy.combinatorics import GrayCode
|
| 112 |
+
>>> a = GrayCode(3, start='110')
|
| 113 |
+
>>> a.next().current
|
| 114 |
+
'111'
|
| 115 |
+
>>> a.next(-1).current
|
| 116 |
+
'010'
|
| 117 |
+
"""
|
| 118 |
+
return GrayCode(self.n, rank=(self.rank + delta) % self.selections)
|
| 119 |
+
|
| 120 |
+
@property
|
| 121 |
+
def selections(self):
|
| 122 |
+
"""
|
| 123 |
+
Returns the number of bit vectors in the Gray code.
|
| 124 |
+
|
| 125 |
+
Examples
|
| 126 |
+
========
|
| 127 |
+
|
| 128 |
+
>>> from sympy.combinatorics import GrayCode
|
| 129 |
+
>>> a = GrayCode(3)
|
| 130 |
+
>>> a.selections
|
| 131 |
+
8
|
| 132 |
+
"""
|
| 133 |
+
return 2**self.n
|
| 134 |
+
|
| 135 |
+
@property
|
| 136 |
+
def n(self):
|
| 137 |
+
"""
|
| 138 |
+
Returns the dimension of the Gray code.
|
| 139 |
+
|
| 140 |
+
Examples
|
| 141 |
+
========
|
| 142 |
+
|
| 143 |
+
>>> from sympy.combinatorics import GrayCode
|
| 144 |
+
>>> a = GrayCode(5)
|
| 145 |
+
>>> a.n
|
| 146 |
+
5
|
| 147 |
+
"""
|
| 148 |
+
return self.args[0]
|
| 149 |
+
|
| 150 |
+
def generate_gray(self, **hints):
|
| 151 |
+
"""
|
| 152 |
+
Generates the sequence of bit vectors of a Gray Code.
|
| 153 |
+
|
| 154 |
+
Examples
|
| 155 |
+
========
|
| 156 |
+
|
| 157 |
+
>>> from sympy.combinatorics import GrayCode
|
| 158 |
+
>>> a = GrayCode(3)
|
| 159 |
+
>>> list(a.generate_gray())
|
| 160 |
+
['000', '001', '011', '010', '110', '111', '101', '100']
|
| 161 |
+
>>> list(a.generate_gray(start='011'))
|
| 162 |
+
['011', '010', '110', '111', '101', '100']
|
| 163 |
+
>>> list(a.generate_gray(rank=4))
|
| 164 |
+
['110', '111', '101', '100']
|
| 165 |
+
|
| 166 |
+
See Also
|
| 167 |
+
========
|
| 168 |
+
|
| 169 |
+
skip
|
| 170 |
+
|
| 171 |
+
References
|
| 172 |
+
==========
|
| 173 |
+
|
| 174 |
+
.. [1] Knuth, D. (2011). The Art of Computer Programming,
|
| 175 |
+
Vol 4, Addison Wesley
|
| 176 |
+
|
| 177 |
+
"""
|
| 178 |
+
bits = self.n
|
| 179 |
+
start = None
|
| 180 |
+
if "start" in hints:
|
| 181 |
+
start = hints["start"]
|
| 182 |
+
elif "rank" in hints:
|
| 183 |
+
start = GrayCode.unrank(self.n, hints["rank"])
|
| 184 |
+
if start is not None:
|
| 185 |
+
self._current = start
|
| 186 |
+
current = self.current
|
| 187 |
+
graycode_bin = gray_to_bin(current)
|
| 188 |
+
if len(graycode_bin) > self.n:
|
| 189 |
+
raise ValueError('Gray code start has length %i but should '
|
| 190 |
+
'not be greater than %i' % (len(graycode_bin), bits))
|
| 191 |
+
self._current = int(current, 2)
|
| 192 |
+
graycode_int = int(''.join(graycode_bin), 2)
|
| 193 |
+
for i in range(graycode_int, 1 << bits):
|
| 194 |
+
if self._skip:
|
| 195 |
+
self._skip = False
|
| 196 |
+
else:
|
| 197 |
+
yield self.current
|
| 198 |
+
bbtc = (i ^ (i + 1))
|
| 199 |
+
gbtc = (bbtc ^ (bbtc >> 1))
|
| 200 |
+
self._current = (self._current ^ gbtc)
|
| 201 |
+
self._current = 0
|
| 202 |
+
|
| 203 |
+
def skip(self):
|
| 204 |
+
"""
|
| 205 |
+
Skips the bit generation.
|
| 206 |
+
|
| 207 |
+
Examples
|
| 208 |
+
========
|
| 209 |
+
|
| 210 |
+
>>> from sympy.combinatorics import GrayCode
|
| 211 |
+
>>> a = GrayCode(3)
|
| 212 |
+
>>> for i in a.generate_gray():
|
| 213 |
+
... if i == '010':
|
| 214 |
+
... a.skip()
|
| 215 |
+
... print(i)
|
| 216 |
+
...
|
| 217 |
+
000
|
| 218 |
+
001
|
| 219 |
+
011
|
| 220 |
+
010
|
| 221 |
+
111
|
| 222 |
+
101
|
| 223 |
+
100
|
| 224 |
+
|
| 225 |
+
See Also
|
| 226 |
+
========
|
| 227 |
+
|
| 228 |
+
generate_gray
|
| 229 |
+
"""
|
| 230 |
+
self._skip = True
|
| 231 |
+
|
| 232 |
+
@property
|
| 233 |
+
def rank(self):
|
| 234 |
+
"""
|
| 235 |
+
Ranks the Gray code.
|
| 236 |
+
|
| 237 |
+
A ranking algorithm determines the position (or rank)
|
| 238 |
+
of a combinatorial object among all the objects w.r.t.
|
| 239 |
+
a given order. For example, the 4 bit binary reflected
|
| 240 |
+
Gray code (BRGC) '0101' has a rank of 6 as it appears in
|
| 241 |
+
the 6th position in the canonical ordering of the family
|
| 242 |
+
of 4 bit Gray codes.
|
| 243 |
+
|
| 244 |
+
Examples
|
| 245 |
+
========
|
| 246 |
+
|
| 247 |
+
>>> from sympy.combinatorics import GrayCode
|
| 248 |
+
>>> a = GrayCode(3)
|
| 249 |
+
>>> list(a.generate_gray())
|
| 250 |
+
['000', '001', '011', '010', '110', '111', '101', '100']
|
| 251 |
+
>>> GrayCode(3, start='100').rank
|
| 252 |
+
7
|
| 253 |
+
>>> GrayCode(3, rank=7).current
|
| 254 |
+
'100'
|
| 255 |
+
|
| 256 |
+
See Also
|
| 257 |
+
========
|
| 258 |
+
|
| 259 |
+
unrank
|
| 260 |
+
|
| 261 |
+
References
|
| 262 |
+
==========
|
| 263 |
+
|
| 264 |
+
.. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html
|
| 265 |
+
|
| 266 |
+
"""
|
| 267 |
+
if self._rank is None:
|
| 268 |
+
self._rank = int(gray_to_bin(self.current), 2)
|
| 269 |
+
return self._rank
|
| 270 |
+
|
| 271 |
+
@property
|
| 272 |
+
def current(self):
|
| 273 |
+
"""
|
| 274 |
+
Returns the currently referenced Gray code as a bit string.
|
| 275 |
+
|
| 276 |
+
Examples
|
| 277 |
+
========
|
| 278 |
+
|
| 279 |
+
>>> from sympy.combinatorics import GrayCode
|
| 280 |
+
>>> GrayCode(3, start='100').current
|
| 281 |
+
'100'
|
| 282 |
+
"""
|
| 283 |
+
rv = self._current or '0'
|
| 284 |
+
if not isinstance(rv, str):
|
| 285 |
+
rv = bin(rv)[2:]
|
| 286 |
+
return rv.rjust(self.n, '0')
|
| 287 |
+
|
| 288 |
+
@classmethod
|
| 289 |
+
def unrank(self, n, rank):
|
| 290 |
+
"""
|
| 291 |
+
Unranks an n-bit sized Gray code of rank k. This method exists
|
| 292 |
+
so that a derivative GrayCode class can define its own code of
|
| 293 |
+
a given rank.
|
| 294 |
+
|
| 295 |
+
The string here is generated in reverse order to allow for tail-call
|
| 296 |
+
optimization.
|
| 297 |
+
|
| 298 |
+
Examples
|
| 299 |
+
========
|
| 300 |
+
|
| 301 |
+
>>> from sympy.combinatorics import GrayCode
|
| 302 |
+
>>> GrayCode(5, rank=3).current
|
| 303 |
+
'00010'
|
| 304 |
+
>>> GrayCode.unrank(5, 3)
|
| 305 |
+
'00010'
|
| 306 |
+
|
| 307 |
+
See Also
|
| 308 |
+
========
|
| 309 |
+
|
| 310 |
+
rank
|
| 311 |
+
"""
|
| 312 |
+
def _unrank(k, n):
|
| 313 |
+
if n == 1:
|
| 314 |
+
return str(k % 2)
|
| 315 |
+
m = 2**(n - 1)
|
| 316 |
+
if k < m:
|
| 317 |
+
return '0' + _unrank(k, n - 1)
|
| 318 |
+
return '1' + _unrank(m - (k % m) - 1, n - 1)
|
| 319 |
+
return _unrank(rank, n)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def random_bitstring(n):
|
| 323 |
+
"""
|
| 324 |
+
Generates a random bitlist of length n.
|
| 325 |
+
|
| 326 |
+
Examples
|
| 327 |
+
========
|
| 328 |
+
|
| 329 |
+
>>> from sympy.combinatorics.graycode import random_bitstring
|
| 330 |
+
>>> random_bitstring(3) # doctest: +SKIP
|
| 331 |
+
100
|
| 332 |
+
"""
|
| 333 |
+
return ''.join([random.choice('01') for i in range(n)])
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def gray_to_bin(bin_list):
|
| 337 |
+
"""
|
| 338 |
+
Convert from Gray coding to binary coding.
|
| 339 |
+
|
| 340 |
+
We assume big endian encoding.
|
| 341 |
+
|
| 342 |
+
Examples
|
| 343 |
+
========
|
| 344 |
+
|
| 345 |
+
>>> from sympy.combinatorics.graycode import gray_to_bin
|
| 346 |
+
>>> gray_to_bin('100')
|
| 347 |
+
'111'
|
| 348 |
+
|
| 349 |
+
See Also
|
| 350 |
+
========
|
| 351 |
+
|
| 352 |
+
bin_to_gray
|
| 353 |
+
"""
|
| 354 |
+
b = [bin_list[0]]
|
| 355 |
+
for i in range(1, len(bin_list)):
|
| 356 |
+
b += str(int(b[i - 1] != bin_list[i]))
|
| 357 |
+
return ''.join(b)
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def bin_to_gray(bin_list):
|
| 361 |
+
"""
|
| 362 |
+
Convert from binary coding to gray coding.
|
| 363 |
+
|
| 364 |
+
We assume big endian encoding.
|
| 365 |
+
|
| 366 |
+
Examples
|
| 367 |
+
========
|
| 368 |
+
|
| 369 |
+
>>> from sympy.combinatorics.graycode import bin_to_gray
|
| 370 |
+
>>> bin_to_gray('111')
|
| 371 |
+
'100'
|
| 372 |
+
|
| 373 |
+
See Also
|
| 374 |
+
========
|
| 375 |
+
|
| 376 |
+
gray_to_bin
|
| 377 |
+
"""
|
| 378 |
+
b = [bin_list[0]]
|
| 379 |
+
for i in range(1, len(bin_list)):
|
| 380 |
+
b += str(int(bin_list[i]) ^ int(bin_list[i - 1]))
|
| 381 |
+
return ''.join(b)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def get_subset_from_bitstring(super_set, bitstring):
|
| 385 |
+
"""
|
| 386 |
+
Gets the subset defined by the bitstring.
|
| 387 |
+
|
| 388 |
+
Examples
|
| 389 |
+
========
|
| 390 |
+
|
| 391 |
+
>>> from sympy.combinatorics.graycode import get_subset_from_bitstring
|
| 392 |
+
>>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011')
|
| 393 |
+
['c', 'd']
|
| 394 |
+
>>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100')
|
| 395 |
+
['c', 'a']
|
| 396 |
+
|
| 397 |
+
See Also
|
| 398 |
+
========
|
| 399 |
+
|
| 400 |
+
graycode_subsets
|
| 401 |
+
"""
|
| 402 |
+
if len(super_set) != len(bitstring):
|
| 403 |
+
raise ValueError("The sizes of the lists are not equal")
|
| 404 |
+
return [super_set[i] for i, j in enumerate(bitstring)
|
| 405 |
+
if bitstring[i] == '1']
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def graycode_subsets(gray_code_set):
|
| 409 |
+
"""
|
| 410 |
+
Generates the subsets as enumerated by a Gray code.
|
| 411 |
+
|
| 412 |
+
Examples
|
| 413 |
+
========
|
| 414 |
+
|
| 415 |
+
>>> from sympy.combinatorics.graycode import graycode_subsets
|
| 416 |
+
>>> list(graycode_subsets(['a', 'b', 'c']))
|
| 417 |
+
[[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \
|
| 418 |
+
['a', 'c'], ['a']]
|
| 419 |
+
>>> list(graycode_subsets(['a', 'b', 'c', 'c']))
|
| 420 |
+
[[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \
|
| 421 |
+
['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \
|
| 422 |
+
['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']]
|
| 423 |
+
|
| 424 |
+
See Also
|
| 425 |
+
========
|
| 426 |
+
|
| 427 |
+
get_subset_from_bitstring
|
| 428 |
+
"""
|
| 429 |
+
for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()):
|
| 430 |
+
yield get_subset_from_bitstring(gray_code_set, bitstring)
|
lib/python3.12/site-packages/sympy/combinatorics/group_constructs.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 2 |
+
from sympy.combinatorics.permutations import Permutation
|
| 3 |
+
from sympy.utilities.iterables import uniq
|
| 4 |
+
|
| 5 |
+
_af_new = Permutation._af_new
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def DirectProduct(*groups):
|
| 9 |
+
"""
|
| 10 |
+
Returns the direct product of several groups as a permutation group.
|
| 11 |
+
|
| 12 |
+
Explanation
|
| 13 |
+
===========
|
| 14 |
+
|
| 15 |
+
This is implemented much like the __mul__ procedure for taking the direct
|
| 16 |
+
product of two permutation groups, but the idea of shifting the
|
| 17 |
+
generators is realized in the case of an arbitrary number of groups.
|
| 18 |
+
A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster
|
| 19 |
+
than a call to G1*G2*...*Gn (and thus the need for this algorithm).
|
| 20 |
+
|
| 21 |
+
Examples
|
| 22 |
+
========
|
| 23 |
+
|
| 24 |
+
>>> from sympy.combinatorics.group_constructs import DirectProduct
|
| 25 |
+
>>> from sympy.combinatorics.named_groups import CyclicGroup
|
| 26 |
+
>>> C = CyclicGroup(4)
|
| 27 |
+
>>> G = DirectProduct(C, C, C)
|
| 28 |
+
>>> G.order()
|
| 29 |
+
64
|
| 30 |
+
|
| 31 |
+
See Also
|
| 32 |
+
========
|
| 33 |
+
|
| 34 |
+
sympy.combinatorics.perm_groups.PermutationGroup.__mul__
|
| 35 |
+
|
| 36 |
+
"""
|
| 37 |
+
degrees = []
|
| 38 |
+
gens_count = []
|
| 39 |
+
total_degree = 0
|
| 40 |
+
total_gens = 0
|
| 41 |
+
for group in groups:
|
| 42 |
+
current_deg = group.degree
|
| 43 |
+
current_num_gens = len(group.generators)
|
| 44 |
+
degrees.append(current_deg)
|
| 45 |
+
total_degree += current_deg
|
| 46 |
+
gens_count.append(current_num_gens)
|
| 47 |
+
total_gens += current_num_gens
|
| 48 |
+
array_gens = []
|
| 49 |
+
for i in range(total_gens):
|
| 50 |
+
array_gens.append(list(range(total_degree)))
|
| 51 |
+
current_gen = 0
|
| 52 |
+
current_deg = 0
|
| 53 |
+
for i in range(len(gens_count)):
|
| 54 |
+
for j in range(current_gen, current_gen + gens_count[i]):
|
| 55 |
+
gen = ((groups[i].generators)[j - current_gen]).array_form
|
| 56 |
+
array_gens[j][current_deg:current_deg + degrees[i]] = \
|
| 57 |
+
[x + current_deg for x in gen]
|
| 58 |
+
current_gen += gens_count[i]
|
| 59 |
+
current_deg += degrees[i]
|
| 60 |
+
perm_gens = list(uniq([_af_new(list(a)) for a in array_gens]))
|
| 61 |
+
return PermutationGroup(perm_gens, dups=False)
|
lib/python3.12/site-packages/sympy/combinatorics/homomorphisms.py
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import itertools
|
| 2 |
+
from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation
|
| 3 |
+
from sympy.combinatorics.free_groups import FreeGroup
|
| 4 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 5 |
+
from sympy.core.intfunc import igcd
|
| 6 |
+
from sympy.functions.combinatorial.numbers import totient
|
| 7 |
+
from sympy.core.singleton import S
|
| 8 |
+
|
| 9 |
+
class GroupHomomorphism:
|
| 10 |
+
'''
|
| 11 |
+
A class representing group homomorphisms. Instantiate using `homomorphism()`.
|
| 12 |
+
|
| 13 |
+
References
|
| 14 |
+
==========
|
| 15 |
+
|
| 16 |
+
.. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory.
|
| 17 |
+
|
| 18 |
+
'''
|
| 19 |
+
|
| 20 |
+
def __init__(self, domain, codomain, images):
|
| 21 |
+
self.domain = domain
|
| 22 |
+
self.codomain = codomain
|
| 23 |
+
self.images = images
|
| 24 |
+
self._inverses = None
|
| 25 |
+
self._kernel = None
|
| 26 |
+
self._image = None
|
| 27 |
+
|
| 28 |
+
def _invs(self):
|
| 29 |
+
'''
|
| 30 |
+
Return a dictionary with `{gen: inverse}` where `gen` is a rewriting
|
| 31 |
+
generator of `codomain` (e.g. strong generator for permutation groups)
|
| 32 |
+
and `inverse` is an element of its preimage
|
| 33 |
+
|
| 34 |
+
'''
|
| 35 |
+
image = self.image()
|
| 36 |
+
inverses = {}
|
| 37 |
+
for k in list(self.images.keys()):
|
| 38 |
+
v = self.images[k]
|
| 39 |
+
if not (v in inverses
|
| 40 |
+
or v.is_identity):
|
| 41 |
+
inverses[v] = k
|
| 42 |
+
if isinstance(self.codomain, PermutationGroup):
|
| 43 |
+
gens = image.strong_gens
|
| 44 |
+
else:
|
| 45 |
+
gens = image.generators
|
| 46 |
+
for g in gens:
|
| 47 |
+
if g in inverses or g.is_identity:
|
| 48 |
+
continue
|
| 49 |
+
w = self.domain.identity
|
| 50 |
+
if isinstance(self.codomain, PermutationGroup):
|
| 51 |
+
parts = image._strong_gens_slp[g][::-1]
|
| 52 |
+
else:
|
| 53 |
+
parts = g
|
| 54 |
+
for s in parts:
|
| 55 |
+
if s in inverses:
|
| 56 |
+
w = w*inverses[s]
|
| 57 |
+
else:
|
| 58 |
+
w = w*inverses[s**-1]**-1
|
| 59 |
+
inverses[g] = w
|
| 60 |
+
|
| 61 |
+
return inverses
|
| 62 |
+
|
| 63 |
+
def invert(self, g):
|
| 64 |
+
'''
|
| 65 |
+
Return an element of the preimage of ``g`` or of each element
|
| 66 |
+
of ``g`` if ``g`` is a list.
|
| 67 |
+
|
| 68 |
+
Explanation
|
| 69 |
+
===========
|
| 70 |
+
|
| 71 |
+
If the codomain is an FpGroup, the inverse for equal
|
| 72 |
+
elements might not always be the same unless the FpGroup's
|
| 73 |
+
rewriting system is confluent. However, making a system
|
| 74 |
+
confluent can be time-consuming. If it's important, try
|
| 75 |
+
`self.codomain.make_confluent()` first.
|
| 76 |
+
|
| 77 |
+
'''
|
| 78 |
+
from sympy.combinatorics import Permutation
|
| 79 |
+
from sympy.combinatorics.free_groups import FreeGroupElement
|
| 80 |
+
if isinstance(g, (Permutation, FreeGroupElement)):
|
| 81 |
+
if isinstance(self.codomain, FpGroup):
|
| 82 |
+
g = self.codomain.reduce(g)
|
| 83 |
+
if self._inverses is None:
|
| 84 |
+
self._inverses = self._invs()
|
| 85 |
+
image = self.image()
|
| 86 |
+
w = self.domain.identity
|
| 87 |
+
if isinstance(self.codomain, PermutationGroup):
|
| 88 |
+
gens = image.generator_product(g)[::-1]
|
| 89 |
+
else:
|
| 90 |
+
gens = g
|
| 91 |
+
# the following can't be "for s in gens:"
|
| 92 |
+
# because that would be equivalent to
|
| 93 |
+
# "for s in gens.array_form:" when g is
|
| 94 |
+
# a FreeGroupElement. On the other hand,
|
| 95 |
+
# when you call gens by index, the generator
|
| 96 |
+
# (or inverse) at position i is returned.
|
| 97 |
+
for i in range(len(gens)):
|
| 98 |
+
s = gens[i]
|
| 99 |
+
if s.is_identity:
|
| 100 |
+
continue
|
| 101 |
+
if s in self._inverses:
|
| 102 |
+
w = w*self._inverses[s]
|
| 103 |
+
else:
|
| 104 |
+
w = w*self._inverses[s**-1]**-1
|
| 105 |
+
return w
|
| 106 |
+
elif isinstance(g, list):
|
| 107 |
+
return [self.invert(e) for e in g]
|
| 108 |
+
|
| 109 |
+
def kernel(self):
|
| 110 |
+
'''
|
| 111 |
+
Compute the kernel of `self`.
|
| 112 |
+
|
| 113 |
+
'''
|
| 114 |
+
if self._kernel is None:
|
| 115 |
+
self._kernel = self._compute_kernel()
|
| 116 |
+
return self._kernel
|
| 117 |
+
|
| 118 |
+
def _compute_kernel(self):
|
| 119 |
+
G = self.domain
|
| 120 |
+
G_order = G.order()
|
| 121 |
+
if G_order is S.Infinity:
|
| 122 |
+
raise NotImplementedError(
|
| 123 |
+
"Kernel computation is not implemented for infinite groups")
|
| 124 |
+
gens = []
|
| 125 |
+
if isinstance(G, PermutationGroup):
|
| 126 |
+
K = PermutationGroup(G.identity)
|
| 127 |
+
else:
|
| 128 |
+
K = FpSubgroup(G, gens, normal=True)
|
| 129 |
+
i = self.image().order()
|
| 130 |
+
while K.order()*i != G_order:
|
| 131 |
+
r = G.random()
|
| 132 |
+
k = r*self.invert(self(r))**-1
|
| 133 |
+
if k not in K:
|
| 134 |
+
gens.append(k)
|
| 135 |
+
if isinstance(G, PermutationGroup):
|
| 136 |
+
K = PermutationGroup(gens)
|
| 137 |
+
else:
|
| 138 |
+
K = FpSubgroup(G, gens, normal=True)
|
| 139 |
+
return K
|
| 140 |
+
|
| 141 |
+
def image(self):
|
| 142 |
+
'''
|
| 143 |
+
Compute the image of `self`.
|
| 144 |
+
|
| 145 |
+
'''
|
| 146 |
+
if self._image is None:
|
| 147 |
+
values = list(set(self.images.values()))
|
| 148 |
+
if isinstance(self.codomain, PermutationGroup):
|
| 149 |
+
self._image = self.codomain.subgroup(values)
|
| 150 |
+
else:
|
| 151 |
+
self._image = FpSubgroup(self.codomain, values)
|
| 152 |
+
return self._image
|
| 153 |
+
|
| 154 |
+
def _apply(self, elem):
|
| 155 |
+
'''
|
| 156 |
+
Apply `self` to `elem`.
|
| 157 |
+
|
| 158 |
+
'''
|
| 159 |
+
if elem not in self.domain:
|
| 160 |
+
if isinstance(elem, (list, tuple)):
|
| 161 |
+
return [self._apply(e) for e in elem]
|
| 162 |
+
raise ValueError("The supplied element does not belong to the domain")
|
| 163 |
+
if elem.is_identity:
|
| 164 |
+
return self.codomain.identity
|
| 165 |
+
else:
|
| 166 |
+
images = self.images
|
| 167 |
+
value = self.codomain.identity
|
| 168 |
+
if isinstance(self.domain, PermutationGroup):
|
| 169 |
+
gens = self.domain.generator_product(elem, original=True)
|
| 170 |
+
for g in gens:
|
| 171 |
+
if g in self.images:
|
| 172 |
+
value = images[g]*value
|
| 173 |
+
else:
|
| 174 |
+
value = images[g**-1]**-1*value
|
| 175 |
+
else:
|
| 176 |
+
i = 0
|
| 177 |
+
for _, p in elem.array_form:
|
| 178 |
+
if p < 0:
|
| 179 |
+
g = elem[i]**-1
|
| 180 |
+
else:
|
| 181 |
+
g = elem[i]
|
| 182 |
+
value = value*images[g]**p
|
| 183 |
+
i += abs(p)
|
| 184 |
+
return value
|
| 185 |
+
|
| 186 |
+
def __call__(self, elem):
|
| 187 |
+
return self._apply(elem)
|
| 188 |
+
|
| 189 |
+
def is_injective(self):
|
| 190 |
+
'''
|
| 191 |
+
Check if the homomorphism is injective
|
| 192 |
+
|
| 193 |
+
'''
|
| 194 |
+
return self.kernel().order() == 1
|
| 195 |
+
|
| 196 |
+
def is_surjective(self):
|
| 197 |
+
'''
|
| 198 |
+
Check if the homomorphism is surjective
|
| 199 |
+
|
| 200 |
+
'''
|
| 201 |
+
im = self.image().order()
|
| 202 |
+
oth = self.codomain.order()
|
| 203 |
+
if im is S.Infinity and oth is S.Infinity:
|
| 204 |
+
return None
|
| 205 |
+
else:
|
| 206 |
+
return im == oth
|
| 207 |
+
|
| 208 |
+
def is_isomorphism(self):
|
| 209 |
+
'''
|
| 210 |
+
Check if `self` is an isomorphism.
|
| 211 |
+
|
| 212 |
+
'''
|
| 213 |
+
return self.is_injective() and self.is_surjective()
|
| 214 |
+
|
| 215 |
+
def is_trivial(self):
|
| 216 |
+
'''
|
| 217 |
+
Check is `self` is a trivial homomorphism, i.e. all elements
|
| 218 |
+
are mapped to the identity.
|
| 219 |
+
|
| 220 |
+
'''
|
| 221 |
+
return self.image().order() == 1
|
| 222 |
+
|
| 223 |
+
def compose(self, other):
|
| 224 |
+
'''
|
| 225 |
+
Return the composition of `self` and `other`, i.e.
|
| 226 |
+
the homomorphism phi such that for all g in the domain
|
| 227 |
+
of `other`, phi(g) = self(other(g))
|
| 228 |
+
|
| 229 |
+
'''
|
| 230 |
+
if not other.image().is_subgroup(self.domain):
|
| 231 |
+
raise ValueError("The image of `other` must be a subgroup of "
|
| 232 |
+
"the domain of `self`")
|
| 233 |
+
images = {g: self(other(g)) for g in other.images}
|
| 234 |
+
return GroupHomomorphism(other.domain, self.codomain, images)
|
| 235 |
+
|
| 236 |
+
def restrict_to(self, H):
|
| 237 |
+
'''
|
| 238 |
+
Return the restriction of the homomorphism to the subgroup `H`
|
| 239 |
+
of the domain.
|
| 240 |
+
|
| 241 |
+
'''
|
| 242 |
+
if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain):
|
| 243 |
+
raise ValueError("Given H is not a subgroup of the domain")
|
| 244 |
+
domain = H
|
| 245 |
+
images = {g: self(g) for g in H.generators}
|
| 246 |
+
return GroupHomomorphism(domain, self.codomain, images)
|
| 247 |
+
|
| 248 |
+
def invert_subgroup(self, H):
|
| 249 |
+
'''
|
| 250 |
+
Return the subgroup of the domain that is the inverse image
|
| 251 |
+
of the subgroup ``H`` of the homomorphism image
|
| 252 |
+
|
| 253 |
+
'''
|
| 254 |
+
if not H.is_subgroup(self.image()):
|
| 255 |
+
raise ValueError("Given H is not a subgroup of the image")
|
| 256 |
+
gens = []
|
| 257 |
+
P = PermutationGroup(self.image().identity)
|
| 258 |
+
for h in H.generators:
|
| 259 |
+
h_i = self.invert(h)
|
| 260 |
+
if h_i not in P:
|
| 261 |
+
gens.append(h_i)
|
| 262 |
+
P = PermutationGroup(gens)
|
| 263 |
+
for k in self.kernel().generators:
|
| 264 |
+
if k*h_i not in P:
|
| 265 |
+
gens.append(k*h_i)
|
| 266 |
+
P = PermutationGroup(gens)
|
| 267 |
+
return P
|
| 268 |
+
|
| 269 |
+
def homomorphism(domain, codomain, gens, images=(), check=True):
|
| 270 |
+
'''
|
| 271 |
+
Create (if possible) a group homomorphism from the group ``domain``
|
| 272 |
+
to the group ``codomain`` defined by the images of the domain's
|
| 273 |
+
generators ``gens``. ``gens`` and ``images`` can be either lists or tuples
|
| 274 |
+
of equal sizes. If ``gens`` is a proper subset of the group's generators,
|
| 275 |
+
the unspecified generators will be mapped to the identity. If the
|
| 276 |
+
images are not specified, a trivial homomorphism will be created.
|
| 277 |
+
|
| 278 |
+
If the given images of the generators do not define a homomorphism,
|
| 279 |
+
an exception is raised.
|
| 280 |
+
|
| 281 |
+
If ``check`` is ``False``, do not check whether the given images actually
|
| 282 |
+
define a homomorphism.
|
| 283 |
+
|
| 284 |
+
'''
|
| 285 |
+
if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
|
| 286 |
+
raise TypeError("The domain must be a group")
|
| 287 |
+
if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
|
| 288 |
+
raise TypeError("The codomain must be a group")
|
| 289 |
+
|
| 290 |
+
generators = domain.generators
|
| 291 |
+
if not all(g in generators for g in gens):
|
| 292 |
+
raise ValueError("The supplied generators must be a subset of the domain's generators")
|
| 293 |
+
if not all(g in codomain for g in images):
|
| 294 |
+
raise ValueError("The images must be elements of the codomain")
|
| 295 |
+
|
| 296 |
+
if images and len(images) != len(gens):
|
| 297 |
+
raise ValueError("The number of images must be equal to the number of generators")
|
| 298 |
+
|
| 299 |
+
gens = list(gens)
|
| 300 |
+
images = list(images)
|
| 301 |
+
|
| 302 |
+
images.extend([codomain.identity]*(len(generators)-len(images)))
|
| 303 |
+
gens.extend([g for g in generators if g not in gens])
|
| 304 |
+
images = dict(zip(gens,images))
|
| 305 |
+
|
| 306 |
+
if check and not _check_homomorphism(domain, codomain, images):
|
| 307 |
+
raise ValueError("The given images do not define a homomorphism")
|
| 308 |
+
return GroupHomomorphism(domain, codomain, images)
|
| 309 |
+
|
| 310 |
+
def _check_homomorphism(domain, codomain, images):
|
| 311 |
+
"""
|
| 312 |
+
Check that a given mapping of generators to images defines a homomorphism.
|
| 313 |
+
|
| 314 |
+
Parameters
|
| 315 |
+
==========
|
| 316 |
+
domain : PermutationGroup, FpGroup, FreeGroup
|
| 317 |
+
codomain : PermutationGroup, FpGroup, FreeGroup
|
| 318 |
+
images : dict
|
| 319 |
+
The set of keys must be equal to domain.generators.
|
| 320 |
+
The values must be elements of the codomain.
|
| 321 |
+
|
| 322 |
+
"""
|
| 323 |
+
pres = domain if hasattr(domain, 'relators') else domain.presentation()
|
| 324 |
+
rels = pres.relators
|
| 325 |
+
gens = pres.generators
|
| 326 |
+
symbols = [g.ext_rep[0] for g in gens]
|
| 327 |
+
symbols_to_domain_generators = dict(zip(symbols, domain.generators))
|
| 328 |
+
identity = codomain.identity
|
| 329 |
+
|
| 330 |
+
def _image(r):
|
| 331 |
+
w = identity
|
| 332 |
+
for symbol, power in r.array_form:
|
| 333 |
+
g = symbols_to_domain_generators[symbol]
|
| 334 |
+
w *= images[g]**power
|
| 335 |
+
return w
|
| 336 |
+
|
| 337 |
+
for r in rels:
|
| 338 |
+
if isinstance(codomain, FpGroup):
|
| 339 |
+
s = codomain.equals(_image(r), identity)
|
| 340 |
+
if s is None:
|
| 341 |
+
# only try to make the rewriting system
|
| 342 |
+
# confluent when it can't determine the
|
| 343 |
+
# truth of equality otherwise
|
| 344 |
+
success = codomain.make_confluent()
|
| 345 |
+
s = codomain.equals(_image(r), identity)
|
| 346 |
+
if s is None and not success:
|
| 347 |
+
raise RuntimeError("Can't determine if the images "
|
| 348 |
+
"define a homomorphism. Try increasing "
|
| 349 |
+
"the maximum number of rewriting rules "
|
| 350 |
+
"(group._rewriting_system.set_max(new_value); "
|
| 351 |
+
"the current value is stored in group._rewriting"
|
| 352 |
+
"_system.maxeqns)")
|
| 353 |
+
else:
|
| 354 |
+
s = _image(r).is_identity
|
| 355 |
+
if not s:
|
| 356 |
+
return False
|
| 357 |
+
return True
|
| 358 |
+
|
| 359 |
+
def orbit_homomorphism(group, omega):
|
| 360 |
+
'''
|
| 361 |
+
Return the homomorphism induced by the action of the permutation
|
| 362 |
+
group ``group`` on the set ``omega`` that is closed under the action.
|
| 363 |
+
|
| 364 |
+
'''
|
| 365 |
+
from sympy.combinatorics import Permutation
|
| 366 |
+
from sympy.combinatorics.named_groups import SymmetricGroup
|
| 367 |
+
codomain = SymmetricGroup(len(omega))
|
| 368 |
+
identity = codomain.identity
|
| 369 |
+
omega = list(omega)
|
| 370 |
+
images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators}
|
| 371 |
+
group._schreier_sims(base=omega)
|
| 372 |
+
H = GroupHomomorphism(group, codomain, images)
|
| 373 |
+
if len(group.basic_stabilizers) > len(omega):
|
| 374 |
+
H._kernel = group.basic_stabilizers[len(omega)]
|
| 375 |
+
else:
|
| 376 |
+
H._kernel = PermutationGroup([group.identity])
|
| 377 |
+
return H
|
| 378 |
+
|
| 379 |
+
def block_homomorphism(group, blocks):
|
| 380 |
+
'''
|
| 381 |
+
Return the homomorphism induced by the action of the permutation
|
| 382 |
+
group ``group`` on the block system ``blocks``. The latter should be
|
| 383 |
+
of the same form as returned by the ``minimal_block`` method for
|
| 384 |
+
permutation groups, namely a list of length ``group.degree`` where
|
| 385 |
+
the i-th entry is a representative of the block i belongs to.
|
| 386 |
+
|
| 387 |
+
'''
|
| 388 |
+
from sympy.combinatorics import Permutation
|
| 389 |
+
from sympy.combinatorics.named_groups import SymmetricGroup
|
| 390 |
+
|
| 391 |
+
n = len(blocks)
|
| 392 |
+
|
| 393 |
+
# number the blocks; m is the total number,
|
| 394 |
+
# b is such that b[i] is the number of the block i belongs to,
|
| 395 |
+
# p is the list of length m such that p[i] is the representative
|
| 396 |
+
# of the i-th block
|
| 397 |
+
m = 0
|
| 398 |
+
p = []
|
| 399 |
+
b = [None]*n
|
| 400 |
+
for i in range(n):
|
| 401 |
+
if blocks[i] == i:
|
| 402 |
+
p.append(i)
|
| 403 |
+
b[i] = m
|
| 404 |
+
m += 1
|
| 405 |
+
for i in range(n):
|
| 406 |
+
b[i] = b[blocks[i]]
|
| 407 |
+
|
| 408 |
+
codomain = SymmetricGroup(m)
|
| 409 |
+
# the list corresponding to the identity permutation in codomain
|
| 410 |
+
identity = range(m)
|
| 411 |
+
images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators}
|
| 412 |
+
H = GroupHomomorphism(group, codomain, images)
|
| 413 |
+
return H
|
| 414 |
+
|
| 415 |
+
def group_isomorphism(G, H, isomorphism=True):
|
| 416 |
+
'''
|
| 417 |
+
Compute an isomorphism between 2 given groups.
|
| 418 |
+
|
| 419 |
+
Parameters
|
| 420 |
+
==========
|
| 421 |
+
|
| 422 |
+
G : A finite ``FpGroup`` or a ``PermutationGroup``.
|
| 423 |
+
First group.
|
| 424 |
+
|
| 425 |
+
H : A finite ``FpGroup`` or a ``PermutationGroup``
|
| 426 |
+
Second group.
|
| 427 |
+
|
| 428 |
+
isomorphism : bool
|
| 429 |
+
This is used to avoid the computation of homomorphism
|
| 430 |
+
when the user only wants to check if there exists
|
| 431 |
+
an isomorphism between the groups.
|
| 432 |
+
|
| 433 |
+
Returns
|
| 434 |
+
=======
|
| 435 |
+
|
| 436 |
+
If isomorphism = False -- Returns a boolean.
|
| 437 |
+
If isomorphism = True -- Returns a boolean and an isomorphism between `G` and `H`.
|
| 438 |
+
|
| 439 |
+
Examples
|
| 440 |
+
========
|
| 441 |
+
|
| 442 |
+
>>> from sympy.combinatorics import free_group, Permutation
|
| 443 |
+
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
| 444 |
+
>>> from sympy.combinatorics.fp_groups import FpGroup
|
| 445 |
+
>>> from sympy.combinatorics.homomorphisms import group_isomorphism
|
| 446 |
+
>>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup
|
| 447 |
+
|
| 448 |
+
>>> D = DihedralGroup(8)
|
| 449 |
+
>>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
|
| 450 |
+
>>> P = PermutationGroup(p)
|
| 451 |
+
>>> group_isomorphism(D, P)
|
| 452 |
+
(False, None)
|
| 453 |
+
|
| 454 |
+
>>> F, a, b = free_group("a, b")
|
| 455 |
+
>>> G = FpGroup(F, [a**3, b**3, (a*b)**2])
|
| 456 |
+
>>> H = AlternatingGroup(4)
|
| 457 |
+
>>> (check, T) = group_isomorphism(G, H)
|
| 458 |
+
>>> check
|
| 459 |
+
True
|
| 460 |
+
>>> T(b*a*b**-1*a**-1*b**-1)
|
| 461 |
+
(0 2 3)
|
| 462 |
+
|
| 463 |
+
Notes
|
| 464 |
+
=====
|
| 465 |
+
|
| 466 |
+
Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups.
|
| 467 |
+
First, the generators of ``G`` are mapped to the elements of ``H`` and
|
| 468 |
+
we check if the mapping induces an isomorphism.
|
| 469 |
+
|
| 470 |
+
'''
|
| 471 |
+
if not isinstance(G, (PermutationGroup, FpGroup)):
|
| 472 |
+
raise TypeError("The group must be a PermutationGroup or an FpGroup")
|
| 473 |
+
if not isinstance(H, (PermutationGroup, FpGroup)):
|
| 474 |
+
raise TypeError("The group must be a PermutationGroup or an FpGroup")
|
| 475 |
+
|
| 476 |
+
if isinstance(G, FpGroup) and isinstance(H, FpGroup):
|
| 477 |
+
G = simplify_presentation(G)
|
| 478 |
+
H = simplify_presentation(H)
|
| 479 |
+
# Two infinite FpGroups with the same generators are isomorphic
|
| 480 |
+
# when the relators are same but are ordered differently.
|
| 481 |
+
if G.generators == H.generators and (G.relators).sort() == (H.relators).sort():
|
| 482 |
+
if not isomorphism:
|
| 483 |
+
return True
|
| 484 |
+
return (True, homomorphism(G, H, G.generators, H.generators))
|
| 485 |
+
|
| 486 |
+
# `_H` is the permutation group isomorphic to `H`.
|
| 487 |
+
_H = H
|
| 488 |
+
g_order = G.order()
|
| 489 |
+
h_order = H.order()
|
| 490 |
+
|
| 491 |
+
if g_order is S.Infinity:
|
| 492 |
+
raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
|
| 493 |
+
|
| 494 |
+
if isinstance(H, FpGroup):
|
| 495 |
+
if h_order is S.Infinity:
|
| 496 |
+
raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
|
| 497 |
+
_H, h_isomorphism = H._to_perm_group()
|
| 498 |
+
|
| 499 |
+
if (g_order != h_order) or (G.is_abelian != H.is_abelian):
|
| 500 |
+
if not isomorphism:
|
| 501 |
+
return False
|
| 502 |
+
return (False, None)
|
| 503 |
+
|
| 504 |
+
if not isomorphism:
|
| 505 |
+
# Two groups of the same cyclic numbered order
|
| 506 |
+
# are isomorphic to each other.
|
| 507 |
+
n = g_order
|
| 508 |
+
if (igcd(n, totient(n))) == 1:
|
| 509 |
+
return True
|
| 510 |
+
|
| 511 |
+
# Match the generators of `G` with subsets of `_H`
|
| 512 |
+
gens = list(G.generators)
|
| 513 |
+
for subset in itertools.permutations(_H, len(gens)):
|
| 514 |
+
images = list(subset)
|
| 515 |
+
images.extend([_H.identity]*(len(G.generators)-len(images)))
|
| 516 |
+
_images = dict(zip(gens,images))
|
| 517 |
+
if _check_homomorphism(G, _H, _images):
|
| 518 |
+
if isinstance(H, FpGroup):
|
| 519 |
+
images = h_isomorphism.invert(images)
|
| 520 |
+
T = homomorphism(G, H, G.generators, images, check=False)
|
| 521 |
+
if T.is_isomorphism():
|
| 522 |
+
# It is a valid isomorphism
|
| 523 |
+
if not isomorphism:
|
| 524 |
+
return True
|
| 525 |
+
return (True, T)
|
| 526 |
+
|
| 527 |
+
if not isomorphism:
|
| 528 |
+
return False
|
| 529 |
+
return (False, None)
|
| 530 |
+
|
| 531 |
+
def is_isomorphic(G, H):
|
| 532 |
+
'''
|
| 533 |
+
Check if the groups are isomorphic to each other
|
| 534 |
+
|
| 535 |
+
Parameters
|
| 536 |
+
==========
|
| 537 |
+
|
| 538 |
+
G : A finite ``FpGroup`` or a ``PermutationGroup``
|
| 539 |
+
First group.
|
| 540 |
+
|
| 541 |
+
H : A finite ``FpGroup`` or a ``PermutationGroup``
|
| 542 |
+
Second group.
|
| 543 |
+
|
| 544 |
+
Returns
|
| 545 |
+
=======
|
| 546 |
+
|
| 547 |
+
boolean
|
| 548 |
+
'''
|
| 549 |
+
return group_isomorphism(G, H, isomorphism=False)
|
lib/python3.12/site-packages/sympy/combinatorics/named_groups.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.group_constructs import DirectProduct
|
| 2 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 3 |
+
from sympy.combinatorics.permutations import Permutation
|
| 4 |
+
|
| 5 |
+
_af_new = Permutation._af_new
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def AbelianGroup(*cyclic_orders):
|
| 9 |
+
"""
|
| 10 |
+
Returns the direct product of cyclic groups with the given orders.
|
| 11 |
+
|
| 12 |
+
Explanation
|
| 13 |
+
===========
|
| 14 |
+
|
| 15 |
+
According to the structure theorem for finite abelian groups ([1]),
|
| 16 |
+
every finite abelian group can be written as the direct product of
|
| 17 |
+
finitely many cyclic groups.
|
| 18 |
+
|
| 19 |
+
Examples
|
| 20 |
+
========
|
| 21 |
+
|
| 22 |
+
>>> from sympy.combinatorics.named_groups import AbelianGroup
|
| 23 |
+
>>> AbelianGroup(3, 4)
|
| 24 |
+
PermutationGroup([
|
| 25 |
+
(6)(0 1 2),
|
| 26 |
+
(3 4 5 6)])
|
| 27 |
+
>>> _.is_group
|
| 28 |
+
True
|
| 29 |
+
|
| 30 |
+
See Also
|
| 31 |
+
========
|
| 32 |
+
|
| 33 |
+
DirectProduct
|
| 34 |
+
|
| 35 |
+
References
|
| 36 |
+
==========
|
| 37 |
+
|
| 38 |
+
.. [1] https://groupprops.subwiki.org/wiki/Structure_theorem_for_finitely_generated_abelian_groups
|
| 39 |
+
|
| 40 |
+
"""
|
| 41 |
+
groups = []
|
| 42 |
+
degree = 0
|
| 43 |
+
order = 1
|
| 44 |
+
for size in cyclic_orders:
|
| 45 |
+
degree += size
|
| 46 |
+
order *= size
|
| 47 |
+
groups.append(CyclicGroup(size))
|
| 48 |
+
G = DirectProduct(*groups)
|
| 49 |
+
G._is_abelian = True
|
| 50 |
+
G._degree = degree
|
| 51 |
+
G._order = order
|
| 52 |
+
|
| 53 |
+
return G
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def AlternatingGroup(n):
|
| 57 |
+
"""
|
| 58 |
+
Generates the alternating group on ``n`` elements as a permutation group.
|
| 59 |
+
|
| 60 |
+
Explanation
|
| 61 |
+
===========
|
| 62 |
+
|
| 63 |
+
For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for
|
| 64 |
+
``n`` odd
|
| 65 |
+
and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.).
|
| 66 |
+
After the group is generated, some of its basic properties are set.
|
| 67 |
+
The cases ``n = 1, 2`` are handled separately.
|
| 68 |
+
|
| 69 |
+
Examples
|
| 70 |
+
========
|
| 71 |
+
|
| 72 |
+
>>> from sympy.combinatorics.named_groups import AlternatingGroup
|
| 73 |
+
>>> G = AlternatingGroup(4)
|
| 74 |
+
>>> G.is_group
|
| 75 |
+
True
|
| 76 |
+
>>> a = list(G.generate_dimino())
|
| 77 |
+
>>> len(a)
|
| 78 |
+
12
|
| 79 |
+
>>> all(perm.is_even for perm in a)
|
| 80 |
+
True
|
| 81 |
+
|
| 82 |
+
See Also
|
| 83 |
+
========
|
| 84 |
+
|
| 85 |
+
SymmetricGroup, CyclicGroup, DihedralGroup
|
| 86 |
+
|
| 87 |
+
References
|
| 88 |
+
==========
|
| 89 |
+
|
| 90 |
+
.. [1] Armstrong, M. "Groups and Symmetry"
|
| 91 |
+
|
| 92 |
+
"""
|
| 93 |
+
# small cases are special
|
| 94 |
+
if n in (1, 2):
|
| 95 |
+
return PermutationGroup([Permutation([0])])
|
| 96 |
+
|
| 97 |
+
a = list(range(n))
|
| 98 |
+
a[0], a[1], a[2] = a[1], a[2], a[0]
|
| 99 |
+
gen1 = a
|
| 100 |
+
if n % 2:
|
| 101 |
+
a = list(range(1, n))
|
| 102 |
+
a.append(0)
|
| 103 |
+
gen2 = a
|
| 104 |
+
else:
|
| 105 |
+
a = list(range(2, n))
|
| 106 |
+
a.append(1)
|
| 107 |
+
a.insert(0, 0)
|
| 108 |
+
gen2 = a
|
| 109 |
+
gens = [gen1, gen2]
|
| 110 |
+
if gen1 == gen2:
|
| 111 |
+
gens = gens[:1]
|
| 112 |
+
G = PermutationGroup([_af_new(a) for a in gens], dups=False)
|
| 113 |
+
|
| 114 |
+
set_alternating_group_properties(G, n, n)
|
| 115 |
+
G._is_alt = True
|
| 116 |
+
return G
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def set_alternating_group_properties(G, n, degree):
|
| 120 |
+
"""Set known properties of an alternating group. """
|
| 121 |
+
if n < 4:
|
| 122 |
+
G._is_abelian = True
|
| 123 |
+
G._is_nilpotent = True
|
| 124 |
+
else:
|
| 125 |
+
G._is_abelian = False
|
| 126 |
+
G._is_nilpotent = False
|
| 127 |
+
if n < 5:
|
| 128 |
+
G._is_solvable = True
|
| 129 |
+
else:
|
| 130 |
+
G._is_solvable = False
|
| 131 |
+
G._degree = degree
|
| 132 |
+
G._is_transitive = True
|
| 133 |
+
G._is_dihedral = False
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def CyclicGroup(n):
|
| 137 |
+
"""
|
| 138 |
+
Generates the cyclic group of order ``n`` as a permutation group.
|
| 139 |
+
|
| 140 |
+
Explanation
|
| 141 |
+
===========
|
| 142 |
+
|
| 143 |
+
The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)``
|
| 144 |
+
(in cycle notation). After the group is generated, some of its basic
|
| 145 |
+
properties are set.
|
| 146 |
+
|
| 147 |
+
Examples
|
| 148 |
+
========
|
| 149 |
+
|
| 150 |
+
>>> from sympy.combinatorics.named_groups import CyclicGroup
|
| 151 |
+
>>> G = CyclicGroup(6)
|
| 152 |
+
>>> G.is_group
|
| 153 |
+
True
|
| 154 |
+
>>> G.order()
|
| 155 |
+
6
|
| 156 |
+
>>> list(G.generate_schreier_sims(af=True))
|
| 157 |
+
[[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1],
|
| 158 |
+
[3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]]
|
| 159 |
+
|
| 160 |
+
See Also
|
| 161 |
+
========
|
| 162 |
+
|
| 163 |
+
SymmetricGroup, DihedralGroup, AlternatingGroup
|
| 164 |
+
|
| 165 |
+
"""
|
| 166 |
+
a = list(range(1, n))
|
| 167 |
+
a.append(0)
|
| 168 |
+
gen = _af_new(a)
|
| 169 |
+
G = PermutationGroup([gen])
|
| 170 |
+
|
| 171 |
+
G._is_abelian = True
|
| 172 |
+
G._is_nilpotent = True
|
| 173 |
+
G._is_solvable = True
|
| 174 |
+
G._degree = n
|
| 175 |
+
G._is_transitive = True
|
| 176 |
+
G._order = n
|
| 177 |
+
G._is_dihedral = (n == 2)
|
| 178 |
+
return G
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def DihedralGroup(n):
|
| 182 |
+
r"""
|
| 183 |
+
Generates the dihedral group `D_n` as a permutation group.
|
| 184 |
+
|
| 185 |
+
Explanation
|
| 186 |
+
===========
|
| 187 |
+
|
| 188 |
+
The dihedral group `D_n` is the group of symmetries of the regular
|
| 189 |
+
``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)``
|
| 190 |
+
(a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...``
|
| 191 |
+
(a reflection of the ``n``-gon) in cycle rotation. It is easy to see that
|
| 192 |
+
these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate
|
| 193 |
+
`D_n` (See [1]). After the group is generated, some of its basic properties
|
| 194 |
+
are set.
|
| 195 |
+
|
| 196 |
+
Examples
|
| 197 |
+
========
|
| 198 |
+
|
| 199 |
+
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
| 200 |
+
>>> G = DihedralGroup(5)
|
| 201 |
+
>>> G.is_group
|
| 202 |
+
True
|
| 203 |
+
>>> a = list(G.generate_dimino())
|
| 204 |
+
>>> [perm.cyclic_form for perm in a]
|
| 205 |
+
[[], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]],
|
| 206 |
+
[[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[0, 4], [1, 3]],
|
| 207 |
+
[[1, 4], [2, 3]], [[0, 1], [2, 4]], [[0, 2], [3, 4]],
|
| 208 |
+
[[0, 3], [1, 2]]]
|
| 209 |
+
|
| 210 |
+
See Also
|
| 211 |
+
========
|
| 212 |
+
|
| 213 |
+
SymmetricGroup, CyclicGroup, AlternatingGroup
|
| 214 |
+
|
| 215 |
+
References
|
| 216 |
+
==========
|
| 217 |
+
|
| 218 |
+
.. [1] https://en.wikipedia.org/wiki/Dihedral_group
|
| 219 |
+
|
| 220 |
+
"""
|
| 221 |
+
# small cases are special
|
| 222 |
+
if n == 1:
|
| 223 |
+
return PermutationGroup([Permutation([1, 0])])
|
| 224 |
+
if n == 2:
|
| 225 |
+
return PermutationGroup([Permutation([1, 0, 3, 2]),
|
| 226 |
+
Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])])
|
| 227 |
+
|
| 228 |
+
a = list(range(1, n))
|
| 229 |
+
a.append(0)
|
| 230 |
+
gen1 = _af_new(a)
|
| 231 |
+
a = list(range(n))
|
| 232 |
+
a.reverse()
|
| 233 |
+
gen2 = _af_new(a)
|
| 234 |
+
G = PermutationGroup([gen1, gen2])
|
| 235 |
+
# if n is a power of 2, group is nilpotent
|
| 236 |
+
if n & (n-1) == 0:
|
| 237 |
+
G._is_nilpotent = True
|
| 238 |
+
else:
|
| 239 |
+
G._is_nilpotent = False
|
| 240 |
+
G._is_dihedral = True
|
| 241 |
+
G._is_abelian = False
|
| 242 |
+
G._is_solvable = True
|
| 243 |
+
G._degree = n
|
| 244 |
+
G._is_transitive = True
|
| 245 |
+
G._order = 2*n
|
| 246 |
+
return G
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def SymmetricGroup(n):
|
| 250 |
+
"""
|
| 251 |
+
Generates the symmetric group on ``n`` elements as a permutation group.
|
| 252 |
+
|
| 253 |
+
Explanation
|
| 254 |
+
===========
|
| 255 |
+
|
| 256 |
+
The generators taken are the ``n``-cycle
|
| 257 |
+
``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation).
|
| 258 |
+
(See [1]). After the group is generated, some of its basic properties
|
| 259 |
+
are set.
|
| 260 |
+
|
| 261 |
+
Examples
|
| 262 |
+
========
|
| 263 |
+
|
| 264 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 265 |
+
>>> G = SymmetricGroup(4)
|
| 266 |
+
>>> G.is_group
|
| 267 |
+
True
|
| 268 |
+
>>> G.order()
|
| 269 |
+
24
|
| 270 |
+
>>> list(G.generate_schreier_sims(af=True))
|
| 271 |
+
[[0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 1], [3, 1, 2, 0], [0, 2, 3, 1],
|
| 272 |
+
[1, 3, 0, 2], [2, 0, 1, 3], [3, 2, 0, 1], [0, 3, 1, 2], [1, 0, 2, 3],
|
| 273 |
+
[2, 1, 3, 0], [3, 0, 1, 2], [0, 1, 3, 2], [1, 2, 0, 3], [2, 3, 1, 0],
|
| 274 |
+
[3, 1, 0, 2], [0, 2, 1, 3], [1, 3, 2, 0], [2, 0, 3, 1], [3, 2, 1, 0],
|
| 275 |
+
[0, 3, 2, 1], [1, 0, 3, 2], [2, 1, 0, 3], [3, 0, 2, 1]]
|
| 276 |
+
|
| 277 |
+
See Also
|
| 278 |
+
========
|
| 279 |
+
|
| 280 |
+
CyclicGroup, DihedralGroup, AlternatingGroup
|
| 281 |
+
|
| 282 |
+
References
|
| 283 |
+
==========
|
| 284 |
+
|
| 285 |
+
.. [1] https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations
|
| 286 |
+
|
| 287 |
+
"""
|
| 288 |
+
if n == 1:
|
| 289 |
+
G = PermutationGroup([Permutation([0])])
|
| 290 |
+
elif n == 2:
|
| 291 |
+
G = PermutationGroup([Permutation([1, 0])])
|
| 292 |
+
else:
|
| 293 |
+
a = list(range(1, n))
|
| 294 |
+
a.append(0)
|
| 295 |
+
gen1 = _af_new(a)
|
| 296 |
+
a = list(range(n))
|
| 297 |
+
a[0], a[1] = a[1], a[0]
|
| 298 |
+
gen2 = _af_new(a)
|
| 299 |
+
G = PermutationGroup([gen1, gen2])
|
| 300 |
+
set_symmetric_group_properties(G, n, n)
|
| 301 |
+
G._is_sym = True
|
| 302 |
+
return G
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def set_symmetric_group_properties(G, n, degree):
|
| 306 |
+
"""Set known properties of a symmetric group. """
|
| 307 |
+
if n < 3:
|
| 308 |
+
G._is_abelian = True
|
| 309 |
+
G._is_nilpotent = True
|
| 310 |
+
else:
|
| 311 |
+
G._is_abelian = False
|
| 312 |
+
G._is_nilpotent = False
|
| 313 |
+
if n < 5:
|
| 314 |
+
G._is_solvable = True
|
| 315 |
+
else:
|
| 316 |
+
G._is_solvable = False
|
| 317 |
+
G._degree = degree
|
| 318 |
+
G._is_transitive = True
|
| 319 |
+
G._is_dihedral = (n in [2, 3]) # cf Landau's func and Stirling's approx
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def RubikGroup(n):
|
| 323 |
+
"""Return a group of Rubik's cube generators
|
| 324 |
+
|
| 325 |
+
>>> from sympy.combinatorics.named_groups import RubikGroup
|
| 326 |
+
>>> RubikGroup(2).is_group
|
| 327 |
+
True
|
| 328 |
+
"""
|
| 329 |
+
from sympy.combinatorics.generators import rubik
|
| 330 |
+
if n <= 1:
|
| 331 |
+
raise ValueError("Invalid cube. n has to be greater than 1")
|
| 332 |
+
return PermutationGroup(rubik(n))
|
lib/python3.12/site-packages/sympy/combinatorics/pc_groups.py
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.ntheory.primetest import isprime
|
| 2 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 3 |
+
from sympy.printing.defaults import DefaultPrinting
|
| 4 |
+
from sympy.combinatorics.free_groups import free_group
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class PolycyclicGroup(DefaultPrinting):
|
| 8 |
+
|
| 9 |
+
is_group = True
|
| 10 |
+
is_solvable = True
|
| 11 |
+
|
| 12 |
+
def __init__(self, pc_sequence, pc_series, relative_order, collector=None):
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
Parameters
|
| 16 |
+
==========
|
| 17 |
+
|
| 18 |
+
pc_sequence : list
|
| 19 |
+
A sequence of elements whose classes generate the cyclic factor
|
| 20 |
+
groups of pc_series.
|
| 21 |
+
pc_series : list
|
| 22 |
+
A subnormal sequence of subgroups where each factor group is cyclic.
|
| 23 |
+
relative_order : list
|
| 24 |
+
The orders of factor groups of pc_series.
|
| 25 |
+
collector : Collector
|
| 26 |
+
By default, it is None. Collector class provides the
|
| 27 |
+
polycyclic presentation with various other functionalities.
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
self.pcgs = pc_sequence
|
| 31 |
+
self.pc_series = pc_series
|
| 32 |
+
self.relative_order = relative_order
|
| 33 |
+
self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector
|
| 34 |
+
|
| 35 |
+
def is_prime_order(self):
|
| 36 |
+
return all(isprime(order) for order in self.relative_order)
|
| 37 |
+
|
| 38 |
+
def length(self):
|
| 39 |
+
return len(self.pcgs)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class Collector(DefaultPrinting):
|
| 43 |
+
|
| 44 |
+
"""
|
| 45 |
+
References
|
| 46 |
+
==========
|
| 47 |
+
|
| 48 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 49 |
+
"Handbook of Computational Group Theory"
|
| 50 |
+
Section 8.1.3
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None):
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
Most of the parameters for the Collector class are the same as for PolycyclicGroup.
|
| 57 |
+
Others are described below.
|
| 58 |
+
|
| 59 |
+
Parameters
|
| 60 |
+
==========
|
| 61 |
+
|
| 62 |
+
free_group_ : tuple
|
| 63 |
+
free_group_ provides the mapping of polycyclic generating
|
| 64 |
+
sequence with the free group elements.
|
| 65 |
+
pc_presentation : dict
|
| 66 |
+
Provides the presentation of polycyclic groups with the
|
| 67 |
+
help of power and conjugate relators.
|
| 68 |
+
|
| 69 |
+
See Also
|
| 70 |
+
========
|
| 71 |
+
|
| 72 |
+
PolycyclicGroup
|
| 73 |
+
|
| 74 |
+
"""
|
| 75 |
+
self.pcgs = pcgs
|
| 76 |
+
self.pc_series = pc_series
|
| 77 |
+
self.relative_order = relative_order
|
| 78 |
+
self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_
|
| 79 |
+
self.index = {s: i for i, s in enumerate(self.free_group.symbols)}
|
| 80 |
+
self.pc_presentation = self.pc_relators()
|
| 81 |
+
|
| 82 |
+
def minimal_uncollected_subword(self, word):
|
| 83 |
+
r"""
|
| 84 |
+
Returns the minimal uncollected subwords.
|
| 85 |
+
|
| 86 |
+
Explanation
|
| 87 |
+
===========
|
| 88 |
+
|
| 89 |
+
A word ``v`` defined on generators in ``X`` is a minimal
|
| 90 |
+
uncollected subword of the word ``w`` if ``v`` is a subword
|
| 91 |
+
of ``w`` and it has one of the following form
|
| 92 |
+
|
| 93 |
+
* `v = {x_{i+1}}^{a_j}x_i`
|
| 94 |
+
|
| 95 |
+
* `v = {x_{i+1}}^{a_j}{x_i}^{-1}`
|
| 96 |
+
|
| 97 |
+
* `v = {x_i}^{a_j}`
|
| 98 |
+
|
| 99 |
+
for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power
|
| 100 |
+
exponent of the corresponding generator.
|
| 101 |
+
|
| 102 |
+
Examples
|
| 103 |
+
========
|
| 104 |
+
|
| 105 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 106 |
+
>>> from sympy.combinatorics import free_group
|
| 107 |
+
>>> G = SymmetricGroup(4)
|
| 108 |
+
>>> PcGroup = G.polycyclic_group()
|
| 109 |
+
>>> collector = PcGroup.collector
|
| 110 |
+
>>> F, x1, x2 = free_group("x1, x2")
|
| 111 |
+
>>> word = x2**2*x1**7
|
| 112 |
+
>>> collector.minimal_uncollected_subword(word)
|
| 113 |
+
((x2, 2),)
|
| 114 |
+
|
| 115 |
+
"""
|
| 116 |
+
# To handle the case word = <identity>
|
| 117 |
+
if not word:
|
| 118 |
+
return None
|
| 119 |
+
|
| 120 |
+
array = word.array_form
|
| 121 |
+
re = self.relative_order
|
| 122 |
+
index = self.index
|
| 123 |
+
|
| 124 |
+
for i in range(len(array)):
|
| 125 |
+
s1, e1 = array[i]
|
| 126 |
+
|
| 127 |
+
if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1):
|
| 128 |
+
return ((s1, e1), )
|
| 129 |
+
|
| 130 |
+
for i in range(len(array)-1):
|
| 131 |
+
s1, e1 = array[i]
|
| 132 |
+
s2, e2 = array[i+1]
|
| 133 |
+
|
| 134 |
+
if index[s1] > index[s2]:
|
| 135 |
+
e = 1 if e2 > 0 else -1
|
| 136 |
+
return ((s1, e1), (s2, e))
|
| 137 |
+
|
| 138 |
+
return None
|
| 139 |
+
|
| 140 |
+
def relations(self):
|
| 141 |
+
"""
|
| 142 |
+
Separates the given relators of pc presentation in power and
|
| 143 |
+
conjugate relations.
|
| 144 |
+
|
| 145 |
+
Returns
|
| 146 |
+
=======
|
| 147 |
+
|
| 148 |
+
(power_rel, conj_rel)
|
| 149 |
+
Separates pc presentation into power and conjugate relations.
|
| 150 |
+
|
| 151 |
+
Examples
|
| 152 |
+
========
|
| 153 |
+
|
| 154 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 155 |
+
>>> G = SymmetricGroup(3)
|
| 156 |
+
>>> PcGroup = G.polycyclic_group()
|
| 157 |
+
>>> collector = PcGroup.collector
|
| 158 |
+
>>> power_rel, conj_rel = collector.relations()
|
| 159 |
+
>>> power_rel
|
| 160 |
+
{x0**2: (), x1**3: ()}
|
| 161 |
+
>>> conj_rel
|
| 162 |
+
{x0**-1*x1*x0: x1**2}
|
| 163 |
+
|
| 164 |
+
See Also
|
| 165 |
+
========
|
| 166 |
+
|
| 167 |
+
pc_relators
|
| 168 |
+
|
| 169 |
+
"""
|
| 170 |
+
power_relators = {}
|
| 171 |
+
conjugate_relators = {}
|
| 172 |
+
for key, value in self.pc_presentation.items():
|
| 173 |
+
if len(key.array_form) == 1:
|
| 174 |
+
power_relators[key] = value
|
| 175 |
+
else:
|
| 176 |
+
conjugate_relators[key] = value
|
| 177 |
+
return power_relators, conjugate_relators
|
| 178 |
+
|
| 179 |
+
def subword_index(self, word, w):
|
| 180 |
+
"""
|
| 181 |
+
Returns the start and ending index of a given
|
| 182 |
+
subword in a word.
|
| 183 |
+
|
| 184 |
+
Parameters
|
| 185 |
+
==========
|
| 186 |
+
|
| 187 |
+
word : FreeGroupElement
|
| 188 |
+
word defined on free group elements for a
|
| 189 |
+
polycyclic group.
|
| 190 |
+
w : FreeGroupElement
|
| 191 |
+
subword of a given word, whose starting and
|
| 192 |
+
ending index to be computed.
|
| 193 |
+
|
| 194 |
+
Returns
|
| 195 |
+
=======
|
| 196 |
+
|
| 197 |
+
(i, j)
|
| 198 |
+
A tuple containing starting and ending index of ``w``
|
| 199 |
+
in the given word.
|
| 200 |
+
|
| 201 |
+
Examples
|
| 202 |
+
========
|
| 203 |
+
|
| 204 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 205 |
+
>>> from sympy.combinatorics import free_group
|
| 206 |
+
>>> G = SymmetricGroup(4)
|
| 207 |
+
>>> PcGroup = G.polycyclic_group()
|
| 208 |
+
>>> collector = PcGroup.collector
|
| 209 |
+
>>> F, x1, x2 = free_group("x1, x2")
|
| 210 |
+
>>> word = x2**2*x1**7
|
| 211 |
+
>>> w = x2**2*x1
|
| 212 |
+
>>> collector.subword_index(word, w)
|
| 213 |
+
(0, 3)
|
| 214 |
+
>>> w = x1**7
|
| 215 |
+
>>> collector.subword_index(word, w)
|
| 216 |
+
(2, 9)
|
| 217 |
+
|
| 218 |
+
"""
|
| 219 |
+
low = -1
|
| 220 |
+
high = -1
|
| 221 |
+
for i in range(len(word)-len(w)+1):
|
| 222 |
+
if word.subword(i, i+len(w)) == w:
|
| 223 |
+
low = i
|
| 224 |
+
high = i+len(w)
|
| 225 |
+
break
|
| 226 |
+
if low == high == -1:
|
| 227 |
+
return -1, -1
|
| 228 |
+
return low, high
|
| 229 |
+
|
| 230 |
+
def map_relation(self, w):
|
| 231 |
+
"""
|
| 232 |
+
Return a conjugate relation.
|
| 233 |
+
|
| 234 |
+
Explanation
|
| 235 |
+
===========
|
| 236 |
+
|
| 237 |
+
Given a word formed by two free group elements, the
|
| 238 |
+
corresponding conjugate relation with those free
|
| 239 |
+
group elements is formed and mapped with the collected
|
| 240 |
+
word in the polycyclic presentation.
|
| 241 |
+
|
| 242 |
+
Examples
|
| 243 |
+
========
|
| 244 |
+
|
| 245 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 246 |
+
>>> from sympy.combinatorics import free_group
|
| 247 |
+
>>> G = SymmetricGroup(3)
|
| 248 |
+
>>> PcGroup = G.polycyclic_group()
|
| 249 |
+
>>> collector = PcGroup.collector
|
| 250 |
+
>>> F, x0, x1 = free_group("x0, x1")
|
| 251 |
+
>>> w = x1*x0
|
| 252 |
+
>>> collector.map_relation(w)
|
| 253 |
+
x1**2
|
| 254 |
+
|
| 255 |
+
See Also
|
| 256 |
+
========
|
| 257 |
+
|
| 258 |
+
pc_presentation
|
| 259 |
+
|
| 260 |
+
"""
|
| 261 |
+
array = w.array_form
|
| 262 |
+
s1 = array[0][0]
|
| 263 |
+
s2 = array[1][0]
|
| 264 |
+
key = ((s2, -1), (s1, 1), (s2, 1))
|
| 265 |
+
key = self.free_group.dtype(key)
|
| 266 |
+
return self.pc_presentation[key]
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def collected_word(self, word):
|
| 270 |
+
r"""
|
| 271 |
+
Return the collected form of a word.
|
| 272 |
+
|
| 273 |
+
Explanation
|
| 274 |
+
===========
|
| 275 |
+
|
| 276 |
+
A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
|
| 277 |
+
{x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
|
| 278 |
+
`\{1, \ldots, {s_j}-1\}`.
|
| 279 |
+
|
| 280 |
+
Otherwise w is uncollected.
|
| 281 |
+
|
| 282 |
+
Parameters
|
| 283 |
+
==========
|
| 284 |
+
|
| 285 |
+
word : FreeGroupElement
|
| 286 |
+
An uncollected word.
|
| 287 |
+
|
| 288 |
+
Returns
|
| 289 |
+
=======
|
| 290 |
+
|
| 291 |
+
word
|
| 292 |
+
A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
|
| 293 |
+
{x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
|
| 294 |
+
\{1, \ldots, {s_j}-1\}`.
|
| 295 |
+
|
| 296 |
+
Examples
|
| 297 |
+
========
|
| 298 |
+
|
| 299 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 300 |
+
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
| 301 |
+
>>> from sympy.combinatorics import free_group
|
| 302 |
+
>>> G = SymmetricGroup(4)
|
| 303 |
+
>>> PcGroup = G.polycyclic_group()
|
| 304 |
+
>>> collector = PcGroup.collector
|
| 305 |
+
>>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
|
| 306 |
+
>>> word = x3*x2*x1*x0
|
| 307 |
+
>>> collected_word = collector.collected_word(word)
|
| 308 |
+
>>> free_to_perm = {}
|
| 309 |
+
>>> free_group = collector.free_group
|
| 310 |
+
>>> for sym, gen in zip(free_group.symbols, collector.pcgs):
|
| 311 |
+
... free_to_perm[sym] = gen
|
| 312 |
+
>>> G1 = PermutationGroup()
|
| 313 |
+
>>> for w in word:
|
| 314 |
+
... sym = w[0]
|
| 315 |
+
... perm = free_to_perm[sym]
|
| 316 |
+
... G1 = PermutationGroup([perm] + G1.generators)
|
| 317 |
+
>>> G2 = PermutationGroup()
|
| 318 |
+
>>> for w in collected_word:
|
| 319 |
+
... sym = w[0]
|
| 320 |
+
... perm = free_to_perm[sym]
|
| 321 |
+
... G2 = PermutationGroup([perm] + G2.generators)
|
| 322 |
+
|
| 323 |
+
The two are not identical, but they are equivalent:
|
| 324 |
+
|
| 325 |
+
>>> G1.equals(G2), G1 == G2
|
| 326 |
+
(True, False)
|
| 327 |
+
|
| 328 |
+
See Also
|
| 329 |
+
========
|
| 330 |
+
|
| 331 |
+
minimal_uncollected_subword
|
| 332 |
+
|
| 333 |
+
"""
|
| 334 |
+
free_group = self.free_group
|
| 335 |
+
while True:
|
| 336 |
+
w = self.minimal_uncollected_subword(word)
|
| 337 |
+
if not w:
|
| 338 |
+
break
|
| 339 |
+
|
| 340 |
+
low, high = self.subword_index(word, free_group.dtype(w))
|
| 341 |
+
if low == -1:
|
| 342 |
+
continue
|
| 343 |
+
|
| 344 |
+
s1, e1 = w[0]
|
| 345 |
+
if len(w) == 1:
|
| 346 |
+
re = self.relative_order[self.index[s1]]
|
| 347 |
+
q = e1 // re
|
| 348 |
+
r = e1-q*re
|
| 349 |
+
|
| 350 |
+
key = ((w[0][0], re), )
|
| 351 |
+
key = free_group.dtype(key)
|
| 352 |
+
if self.pc_presentation[key]:
|
| 353 |
+
presentation = self.pc_presentation[key].array_form
|
| 354 |
+
sym, exp = presentation[0]
|
| 355 |
+
word_ = ((w[0][0], r), (sym, q*exp))
|
| 356 |
+
word_ = free_group.dtype(word_)
|
| 357 |
+
else:
|
| 358 |
+
if r != 0:
|
| 359 |
+
word_ = ((w[0][0], r), )
|
| 360 |
+
word_ = free_group.dtype(word_)
|
| 361 |
+
else:
|
| 362 |
+
word_ = None
|
| 363 |
+
word = word.eliminate_word(free_group.dtype(w), word_)
|
| 364 |
+
|
| 365 |
+
if len(w) == 2 and w[1][1] > 0:
|
| 366 |
+
s2, e2 = w[1]
|
| 367 |
+
s2 = ((s2, 1), )
|
| 368 |
+
s2 = free_group.dtype(s2)
|
| 369 |
+
word_ = self.map_relation(free_group.dtype(w))
|
| 370 |
+
word_ = s2*word_**e1
|
| 371 |
+
word_ = free_group.dtype(word_)
|
| 372 |
+
word = word.substituted_word(low, high, word_)
|
| 373 |
+
|
| 374 |
+
elif len(w) == 2 and w[1][1] < 0:
|
| 375 |
+
s2, e2 = w[1]
|
| 376 |
+
s2 = ((s2, 1), )
|
| 377 |
+
s2 = free_group.dtype(s2)
|
| 378 |
+
word_ = self.map_relation(free_group.dtype(w))
|
| 379 |
+
word_ = s2**-1*word_**e1
|
| 380 |
+
word_ = free_group.dtype(word_)
|
| 381 |
+
word = word.substituted_word(low, high, word_)
|
| 382 |
+
|
| 383 |
+
return word
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def pc_relators(self):
|
| 387 |
+
r"""
|
| 388 |
+
Return the polycyclic presentation.
|
| 389 |
+
|
| 390 |
+
Explanation
|
| 391 |
+
===========
|
| 392 |
+
|
| 393 |
+
There are two types of relations used in polycyclic
|
| 394 |
+
presentation.
|
| 395 |
+
|
| 396 |
+
* Power relations : Power relators are of the form `x_i^{re_i}`,
|
| 397 |
+
where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
|
| 398 |
+
generator and ``re`` is the corresponding relative order.
|
| 399 |
+
|
| 400 |
+
* Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
|
| 401 |
+
where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.
|
| 402 |
+
|
| 403 |
+
Returns
|
| 404 |
+
=======
|
| 405 |
+
|
| 406 |
+
A dictionary with power and conjugate relations as key and
|
| 407 |
+
their collected form as corresponding values.
|
| 408 |
+
|
| 409 |
+
Notes
|
| 410 |
+
=====
|
| 411 |
+
|
| 412 |
+
Identity Permutation is mapped with empty ``()``.
|
| 413 |
+
|
| 414 |
+
Examples
|
| 415 |
+
========
|
| 416 |
+
|
| 417 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 418 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 419 |
+
>>> S = SymmetricGroup(49).sylow_subgroup(7)
|
| 420 |
+
>>> der = S.derived_series()
|
| 421 |
+
>>> G = der[len(der)-2]
|
| 422 |
+
>>> PcGroup = G.polycyclic_group()
|
| 423 |
+
>>> collector = PcGroup.collector
|
| 424 |
+
>>> pcgs = PcGroup.pcgs
|
| 425 |
+
>>> len(pcgs)
|
| 426 |
+
6
|
| 427 |
+
>>> free_group = collector.free_group
|
| 428 |
+
>>> pc_resentation = collector.pc_presentation
|
| 429 |
+
>>> free_to_perm = {}
|
| 430 |
+
>>> for s, g in zip(free_group.symbols, pcgs):
|
| 431 |
+
... free_to_perm[s] = g
|
| 432 |
+
|
| 433 |
+
>>> for k, v in pc_resentation.items():
|
| 434 |
+
... k_array = k.array_form
|
| 435 |
+
... if v != ():
|
| 436 |
+
... v_array = v.array_form
|
| 437 |
+
... lhs = Permutation()
|
| 438 |
+
... for gen in k_array:
|
| 439 |
+
... s = gen[0]
|
| 440 |
+
... e = gen[1]
|
| 441 |
+
... lhs = lhs*free_to_perm[s]**e
|
| 442 |
+
... if v == ():
|
| 443 |
+
... assert lhs.is_identity
|
| 444 |
+
... continue
|
| 445 |
+
... rhs = Permutation()
|
| 446 |
+
... for gen in v_array:
|
| 447 |
+
... s = gen[0]
|
| 448 |
+
... e = gen[1]
|
| 449 |
+
... rhs = rhs*free_to_perm[s]**e
|
| 450 |
+
... assert lhs == rhs
|
| 451 |
+
|
| 452 |
+
"""
|
| 453 |
+
free_group = self.free_group
|
| 454 |
+
rel_order = self.relative_order
|
| 455 |
+
pc_relators = {}
|
| 456 |
+
perm_to_free = {}
|
| 457 |
+
pcgs = self.pcgs
|
| 458 |
+
|
| 459 |
+
for gen, s in zip(pcgs, free_group.generators):
|
| 460 |
+
perm_to_free[gen**-1] = s**-1
|
| 461 |
+
perm_to_free[gen] = s
|
| 462 |
+
|
| 463 |
+
pcgs = pcgs[::-1]
|
| 464 |
+
series = self.pc_series[::-1]
|
| 465 |
+
rel_order = rel_order[::-1]
|
| 466 |
+
collected_gens = []
|
| 467 |
+
|
| 468 |
+
for i, gen in enumerate(pcgs):
|
| 469 |
+
re = rel_order[i]
|
| 470 |
+
relation = perm_to_free[gen]**re
|
| 471 |
+
G = series[i]
|
| 472 |
+
|
| 473 |
+
l = G.generator_product(gen**re, original = True)
|
| 474 |
+
l.reverse()
|
| 475 |
+
|
| 476 |
+
word = free_group.identity
|
| 477 |
+
for g in l:
|
| 478 |
+
word = word*perm_to_free[g]
|
| 479 |
+
|
| 480 |
+
word = self.collected_word(word)
|
| 481 |
+
pc_relators[relation] = word if word else ()
|
| 482 |
+
self.pc_presentation = pc_relators
|
| 483 |
+
|
| 484 |
+
collected_gens.append(gen)
|
| 485 |
+
if len(collected_gens) > 1:
|
| 486 |
+
conj = collected_gens[len(collected_gens)-1]
|
| 487 |
+
conjugator = perm_to_free[conj]
|
| 488 |
+
|
| 489 |
+
for j in range(len(collected_gens)-1):
|
| 490 |
+
conjugated = perm_to_free[collected_gens[j]]
|
| 491 |
+
|
| 492 |
+
relation = conjugator**-1*conjugated*conjugator
|
| 493 |
+
gens = conj**-1*collected_gens[j]*conj
|
| 494 |
+
|
| 495 |
+
l = G.generator_product(gens, original = True)
|
| 496 |
+
l.reverse()
|
| 497 |
+
word = free_group.identity
|
| 498 |
+
for g in l:
|
| 499 |
+
word = word*perm_to_free[g]
|
| 500 |
+
|
| 501 |
+
word = self.collected_word(word)
|
| 502 |
+
pc_relators[relation] = word if word else ()
|
| 503 |
+
self.pc_presentation = pc_relators
|
| 504 |
+
|
| 505 |
+
return pc_relators
|
| 506 |
+
|
| 507 |
+
def exponent_vector(self, element):
|
| 508 |
+
r"""
|
| 509 |
+
Return the exponent vector of length equal to the
|
| 510 |
+
length of polycyclic generating sequence.
|
| 511 |
+
|
| 512 |
+
Explanation
|
| 513 |
+
===========
|
| 514 |
+
|
| 515 |
+
For a given generator/element ``g`` of the polycyclic group,
|
| 516 |
+
it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
|
| 517 |
+
where `x_i` represents polycyclic generators and ``n`` is
|
| 518 |
+
the number of generators in the free_group equal to the length
|
| 519 |
+
of pcgs.
|
| 520 |
+
|
| 521 |
+
Parameters
|
| 522 |
+
==========
|
| 523 |
+
|
| 524 |
+
element : Permutation
|
| 525 |
+
Generator of a polycyclic group.
|
| 526 |
+
|
| 527 |
+
Examples
|
| 528 |
+
========
|
| 529 |
+
|
| 530 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 531 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 532 |
+
>>> G = SymmetricGroup(4)
|
| 533 |
+
>>> PcGroup = G.polycyclic_group()
|
| 534 |
+
>>> collector = PcGroup.collector
|
| 535 |
+
>>> pcgs = PcGroup.pcgs
|
| 536 |
+
>>> collector.exponent_vector(G[0])
|
| 537 |
+
[1, 0, 0, 0]
|
| 538 |
+
>>> exp = collector.exponent_vector(G[1])
|
| 539 |
+
>>> g = Permutation()
|
| 540 |
+
>>> for i in range(len(exp)):
|
| 541 |
+
... g = g*pcgs[i]**exp[i] if exp[i] else g
|
| 542 |
+
>>> assert g == G[1]
|
| 543 |
+
|
| 544 |
+
References
|
| 545 |
+
==========
|
| 546 |
+
|
| 547 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 548 |
+
"Handbook of Computational Group Theory"
|
| 549 |
+
Section 8.1.1, Definition 8.4
|
| 550 |
+
|
| 551 |
+
"""
|
| 552 |
+
free_group = self.free_group
|
| 553 |
+
G = PermutationGroup()
|
| 554 |
+
for g in self.pcgs:
|
| 555 |
+
G = PermutationGroup([g] + G.generators)
|
| 556 |
+
gens = G.generator_product(element, original = True)
|
| 557 |
+
gens.reverse()
|
| 558 |
+
|
| 559 |
+
perm_to_free = {}
|
| 560 |
+
for sym, g in zip(free_group.generators, self.pcgs):
|
| 561 |
+
perm_to_free[g**-1] = sym**-1
|
| 562 |
+
perm_to_free[g] = sym
|
| 563 |
+
w = free_group.identity
|
| 564 |
+
for g in gens:
|
| 565 |
+
w = w*perm_to_free[g]
|
| 566 |
+
|
| 567 |
+
word = self.collected_word(w)
|
| 568 |
+
|
| 569 |
+
index = self.index
|
| 570 |
+
exp_vector = [0]*len(free_group)
|
| 571 |
+
word = word.array_form
|
| 572 |
+
for t in word:
|
| 573 |
+
exp_vector[index[t[0]]] = t[1]
|
| 574 |
+
return exp_vector
|
| 575 |
+
|
| 576 |
+
def depth(self, element):
|
| 577 |
+
r"""
|
| 578 |
+
Return the depth of a given element.
|
| 579 |
+
|
| 580 |
+
Explanation
|
| 581 |
+
===========
|
| 582 |
+
|
| 583 |
+
The depth of a given element ``g`` is defined by
|
| 584 |
+
`\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
|
| 585 |
+
and `e_i != 0`, where ``e`` represents the exponent-vector.
|
| 586 |
+
|
| 587 |
+
Examples
|
| 588 |
+
========
|
| 589 |
+
|
| 590 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 591 |
+
>>> G = SymmetricGroup(3)
|
| 592 |
+
>>> PcGroup = G.polycyclic_group()
|
| 593 |
+
>>> collector = PcGroup.collector
|
| 594 |
+
>>> collector.depth(G[0])
|
| 595 |
+
2
|
| 596 |
+
>>> collector.depth(G[1])
|
| 597 |
+
1
|
| 598 |
+
|
| 599 |
+
References
|
| 600 |
+
==========
|
| 601 |
+
|
| 602 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 603 |
+
"Handbook of Computational Group Theory"
|
| 604 |
+
Section 8.1.1, Definition 8.5
|
| 605 |
+
|
| 606 |
+
"""
|
| 607 |
+
exp_vector = self.exponent_vector(element)
|
| 608 |
+
return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)
|
| 609 |
+
|
| 610 |
+
def leading_exponent(self, element):
|
| 611 |
+
r"""
|
| 612 |
+
Return the leading non-zero exponent.
|
| 613 |
+
|
| 614 |
+
Explanation
|
| 615 |
+
===========
|
| 616 |
+
|
| 617 |
+
The leading exponent for a given element `g` is defined
|
| 618 |
+
by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.
|
| 619 |
+
|
| 620 |
+
Examples
|
| 621 |
+
========
|
| 622 |
+
|
| 623 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 624 |
+
>>> G = SymmetricGroup(3)
|
| 625 |
+
>>> PcGroup = G.polycyclic_group()
|
| 626 |
+
>>> collector = PcGroup.collector
|
| 627 |
+
>>> collector.leading_exponent(G[1])
|
| 628 |
+
1
|
| 629 |
+
|
| 630 |
+
"""
|
| 631 |
+
exp_vector = self.exponent_vector(element)
|
| 632 |
+
depth = self.depth(element)
|
| 633 |
+
if depth != len(self.pcgs)+1:
|
| 634 |
+
return exp_vector[depth-1]
|
| 635 |
+
return None
|
| 636 |
+
|
| 637 |
+
def _sift(self, z, g):
|
| 638 |
+
h = g
|
| 639 |
+
d = self.depth(h)
|
| 640 |
+
while d < len(self.pcgs) and z[d-1] != 1:
|
| 641 |
+
k = z[d-1]
|
| 642 |
+
e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
|
| 643 |
+
e = e % self.relative_order[d-1]
|
| 644 |
+
h = k**-e*h
|
| 645 |
+
d = self.depth(h)
|
| 646 |
+
return h
|
| 647 |
+
|
| 648 |
+
def induced_pcgs(self, gens):
|
| 649 |
+
"""
|
| 650 |
+
|
| 651 |
+
Parameters
|
| 652 |
+
==========
|
| 653 |
+
|
| 654 |
+
gens : list
|
| 655 |
+
A list of generators on which polycyclic subgroup
|
| 656 |
+
is to be defined.
|
| 657 |
+
|
| 658 |
+
Examples
|
| 659 |
+
========
|
| 660 |
+
|
| 661 |
+
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
| 662 |
+
>>> S = SymmetricGroup(8)
|
| 663 |
+
>>> G = S.sylow_subgroup(2)
|
| 664 |
+
>>> PcGroup = G.polycyclic_group()
|
| 665 |
+
>>> collector = PcGroup.collector
|
| 666 |
+
>>> gens = [G[0], G[1]]
|
| 667 |
+
>>> ipcgs = collector.induced_pcgs(gens)
|
| 668 |
+
>>> [gen.order() for gen in ipcgs]
|
| 669 |
+
[2, 2, 2]
|
| 670 |
+
>>> G = S.sylow_subgroup(3)
|
| 671 |
+
>>> PcGroup = G.polycyclic_group()
|
| 672 |
+
>>> collector = PcGroup.collector
|
| 673 |
+
>>> gens = [G[0], G[1]]
|
| 674 |
+
>>> ipcgs = collector.induced_pcgs(gens)
|
| 675 |
+
>>> [gen.order() for gen in ipcgs]
|
| 676 |
+
[3]
|
| 677 |
+
|
| 678 |
+
"""
|
| 679 |
+
z = [1]*len(self.pcgs)
|
| 680 |
+
G = gens
|
| 681 |
+
while G:
|
| 682 |
+
g = G.pop(0)
|
| 683 |
+
h = self._sift(z, g)
|
| 684 |
+
d = self.depth(h)
|
| 685 |
+
if d < len(self.pcgs):
|
| 686 |
+
for gen in z:
|
| 687 |
+
if gen != 1:
|
| 688 |
+
G.append(h**-1*gen**-1*h*gen)
|
| 689 |
+
z[d-1] = h;
|
| 690 |
+
z = [gen for gen in z if gen != 1]
|
| 691 |
+
return z
|
| 692 |
+
|
| 693 |
+
def constructive_membership_test(self, ipcgs, g):
|
| 694 |
+
"""
|
| 695 |
+
Return the exponent vector for induced pcgs.
|
| 696 |
+
"""
|
| 697 |
+
e = [0]*len(ipcgs)
|
| 698 |
+
h = g
|
| 699 |
+
d = self.depth(h)
|
| 700 |
+
for i, gen in enumerate(ipcgs):
|
| 701 |
+
while self.depth(gen) == d:
|
| 702 |
+
f = self.leading_exponent(h)*self.leading_exponent(gen)
|
| 703 |
+
f = f % self.relative_order[d-1]
|
| 704 |
+
h = gen**(-f)*h
|
| 705 |
+
e[i] = f
|
| 706 |
+
d = self.depth(h)
|
| 707 |
+
if h == 1:
|
| 708 |
+
return e
|
| 709 |
+
return False
|
lib/python3.12/site-packages/sympy/combinatorics/perm_groups.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
lib/python3.12/site-packages/sympy/combinatorics/permutations.py
ADDED
|
@@ -0,0 +1,3115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from collections import defaultdict
|
| 3 |
+
from collections.abc import Iterable
|
| 4 |
+
from functools import reduce
|
| 5 |
+
|
| 6 |
+
from sympy.core.parameters import global_parameters
|
| 7 |
+
from sympy.core.basic import Atom
|
| 8 |
+
from sympy.core.expr import Expr
|
| 9 |
+
from sympy.core.numbers import int_valued
|
| 10 |
+
from sympy.core.numbers import Integer
|
| 11 |
+
from sympy.core.sympify import _sympify
|
| 12 |
+
from sympy.matrices import zeros
|
| 13 |
+
from sympy.polys.polytools import lcm
|
| 14 |
+
from sympy.printing.repr import srepr
|
| 15 |
+
from sympy.utilities.iterables import (flatten, has_variety, minlex,
|
| 16 |
+
has_dups, runs, is_sequence)
|
| 17 |
+
from sympy.utilities.misc import as_int
|
| 18 |
+
from mpmath.libmp.libintmath import ifac
|
| 19 |
+
from sympy.multipledispatch import dispatch
|
| 20 |
+
|
| 21 |
+
def _af_rmul(a, b):
|
| 22 |
+
"""
|
| 23 |
+
Return the product b*a; input and output are array forms. The ith value
|
| 24 |
+
is a[b[i]].
|
| 25 |
+
|
| 26 |
+
Examples
|
| 27 |
+
========
|
| 28 |
+
|
| 29 |
+
>>> from sympy.combinatorics.permutations import _af_rmul, Permutation
|
| 30 |
+
|
| 31 |
+
>>> a, b = [1, 0, 2], [0, 2, 1]
|
| 32 |
+
>>> _af_rmul(a, b)
|
| 33 |
+
[1, 2, 0]
|
| 34 |
+
>>> [a[b[i]] for i in range(3)]
|
| 35 |
+
[1, 2, 0]
|
| 36 |
+
|
| 37 |
+
This handles the operands in reverse order compared to the ``*`` operator:
|
| 38 |
+
|
| 39 |
+
>>> a = Permutation(a)
|
| 40 |
+
>>> b = Permutation(b)
|
| 41 |
+
>>> list(a*b)
|
| 42 |
+
[2, 0, 1]
|
| 43 |
+
>>> [b(a(i)) for i in range(3)]
|
| 44 |
+
[2, 0, 1]
|
| 45 |
+
|
| 46 |
+
See Also
|
| 47 |
+
========
|
| 48 |
+
|
| 49 |
+
rmul, _af_rmuln
|
| 50 |
+
"""
|
| 51 |
+
return [a[i] for i in b]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _af_rmuln(*abc):
|
| 55 |
+
"""
|
| 56 |
+
Given [a, b, c, ...] return the product of ...*c*b*a using array forms.
|
| 57 |
+
The ith value is a[b[c[i]]].
|
| 58 |
+
|
| 59 |
+
Examples
|
| 60 |
+
========
|
| 61 |
+
|
| 62 |
+
>>> from sympy.combinatorics.permutations import _af_rmul, Permutation
|
| 63 |
+
|
| 64 |
+
>>> a, b = [1, 0, 2], [0, 2, 1]
|
| 65 |
+
>>> _af_rmul(a, b)
|
| 66 |
+
[1, 2, 0]
|
| 67 |
+
>>> [a[b[i]] for i in range(3)]
|
| 68 |
+
[1, 2, 0]
|
| 69 |
+
|
| 70 |
+
This handles the operands in reverse order compared to the ``*`` operator:
|
| 71 |
+
|
| 72 |
+
>>> a = Permutation(a); b = Permutation(b)
|
| 73 |
+
>>> list(a*b)
|
| 74 |
+
[2, 0, 1]
|
| 75 |
+
>>> [b(a(i)) for i in range(3)]
|
| 76 |
+
[2, 0, 1]
|
| 77 |
+
|
| 78 |
+
See Also
|
| 79 |
+
========
|
| 80 |
+
|
| 81 |
+
rmul, _af_rmul
|
| 82 |
+
"""
|
| 83 |
+
a = abc
|
| 84 |
+
m = len(a)
|
| 85 |
+
if m == 3:
|
| 86 |
+
p0, p1, p2 = a
|
| 87 |
+
return [p0[p1[i]] for i in p2]
|
| 88 |
+
if m == 4:
|
| 89 |
+
p0, p1, p2, p3 = a
|
| 90 |
+
return [p0[p1[p2[i]]] for i in p3]
|
| 91 |
+
if m == 5:
|
| 92 |
+
p0, p1, p2, p3, p4 = a
|
| 93 |
+
return [p0[p1[p2[p3[i]]]] for i in p4]
|
| 94 |
+
if m == 6:
|
| 95 |
+
p0, p1, p2, p3, p4, p5 = a
|
| 96 |
+
return [p0[p1[p2[p3[p4[i]]]]] for i in p5]
|
| 97 |
+
if m == 7:
|
| 98 |
+
p0, p1, p2, p3, p4, p5, p6 = a
|
| 99 |
+
return [p0[p1[p2[p3[p4[p5[i]]]]]] for i in p6]
|
| 100 |
+
if m == 8:
|
| 101 |
+
p0, p1, p2, p3, p4, p5, p6, p7 = a
|
| 102 |
+
return [p0[p1[p2[p3[p4[p5[p6[i]]]]]]] for i in p7]
|
| 103 |
+
if m == 1:
|
| 104 |
+
return a[0][:]
|
| 105 |
+
if m == 2:
|
| 106 |
+
a, b = a
|
| 107 |
+
return [a[i] for i in b]
|
| 108 |
+
if m == 0:
|
| 109 |
+
raise ValueError("String must not be empty")
|
| 110 |
+
p0 = _af_rmuln(*a[:m//2])
|
| 111 |
+
p1 = _af_rmuln(*a[m//2:])
|
| 112 |
+
return [p0[i] for i in p1]
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def _af_parity(pi):
|
| 116 |
+
"""
|
| 117 |
+
Computes the parity of a permutation in array form.
|
| 118 |
+
|
| 119 |
+
Explanation
|
| 120 |
+
===========
|
| 121 |
+
|
| 122 |
+
The parity of a permutation reflects the parity of the
|
| 123 |
+
number of inversions in the permutation, i.e., the
|
| 124 |
+
number of pairs of x and y such that x > y but p[x] < p[y].
|
| 125 |
+
|
| 126 |
+
Examples
|
| 127 |
+
========
|
| 128 |
+
|
| 129 |
+
>>> from sympy.combinatorics.permutations import _af_parity
|
| 130 |
+
>>> _af_parity([0, 1, 2, 3])
|
| 131 |
+
0
|
| 132 |
+
>>> _af_parity([3, 2, 0, 1])
|
| 133 |
+
1
|
| 134 |
+
|
| 135 |
+
See Also
|
| 136 |
+
========
|
| 137 |
+
|
| 138 |
+
Permutation
|
| 139 |
+
"""
|
| 140 |
+
n = len(pi)
|
| 141 |
+
a = [0] * n
|
| 142 |
+
c = 0
|
| 143 |
+
for j in range(n):
|
| 144 |
+
if a[j] == 0:
|
| 145 |
+
c += 1
|
| 146 |
+
a[j] = 1
|
| 147 |
+
i = j
|
| 148 |
+
while pi[i] != j:
|
| 149 |
+
i = pi[i]
|
| 150 |
+
a[i] = 1
|
| 151 |
+
return (n - c) % 2
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _af_invert(a):
|
| 155 |
+
"""
|
| 156 |
+
Finds the inverse, ~A, of a permutation, A, given in array form.
|
| 157 |
+
|
| 158 |
+
Examples
|
| 159 |
+
========
|
| 160 |
+
|
| 161 |
+
>>> from sympy.combinatorics.permutations import _af_invert, _af_rmul
|
| 162 |
+
>>> A = [1, 2, 0, 3]
|
| 163 |
+
>>> _af_invert(A)
|
| 164 |
+
[2, 0, 1, 3]
|
| 165 |
+
>>> _af_rmul(_, A)
|
| 166 |
+
[0, 1, 2, 3]
|
| 167 |
+
|
| 168 |
+
See Also
|
| 169 |
+
========
|
| 170 |
+
|
| 171 |
+
Permutation, __invert__
|
| 172 |
+
"""
|
| 173 |
+
inv_form = [0] * len(a)
|
| 174 |
+
for i, ai in enumerate(a):
|
| 175 |
+
inv_form[ai] = i
|
| 176 |
+
return inv_form
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def _af_pow(a, n):
|
| 180 |
+
"""
|
| 181 |
+
Routine for finding powers of a permutation.
|
| 182 |
+
|
| 183 |
+
Examples
|
| 184 |
+
========
|
| 185 |
+
|
| 186 |
+
>>> from sympy.combinatorics import Permutation
|
| 187 |
+
>>> from sympy.combinatorics.permutations import _af_pow
|
| 188 |
+
>>> p = Permutation([2, 0, 3, 1])
|
| 189 |
+
>>> p.order()
|
| 190 |
+
4
|
| 191 |
+
>>> _af_pow(p._array_form, 4)
|
| 192 |
+
[0, 1, 2, 3]
|
| 193 |
+
"""
|
| 194 |
+
if n == 0:
|
| 195 |
+
return list(range(len(a)))
|
| 196 |
+
if n < 0:
|
| 197 |
+
return _af_pow(_af_invert(a), -n)
|
| 198 |
+
if n == 1:
|
| 199 |
+
return a[:]
|
| 200 |
+
elif n == 2:
|
| 201 |
+
b = [a[i] for i in a]
|
| 202 |
+
elif n == 3:
|
| 203 |
+
b = [a[a[i]] for i in a]
|
| 204 |
+
elif n == 4:
|
| 205 |
+
b = [a[a[a[i]]] for i in a]
|
| 206 |
+
else:
|
| 207 |
+
# use binary multiplication
|
| 208 |
+
b = list(range(len(a)))
|
| 209 |
+
while 1:
|
| 210 |
+
if n & 1:
|
| 211 |
+
b = [b[i] for i in a]
|
| 212 |
+
n -= 1
|
| 213 |
+
if not n:
|
| 214 |
+
break
|
| 215 |
+
if n % 4 == 0:
|
| 216 |
+
a = [a[a[a[i]]] for i in a]
|
| 217 |
+
n = n // 4
|
| 218 |
+
elif n % 2 == 0:
|
| 219 |
+
a = [a[i] for i in a]
|
| 220 |
+
n = n // 2
|
| 221 |
+
return b
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def _af_commutes_with(a, b):
|
| 225 |
+
"""
|
| 226 |
+
Checks if the two permutations with array forms
|
| 227 |
+
given by ``a`` and ``b`` commute.
|
| 228 |
+
|
| 229 |
+
Examples
|
| 230 |
+
========
|
| 231 |
+
|
| 232 |
+
>>> from sympy.combinatorics.permutations import _af_commutes_with
|
| 233 |
+
>>> _af_commutes_with([1, 2, 0], [0, 2, 1])
|
| 234 |
+
False
|
| 235 |
+
|
| 236 |
+
See Also
|
| 237 |
+
========
|
| 238 |
+
|
| 239 |
+
Permutation, commutes_with
|
| 240 |
+
"""
|
| 241 |
+
return not any(a[b[i]] != b[a[i]] for i in range(len(a) - 1))
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class Cycle(dict):
|
| 245 |
+
"""
|
| 246 |
+
Wrapper around dict which provides the functionality of a disjoint cycle.
|
| 247 |
+
|
| 248 |
+
Explanation
|
| 249 |
+
===========
|
| 250 |
+
|
| 251 |
+
A cycle shows the rule to use to move subsets of elements to obtain
|
| 252 |
+
a permutation. The Cycle class is more flexible than Permutation in
|
| 253 |
+
that 1) all elements need not be present in order to investigate how
|
| 254 |
+
multiple cycles act in sequence and 2) it can contain singletons:
|
| 255 |
+
|
| 256 |
+
>>> from sympy.combinatorics.permutations import Perm, Cycle
|
| 257 |
+
|
| 258 |
+
A Cycle will automatically parse a cycle given as a tuple on the rhs:
|
| 259 |
+
|
| 260 |
+
>>> Cycle(1, 2)(2, 3)
|
| 261 |
+
(1 3 2)
|
| 262 |
+
|
| 263 |
+
The identity cycle, Cycle(), can be used to start a product:
|
| 264 |
+
|
| 265 |
+
>>> Cycle()(1, 2)(2, 3)
|
| 266 |
+
(1 3 2)
|
| 267 |
+
|
| 268 |
+
The array form of a Cycle can be obtained by calling the list
|
| 269 |
+
method (or passing it to the list function) and all elements from
|
| 270 |
+
0 will be shown:
|
| 271 |
+
|
| 272 |
+
>>> a = Cycle(1, 2)
|
| 273 |
+
>>> a.list()
|
| 274 |
+
[0, 2, 1]
|
| 275 |
+
>>> list(a)
|
| 276 |
+
[0, 2, 1]
|
| 277 |
+
|
| 278 |
+
If a larger (or smaller) range is desired use the list method and
|
| 279 |
+
provide the desired size -- but the Cycle cannot be truncated to
|
| 280 |
+
a size smaller than the largest element that is out of place:
|
| 281 |
+
|
| 282 |
+
>>> b = Cycle(2, 4)(1, 2)(3, 1, 4)(1, 3)
|
| 283 |
+
>>> b.list()
|
| 284 |
+
[0, 2, 1, 3, 4]
|
| 285 |
+
>>> b.list(b.size + 1)
|
| 286 |
+
[0, 2, 1, 3, 4, 5]
|
| 287 |
+
>>> b.list(-1)
|
| 288 |
+
[0, 2, 1]
|
| 289 |
+
|
| 290 |
+
Singletons are not shown when printing with one exception: the largest
|
| 291 |
+
element is always shown -- as a singleton if necessary:
|
| 292 |
+
|
| 293 |
+
>>> Cycle(1, 4, 10)(4, 5)
|
| 294 |
+
(1 5 4 10)
|
| 295 |
+
>>> Cycle(1, 2)(4)(5)(10)
|
| 296 |
+
(1 2)(10)
|
| 297 |
+
|
| 298 |
+
The array form can be used to instantiate a Permutation so other
|
| 299 |
+
properties of the permutation can be investigated:
|
| 300 |
+
|
| 301 |
+
>>> Perm(Cycle(1, 2)(3, 4).list()).transpositions()
|
| 302 |
+
[(1, 2), (3, 4)]
|
| 303 |
+
|
| 304 |
+
Notes
|
| 305 |
+
=====
|
| 306 |
+
|
| 307 |
+
The underlying structure of the Cycle is a dictionary and although
|
| 308 |
+
the __iter__ method has been redefined to give the array form of the
|
| 309 |
+
cycle, the underlying dictionary items are still available with the
|
| 310 |
+
such methods as items():
|
| 311 |
+
|
| 312 |
+
>>> list(Cycle(1, 2).items())
|
| 313 |
+
[(1, 2), (2, 1)]
|
| 314 |
+
|
| 315 |
+
See Also
|
| 316 |
+
========
|
| 317 |
+
|
| 318 |
+
Permutation
|
| 319 |
+
"""
|
| 320 |
+
def __missing__(self, arg):
|
| 321 |
+
"""Enter arg into dictionary and return arg."""
|
| 322 |
+
return as_int(arg)
|
| 323 |
+
|
| 324 |
+
def __iter__(self):
|
| 325 |
+
yield from self.list()
|
| 326 |
+
|
| 327 |
+
def __call__(self, *other):
|
| 328 |
+
"""Return product of cycles processed from R to L.
|
| 329 |
+
|
| 330 |
+
Examples
|
| 331 |
+
========
|
| 332 |
+
|
| 333 |
+
>>> from sympy.combinatorics import Cycle
|
| 334 |
+
>>> Cycle(1, 2)(2, 3)
|
| 335 |
+
(1 3 2)
|
| 336 |
+
|
| 337 |
+
An instance of a Cycle will automatically parse list-like
|
| 338 |
+
objects and Permutations that are on the right. It is more
|
| 339 |
+
flexible than the Permutation in that all elements need not
|
| 340 |
+
be present:
|
| 341 |
+
|
| 342 |
+
>>> a = Cycle(1, 2)
|
| 343 |
+
>>> a(2, 3)
|
| 344 |
+
(1 3 2)
|
| 345 |
+
>>> a(2, 3)(4, 5)
|
| 346 |
+
(1 3 2)(4 5)
|
| 347 |
+
|
| 348 |
+
"""
|
| 349 |
+
rv = Cycle(*other)
|
| 350 |
+
for k, v in zip(list(self.keys()), [rv[self[k]] for k in self.keys()]):
|
| 351 |
+
rv[k] = v
|
| 352 |
+
return rv
|
| 353 |
+
|
| 354 |
+
def list(self, size=None):
|
| 355 |
+
"""Return the cycles as an explicit list starting from 0 up
|
| 356 |
+
to the greater of the largest value in the cycles and size.
|
| 357 |
+
|
| 358 |
+
Truncation of trailing unmoved items will occur when size
|
| 359 |
+
is less than the maximum element in the cycle; if this is
|
| 360 |
+
desired, setting ``size=-1`` will guarantee such trimming.
|
| 361 |
+
|
| 362 |
+
Examples
|
| 363 |
+
========
|
| 364 |
+
|
| 365 |
+
>>> from sympy.combinatorics import Cycle
|
| 366 |
+
>>> p = Cycle(2, 3)(4, 5)
|
| 367 |
+
>>> p.list()
|
| 368 |
+
[0, 1, 3, 2, 5, 4]
|
| 369 |
+
>>> p.list(10)
|
| 370 |
+
[0, 1, 3, 2, 5, 4, 6, 7, 8, 9]
|
| 371 |
+
|
| 372 |
+
Passing a length too small will trim trailing, unchanged elements
|
| 373 |
+
in the permutation:
|
| 374 |
+
|
| 375 |
+
>>> Cycle(2, 4)(1, 2, 4).list(-1)
|
| 376 |
+
[0, 2, 1]
|
| 377 |
+
"""
|
| 378 |
+
if not self and size is None:
|
| 379 |
+
raise ValueError('must give size for empty Cycle')
|
| 380 |
+
if size is not None:
|
| 381 |
+
big = max([i for i in self.keys() if self[i] != i] + [0])
|
| 382 |
+
size = max(size, big + 1)
|
| 383 |
+
else:
|
| 384 |
+
size = self.size
|
| 385 |
+
return [self[i] for i in range(size)]
|
| 386 |
+
|
| 387 |
+
def __repr__(self):
|
| 388 |
+
"""We want it to print as a Cycle, not as a dict.
|
| 389 |
+
|
| 390 |
+
Examples
|
| 391 |
+
========
|
| 392 |
+
|
| 393 |
+
>>> from sympy.combinatorics import Cycle
|
| 394 |
+
>>> Cycle(1, 2)
|
| 395 |
+
(1 2)
|
| 396 |
+
>>> print(_)
|
| 397 |
+
(1 2)
|
| 398 |
+
>>> list(Cycle(1, 2).items())
|
| 399 |
+
[(1, 2), (2, 1)]
|
| 400 |
+
"""
|
| 401 |
+
if not self:
|
| 402 |
+
return 'Cycle()'
|
| 403 |
+
cycles = Permutation(self).cyclic_form
|
| 404 |
+
s = ''.join(str(tuple(c)) for c in cycles)
|
| 405 |
+
big = self.size - 1
|
| 406 |
+
if not any(i == big for c in cycles for i in c):
|
| 407 |
+
s += '(%s)' % big
|
| 408 |
+
return 'Cycle%s' % s
|
| 409 |
+
|
| 410 |
+
def __str__(self):
|
| 411 |
+
"""We want it to be printed in a Cycle notation with no
|
| 412 |
+
comma in-between.
|
| 413 |
+
|
| 414 |
+
Examples
|
| 415 |
+
========
|
| 416 |
+
|
| 417 |
+
>>> from sympy.combinatorics import Cycle
|
| 418 |
+
>>> Cycle(1, 2)
|
| 419 |
+
(1 2)
|
| 420 |
+
>>> Cycle(1, 2, 4)(5, 6)
|
| 421 |
+
(1 2 4)(5 6)
|
| 422 |
+
"""
|
| 423 |
+
if not self:
|
| 424 |
+
return '()'
|
| 425 |
+
cycles = Permutation(self).cyclic_form
|
| 426 |
+
s = ''.join(str(tuple(c)) for c in cycles)
|
| 427 |
+
big = self.size - 1
|
| 428 |
+
if not any(i == big for c in cycles for i in c):
|
| 429 |
+
s += '(%s)' % big
|
| 430 |
+
s = s.replace(',', '')
|
| 431 |
+
return s
|
| 432 |
+
|
| 433 |
+
def __init__(self, *args):
|
| 434 |
+
"""Load up a Cycle instance with the values for the cycle.
|
| 435 |
+
|
| 436 |
+
Examples
|
| 437 |
+
========
|
| 438 |
+
|
| 439 |
+
>>> from sympy.combinatorics import Cycle
|
| 440 |
+
>>> Cycle(1, 2, 6)
|
| 441 |
+
(1 2 6)
|
| 442 |
+
"""
|
| 443 |
+
|
| 444 |
+
if not args:
|
| 445 |
+
return
|
| 446 |
+
if len(args) == 1:
|
| 447 |
+
if isinstance(args[0], Permutation):
|
| 448 |
+
for c in args[0].cyclic_form:
|
| 449 |
+
self.update(self(*c))
|
| 450 |
+
return
|
| 451 |
+
elif isinstance(args[0], Cycle):
|
| 452 |
+
for k, v in args[0].items():
|
| 453 |
+
self[k] = v
|
| 454 |
+
return
|
| 455 |
+
args = [as_int(a) for a in args]
|
| 456 |
+
if any(i < 0 for i in args):
|
| 457 |
+
raise ValueError('negative integers are not allowed in a cycle.')
|
| 458 |
+
if has_dups(args):
|
| 459 |
+
raise ValueError('All elements must be unique in a cycle.')
|
| 460 |
+
for i in range(-len(args), 0):
|
| 461 |
+
self[args[i]] = args[i + 1]
|
| 462 |
+
|
| 463 |
+
@property
|
| 464 |
+
def size(self):
|
| 465 |
+
if not self:
|
| 466 |
+
return 0
|
| 467 |
+
return max(self.keys()) + 1
|
| 468 |
+
|
| 469 |
+
def copy(self):
|
| 470 |
+
return Cycle(self)
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
class Permutation(Atom):
|
| 474 |
+
r"""
|
| 475 |
+
A permutation, alternatively known as an 'arrangement number' or 'ordering'
|
| 476 |
+
is an arrangement of the elements of an ordered list into a one-to-one
|
| 477 |
+
mapping with itself. The permutation of a given arrangement is given by
|
| 478 |
+
indicating the positions of the elements after re-arrangement [2]_. For
|
| 479 |
+
example, if one started with elements ``[x, y, a, b]`` (in that order) and
|
| 480 |
+
they were reordered as ``[x, y, b, a]`` then the permutation would be
|
| 481 |
+
``[0, 1, 3, 2]``. Notice that (in SymPy) the first element is always referred
|
| 482 |
+
to as 0 and the permutation uses the indices of the elements in the
|
| 483 |
+
original ordering, not the elements ``(a, b, ...)`` themselves.
|
| 484 |
+
|
| 485 |
+
>>> from sympy.combinatorics import Permutation
|
| 486 |
+
>>> from sympy import init_printing
|
| 487 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 488 |
+
|
| 489 |
+
Permutations Notation
|
| 490 |
+
=====================
|
| 491 |
+
|
| 492 |
+
Permutations are commonly represented in disjoint cycle or array forms.
|
| 493 |
+
|
| 494 |
+
Array Notation and 2-line Form
|
| 495 |
+
------------------------------------
|
| 496 |
+
|
| 497 |
+
In the 2-line form, the elements and their final positions are shown
|
| 498 |
+
as a matrix with 2 rows:
|
| 499 |
+
|
| 500 |
+
[0 1 2 ... n-1]
|
| 501 |
+
[p(0) p(1) p(2) ... p(n-1)]
|
| 502 |
+
|
| 503 |
+
Since the first line is always ``range(n)``, where n is the size of p,
|
| 504 |
+
it is sufficient to represent the permutation by the second line,
|
| 505 |
+
referred to as the "array form" of the permutation. This is entered
|
| 506 |
+
in brackets as the argument to the Permutation class:
|
| 507 |
+
|
| 508 |
+
>>> p = Permutation([0, 2, 1]); p
|
| 509 |
+
Permutation([0, 2, 1])
|
| 510 |
+
|
| 511 |
+
Given i in range(p.size), the permutation maps i to i^p
|
| 512 |
+
|
| 513 |
+
>>> [i^p for i in range(p.size)]
|
| 514 |
+
[0, 2, 1]
|
| 515 |
+
|
| 516 |
+
The composite of two permutations p*q means first apply p, then q, so
|
| 517 |
+
i^(p*q) = (i^p)^q which is i^p^q according to Python precedence rules:
|
| 518 |
+
|
| 519 |
+
>>> q = Permutation([2, 1, 0])
|
| 520 |
+
>>> [i^p^q for i in range(3)]
|
| 521 |
+
[2, 0, 1]
|
| 522 |
+
>>> [i^(p*q) for i in range(3)]
|
| 523 |
+
[2, 0, 1]
|
| 524 |
+
|
| 525 |
+
One can use also the notation p(i) = i^p, but then the composition
|
| 526 |
+
rule is (p*q)(i) = q(p(i)), not p(q(i)):
|
| 527 |
+
|
| 528 |
+
>>> [(p*q)(i) for i in range(p.size)]
|
| 529 |
+
[2, 0, 1]
|
| 530 |
+
>>> [q(p(i)) for i in range(p.size)]
|
| 531 |
+
[2, 0, 1]
|
| 532 |
+
>>> [p(q(i)) for i in range(p.size)]
|
| 533 |
+
[1, 2, 0]
|
| 534 |
+
|
| 535 |
+
Disjoint Cycle Notation
|
| 536 |
+
-----------------------
|
| 537 |
+
|
| 538 |
+
In disjoint cycle notation, only the elements that have shifted are
|
| 539 |
+
indicated.
|
| 540 |
+
|
| 541 |
+
For example, [1, 3, 2, 0] can be represented as (0, 1, 3)(2).
|
| 542 |
+
This can be understood from the 2 line format of the given permutation.
|
| 543 |
+
In the 2-line form,
|
| 544 |
+
[0 1 2 3]
|
| 545 |
+
[1 3 2 0]
|
| 546 |
+
|
| 547 |
+
The element in the 0th position is 1, so 0 -> 1. The element in the 1st
|
| 548 |
+
position is three, so 1 -> 3. And the element in the third position is again
|
| 549 |
+
0, so 3 -> 0. Thus, 0 -> 1 -> 3 -> 0, and 2 -> 2. Thus, this can be represented
|
| 550 |
+
as 2 cycles: (0, 1, 3)(2).
|
| 551 |
+
In common notation, singular cycles are not explicitly written as they can be
|
| 552 |
+
inferred implicitly.
|
| 553 |
+
|
| 554 |
+
Only the relative ordering of elements in a cycle matter:
|
| 555 |
+
|
| 556 |
+
>>> Permutation(1,2,3) == Permutation(2,3,1) == Permutation(3,1,2)
|
| 557 |
+
True
|
| 558 |
+
|
| 559 |
+
The disjoint cycle notation is convenient when representing
|
| 560 |
+
permutations that have several cycles in them:
|
| 561 |
+
|
| 562 |
+
>>> Permutation(1, 2)(3, 5) == Permutation([[1, 2], [3, 5]])
|
| 563 |
+
True
|
| 564 |
+
|
| 565 |
+
It also provides some economy in entry when computing products of
|
| 566 |
+
permutations that are written in disjoint cycle notation:
|
| 567 |
+
|
| 568 |
+
>>> Permutation(1, 2)(1, 3)(2, 3)
|
| 569 |
+
Permutation([0, 3, 2, 1])
|
| 570 |
+
>>> _ == Permutation([[1, 2]])*Permutation([[1, 3]])*Permutation([[2, 3]])
|
| 571 |
+
True
|
| 572 |
+
|
| 573 |
+
Caution: when the cycles have common elements between them then the order
|
| 574 |
+
in which the permutations are applied matters. This module applies
|
| 575 |
+
the permutations from *left to right*.
|
| 576 |
+
|
| 577 |
+
>>> Permutation(1, 2)(2, 3) == Permutation([(1, 2), (2, 3)])
|
| 578 |
+
True
|
| 579 |
+
>>> Permutation(1, 2)(2, 3).list()
|
| 580 |
+
[0, 3, 1, 2]
|
| 581 |
+
|
| 582 |
+
In the above case, (1,2) is computed before (2,3).
|
| 583 |
+
As 0 -> 0, 0 -> 0, element in position 0 is 0.
|
| 584 |
+
As 1 -> 2, 2 -> 3, element in position 1 is 3.
|
| 585 |
+
As 2 -> 1, 1 -> 1, element in position 2 is 1.
|
| 586 |
+
As 3 -> 3, 3 -> 2, element in position 3 is 2.
|
| 587 |
+
|
| 588 |
+
If the first and second elements had been
|
| 589 |
+
swapped first, followed by the swapping of the second
|
| 590 |
+
and third, the result would have been [0, 2, 3, 1].
|
| 591 |
+
If, you want to apply the cycles in the conventional
|
| 592 |
+
right to left order, call the function with arguments in reverse order
|
| 593 |
+
as demonstrated below:
|
| 594 |
+
|
| 595 |
+
>>> Permutation([(1, 2), (2, 3)][::-1]).list()
|
| 596 |
+
[0, 2, 3, 1]
|
| 597 |
+
|
| 598 |
+
Entering a singleton in a permutation is a way to indicate the size of the
|
| 599 |
+
permutation. The ``size`` keyword can also be used.
|
| 600 |
+
|
| 601 |
+
Array-form entry:
|
| 602 |
+
|
| 603 |
+
>>> Permutation([[1, 2], [9]])
|
| 604 |
+
Permutation([0, 2, 1], size=10)
|
| 605 |
+
>>> Permutation([[1, 2]], size=10)
|
| 606 |
+
Permutation([0, 2, 1], size=10)
|
| 607 |
+
|
| 608 |
+
Cyclic-form entry:
|
| 609 |
+
|
| 610 |
+
>>> Permutation(1, 2, size=10)
|
| 611 |
+
Permutation([0, 2, 1], size=10)
|
| 612 |
+
>>> Permutation(9)(1, 2)
|
| 613 |
+
Permutation([0, 2, 1], size=10)
|
| 614 |
+
|
| 615 |
+
Caution: no singleton containing an element larger than the largest
|
| 616 |
+
in any previous cycle can be entered. This is an important difference
|
| 617 |
+
in how Permutation and Cycle handle the ``__call__`` syntax. A singleton
|
| 618 |
+
argument at the start of a Permutation performs instantiation of the
|
| 619 |
+
Permutation and is permitted:
|
| 620 |
+
|
| 621 |
+
>>> Permutation(5)
|
| 622 |
+
Permutation([], size=6)
|
| 623 |
+
|
| 624 |
+
A singleton entered after instantiation is a call to the permutation
|
| 625 |
+
-- a function call -- and if the argument is out of range it will
|
| 626 |
+
trigger an error. For this reason, it is better to start the cycle
|
| 627 |
+
with the singleton:
|
| 628 |
+
|
| 629 |
+
The following fails because there is no element 3:
|
| 630 |
+
|
| 631 |
+
>>> Permutation(1, 2)(3)
|
| 632 |
+
Traceback (most recent call last):
|
| 633 |
+
...
|
| 634 |
+
IndexError: list index out of range
|
| 635 |
+
|
| 636 |
+
This is ok: only the call to an out of range singleton is prohibited;
|
| 637 |
+
otherwise the permutation autosizes:
|
| 638 |
+
|
| 639 |
+
>>> Permutation(3)(1, 2)
|
| 640 |
+
Permutation([0, 2, 1, 3])
|
| 641 |
+
>>> Permutation(1, 2)(3, 4) == Permutation(3, 4)(1, 2)
|
| 642 |
+
True
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
Equality testing
|
| 646 |
+
----------------
|
| 647 |
+
|
| 648 |
+
The array forms must be the same in order for permutations to be equal:
|
| 649 |
+
|
| 650 |
+
>>> Permutation([1, 0, 2, 3]) == Permutation([1, 0])
|
| 651 |
+
False
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
Identity Permutation
|
| 655 |
+
--------------------
|
| 656 |
+
|
| 657 |
+
The identity permutation is a permutation in which no element is out of
|
| 658 |
+
place. It can be entered in a variety of ways. All the following create
|
| 659 |
+
an identity permutation of size 4:
|
| 660 |
+
|
| 661 |
+
>>> I = Permutation([0, 1, 2, 3])
|
| 662 |
+
>>> all(p == I for p in [
|
| 663 |
+
... Permutation(3),
|
| 664 |
+
... Permutation(range(4)),
|
| 665 |
+
... Permutation([], size=4),
|
| 666 |
+
... Permutation(size=4)])
|
| 667 |
+
True
|
| 668 |
+
|
| 669 |
+
Watch out for entering the range *inside* a set of brackets (which is
|
| 670 |
+
cycle notation):
|
| 671 |
+
|
| 672 |
+
>>> I == Permutation([range(4)])
|
| 673 |
+
False
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
Permutation Printing
|
| 677 |
+
====================
|
| 678 |
+
|
| 679 |
+
There are a few things to note about how Permutations are printed.
|
| 680 |
+
|
| 681 |
+
.. deprecated:: 1.6
|
| 682 |
+
|
| 683 |
+
Configuring Permutation printing by setting
|
| 684 |
+
``Permutation.print_cyclic`` is deprecated. Users should use the
|
| 685 |
+
``perm_cyclic`` flag to the printers, as described below.
|
| 686 |
+
|
| 687 |
+
1) If you prefer one form (array or cycle) over another, you can set
|
| 688 |
+
``init_printing`` with the ``perm_cyclic`` flag.
|
| 689 |
+
|
| 690 |
+
>>> from sympy import init_printing
|
| 691 |
+
>>> p = Permutation(1, 2)(4, 5)(3, 4)
|
| 692 |
+
>>> p
|
| 693 |
+
Permutation([0, 2, 1, 4, 5, 3])
|
| 694 |
+
|
| 695 |
+
>>> init_printing(perm_cyclic=True, pretty_print=False)
|
| 696 |
+
>>> p
|
| 697 |
+
(1 2)(3 4 5)
|
| 698 |
+
|
| 699 |
+
2) Regardless of the setting, a list of elements in the array for cyclic
|
| 700 |
+
form can be obtained and either of those can be copied and supplied as
|
| 701 |
+
the argument to Permutation:
|
| 702 |
+
|
| 703 |
+
>>> p.array_form
|
| 704 |
+
[0, 2, 1, 4, 5, 3]
|
| 705 |
+
>>> p.cyclic_form
|
| 706 |
+
[[1, 2], [3, 4, 5]]
|
| 707 |
+
>>> Permutation(_) == p
|
| 708 |
+
True
|
| 709 |
+
|
| 710 |
+
3) Printing is economical in that as little as possible is printed while
|
| 711 |
+
retaining all information about the size of the permutation:
|
| 712 |
+
|
| 713 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 714 |
+
>>> Permutation([1, 0, 2, 3])
|
| 715 |
+
Permutation([1, 0, 2, 3])
|
| 716 |
+
>>> Permutation([1, 0, 2, 3], size=20)
|
| 717 |
+
Permutation([1, 0], size=20)
|
| 718 |
+
>>> Permutation([1, 0, 2, 4, 3, 5, 6], size=20)
|
| 719 |
+
Permutation([1, 0, 2, 4, 3], size=20)
|
| 720 |
+
|
| 721 |
+
>>> p = Permutation([1, 0, 2, 3])
|
| 722 |
+
>>> init_printing(perm_cyclic=True, pretty_print=False)
|
| 723 |
+
>>> p
|
| 724 |
+
(3)(0 1)
|
| 725 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 726 |
+
|
| 727 |
+
The 2 was not printed but it is still there as can be seen with the
|
| 728 |
+
array_form and size methods:
|
| 729 |
+
|
| 730 |
+
>>> p.array_form
|
| 731 |
+
[1, 0, 2, 3]
|
| 732 |
+
>>> p.size
|
| 733 |
+
4
|
| 734 |
+
|
| 735 |
+
Short introduction to other methods
|
| 736 |
+
===================================
|
| 737 |
+
|
| 738 |
+
The permutation can act as a bijective function, telling what element is
|
| 739 |
+
located at a given position
|
| 740 |
+
|
| 741 |
+
>>> q = Permutation([5, 2, 3, 4, 1, 0])
|
| 742 |
+
>>> q.array_form[1] # the hard way
|
| 743 |
+
2
|
| 744 |
+
>>> q(1) # the easy way
|
| 745 |
+
2
|
| 746 |
+
>>> {i: q(i) for i in range(q.size)} # showing the bijection
|
| 747 |
+
{0: 5, 1: 2, 2: 3, 3: 4, 4: 1, 5: 0}
|
| 748 |
+
|
| 749 |
+
The full cyclic form (including singletons) can be obtained:
|
| 750 |
+
|
| 751 |
+
>>> p.full_cyclic_form
|
| 752 |
+
[[0, 1], [2], [3]]
|
| 753 |
+
|
| 754 |
+
Any permutation can be factored into transpositions of pairs of elements:
|
| 755 |
+
|
| 756 |
+
>>> Permutation([[1, 2], [3, 4, 5]]).transpositions()
|
| 757 |
+
[(1, 2), (3, 5), (3, 4)]
|
| 758 |
+
>>> Permutation.rmul(*[Permutation([ti], size=6) for ti in _]).cyclic_form
|
| 759 |
+
[[1, 2], [3, 4, 5]]
|
| 760 |
+
|
| 761 |
+
The number of permutations on a set of n elements is given by n! and is
|
| 762 |
+
called the cardinality.
|
| 763 |
+
|
| 764 |
+
>>> p.size
|
| 765 |
+
4
|
| 766 |
+
>>> p.cardinality
|
| 767 |
+
24
|
| 768 |
+
|
| 769 |
+
A given permutation has a rank among all the possible permutations of the
|
| 770 |
+
same elements, but what that rank is depends on how the permutations are
|
| 771 |
+
enumerated. (There are a number of different methods of doing so.) The
|
| 772 |
+
lexicographic rank is given by the rank method and this rank is used to
|
| 773 |
+
increment a permutation with addition/subtraction:
|
| 774 |
+
|
| 775 |
+
>>> p.rank()
|
| 776 |
+
6
|
| 777 |
+
>>> p + 1
|
| 778 |
+
Permutation([1, 0, 3, 2])
|
| 779 |
+
>>> p.next_lex()
|
| 780 |
+
Permutation([1, 0, 3, 2])
|
| 781 |
+
>>> _.rank()
|
| 782 |
+
7
|
| 783 |
+
>>> p.unrank_lex(p.size, rank=7)
|
| 784 |
+
Permutation([1, 0, 3, 2])
|
| 785 |
+
|
| 786 |
+
The product of two permutations p and q is defined as their composition as
|
| 787 |
+
functions, (p*q)(i) = q(p(i)) [6]_.
|
| 788 |
+
|
| 789 |
+
>>> p = Permutation([1, 0, 2, 3])
|
| 790 |
+
>>> q = Permutation([2, 3, 1, 0])
|
| 791 |
+
>>> list(q*p)
|
| 792 |
+
[2, 3, 0, 1]
|
| 793 |
+
>>> list(p*q)
|
| 794 |
+
[3, 2, 1, 0]
|
| 795 |
+
>>> [q(p(i)) for i in range(p.size)]
|
| 796 |
+
[3, 2, 1, 0]
|
| 797 |
+
|
| 798 |
+
The permutation can be 'applied' to any list-like object, not only
|
| 799 |
+
Permutations:
|
| 800 |
+
|
| 801 |
+
>>> p(['zero', 'one', 'four', 'two'])
|
| 802 |
+
['one', 'zero', 'four', 'two']
|
| 803 |
+
>>> p('zo42')
|
| 804 |
+
['o', 'z', '4', '2']
|
| 805 |
+
|
| 806 |
+
If you have a list of arbitrary elements, the corresponding permutation
|
| 807 |
+
can be found with the from_sequence method:
|
| 808 |
+
|
| 809 |
+
>>> Permutation.from_sequence('SymPy')
|
| 810 |
+
Permutation([1, 3, 2, 0, 4])
|
| 811 |
+
|
| 812 |
+
Checking if a Permutation is contained in a Group
|
| 813 |
+
=================================================
|
| 814 |
+
|
| 815 |
+
Generally if you have a group of permutations G on n symbols, and
|
| 816 |
+
you're checking if a permutation on less than n symbols is part
|
| 817 |
+
of that group, the check will fail.
|
| 818 |
+
|
| 819 |
+
Here is an example for n=5 and we check if the cycle
|
| 820 |
+
(1,2,3) is in G:
|
| 821 |
+
|
| 822 |
+
>>> from sympy import init_printing
|
| 823 |
+
>>> init_printing(perm_cyclic=True, pretty_print=False)
|
| 824 |
+
>>> from sympy.combinatorics import Cycle, Permutation
|
| 825 |
+
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
| 826 |
+
>>> G = PermutationGroup(Cycle(2, 3)(4, 5), Cycle(1, 2, 3, 4, 5))
|
| 827 |
+
>>> p1 = Permutation(Cycle(2, 5, 3))
|
| 828 |
+
>>> p2 = Permutation(Cycle(1, 2, 3))
|
| 829 |
+
>>> a1 = Permutation(Cycle(1, 2, 3).list(6))
|
| 830 |
+
>>> a2 = Permutation(Cycle(1, 2, 3)(5))
|
| 831 |
+
>>> a3 = Permutation(Cycle(1, 2, 3),size=6)
|
| 832 |
+
>>> for p in [p1,p2,a1,a2,a3]: p, G.contains(p)
|
| 833 |
+
((2 5 3), True)
|
| 834 |
+
((1 2 3), False)
|
| 835 |
+
((5)(1 2 3), True)
|
| 836 |
+
((5)(1 2 3), True)
|
| 837 |
+
((5)(1 2 3), True)
|
| 838 |
+
|
| 839 |
+
The check for p2 above will fail.
|
| 840 |
+
|
| 841 |
+
Checking if p1 is in G works because SymPy knows
|
| 842 |
+
G is a group on 5 symbols, and p1 is also on 5 symbols
|
| 843 |
+
(its largest element is 5).
|
| 844 |
+
|
| 845 |
+
For ``a1``, the ``.list(6)`` call will extend the permutation to 5
|
| 846 |
+
symbols, so the test will work as well. In the case of ``a2`` the
|
| 847 |
+
permutation is being extended to 5 symbols by using a singleton,
|
| 848 |
+
and in the case of ``a3`` it's extended through the constructor
|
| 849 |
+
argument ``size=6``.
|
| 850 |
+
|
| 851 |
+
There is another way to do this, which is to tell the ``contains``
|
| 852 |
+
method that the number of symbols the group is on does not need to
|
| 853 |
+
match perfectly the number of symbols for the permutation:
|
| 854 |
+
|
| 855 |
+
>>> G.contains(p2,strict=False)
|
| 856 |
+
True
|
| 857 |
+
|
| 858 |
+
This can be via the ``strict`` argument to the ``contains`` method,
|
| 859 |
+
and SymPy will try to extend the permutation on its own and then
|
| 860 |
+
perform the containment check.
|
| 861 |
+
|
| 862 |
+
See Also
|
| 863 |
+
========
|
| 864 |
+
|
| 865 |
+
Cycle
|
| 866 |
+
|
| 867 |
+
References
|
| 868 |
+
==========
|
| 869 |
+
|
| 870 |
+
.. [1] Skiena, S. 'Permutations.' 1.1 in Implementing Discrete Mathematics
|
| 871 |
+
Combinatorics and Graph Theory with Mathematica. Reading, MA:
|
| 872 |
+
Addison-Wesley, pp. 3-16, 1990.
|
| 873 |
+
|
| 874 |
+
.. [2] Knuth, D. E. The Art of Computer Programming, Vol. 4: Combinatorial
|
| 875 |
+
Algorithms, 1st ed. Reading, MA: Addison-Wesley, 2011.
|
| 876 |
+
|
| 877 |
+
.. [3] Wendy Myrvold and Frank Ruskey. 2001. Ranking and unranking
|
| 878 |
+
permutations in linear time. Inf. Process. Lett. 79, 6 (September 2001),
|
| 879 |
+
281-284. DOI=10.1016/S0020-0190(01)00141-7
|
| 880 |
+
|
| 881 |
+
.. [4] D. L. Kreher, D. R. Stinson 'Combinatorial Algorithms'
|
| 882 |
+
CRC Press, 1999
|
| 883 |
+
|
| 884 |
+
.. [5] Graham, R. L.; Knuth, D. E.; and Patashnik, O.
|
| 885 |
+
Concrete Mathematics: A Foundation for Computer Science, 2nd ed.
|
| 886 |
+
Reading, MA: Addison-Wesley, 1994.
|
| 887 |
+
|
| 888 |
+
.. [6] https://en.wikipedia.org/w/index.php?oldid=499948155#Product_and_inverse
|
| 889 |
+
|
| 890 |
+
.. [7] https://en.wikipedia.org/wiki/Lehmer_code
|
| 891 |
+
|
| 892 |
+
"""
|
| 893 |
+
|
| 894 |
+
is_Permutation = True
|
| 895 |
+
|
| 896 |
+
_array_form = None
|
| 897 |
+
_cyclic_form = None
|
| 898 |
+
_cycle_structure = None
|
| 899 |
+
_size = None
|
| 900 |
+
_rank = None
|
| 901 |
+
|
| 902 |
+
def __new__(cls, *args, size=None, **kwargs):
|
| 903 |
+
"""
|
| 904 |
+
Constructor for the Permutation object from a list or a
|
| 905 |
+
list of lists in which all elements of the permutation may
|
| 906 |
+
appear only once.
|
| 907 |
+
|
| 908 |
+
Examples
|
| 909 |
+
========
|
| 910 |
+
|
| 911 |
+
>>> from sympy.combinatorics import Permutation
|
| 912 |
+
>>> from sympy import init_printing
|
| 913 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 914 |
+
|
| 915 |
+
Permutations entered in array-form are left unaltered:
|
| 916 |
+
|
| 917 |
+
>>> Permutation([0, 2, 1])
|
| 918 |
+
Permutation([0, 2, 1])
|
| 919 |
+
|
| 920 |
+
Permutations entered in cyclic form are converted to array form;
|
| 921 |
+
singletons need not be entered, but can be entered to indicate the
|
| 922 |
+
largest element:
|
| 923 |
+
|
| 924 |
+
>>> Permutation([[4, 5, 6], [0, 1]])
|
| 925 |
+
Permutation([1, 0, 2, 3, 5, 6, 4])
|
| 926 |
+
>>> Permutation([[4, 5, 6], [0, 1], [19]])
|
| 927 |
+
Permutation([1, 0, 2, 3, 5, 6, 4], size=20)
|
| 928 |
+
|
| 929 |
+
All manipulation of permutations assumes that the smallest element
|
| 930 |
+
is 0 (in keeping with 0-based indexing in Python) so if the 0 is
|
| 931 |
+
missing when entering a permutation in array form, an error will be
|
| 932 |
+
raised:
|
| 933 |
+
|
| 934 |
+
>>> Permutation([2, 1])
|
| 935 |
+
Traceback (most recent call last):
|
| 936 |
+
...
|
| 937 |
+
ValueError: Integers 0 through 2 must be present.
|
| 938 |
+
|
| 939 |
+
If a permutation is entered in cyclic form, it can be entered without
|
| 940 |
+
singletons and the ``size`` specified so those values can be filled
|
| 941 |
+
in, otherwise the array form will only extend to the maximum value
|
| 942 |
+
in the cycles:
|
| 943 |
+
|
| 944 |
+
>>> Permutation([[1, 4], [3, 5, 2]], size=10)
|
| 945 |
+
Permutation([0, 4, 3, 5, 1, 2], size=10)
|
| 946 |
+
>>> _.array_form
|
| 947 |
+
[0, 4, 3, 5, 1, 2, 6, 7, 8, 9]
|
| 948 |
+
"""
|
| 949 |
+
if size is not None:
|
| 950 |
+
size = int(size)
|
| 951 |
+
|
| 952 |
+
#a) ()
|
| 953 |
+
#b) (1) = identity
|
| 954 |
+
#c) (1, 2) = cycle
|
| 955 |
+
#d) ([1, 2, 3]) = array form
|
| 956 |
+
#e) ([[1, 2]]) = cyclic form
|
| 957 |
+
#f) (Cycle) = conversion to permutation
|
| 958 |
+
#g) (Permutation) = adjust size or return copy
|
| 959 |
+
ok = True
|
| 960 |
+
if not args: # a
|
| 961 |
+
return cls._af_new(list(range(size or 0)))
|
| 962 |
+
elif len(args) > 1: # c
|
| 963 |
+
return cls._af_new(Cycle(*args).list(size))
|
| 964 |
+
if len(args) == 1:
|
| 965 |
+
a = args[0]
|
| 966 |
+
if isinstance(a, cls): # g
|
| 967 |
+
if size is None or size == a.size:
|
| 968 |
+
return a
|
| 969 |
+
return cls(a.array_form, size=size)
|
| 970 |
+
if isinstance(a, Cycle): # f
|
| 971 |
+
return cls._af_new(a.list(size))
|
| 972 |
+
if not is_sequence(a): # b
|
| 973 |
+
if size is not None and a + 1 > size:
|
| 974 |
+
raise ValueError('size is too small when max is %s' % a)
|
| 975 |
+
return cls._af_new(list(range(a + 1)))
|
| 976 |
+
if has_variety(is_sequence(ai) for ai in a):
|
| 977 |
+
ok = False
|
| 978 |
+
else:
|
| 979 |
+
ok = False
|
| 980 |
+
if not ok:
|
| 981 |
+
raise ValueError("Permutation argument must be a list of ints, "
|
| 982 |
+
"a list of lists, Permutation or Cycle.")
|
| 983 |
+
|
| 984 |
+
# safe to assume args are valid; this also makes a copy
|
| 985 |
+
# of the args
|
| 986 |
+
args = list(args[0])
|
| 987 |
+
|
| 988 |
+
is_cycle = args and is_sequence(args[0])
|
| 989 |
+
if is_cycle: # e
|
| 990 |
+
args = [[int(i) for i in c] for c in args]
|
| 991 |
+
else: # d
|
| 992 |
+
args = [int(i) for i in args]
|
| 993 |
+
|
| 994 |
+
# if there are n elements present, 0, 1, ..., n-1 should be present
|
| 995 |
+
# unless a cycle notation has been provided. A 0 will be added
|
| 996 |
+
# for convenience in case one wants to enter permutations where
|
| 997 |
+
# counting starts from 1.
|
| 998 |
+
|
| 999 |
+
temp = flatten(args)
|
| 1000 |
+
if has_dups(temp) and not is_cycle:
|
| 1001 |
+
raise ValueError('there were repeated elements.')
|
| 1002 |
+
temp = set(temp)
|
| 1003 |
+
|
| 1004 |
+
if not is_cycle:
|
| 1005 |
+
if temp != set(range(len(temp))):
|
| 1006 |
+
raise ValueError('Integers 0 through %s must be present.' %
|
| 1007 |
+
max(temp))
|
| 1008 |
+
if size is not None and temp and max(temp) + 1 > size:
|
| 1009 |
+
raise ValueError('max element should not exceed %s' % (size - 1))
|
| 1010 |
+
|
| 1011 |
+
if is_cycle:
|
| 1012 |
+
# it's not necessarily canonical so we won't store
|
| 1013 |
+
# it -- use the array form instead
|
| 1014 |
+
c = Cycle()
|
| 1015 |
+
for ci in args:
|
| 1016 |
+
c = c(*ci)
|
| 1017 |
+
aform = c.list()
|
| 1018 |
+
else:
|
| 1019 |
+
aform = list(args)
|
| 1020 |
+
if size and size > len(aform):
|
| 1021 |
+
# don't allow for truncation of permutation which
|
| 1022 |
+
# might split a cycle and lead to an invalid aform
|
| 1023 |
+
# but do allow the permutation size to be increased
|
| 1024 |
+
aform.extend(list(range(len(aform), size)))
|
| 1025 |
+
|
| 1026 |
+
return cls._af_new(aform)
|
| 1027 |
+
|
| 1028 |
+
@classmethod
|
| 1029 |
+
def _af_new(cls, perm):
|
| 1030 |
+
"""A method to produce a Permutation object from a list;
|
| 1031 |
+
the list is bound to the _array_form attribute, so it must
|
| 1032 |
+
not be modified; this method is meant for internal use only;
|
| 1033 |
+
the list ``a`` is supposed to be generated as a temporary value
|
| 1034 |
+
in a method, so p = Perm._af_new(a) is the only object
|
| 1035 |
+
to hold a reference to ``a``::
|
| 1036 |
+
|
| 1037 |
+
Examples
|
| 1038 |
+
========
|
| 1039 |
+
|
| 1040 |
+
>>> from sympy.combinatorics.permutations import Perm
|
| 1041 |
+
>>> from sympy import init_printing
|
| 1042 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1043 |
+
>>> a = [2, 1, 3, 0]
|
| 1044 |
+
>>> p = Perm._af_new(a)
|
| 1045 |
+
>>> p
|
| 1046 |
+
Permutation([2, 1, 3, 0])
|
| 1047 |
+
|
| 1048 |
+
"""
|
| 1049 |
+
p = super().__new__(cls)
|
| 1050 |
+
p._array_form = perm
|
| 1051 |
+
p._size = len(perm)
|
| 1052 |
+
return p
|
| 1053 |
+
|
| 1054 |
+
def copy(self):
|
| 1055 |
+
return self.__class__(self.array_form)
|
| 1056 |
+
|
| 1057 |
+
def __getnewargs__(self):
|
| 1058 |
+
return (self.array_form,)
|
| 1059 |
+
|
| 1060 |
+
def _hashable_content(self):
|
| 1061 |
+
# the array_form (a list) is the Permutation arg, so we need to
|
| 1062 |
+
# return a tuple, instead
|
| 1063 |
+
return tuple(self.array_form)
|
| 1064 |
+
|
| 1065 |
+
@property
|
| 1066 |
+
def array_form(self):
|
| 1067 |
+
"""
|
| 1068 |
+
Return a copy of the attribute _array_form
|
| 1069 |
+
Examples
|
| 1070 |
+
========
|
| 1071 |
+
|
| 1072 |
+
>>> from sympy.combinatorics import Permutation
|
| 1073 |
+
>>> p = Permutation([[2, 0], [3, 1]])
|
| 1074 |
+
>>> p.array_form
|
| 1075 |
+
[2, 3, 0, 1]
|
| 1076 |
+
>>> Permutation([[2, 0, 3, 1]]).array_form
|
| 1077 |
+
[3, 2, 0, 1]
|
| 1078 |
+
>>> Permutation([2, 0, 3, 1]).array_form
|
| 1079 |
+
[2, 0, 3, 1]
|
| 1080 |
+
>>> Permutation([[1, 2], [4, 5]]).array_form
|
| 1081 |
+
[0, 2, 1, 3, 5, 4]
|
| 1082 |
+
"""
|
| 1083 |
+
return self._array_form[:]
|
| 1084 |
+
|
| 1085 |
+
def list(self, size=None):
|
| 1086 |
+
"""Return the permutation as an explicit list, possibly
|
| 1087 |
+
trimming unmoved elements if size is less than the maximum
|
| 1088 |
+
element in the permutation; if this is desired, setting
|
| 1089 |
+
``size=-1`` will guarantee such trimming.
|
| 1090 |
+
|
| 1091 |
+
Examples
|
| 1092 |
+
========
|
| 1093 |
+
|
| 1094 |
+
>>> from sympy.combinatorics import Permutation
|
| 1095 |
+
>>> p = Permutation(2, 3)(4, 5)
|
| 1096 |
+
>>> p.list()
|
| 1097 |
+
[0, 1, 3, 2, 5, 4]
|
| 1098 |
+
>>> p.list(10)
|
| 1099 |
+
[0, 1, 3, 2, 5, 4, 6, 7, 8, 9]
|
| 1100 |
+
|
| 1101 |
+
Passing a length too small will trim trailing, unchanged elements
|
| 1102 |
+
in the permutation:
|
| 1103 |
+
|
| 1104 |
+
>>> Permutation(2, 4)(1, 2, 4).list(-1)
|
| 1105 |
+
[0, 2, 1]
|
| 1106 |
+
>>> Permutation(3).list(-1)
|
| 1107 |
+
[]
|
| 1108 |
+
"""
|
| 1109 |
+
if not self and size is None:
|
| 1110 |
+
raise ValueError('must give size for empty Cycle')
|
| 1111 |
+
rv = self.array_form
|
| 1112 |
+
if size is not None:
|
| 1113 |
+
if size > self.size:
|
| 1114 |
+
rv.extend(list(range(self.size, size)))
|
| 1115 |
+
else:
|
| 1116 |
+
# find first value from rhs where rv[i] != i
|
| 1117 |
+
i = self.size - 1
|
| 1118 |
+
while rv:
|
| 1119 |
+
if rv[-1] != i:
|
| 1120 |
+
break
|
| 1121 |
+
rv.pop()
|
| 1122 |
+
i -= 1
|
| 1123 |
+
return rv
|
| 1124 |
+
|
| 1125 |
+
@property
|
| 1126 |
+
def cyclic_form(self):
|
| 1127 |
+
"""
|
| 1128 |
+
This is used to convert to the cyclic notation
|
| 1129 |
+
from the canonical notation. Singletons are omitted.
|
| 1130 |
+
|
| 1131 |
+
Examples
|
| 1132 |
+
========
|
| 1133 |
+
|
| 1134 |
+
>>> from sympy.combinatorics import Permutation
|
| 1135 |
+
>>> p = Permutation([0, 3, 1, 2])
|
| 1136 |
+
>>> p.cyclic_form
|
| 1137 |
+
[[1, 3, 2]]
|
| 1138 |
+
>>> Permutation([1, 0, 2, 4, 3, 5]).cyclic_form
|
| 1139 |
+
[[0, 1], [3, 4]]
|
| 1140 |
+
|
| 1141 |
+
See Also
|
| 1142 |
+
========
|
| 1143 |
+
|
| 1144 |
+
array_form, full_cyclic_form
|
| 1145 |
+
"""
|
| 1146 |
+
if self._cyclic_form is not None:
|
| 1147 |
+
return list(self._cyclic_form)
|
| 1148 |
+
array_form = self.array_form
|
| 1149 |
+
unchecked = [True] * len(array_form)
|
| 1150 |
+
cyclic_form = []
|
| 1151 |
+
for i in range(len(array_form)):
|
| 1152 |
+
if unchecked[i]:
|
| 1153 |
+
cycle = []
|
| 1154 |
+
cycle.append(i)
|
| 1155 |
+
unchecked[i] = False
|
| 1156 |
+
j = i
|
| 1157 |
+
while unchecked[array_form[j]]:
|
| 1158 |
+
j = array_form[j]
|
| 1159 |
+
cycle.append(j)
|
| 1160 |
+
unchecked[j] = False
|
| 1161 |
+
if len(cycle) > 1:
|
| 1162 |
+
cyclic_form.append(cycle)
|
| 1163 |
+
assert cycle == list(minlex(cycle))
|
| 1164 |
+
cyclic_form.sort()
|
| 1165 |
+
self._cyclic_form = cyclic_form[:]
|
| 1166 |
+
return cyclic_form
|
| 1167 |
+
|
| 1168 |
+
@property
|
| 1169 |
+
def full_cyclic_form(self):
|
| 1170 |
+
"""Return permutation in cyclic form including singletons.
|
| 1171 |
+
|
| 1172 |
+
Examples
|
| 1173 |
+
========
|
| 1174 |
+
|
| 1175 |
+
>>> from sympy.combinatorics import Permutation
|
| 1176 |
+
>>> Permutation([0, 2, 1]).full_cyclic_form
|
| 1177 |
+
[[0], [1, 2]]
|
| 1178 |
+
"""
|
| 1179 |
+
need = set(range(self.size)) - set(flatten(self.cyclic_form))
|
| 1180 |
+
rv = self.cyclic_form + [[i] for i in need]
|
| 1181 |
+
rv.sort()
|
| 1182 |
+
return rv
|
| 1183 |
+
|
| 1184 |
+
@property
|
| 1185 |
+
def size(self):
|
| 1186 |
+
"""
|
| 1187 |
+
Returns the number of elements in the permutation.
|
| 1188 |
+
|
| 1189 |
+
Examples
|
| 1190 |
+
========
|
| 1191 |
+
|
| 1192 |
+
>>> from sympy.combinatorics import Permutation
|
| 1193 |
+
>>> Permutation([[3, 2], [0, 1]]).size
|
| 1194 |
+
4
|
| 1195 |
+
|
| 1196 |
+
See Also
|
| 1197 |
+
========
|
| 1198 |
+
|
| 1199 |
+
cardinality, length, order, rank
|
| 1200 |
+
"""
|
| 1201 |
+
return self._size
|
| 1202 |
+
|
| 1203 |
+
def support(self):
|
| 1204 |
+
"""Return the elements in permutation, P, for which P[i] != i.
|
| 1205 |
+
|
| 1206 |
+
Examples
|
| 1207 |
+
========
|
| 1208 |
+
|
| 1209 |
+
>>> from sympy.combinatorics import Permutation
|
| 1210 |
+
>>> p = Permutation([[3, 2], [0, 1], [4]])
|
| 1211 |
+
>>> p.array_form
|
| 1212 |
+
[1, 0, 3, 2, 4]
|
| 1213 |
+
>>> p.support()
|
| 1214 |
+
[0, 1, 2, 3]
|
| 1215 |
+
"""
|
| 1216 |
+
a = self.array_form
|
| 1217 |
+
return [i for i, e in enumerate(a) if a[i] != i]
|
| 1218 |
+
|
| 1219 |
+
def __add__(self, other):
|
| 1220 |
+
"""Return permutation that is other higher in rank than self.
|
| 1221 |
+
|
| 1222 |
+
The rank is the lexicographical rank, with the identity permutation
|
| 1223 |
+
having rank of 0.
|
| 1224 |
+
|
| 1225 |
+
Examples
|
| 1226 |
+
========
|
| 1227 |
+
|
| 1228 |
+
>>> from sympy.combinatorics import Permutation
|
| 1229 |
+
>>> I = Permutation([0, 1, 2, 3])
|
| 1230 |
+
>>> a = Permutation([2, 1, 3, 0])
|
| 1231 |
+
>>> I + a.rank() == a
|
| 1232 |
+
True
|
| 1233 |
+
|
| 1234 |
+
See Also
|
| 1235 |
+
========
|
| 1236 |
+
|
| 1237 |
+
__sub__, inversion_vector
|
| 1238 |
+
|
| 1239 |
+
"""
|
| 1240 |
+
rank = (self.rank() + other) % self.cardinality
|
| 1241 |
+
rv = self.unrank_lex(self.size, rank)
|
| 1242 |
+
rv._rank = rank
|
| 1243 |
+
return rv
|
| 1244 |
+
|
| 1245 |
+
def __sub__(self, other):
|
| 1246 |
+
"""Return the permutation that is other lower in rank than self.
|
| 1247 |
+
|
| 1248 |
+
See Also
|
| 1249 |
+
========
|
| 1250 |
+
|
| 1251 |
+
__add__
|
| 1252 |
+
"""
|
| 1253 |
+
return self.__add__(-other)
|
| 1254 |
+
|
| 1255 |
+
@staticmethod
|
| 1256 |
+
def rmul(*args):
|
| 1257 |
+
"""
|
| 1258 |
+
Return product of Permutations [a, b, c, ...] as the Permutation whose
|
| 1259 |
+
ith value is a(b(c(i))).
|
| 1260 |
+
|
| 1261 |
+
a, b, c, ... can be Permutation objects or tuples.
|
| 1262 |
+
|
| 1263 |
+
Examples
|
| 1264 |
+
========
|
| 1265 |
+
|
| 1266 |
+
>>> from sympy.combinatorics import Permutation
|
| 1267 |
+
|
| 1268 |
+
>>> a, b = [1, 0, 2], [0, 2, 1]
|
| 1269 |
+
>>> a = Permutation(a); b = Permutation(b)
|
| 1270 |
+
>>> list(Permutation.rmul(a, b))
|
| 1271 |
+
[1, 2, 0]
|
| 1272 |
+
>>> [a(b(i)) for i in range(3)]
|
| 1273 |
+
[1, 2, 0]
|
| 1274 |
+
|
| 1275 |
+
This handles the operands in reverse order compared to the ``*`` operator:
|
| 1276 |
+
|
| 1277 |
+
>>> a = Permutation(a); b = Permutation(b)
|
| 1278 |
+
>>> list(a*b)
|
| 1279 |
+
[2, 0, 1]
|
| 1280 |
+
>>> [b(a(i)) for i in range(3)]
|
| 1281 |
+
[2, 0, 1]
|
| 1282 |
+
|
| 1283 |
+
Notes
|
| 1284 |
+
=====
|
| 1285 |
+
|
| 1286 |
+
All items in the sequence will be parsed by Permutation as
|
| 1287 |
+
necessary as long as the first item is a Permutation:
|
| 1288 |
+
|
| 1289 |
+
>>> Permutation.rmul(a, [0, 2, 1]) == Permutation.rmul(a, b)
|
| 1290 |
+
True
|
| 1291 |
+
|
| 1292 |
+
The reverse order of arguments will raise a TypeError.
|
| 1293 |
+
|
| 1294 |
+
"""
|
| 1295 |
+
rv = args[0]
|
| 1296 |
+
for i in range(1, len(args)):
|
| 1297 |
+
rv = args[i]*rv
|
| 1298 |
+
return rv
|
| 1299 |
+
|
| 1300 |
+
@classmethod
|
| 1301 |
+
def rmul_with_af(cls, *args):
|
| 1302 |
+
"""
|
| 1303 |
+
same as rmul, but the elements of args are Permutation objects
|
| 1304 |
+
which have _array_form
|
| 1305 |
+
"""
|
| 1306 |
+
a = [x._array_form for x in args]
|
| 1307 |
+
rv = cls._af_new(_af_rmuln(*a))
|
| 1308 |
+
return rv
|
| 1309 |
+
|
| 1310 |
+
def mul_inv(self, other):
|
| 1311 |
+
"""
|
| 1312 |
+
other*~self, self and other have _array_form
|
| 1313 |
+
"""
|
| 1314 |
+
a = _af_invert(self._array_form)
|
| 1315 |
+
b = other._array_form
|
| 1316 |
+
return self._af_new(_af_rmul(a, b))
|
| 1317 |
+
|
| 1318 |
+
def __rmul__(self, other):
|
| 1319 |
+
"""This is needed to coerce other to Permutation in rmul."""
|
| 1320 |
+
cls = type(self)
|
| 1321 |
+
return cls(other)*self
|
| 1322 |
+
|
| 1323 |
+
def __mul__(self, other):
|
| 1324 |
+
"""
|
| 1325 |
+
Return the product a*b as a Permutation; the ith value is b(a(i)).
|
| 1326 |
+
|
| 1327 |
+
Examples
|
| 1328 |
+
========
|
| 1329 |
+
|
| 1330 |
+
>>> from sympy.combinatorics.permutations import _af_rmul, Permutation
|
| 1331 |
+
|
| 1332 |
+
>>> a, b = [1, 0, 2], [0, 2, 1]
|
| 1333 |
+
>>> a = Permutation(a); b = Permutation(b)
|
| 1334 |
+
>>> list(a*b)
|
| 1335 |
+
[2, 0, 1]
|
| 1336 |
+
>>> [b(a(i)) for i in range(3)]
|
| 1337 |
+
[2, 0, 1]
|
| 1338 |
+
|
| 1339 |
+
This handles operands in reverse order compared to _af_rmul and rmul:
|
| 1340 |
+
|
| 1341 |
+
>>> al = list(a); bl = list(b)
|
| 1342 |
+
>>> _af_rmul(al, bl)
|
| 1343 |
+
[1, 2, 0]
|
| 1344 |
+
>>> [al[bl[i]] for i in range(3)]
|
| 1345 |
+
[1, 2, 0]
|
| 1346 |
+
|
| 1347 |
+
It is acceptable for the arrays to have different lengths; the shorter
|
| 1348 |
+
one will be padded to match the longer one:
|
| 1349 |
+
|
| 1350 |
+
>>> from sympy import init_printing
|
| 1351 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1352 |
+
>>> b*Permutation([1, 0])
|
| 1353 |
+
Permutation([1, 2, 0])
|
| 1354 |
+
>>> Permutation([1, 0])*b
|
| 1355 |
+
Permutation([2, 0, 1])
|
| 1356 |
+
|
| 1357 |
+
It is also acceptable to allow coercion to handle conversion of a
|
| 1358 |
+
single list to the left of a Permutation:
|
| 1359 |
+
|
| 1360 |
+
>>> [0, 1]*a # no change: 2-element identity
|
| 1361 |
+
Permutation([1, 0, 2])
|
| 1362 |
+
>>> [[0, 1]]*a # exchange first two elements
|
| 1363 |
+
Permutation([0, 1, 2])
|
| 1364 |
+
|
| 1365 |
+
You cannot use more than 1 cycle notation in a product of cycles
|
| 1366 |
+
since coercion can only handle one argument to the left. To handle
|
| 1367 |
+
multiple cycles it is convenient to use Cycle instead of Permutation:
|
| 1368 |
+
|
| 1369 |
+
>>> [[1, 2]]*[[2, 3]]*Permutation([]) # doctest: +SKIP
|
| 1370 |
+
>>> from sympy.combinatorics.permutations import Cycle
|
| 1371 |
+
>>> Cycle(1, 2)(2, 3)
|
| 1372 |
+
(1 3 2)
|
| 1373 |
+
|
| 1374 |
+
"""
|
| 1375 |
+
from sympy.combinatorics.perm_groups import PermutationGroup, Coset
|
| 1376 |
+
if isinstance(other, PermutationGroup):
|
| 1377 |
+
return Coset(self, other, dir='-')
|
| 1378 |
+
a = self.array_form
|
| 1379 |
+
# __rmul__ makes sure the other is a Permutation
|
| 1380 |
+
b = other.array_form
|
| 1381 |
+
if not b:
|
| 1382 |
+
perm = a
|
| 1383 |
+
else:
|
| 1384 |
+
b.extend(list(range(len(b), len(a))))
|
| 1385 |
+
perm = [b[i] for i in a] + b[len(a):]
|
| 1386 |
+
return self._af_new(perm)
|
| 1387 |
+
|
| 1388 |
+
def commutes_with(self, other):
|
| 1389 |
+
"""
|
| 1390 |
+
Checks if the elements are commuting.
|
| 1391 |
+
|
| 1392 |
+
Examples
|
| 1393 |
+
========
|
| 1394 |
+
|
| 1395 |
+
>>> from sympy.combinatorics import Permutation
|
| 1396 |
+
>>> a = Permutation([1, 4, 3, 0, 2, 5])
|
| 1397 |
+
>>> b = Permutation([0, 1, 2, 3, 4, 5])
|
| 1398 |
+
>>> a.commutes_with(b)
|
| 1399 |
+
True
|
| 1400 |
+
>>> b = Permutation([2, 3, 5, 4, 1, 0])
|
| 1401 |
+
>>> a.commutes_with(b)
|
| 1402 |
+
False
|
| 1403 |
+
"""
|
| 1404 |
+
a = self.array_form
|
| 1405 |
+
b = other.array_form
|
| 1406 |
+
return _af_commutes_with(a, b)
|
| 1407 |
+
|
| 1408 |
+
def __pow__(self, n):
|
| 1409 |
+
"""
|
| 1410 |
+
Routine for finding powers of a permutation.
|
| 1411 |
+
|
| 1412 |
+
Examples
|
| 1413 |
+
========
|
| 1414 |
+
|
| 1415 |
+
>>> from sympy.combinatorics import Permutation
|
| 1416 |
+
>>> from sympy import init_printing
|
| 1417 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1418 |
+
>>> p = Permutation([2, 0, 3, 1])
|
| 1419 |
+
>>> p.order()
|
| 1420 |
+
4
|
| 1421 |
+
>>> p**4
|
| 1422 |
+
Permutation([0, 1, 2, 3])
|
| 1423 |
+
"""
|
| 1424 |
+
if isinstance(n, Permutation):
|
| 1425 |
+
raise NotImplementedError(
|
| 1426 |
+
'p**p is not defined; do you mean p^p (conjugate)?')
|
| 1427 |
+
n = int(n)
|
| 1428 |
+
return self._af_new(_af_pow(self.array_form, n))
|
| 1429 |
+
|
| 1430 |
+
def __rxor__(self, i):
|
| 1431 |
+
"""Return self(i) when ``i`` is an int.
|
| 1432 |
+
|
| 1433 |
+
Examples
|
| 1434 |
+
========
|
| 1435 |
+
|
| 1436 |
+
>>> from sympy.combinatorics import Permutation
|
| 1437 |
+
>>> p = Permutation(1, 2, 9)
|
| 1438 |
+
>>> 2^p == p(2) == 9
|
| 1439 |
+
True
|
| 1440 |
+
"""
|
| 1441 |
+
if int_valued(i):
|
| 1442 |
+
return self(i)
|
| 1443 |
+
else:
|
| 1444 |
+
raise NotImplementedError(
|
| 1445 |
+
"i^p = p(i) when i is an integer, not %s." % i)
|
| 1446 |
+
|
| 1447 |
+
def __xor__(self, h):
|
| 1448 |
+
"""Return the conjugate permutation ``~h*self*h` `.
|
| 1449 |
+
|
| 1450 |
+
Explanation
|
| 1451 |
+
===========
|
| 1452 |
+
|
| 1453 |
+
If ``a`` and ``b`` are conjugates, ``a = h*b*~h`` and
|
| 1454 |
+
``b = ~h*a*h`` and both have the same cycle structure.
|
| 1455 |
+
|
| 1456 |
+
Examples
|
| 1457 |
+
========
|
| 1458 |
+
|
| 1459 |
+
>>> from sympy.combinatorics import Permutation
|
| 1460 |
+
>>> p = Permutation(1, 2, 9)
|
| 1461 |
+
>>> q = Permutation(6, 9, 8)
|
| 1462 |
+
>>> p*q != q*p
|
| 1463 |
+
True
|
| 1464 |
+
|
| 1465 |
+
Calculate and check properties of the conjugate:
|
| 1466 |
+
|
| 1467 |
+
>>> c = p^q
|
| 1468 |
+
>>> c == ~q*p*q and p == q*c*~q
|
| 1469 |
+
True
|
| 1470 |
+
|
| 1471 |
+
The expression q^p^r is equivalent to q^(p*r):
|
| 1472 |
+
|
| 1473 |
+
>>> r = Permutation(9)(4, 6, 8)
|
| 1474 |
+
>>> q^p^r == q^(p*r)
|
| 1475 |
+
True
|
| 1476 |
+
|
| 1477 |
+
If the term to the left of the conjugate operator, i, is an integer
|
| 1478 |
+
then this is interpreted as selecting the ith element from the
|
| 1479 |
+
permutation to the right:
|
| 1480 |
+
|
| 1481 |
+
>>> all(i^p == p(i) for i in range(p.size))
|
| 1482 |
+
True
|
| 1483 |
+
|
| 1484 |
+
Note that the * operator as higher precedence than the ^ operator:
|
| 1485 |
+
|
| 1486 |
+
>>> q^r*p^r == q^(r*p)^r == Permutation(9)(1, 6, 4)
|
| 1487 |
+
True
|
| 1488 |
+
|
| 1489 |
+
Notes
|
| 1490 |
+
=====
|
| 1491 |
+
|
| 1492 |
+
In Python the precedence rule is p^q^r = (p^q)^r which differs
|
| 1493 |
+
in general from p^(q^r)
|
| 1494 |
+
|
| 1495 |
+
>>> q^p^r
|
| 1496 |
+
(9)(1 4 8)
|
| 1497 |
+
>>> q^(p^r)
|
| 1498 |
+
(9)(1 8 6)
|
| 1499 |
+
|
| 1500 |
+
For a given r and p, both of the following are conjugates of p:
|
| 1501 |
+
~r*p*r and r*p*~r. But these are not necessarily the same:
|
| 1502 |
+
|
| 1503 |
+
>>> ~r*p*r == r*p*~r
|
| 1504 |
+
True
|
| 1505 |
+
|
| 1506 |
+
>>> p = Permutation(1, 2, 9)(5, 6)
|
| 1507 |
+
>>> ~r*p*r == r*p*~r
|
| 1508 |
+
False
|
| 1509 |
+
|
| 1510 |
+
The conjugate ~r*p*r was chosen so that ``p^q^r`` would be equivalent
|
| 1511 |
+
to ``p^(q*r)`` rather than ``p^(r*q)``. To obtain r*p*~r, pass ~r to
|
| 1512 |
+
this method:
|
| 1513 |
+
|
| 1514 |
+
>>> p^~r == r*p*~r
|
| 1515 |
+
True
|
| 1516 |
+
"""
|
| 1517 |
+
|
| 1518 |
+
if self.size != h.size:
|
| 1519 |
+
raise ValueError("The permutations must be of equal size.")
|
| 1520 |
+
a = [None]*self.size
|
| 1521 |
+
h = h._array_form
|
| 1522 |
+
p = self._array_form
|
| 1523 |
+
for i in range(self.size):
|
| 1524 |
+
a[h[i]] = h[p[i]]
|
| 1525 |
+
return self._af_new(a)
|
| 1526 |
+
|
| 1527 |
+
def transpositions(self):
|
| 1528 |
+
"""
|
| 1529 |
+
Return the permutation decomposed into a list of transpositions.
|
| 1530 |
+
|
| 1531 |
+
Explanation
|
| 1532 |
+
===========
|
| 1533 |
+
|
| 1534 |
+
It is always possible to express a permutation as the product of
|
| 1535 |
+
transpositions, see [1]
|
| 1536 |
+
|
| 1537 |
+
Examples
|
| 1538 |
+
========
|
| 1539 |
+
|
| 1540 |
+
>>> from sympy.combinatorics import Permutation
|
| 1541 |
+
>>> p = Permutation([[1, 2, 3], [0, 4, 5, 6, 7]])
|
| 1542 |
+
>>> t = p.transpositions()
|
| 1543 |
+
>>> t
|
| 1544 |
+
[(0, 7), (0, 6), (0, 5), (0, 4), (1, 3), (1, 2)]
|
| 1545 |
+
>>> print(''.join(str(c) for c in t))
|
| 1546 |
+
(0, 7)(0, 6)(0, 5)(0, 4)(1, 3)(1, 2)
|
| 1547 |
+
>>> Permutation.rmul(*[Permutation([ti], size=p.size) for ti in t]) == p
|
| 1548 |
+
True
|
| 1549 |
+
|
| 1550 |
+
References
|
| 1551 |
+
==========
|
| 1552 |
+
|
| 1553 |
+
.. [1] https://en.wikipedia.org/wiki/Transposition_%28mathematics%29#Properties
|
| 1554 |
+
|
| 1555 |
+
"""
|
| 1556 |
+
a = self.cyclic_form
|
| 1557 |
+
res = []
|
| 1558 |
+
for x in a:
|
| 1559 |
+
nx = len(x)
|
| 1560 |
+
if nx == 2:
|
| 1561 |
+
res.append(tuple(x))
|
| 1562 |
+
elif nx > 2:
|
| 1563 |
+
first = x[0]
|
| 1564 |
+
for y in x[nx - 1:0:-1]:
|
| 1565 |
+
res.append((first, y))
|
| 1566 |
+
return res
|
| 1567 |
+
|
| 1568 |
+
@classmethod
|
| 1569 |
+
def from_sequence(self, i, key=None):
|
| 1570 |
+
"""Return the permutation needed to obtain ``i`` from the sorted
|
| 1571 |
+
elements of ``i``. If custom sorting is desired, a key can be given.
|
| 1572 |
+
|
| 1573 |
+
Examples
|
| 1574 |
+
========
|
| 1575 |
+
|
| 1576 |
+
>>> from sympy.combinatorics import Permutation
|
| 1577 |
+
|
| 1578 |
+
>>> Permutation.from_sequence('SymPy')
|
| 1579 |
+
(4)(0 1 3)
|
| 1580 |
+
>>> _(sorted("SymPy"))
|
| 1581 |
+
['S', 'y', 'm', 'P', 'y']
|
| 1582 |
+
>>> Permutation.from_sequence('SymPy', key=lambda x: x.lower())
|
| 1583 |
+
(4)(0 2)(1 3)
|
| 1584 |
+
"""
|
| 1585 |
+
ic = list(zip(i, list(range(len(i)))))
|
| 1586 |
+
if key:
|
| 1587 |
+
ic.sort(key=lambda x: key(x[0]))
|
| 1588 |
+
else:
|
| 1589 |
+
ic.sort()
|
| 1590 |
+
return ~Permutation([i[1] for i in ic])
|
| 1591 |
+
|
| 1592 |
+
def __invert__(self):
|
| 1593 |
+
"""
|
| 1594 |
+
Return the inverse of the permutation.
|
| 1595 |
+
|
| 1596 |
+
A permutation multiplied by its inverse is the identity permutation.
|
| 1597 |
+
|
| 1598 |
+
Examples
|
| 1599 |
+
========
|
| 1600 |
+
|
| 1601 |
+
>>> from sympy.combinatorics import Permutation
|
| 1602 |
+
>>> from sympy import init_printing
|
| 1603 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1604 |
+
>>> p = Permutation([[2, 0], [3, 1]])
|
| 1605 |
+
>>> ~p
|
| 1606 |
+
Permutation([2, 3, 0, 1])
|
| 1607 |
+
>>> _ == p**-1
|
| 1608 |
+
True
|
| 1609 |
+
>>> p*~p == ~p*p == Permutation([0, 1, 2, 3])
|
| 1610 |
+
True
|
| 1611 |
+
"""
|
| 1612 |
+
return self._af_new(_af_invert(self._array_form))
|
| 1613 |
+
|
| 1614 |
+
def __iter__(self):
|
| 1615 |
+
"""Yield elements from array form.
|
| 1616 |
+
|
| 1617 |
+
Examples
|
| 1618 |
+
========
|
| 1619 |
+
|
| 1620 |
+
>>> from sympy.combinatorics import Permutation
|
| 1621 |
+
>>> list(Permutation(range(3)))
|
| 1622 |
+
[0, 1, 2]
|
| 1623 |
+
"""
|
| 1624 |
+
yield from self.array_form
|
| 1625 |
+
|
| 1626 |
+
def __repr__(self):
|
| 1627 |
+
return srepr(self)
|
| 1628 |
+
|
| 1629 |
+
def __call__(self, *i):
|
| 1630 |
+
"""
|
| 1631 |
+
Allows applying a permutation instance as a bijective function.
|
| 1632 |
+
|
| 1633 |
+
Examples
|
| 1634 |
+
========
|
| 1635 |
+
|
| 1636 |
+
>>> from sympy.combinatorics import Permutation
|
| 1637 |
+
>>> p = Permutation([[2, 0], [3, 1]])
|
| 1638 |
+
>>> p.array_form
|
| 1639 |
+
[2, 3, 0, 1]
|
| 1640 |
+
>>> [p(i) for i in range(4)]
|
| 1641 |
+
[2, 3, 0, 1]
|
| 1642 |
+
|
| 1643 |
+
If an array is given then the permutation selects the items
|
| 1644 |
+
from the array (i.e. the permutation is applied to the array):
|
| 1645 |
+
|
| 1646 |
+
>>> from sympy.abc import x
|
| 1647 |
+
>>> p([x, 1, 0, x**2])
|
| 1648 |
+
[0, x**2, x, 1]
|
| 1649 |
+
"""
|
| 1650 |
+
# list indices can be Integer or int; leave this
|
| 1651 |
+
# as it is (don't test or convert it) because this
|
| 1652 |
+
# gets called a lot and should be fast
|
| 1653 |
+
if len(i) == 1:
|
| 1654 |
+
i = i[0]
|
| 1655 |
+
if not isinstance(i, Iterable):
|
| 1656 |
+
i = as_int(i)
|
| 1657 |
+
if i < 0 or i > self.size:
|
| 1658 |
+
raise TypeError(
|
| 1659 |
+
"{} should be an integer between 0 and {}"
|
| 1660 |
+
.format(i, self.size-1))
|
| 1661 |
+
return self._array_form[i]
|
| 1662 |
+
# P([a, b, c])
|
| 1663 |
+
if len(i) != self.size:
|
| 1664 |
+
raise TypeError(
|
| 1665 |
+
"{} should have the length {}.".format(i, self.size))
|
| 1666 |
+
return [i[j] for j in self._array_form]
|
| 1667 |
+
# P(1, 2, 3)
|
| 1668 |
+
return self*Permutation(Cycle(*i), size=self.size)
|
| 1669 |
+
|
| 1670 |
+
def atoms(self):
|
| 1671 |
+
"""
|
| 1672 |
+
Returns all the elements of a permutation
|
| 1673 |
+
|
| 1674 |
+
Examples
|
| 1675 |
+
========
|
| 1676 |
+
|
| 1677 |
+
>>> from sympy.combinatorics import Permutation
|
| 1678 |
+
>>> Permutation([0, 1, 2, 3, 4, 5]).atoms()
|
| 1679 |
+
{0, 1, 2, 3, 4, 5}
|
| 1680 |
+
>>> Permutation([[0, 1], [2, 3], [4, 5]]).atoms()
|
| 1681 |
+
{0, 1, 2, 3, 4, 5}
|
| 1682 |
+
"""
|
| 1683 |
+
return set(self.array_form)
|
| 1684 |
+
|
| 1685 |
+
def apply(self, i):
|
| 1686 |
+
r"""Apply the permutation to an expression.
|
| 1687 |
+
|
| 1688 |
+
Parameters
|
| 1689 |
+
==========
|
| 1690 |
+
|
| 1691 |
+
i : Expr
|
| 1692 |
+
It should be an integer between $0$ and $n-1$ where $n$
|
| 1693 |
+
is the size of the permutation.
|
| 1694 |
+
|
| 1695 |
+
If it is a symbol or a symbolic expression that can
|
| 1696 |
+
have integer values, an ``AppliedPermutation`` object
|
| 1697 |
+
will be returned which can represent an unevaluated
|
| 1698 |
+
function.
|
| 1699 |
+
|
| 1700 |
+
Notes
|
| 1701 |
+
=====
|
| 1702 |
+
|
| 1703 |
+
Any permutation can be defined as a bijective function
|
| 1704 |
+
$\sigma : \{ 0, 1, \dots, n-1 \} \rightarrow \{ 0, 1, \dots, n-1 \}$
|
| 1705 |
+
where $n$ denotes the size of the permutation.
|
| 1706 |
+
|
| 1707 |
+
The definition may even be extended for any set with distinctive
|
| 1708 |
+
elements, such that the permutation can even be applied for
|
| 1709 |
+
real numbers or such, however, it is not implemented for now for
|
| 1710 |
+
computational reasons and the integrity with the group theory
|
| 1711 |
+
module.
|
| 1712 |
+
|
| 1713 |
+
This function is similar to the ``__call__`` magic, however,
|
| 1714 |
+
``__call__`` magic already has some other applications like
|
| 1715 |
+
permuting an array or attaching new cycles, which would
|
| 1716 |
+
not always be mathematically consistent.
|
| 1717 |
+
|
| 1718 |
+
This also guarantees that the return type is a SymPy integer,
|
| 1719 |
+
which guarantees the safety to use assumptions.
|
| 1720 |
+
"""
|
| 1721 |
+
i = _sympify(i)
|
| 1722 |
+
if i.is_integer is False:
|
| 1723 |
+
raise NotImplementedError("{} should be an integer.".format(i))
|
| 1724 |
+
|
| 1725 |
+
n = self.size
|
| 1726 |
+
if (i < 0) == True or (i >= n) == True:
|
| 1727 |
+
raise NotImplementedError(
|
| 1728 |
+
"{} should be an integer between 0 and {}".format(i, n-1))
|
| 1729 |
+
|
| 1730 |
+
if i.is_Integer:
|
| 1731 |
+
return Integer(self._array_form[i])
|
| 1732 |
+
return AppliedPermutation(self, i)
|
| 1733 |
+
|
| 1734 |
+
def next_lex(self):
|
| 1735 |
+
"""
|
| 1736 |
+
Returns the next permutation in lexicographical order.
|
| 1737 |
+
If self is the last permutation in lexicographical order
|
| 1738 |
+
it returns None.
|
| 1739 |
+
See [4] section 2.4.
|
| 1740 |
+
|
| 1741 |
+
|
| 1742 |
+
Examples
|
| 1743 |
+
========
|
| 1744 |
+
|
| 1745 |
+
>>> from sympy.combinatorics import Permutation
|
| 1746 |
+
>>> p = Permutation([2, 3, 1, 0])
|
| 1747 |
+
>>> p = Permutation([2, 3, 1, 0]); p.rank()
|
| 1748 |
+
17
|
| 1749 |
+
>>> p = p.next_lex(); p.rank()
|
| 1750 |
+
18
|
| 1751 |
+
|
| 1752 |
+
See Also
|
| 1753 |
+
========
|
| 1754 |
+
|
| 1755 |
+
rank, unrank_lex
|
| 1756 |
+
"""
|
| 1757 |
+
perm = self.array_form[:]
|
| 1758 |
+
n = len(perm)
|
| 1759 |
+
i = n - 2
|
| 1760 |
+
while perm[i + 1] < perm[i]:
|
| 1761 |
+
i -= 1
|
| 1762 |
+
if i == -1:
|
| 1763 |
+
return None
|
| 1764 |
+
else:
|
| 1765 |
+
j = n - 1
|
| 1766 |
+
while perm[j] < perm[i]:
|
| 1767 |
+
j -= 1
|
| 1768 |
+
perm[j], perm[i] = perm[i], perm[j]
|
| 1769 |
+
i += 1
|
| 1770 |
+
j = n - 1
|
| 1771 |
+
while i < j:
|
| 1772 |
+
perm[j], perm[i] = perm[i], perm[j]
|
| 1773 |
+
i += 1
|
| 1774 |
+
j -= 1
|
| 1775 |
+
return self._af_new(perm)
|
| 1776 |
+
|
| 1777 |
+
@classmethod
|
| 1778 |
+
def unrank_nonlex(self, n, r):
|
| 1779 |
+
"""
|
| 1780 |
+
This is a linear time unranking algorithm that does not
|
| 1781 |
+
respect lexicographic order [3].
|
| 1782 |
+
|
| 1783 |
+
Examples
|
| 1784 |
+
========
|
| 1785 |
+
|
| 1786 |
+
>>> from sympy.combinatorics import Permutation
|
| 1787 |
+
>>> from sympy import init_printing
|
| 1788 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1789 |
+
>>> Permutation.unrank_nonlex(4, 5)
|
| 1790 |
+
Permutation([2, 0, 3, 1])
|
| 1791 |
+
>>> Permutation.unrank_nonlex(4, -1)
|
| 1792 |
+
Permutation([0, 1, 2, 3])
|
| 1793 |
+
|
| 1794 |
+
See Also
|
| 1795 |
+
========
|
| 1796 |
+
|
| 1797 |
+
next_nonlex, rank_nonlex
|
| 1798 |
+
"""
|
| 1799 |
+
def _unrank1(n, r, a):
|
| 1800 |
+
if n > 0:
|
| 1801 |
+
a[n - 1], a[r % n] = a[r % n], a[n - 1]
|
| 1802 |
+
_unrank1(n - 1, r//n, a)
|
| 1803 |
+
|
| 1804 |
+
id_perm = list(range(n))
|
| 1805 |
+
n = int(n)
|
| 1806 |
+
r = r % ifac(n)
|
| 1807 |
+
_unrank1(n, r, id_perm)
|
| 1808 |
+
return self._af_new(id_perm)
|
| 1809 |
+
|
| 1810 |
+
def rank_nonlex(self, inv_perm=None):
|
| 1811 |
+
"""
|
| 1812 |
+
This is a linear time ranking algorithm that does not
|
| 1813 |
+
enforce lexicographic order [3].
|
| 1814 |
+
|
| 1815 |
+
|
| 1816 |
+
Examples
|
| 1817 |
+
========
|
| 1818 |
+
|
| 1819 |
+
>>> from sympy.combinatorics import Permutation
|
| 1820 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1821 |
+
>>> p.rank_nonlex()
|
| 1822 |
+
23
|
| 1823 |
+
|
| 1824 |
+
See Also
|
| 1825 |
+
========
|
| 1826 |
+
|
| 1827 |
+
next_nonlex, unrank_nonlex
|
| 1828 |
+
"""
|
| 1829 |
+
def _rank1(n, perm, inv_perm):
|
| 1830 |
+
if n == 1:
|
| 1831 |
+
return 0
|
| 1832 |
+
s = perm[n - 1]
|
| 1833 |
+
t = inv_perm[n - 1]
|
| 1834 |
+
perm[n - 1], perm[t] = perm[t], s
|
| 1835 |
+
inv_perm[n - 1], inv_perm[s] = inv_perm[s], t
|
| 1836 |
+
return s + n*_rank1(n - 1, perm, inv_perm)
|
| 1837 |
+
|
| 1838 |
+
if inv_perm is None:
|
| 1839 |
+
inv_perm = (~self).array_form
|
| 1840 |
+
if not inv_perm:
|
| 1841 |
+
return 0
|
| 1842 |
+
perm = self.array_form[:]
|
| 1843 |
+
r = _rank1(len(perm), perm, inv_perm)
|
| 1844 |
+
return r
|
| 1845 |
+
|
| 1846 |
+
def next_nonlex(self):
|
| 1847 |
+
"""
|
| 1848 |
+
Returns the next permutation in nonlex order [3].
|
| 1849 |
+
If self is the last permutation in this order it returns None.
|
| 1850 |
+
|
| 1851 |
+
Examples
|
| 1852 |
+
========
|
| 1853 |
+
|
| 1854 |
+
>>> from sympy.combinatorics import Permutation
|
| 1855 |
+
>>> from sympy import init_printing
|
| 1856 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 1857 |
+
>>> p = Permutation([2, 0, 3, 1]); p.rank_nonlex()
|
| 1858 |
+
5
|
| 1859 |
+
>>> p = p.next_nonlex(); p
|
| 1860 |
+
Permutation([3, 0, 1, 2])
|
| 1861 |
+
>>> p.rank_nonlex()
|
| 1862 |
+
6
|
| 1863 |
+
|
| 1864 |
+
See Also
|
| 1865 |
+
========
|
| 1866 |
+
|
| 1867 |
+
rank_nonlex, unrank_nonlex
|
| 1868 |
+
"""
|
| 1869 |
+
r = self.rank_nonlex()
|
| 1870 |
+
if r == ifac(self.size) - 1:
|
| 1871 |
+
return None
|
| 1872 |
+
return self.unrank_nonlex(self.size, r + 1)
|
| 1873 |
+
|
| 1874 |
+
def rank(self):
|
| 1875 |
+
"""
|
| 1876 |
+
Returns the lexicographic rank of the permutation.
|
| 1877 |
+
|
| 1878 |
+
Examples
|
| 1879 |
+
========
|
| 1880 |
+
|
| 1881 |
+
>>> from sympy.combinatorics import Permutation
|
| 1882 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1883 |
+
>>> p.rank()
|
| 1884 |
+
0
|
| 1885 |
+
>>> p = Permutation([3, 2, 1, 0])
|
| 1886 |
+
>>> p.rank()
|
| 1887 |
+
23
|
| 1888 |
+
|
| 1889 |
+
See Also
|
| 1890 |
+
========
|
| 1891 |
+
|
| 1892 |
+
next_lex, unrank_lex, cardinality, length, order, size
|
| 1893 |
+
"""
|
| 1894 |
+
if self._rank is not None:
|
| 1895 |
+
return self._rank
|
| 1896 |
+
rank = 0
|
| 1897 |
+
rho = self.array_form[:]
|
| 1898 |
+
n = self.size - 1
|
| 1899 |
+
size = n + 1
|
| 1900 |
+
psize = int(ifac(n))
|
| 1901 |
+
for j in range(size - 1):
|
| 1902 |
+
rank += rho[j]*psize
|
| 1903 |
+
for i in range(j + 1, size):
|
| 1904 |
+
if rho[i] > rho[j]:
|
| 1905 |
+
rho[i] -= 1
|
| 1906 |
+
psize //= n
|
| 1907 |
+
n -= 1
|
| 1908 |
+
self._rank = rank
|
| 1909 |
+
return rank
|
| 1910 |
+
|
| 1911 |
+
@property
|
| 1912 |
+
def cardinality(self):
|
| 1913 |
+
"""
|
| 1914 |
+
Returns the number of all possible permutations.
|
| 1915 |
+
|
| 1916 |
+
Examples
|
| 1917 |
+
========
|
| 1918 |
+
|
| 1919 |
+
>>> from sympy.combinatorics import Permutation
|
| 1920 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1921 |
+
>>> p.cardinality
|
| 1922 |
+
24
|
| 1923 |
+
|
| 1924 |
+
See Also
|
| 1925 |
+
========
|
| 1926 |
+
|
| 1927 |
+
length, order, rank, size
|
| 1928 |
+
"""
|
| 1929 |
+
return int(ifac(self.size))
|
| 1930 |
+
|
| 1931 |
+
def parity(self):
|
| 1932 |
+
"""
|
| 1933 |
+
Computes the parity of a permutation.
|
| 1934 |
+
|
| 1935 |
+
Explanation
|
| 1936 |
+
===========
|
| 1937 |
+
|
| 1938 |
+
The parity of a permutation reflects the parity of the
|
| 1939 |
+
number of inversions in the permutation, i.e., the
|
| 1940 |
+
number of pairs of x and y such that ``x > y`` but ``p[x] < p[y]``.
|
| 1941 |
+
|
| 1942 |
+
Examples
|
| 1943 |
+
========
|
| 1944 |
+
|
| 1945 |
+
>>> from sympy.combinatorics import Permutation
|
| 1946 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1947 |
+
>>> p.parity()
|
| 1948 |
+
0
|
| 1949 |
+
>>> p = Permutation([3, 2, 0, 1])
|
| 1950 |
+
>>> p.parity()
|
| 1951 |
+
1
|
| 1952 |
+
|
| 1953 |
+
See Also
|
| 1954 |
+
========
|
| 1955 |
+
|
| 1956 |
+
_af_parity
|
| 1957 |
+
"""
|
| 1958 |
+
if self._cyclic_form is not None:
|
| 1959 |
+
return (self.size - self.cycles) % 2
|
| 1960 |
+
|
| 1961 |
+
return _af_parity(self.array_form)
|
| 1962 |
+
|
| 1963 |
+
@property
|
| 1964 |
+
def is_even(self):
|
| 1965 |
+
"""
|
| 1966 |
+
Checks if a permutation is even.
|
| 1967 |
+
|
| 1968 |
+
Examples
|
| 1969 |
+
========
|
| 1970 |
+
|
| 1971 |
+
>>> from sympy.combinatorics import Permutation
|
| 1972 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1973 |
+
>>> p.is_even
|
| 1974 |
+
True
|
| 1975 |
+
>>> p = Permutation([3, 2, 1, 0])
|
| 1976 |
+
>>> p.is_even
|
| 1977 |
+
True
|
| 1978 |
+
|
| 1979 |
+
See Also
|
| 1980 |
+
========
|
| 1981 |
+
|
| 1982 |
+
is_odd
|
| 1983 |
+
"""
|
| 1984 |
+
return not self.is_odd
|
| 1985 |
+
|
| 1986 |
+
@property
|
| 1987 |
+
def is_odd(self):
|
| 1988 |
+
"""
|
| 1989 |
+
Checks if a permutation is odd.
|
| 1990 |
+
|
| 1991 |
+
Examples
|
| 1992 |
+
========
|
| 1993 |
+
|
| 1994 |
+
>>> from sympy.combinatorics import Permutation
|
| 1995 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 1996 |
+
>>> p.is_odd
|
| 1997 |
+
False
|
| 1998 |
+
>>> p = Permutation([3, 2, 0, 1])
|
| 1999 |
+
>>> p.is_odd
|
| 2000 |
+
True
|
| 2001 |
+
|
| 2002 |
+
See Also
|
| 2003 |
+
========
|
| 2004 |
+
|
| 2005 |
+
is_even
|
| 2006 |
+
"""
|
| 2007 |
+
return bool(self.parity() % 2)
|
| 2008 |
+
|
| 2009 |
+
@property
|
| 2010 |
+
def is_Singleton(self):
|
| 2011 |
+
"""
|
| 2012 |
+
Checks to see if the permutation contains only one number and is
|
| 2013 |
+
thus the only possible permutation of this set of numbers
|
| 2014 |
+
|
| 2015 |
+
Examples
|
| 2016 |
+
========
|
| 2017 |
+
|
| 2018 |
+
>>> from sympy.combinatorics import Permutation
|
| 2019 |
+
>>> Permutation([0]).is_Singleton
|
| 2020 |
+
True
|
| 2021 |
+
>>> Permutation([0, 1]).is_Singleton
|
| 2022 |
+
False
|
| 2023 |
+
|
| 2024 |
+
See Also
|
| 2025 |
+
========
|
| 2026 |
+
|
| 2027 |
+
is_Empty
|
| 2028 |
+
"""
|
| 2029 |
+
return self.size == 1
|
| 2030 |
+
|
| 2031 |
+
@property
|
| 2032 |
+
def is_Empty(self):
|
| 2033 |
+
"""
|
| 2034 |
+
Checks to see if the permutation is a set with zero elements
|
| 2035 |
+
|
| 2036 |
+
Examples
|
| 2037 |
+
========
|
| 2038 |
+
|
| 2039 |
+
>>> from sympy.combinatorics import Permutation
|
| 2040 |
+
>>> Permutation([]).is_Empty
|
| 2041 |
+
True
|
| 2042 |
+
>>> Permutation([0]).is_Empty
|
| 2043 |
+
False
|
| 2044 |
+
|
| 2045 |
+
See Also
|
| 2046 |
+
========
|
| 2047 |
+
|
| 2048 |
+
is_Singleton
|
| 2049 |
+
"""
|
| 2050 |
+
return self.size == 0
|
| 2051 |
+
|
| 2052 |
+
@property
|
| 2053 |
+
def is_identity(self):
|
| 2054 |
+
return self.is_Identity
|
| 2055 |
+
|
| 2056 |
+
@property
|
| 2057 |
+
def is_Identity(self):
|
| 2058 |
+
"""
|
| 2059 |
+
Returns True if the Permutation is an identity permutation.
|
| 2060 |
+
|
| 2061 |
+
Examples
|
| 2062 |
+
========
|
| 2063 |
+
|
| 2064 |
+
>>> from sympy.combinatorics import Permutation
|
| 2065 |
+
>>> p = Permutation([])
|
| 2066 |
+
>>> p.is_Identity
|
| 2067 |
+
True
|
| 2068 |
+
>>> p = Permutation([[0], [1], [2]])
|
| 2069 |
+
>>> p.is_Identity
|
| 2070 |
+
True
|
| 2071 |
+
>>> p = Permutation([0, 1, 2])
|
| 2072 |
+
>>> p.is_Identity
|
| 2073 |
+
True
|
| 2074 |
+
>>> p = Permutation([0, 2, 1])
|
| 2075 |
+
>>> p.is_Identity
|
| 2076 |
+
False
|
| 2077 |
+
|
| 2078 |
+
See Also
|
| 2079 |
+
========
|
| 2080 |
+
|
| 2081 |
+
order
|
| 2082 |
+
"""
|
| 2083 |
+
af = self.array_form
|
| 2084 |
+
return not af or all(i == af[i] for i in range(self.size))
|
| 2085 |
+
|
| 2086 |
+
def ascents(self):
|
| 2087 |
+
"""
|
| 2088 |
+
Returns the positions of ascents in a permutation, ie, the location
|
| 2089 |
+
where p[i] < p[i+1]
|
| 2090 |
+
|
| 2091 |
+
Examples
|
| 2092 |
+
========
|
| 2093 |
+
|
| 2094 |
+
>>> from sympy.combinatorics import Permutation
|
| 2095 |
+
>>> p = Permutation([4, 0, 1, 3, 2])
|
| 2096 |
+
>>> p.ascents()
|
| 2097 |
+
[1, 2]
|
| 2098 |
+
|
| 2099 |
+
See Also
|
| 2100 |
+
========
|
| 2101 |
+
|
| 2102 |
+
descents, inversions, min, max
|
| 2103 |
+
"""
|
| 2104 |
+
a = self.array_form
|
| 2105 |
+
pos = [i for i in range(len(a) - 1) if a[i] < a[i + 1]]
|
| 2106 |
+
return pos
|
| 2107 |
+
|
| 2108 |
+
def descents(self):
|
| 2109 |
+
"""
|
| 2110 |
+
Returns the positions of descents in a permutation, ie, the location
|
| 2111 |
+
where p[i] > p[i+1]
|
| 2112 |
+
|
| 2113 |
+
Examples
|
| 2114 |
+
========
|
| 2115 |
+
|
| 2116 |
+
>>> from sympy.combinatorics import Permutation
|
| 2117 |
+
>>> p = Permutation([4, 0, 1, 3, 2])
|
| 2118 |
+
>>> p.descents()
|
| 2119 |
+
[0, 3]
|
| 2120 |
+
|
| 2121 |
+
See Also
|
| 2122 |
+
========
|
| 2123 |
+
|
| 2124 |
+
ascents, inversions, min, max
|
| 2125 |
+
"""
|
| 2126 |
+
a = self.array_form
|
| 2127 |
+
pos = [i for i in range(len(a) - 1) if a[i] > a[i + 1]]
|
| 2128 |
+
return pos
|
| 2129 |
+
|
| 2130 |
+
def max(self) -> int:
|
| 2131 |
+
"""
|
| 2132 |
+
The maximum element moved by the permutation.
|
| 2133 |
+
|
| 2134 |
+
Examples
|
| 2135 |
+
========
|
| 2136 |
+
|
| 2137 |
+
>>> from sympy.combinatorics import Permutation
|
| 2138 |
+
>>> p = Permutation([1, 0, 2, 3, 4])
|
| 2139 |
+
>>> p.max()
|
| 2140 |
+
1
|
| 2141 |
+
|
| 2142 |
+
See Also
|
| 2143 |
+
========
|
| 2144 |
+
|
| 2145 |
+
min, descents, ascents, inversions
|
| 2146 |
+
"""
|
| 2147 |
+
a = self.array_form
|
| 2148 |
+
if not a:
|
| 2149 |
+
return 0
|
| 2150 |
+
return max(_a for i, _a in enumerate(a) if _a != i)
|
| 2151 |
+
|
| 2152 |
+
def min(self) -> int:
|
| 2153 |
+
"""
|
| 2154 |
+
The minimum element moved by the permutation.
|
| 2155 |
+
|
| 2156 |
+
Examples
|
| 2157 |
+
========
|
| 2158 |
+
|
| 2159 |
+
>>> from sympy.combinatorics import Permutation
|
| 2160 |
+
>>> p = Permutation([0, 1, 4, 3, 2])
|
| 2161 |
+
>>> p.min()
|
| 2162 |
+
2
|
| 2163 |
+
|
| 2164 |
+
See Also
|
| 2165 |
+
========
|
| 2166 |
+
|
| 2167 |
+
max, descents, ascents, inversions
|
| 2168 |
+
"""
|
| 2169 |
+
a = self.array_form
|
| 2170 |
+
if not a:
|
| 2171 |
+
return 0
|
| 2172 |
+
return min(_a for i, _a in enumerate(a) if _a != i)
|
| 2173 |
+
|
| 2174 |
+
def inversions(self):
|
| 2175 |
+
"""
|
| 2176 |
+
Computes the number of inversions of a permutation.
|
| 2177 |
+
|
| 2178 |
+
Explanation
|
| 2179 |
+
===========
|
| 2180 |
+
|
| 2181 |
+
An inversion is where i > j but p[i] < p[j].
|
| 2182 |
+
|
| 2183 |
+
For small length of p, it iterates over all i and j
|
| 2184 |
+
values and calculates the number of inversions.
|
| 2185 |
+
For large length of p, it uses a variation of merge
|
| 2186 |
+
sort to calculate the number of inversions.
|
| 2187 |
+
|
| 2188 |
+
Examples
|
| 2189 |
+
========
|
| 2190 |
+
|
| 2191 |
+
>>> from sympy.combinatorics import Permutation
|
| 2192 |
+
>>> p = Permutation([0, 1, 2, 3, 4, 5])
|
| 2193 |
+
>>> p.inversions()
|
| 2194 |
+
0
|
| 2195 |
+
>>> Permutation([3, 2, 1, 0]).inversions()
|
| 2196 |
+
6
|
| 2197 |
+
|
| 2198 |
+
See Also
|
| 2199 |
+
========
|
| 2200 |
+
|
| 2201 |
+
descents, ascents, min, max
|
| 2202 |
+
|
| 2203 |
+
References
|
| 2204 |
+
==========
|
| 2205 |
+
|
| 2206 |
+
.. [1] https://www.cp.eng.chula.ac.th/~prabhas//teaching/algo/algo2008/count-inv.htm
|
| 2207 |
+
|
| 2208 |
+
"""
|
| 2209 |
+
inversions = 0
|
| 2210 |
+
a = self.array_form
|
| 2211 |
+
n = len(a)
|
| 2212 |
+
if n < 130:
|
| 2213 |
+
for i in range(n - 1):
|
| 2214 |
+
b = a[i]
|
| 2215 |
+
for c in a[i + 1:]:
|
| 2216 |
+
if b > c:
|
| 2217 |
+
inversions += 1
|
| 2218 |
+
else:
|
| 2219 |
+
k = 1
|
| 2220 |
+
right = 0
|
| 2221 |
+
arr = a[:]
|
| 2222 |
+
temp = a[:]
|
| 2223 |
+
while k < n:
|
| 2224 |
+
i = 0
|
| 2225 |
+
while i + k < n:
|
| 2226 |
+
right = i + k * 2 - 1
|
| 2227 |
+
if right >= n:
|
| 2228 |
+
right = n - 1
|
| 2229 |
+
inversions += _merge(arr, temp, i, i + k, right)
|
| 2230 |
+
i = i + k * 2
|
| 2231 |
+
k = k * 2
|
| 2232 |
+
return inversions
|
| 2233 |
+
|
| 2234 |
+
def commutator(self, x):
|
| 2235 |
+
"""Return the commutator of ``self`` and ``x``: ``~x*~self*x*self``
|
| 2236 |
+
|
| 2237 |
+
If f and g are part of a group, G, then the commutator of f and g
|
| 2238 |
+
is the group identity iff f and g commute, i.e. fg == gf.
|
| 2239 |
+
|
| 2240 |
+
Examples
|
| 2241 |
+
========
|
| 2242 |
+
|
| 2243 |
+
>>> from sympy.combinatorics import Permutation
|
| 2244 |
+
>>> from sympy import init_printing
|
| 2245 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2246 |
+
>>> p = Permutation([0, 2, 3, 1])
|
| 2247 |
+
>>> x = Permutation([2, 0, 3, 1])
|
| 2248 |
+
>>> c = p.commutator(x); c
|
| 2249 |
+
Permutation([2, 1, 3, 0])
|
| 2250 |
+
>>> c == ~x*~p*x*p
|
| 2251 |
+
True
|
| 2252 |
+
|
| 2253 |
+
>>> I = Permutation(3)
|
| 2254 |
+
>>> p = [I + i for i in range(6)]
|
| 2255 |
+
>>> for i in range(len(p)):
|
| 2256 |
+
... for j in range(len(p)):
|
| 2257 |
+
... c = p[i].commutator(p[j])
|
| 2258 |
+
... if p[i]*p[j] == p[j]*p[i]:
|
| 2259 |
+
... assert c == I
|
| 2260 |
+
... else:
|
| 2261 |
+
... assert c != I
|
| 2262 |
+
...
|
| 2263 |
+
|
| 2264 |
+
References
|
| 2265 |
+
==========
|
| 2266 |
+
|
| 2267 |
+
.. [1] https://en.wikipedia.org/wiki/Commutator
|
| 2268 |
+
"""
|
| 2269 |
+
|
| 2270 |
+
a = self.array_form
|
| 2271 |
+
b = x.array_form
|
| 2272 |
+
n = len(a)
|
| 2273 |
+
if len(b) != n:
|
| 2274 |
+
raise ValueError("The permutations must be of equal size.")
|
| 2275 |
+
inva = [None]*n
|
| 2276 |
+
for i in range(n):
|
| 2277 |
+
inva[a[i]] = i
|
| 2278 |
+
invb = [None]*n
|
| 2279 |
+
for i in range(n):
|
| 2280 |
+
invb[b[i]] = i
|
| 2281 |
+
return self._af_new([a[b[inva[i]]] for i in invb])
|
| 2282 |
+
|
| 2283 |
+
def signature(self):
|
| 2284 |
+
"""
|
| 2285 |
+
Gives the signature of the permutation needed to place the
|
| 2286 |
+
elements of the permutation in canonical order.
|
| 2287 |
+
|
| 2288 |
+
The signature is calculated as (-1)^<number of inversions>
|
| 2289 |
+
|
| 2290 |
+
Examples
|
| 2291 |
+
========
|
| 2292 |
+
|
| 2293 |
+
>>> from sympy.combinatorics import Permutation
|
| 2294 |
+
>>> p = Permutation([0, 1, 2])
|
| 2295 |
+
>>> p.inversions()
|
| 2296 |
+
0
|
| 2297 |
+
>>> p.signature()
|
| 2298 |
+
1
|
| 2299 |
+
>>> q = Permutation([0,2,1])
|
| 2300 |
+
>>> q.inversions()
|
| 2301 |
+
1
|
| 2302 |
+
>>> q.signature()
|
| 2303 |
+
-1
|
| 2304 |
+
|
| 2305 |
+
See Also
|
| 2306 |
+
========
|
| 2307 |
+
|
| 2308 |
+
inversions
|
| 2309 |
+
"""
|
| 2310 |
+
if self.is_even:
|
| 2311 |
+
return 1
|
| 2312 |
+
return -1
|
| 2313 |
+
|
| 2314 |
+
def order(self):
|
| 2315 |
+
"""
|
| 2316 |
+
Computes the order of a permutation.
|
| 2317 |
+
|
| 2318 |
+
When the permutation is raised to the power of its
|
| 2319 |
+
order it equals the identity permutation.
|
| 2320 |
+
|
| 2321 |
+
Examples
|
| 2322 |
+
========
|
| 2323 |
+
|
| 2324 |
+
>>> from sympy.combinatorics import Permutation
|
| 2325 |
+
>>> from sympy import init_printing
|
| 2326 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2327 |
+
>>> p = Permutation([3, 1, 5, 2, 4, 0])
|
| 2328 |
+
>>> p.order()
|
| 2329 |
+
4
|
| 2330 |
+
>>> (p**(p.order()))
|
| 2331 |
+
Permutation([], size=6)
|
| 2332 |
+
|
| 2333 |
+
See Also
|
| 2334 |
+
========
|
| 2335 |
+
|
| 2336 |
+
identity, cardinality, length, rank, size
|
| 2337 |
+
"""
|
| 2338 |
+
|
| 2339 |
+
return reduce(lcm, [len(cycle) for cycle in self.cyclic_form], 1)
|
| 2340 |
+
|
| 2341 |
+
def length(self):
|
| 2342 |
+
"""
|
| 2343 |
+
Returns the number of integers moved by a permutation.
|
| 2344 |
+
|
| 2345 |
+
Examples
|
| 2346 |
+
========
|
| 2347 |
+
|
| 2348 |
+
>>> from sympy.combinatorics import Permutation
|
| 2349 |
+
>>> Permutation([0, 3, 2, 1]).length()
|
| 2350 |
+
2
|
| 2351 |
+
>>> Permutation([[0, 1], [2, 3]]).length()
|
| 2352 |
+
4
|
| 2353 |
+
|
| 2354 |
+
See Also
|
| 2355 |
+
========
|
| 2356 |
+
|
| 2357 |
+
min, max, support, cardinality, order, rank, size
|
| 2358 |
+
"""
|
| 2359 |
+
|
| 2360 |
+
return len(self.support())
|
| 2361 |
+
|
| 2362 |
+
@property
|
| 2363 |
+
def cycle_structure(self):
|
| 2364 |
+
"""Return the cycle structure of the permutation as a dictionary
|
| 2365 |
+
indicating the multiplicity of each cycle length.
|
| 2366 |
+
|
| 2367 |
+
Examples
|
| 2368 |
+
========
|
| 2369 |
+
|
| 2370 |
+
>>> from sympy.combinatorics import Permutation
|
| 2371 |
+
>>> Permutation(3).cycle_structure
|
| 2372 |
+
{1: 4}
|
| 2373 |
+
>>> Permutation(0, 4, 3)(1, 2)(5, 6).cycle_structure
|
| 2374 |
+
{2: 2, 3: 1}
|
| 2375 |
+
"""
|
| 2376 |
+
if self._cycle_structure:
|
| 2377 |
+
rv = self._cycle_structure
|
| 2378 |
+
else:
|
| 2379 |
+
rv = defaultdict(int)
|
| 2380 |
+
singletons = self.size
|
| 2381 |
+
for c in self.cyclic_form:
|
| 2382 |
+
rv[len(c)] += 1
|
| 2383 |
+
singletons -= len(c)
|
| 2384 |
+
if singletons:
|
| 2385 |
+
rv[1] = singletons
|
| 2386 |
+
self._cycle_structure = rv
|
| 2387 |
+
return dict(rv) # make a copy
|
| 2388 |
+
|
| 2389 |
+
@property
|
| 2390 |
+
def cycles(self):
|
| 2391 |
+
"""
|
| 2392 |
+
Returns the number of cycles contained in the permutation
|
| 2393 |
+
(including singletons).
|
| 2394 |
+
|
| 2395 |
+
Examples
|
| 2396 |
+
========
|
| 2397 |
+
|
| 2398 |
+
>>> from sympy.combinatorics import Permutation
|
| 2399 |
+
>>> Permutation([0, 1, 2]).cycles
|
| 2400 |
+
3
|
| 2401 |
+
>>> Permutation([0, 1, 2]).full_cyclic_form
|
| 2402 |
+
[[0], [1], [2]]
|
| 2403 |
+
>>> Permutation(0, 1)(2, 3).cycles
|
| 2404 |
+
2
|
| 2405 |
+
|
| 2406 |
+
See Also
|
| 2407 |
+
========
|
| 2408 |
+
sympy.functions.combinatorial.numbers.stirling
|
| 2409 |
+
"""
|
| 2410 |
+
return len(self.full_cyclic_form)
|
| 2411 |
+
|
| 2412 |
+
def index(self):
|
| 2413 |
+
"""
|
| 2414 |
+
Returns the index of a permutation.
|
| 2415 |
+
|
| 2416 |
+
The index of a permutation is the sum of all subscripts j such
|
| 2417 |
+
that p[j] is greater than p[j+1].
|
| 2418 |
+
|
| 2419 |
+
Examples
|
| 2420 |
+
========
|
| 2421 |
+
|
| 2422 |
+
>>> from sympy.combinatorics import Permutation
|
| 2423 |
+
>>> p = Permutation([3, 0, 2, 1, 4])
|
| 2424 |
+
>>> p.index()
|
| 2425 |
+
2
|
| 2426 |
+
"""
|
| 2427 |
+
a = self.array_form
|
| 2428 |
+
|
| 2429 |
+
return sum(j for j in range(len(a) - 1) if a[j] > a[j + 1])
|
| 2430 |
+
|
| 2431 |
+
def runs(self):
|
| 2432 |
+
"""
|
| 2433 |
+
Returns the runs of a permutation.
|
| 2434 |
+
|
| 2435 |
+
An ascending sequence in a permutation is called a run [5].
|
| 2436 |
+
|
| 2437 |
+
|
| 2438 |
+
Examples
|
| 2439 |
+
========
|
| 2440 |
+
|
| 2441 |
+
>>> from sympy.combinatorics import Permutation
|
| 2442 |
+
>>> p = Permutation([2, 5, 7, 3, 6, 0, 1, 4, 8])
|
| 2443 |
+
>>> p.runs()
|
| 2444 |
+
[[2, 5, 7], [3, 6], [0, 1, 4, 8]]
|
| 2445 |
+
>>> q = Permutation([1,3,2,0])
|
| 2446 |
+
>>> q.runs()
|
| 2447 |
+
[[1, 3], [2], [0]]
|
| 2448 |
+
"""
|
| 2449 |
+
return runs(self.array_form)
|
| 2450 |
+
|
| 2451 |
+
def inversion_vector(self):
|
| 2452 |
+
"""Return the inversion vector of the permutation.
|
| 2453 |
+
|
| 2454 |
+
The inversion vector consists of elements whose value
|
| 2455 |
+
indicates the number of elements in the permutation
|
| 2456 |
+
that are lesser than it and lie on its right hand side.
|
| 2457 |
+
|
| 2458 |
+
The inversion vector is the same as the Lehmer encoding of a
|
| 2459 |
+
permutation.
|
| 2460 |
+
|
| 2461 |
+
Examples
|
| 2462 |
+
========
|
| 2463 |
+
|
| 2464 |
+
>>> from sympy.combinatorics import Permutation
|
| 2465 |
+
>>> p = Permutation([4, 8, 0, 7, 1, 5, 3, 6, 2])
|
| 2466 |
+
>>> p.inversion_vector()
|
| 2467 |
+
[4, 7, 0, 5, 0, 2, 1, 1]
|
| 2468 |
+
>>> p = Permutation([3, 2, 1, 0])
|
| 2469 |
+
>>> p.inversion_vector()
|
| 2470 |
+
[3, 2, 1]
|
| 2471 |
+
|
| 2472 |
+
The inversion vector increases lexicographically with the rank
|
| 2473 |
+
of the permutation, the -ith element cycling through 0..i.
|
| 2474 |
+
|
| 2475 |
+
>>> p = Permutation(2)
|
| 2476 |
+
>>> while p:
|
| 2477 |
+
... print('%s %s %s' % (p, p.inversion_vector(), p.rank()))
|
| 2478 |
+
... p = p.next_lex()
|
| 2479 |
+
(2) [0, 0] 0
|
| 2480 |
+
(1 2) [0, 1] 1
|
| 2481 |
+
(2)(0 1) [1, 0] 2
|
| 2482 |
+
(0 1 2) [1, 1] 3
|
| 2483 |
+
(0 2 1) [2, 0] 4
|
| 2484 |
+
(0 2) [2, 1] 5
|
| 2485 |
+
|
| 2486 |
+
See Also
|
| 2487 |
+
========
|
| 2488 |
+
|
| 2489 |
+
from_inversion_vector
|
| 2490 |
+
"""
|
| 2491 |
+
self_array_form = self.array_form
|
| 2492 |
+
n = len(self_array_form)
|
| 2493 |
+
inversion_vector = [0] * (n - 1)
|
| 2494 |
+
|
| 2495 |
+
for i in range(n - 1):
|
| 2496 |
+
val = 0
|
| 2497 |
+
for j in range(i + 1, n):
|
| 2498 |
+
if self_array_form[j] < self_array_form[i]:
|
| 2499 |
+
val += 1
|
| 2500 |
+
inversion_vector[i] = val
|
| 2501 |
+
return inversion_vector
|
| 2502 |
+
|
| 2503 |
+
def rank_trotterjohnson(self):
|
| 2504 |
+
"""
|
| 2505 |
+
Returns the Trotter Johnson rank, which we get from the minimal
|
| 2506 |
+
change algorithm. See [4] section 2.4.
|
| 2507 |
+
|
| 2508 |
+
Examples
|
| 2509 |
+
========
|
| 2510 |
+
|
| 2511 |
+
>>> from sympy.combinatorics import Permutation
|
| 2512 |
+
>>> p = Permutation([0, 1, 2, 3])
|
| 2513 |
+
>>> p.rank_trotterjohnson()
|
| 2514 |
+
0
|
| 2515 |
+
>>> p = Permutation([0, 2, 1, 3])
|
| 2516 |
+
>>> p.rank_trotterjohnson()
|
| 2517 |
+
7
|
| 2518 |
+
|
| 2519 |
+
See Also
|
| 2520 |
+
========
|
| 2521 |
+
|
| 2522 |
+
unrank_trotterjohnson, next_trotterjohnson
|
| 2523 |
+
"""
|
| 2524 |
+
if self.array_form == [] or self.is_Identity:
|
| 2525 |
+
return 0
|
| 2526 |
+
if self.array_form == [1, 0]:
|
| 2527 |
+
return 1
|
| 2528 |
+
perm = self.array_form
|
| 2529 |
+
n = self.size
|
| 2530 |
+
rank = 0
|
| 2531 |
+
for j in range(1, n):
|
| 2532 |
+
k = 1
|
| 2533 |
+
i = 0
|
| 2534 |
+
while perm[i] != j:
|
| 2535 |
+
if perm[i] < j:
|
| 2536 |
+
k += 1
|
| 2537 |
+
i += 1
|
| 2538 |
+
j1 = j + 1
|
| 2539 |
+
if rank % 2 == 0:
|
| 2540 |
+
rank = j1*rank + j1 - k
|
| 2541 |
+
else:
|
| 2542 |
+
rank = j1*rank + k - 1
|
| 2543 |
+
return rank
|
| 2544 |
+
|
| 2545 |
+
@classmethod
|
| 2546 |
+
def unrank_trotterjohnson(cls, size, rank):
|
| 2547 |
+
"""
|
| 2548 |
+
Trotter Johnson permutation unranking. See [4] section 2.4.
|
| 2549 |
+
|
| 2550 |
+
Examples
|
| 2551 |
+
========
|
| 2552 |
+
|
| 2553 |
+
>>> from sympy.combinatorics import Permutation
|
| 2554 |
+
>>> from sympy import init_printing
|
| 2555 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2556 |
+
>>> Permutation.unrank_trotterjohnson(5, 10)
|
| 2557 |
+
Permutation([0, 3, 1, 2, 4])
|
| 2558 |
+
|
| 2559 |
+
See Also
|
| 2560 |
+
========
|
| 2561 |
+
|
| 2562 |
+
rank_trotterjohnson, next_trotterjohnson
|
| 2563 |
+
"""
|
| 2564 |
+
perm = [0]*size
|
| 2565 |
+
r2 = 0
|
| 2566 |
+
n = ifac(size)
|
| 2567 |
+
pj = 1
|
| 2568 |
+
for j in range(2, size + 1):
|
| 2569 |
+
pj *= j
|
| 2570 |
+
r1 = (rank * pj) // n
|
| 2571 |
+
k = r1 - j*r2
|
| 2572 |
+
if r2 % 2 == 0:
|
| 2573 |
+
for i in range(j - 1, j - k - 1, -1):
|
| 2574 |
+
perm[i] = perm[i - 1]
|
| 2575 |
+
perm[j - k - 1] = j - 1
|
| 2576 |
+
else:
|
| 2577 |
+
for i in range(j - 1, k, -1):
|
| 2578 |
+
perm[i] = perm[i - 1]
|
| 2579 |
+
perm[k] = j - 1
|
| 2580 |
+
r2 = r1
|
| 2581 |
+
return cls._af_new(perm)
|
| 2582 |
+
|
| 2583 |
+
def next_trotterjohnson(self):
|
| 2584 |
+
"""
|
| 2585 |
+
Returns the next permutation in Trotter-Johnson order.
|
| 2586 |
+
If self is the last permutation it returns None.
|
| 2587 |
+
See [4] section 2.4. If it is desired to generate all such
|
| 2588 |
+
permutations, they can be generated in order more quickly
|
| 2589 |
+
with the ``generate_bell`` function.
|
| 2590 |
+
|
| 2591 |
+
Examples
|
| 2592 |
+
========
|
| 2593 |
+
|
| 2594 |
+
>>> from sympy.combinatorics import Permutation
|
| 2595 |
+
>>> from sympy import init_printing
|
| 2596 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2597 |
+
>>> p = Permutation([3, 0, 2, 1])
|
| 2598 |
+
>>> p.rank_trotterjohnson()
|
| 2599 |
+
4
|
| 2600 |
+
>>> p = p.next_trotterjohnson(); p
|
| 2601 |
+
Permutation([0, 3, 2, 1])
|
| 2602 |
+
>>> p.rank_trotterjohnson()
|
| 2603 |
+
5
|
| 2604 |
+
|
| 2605 |
+
See Also
|
| 2606 |
+
========
|
| 2607 |
+
|
| 2608 |
+
rank_trotterjohnson, unrank_trotterjohnson, sympy.utilities.iterables.generate_bell
|
| 2609 |
+
"""
|
| 2610 |
+
pi = self.array_form[:]
|
| 2611 |
+
n = len(pi)
|
| 2612 |
+
st = 0
|
| 2613 |
+
rho = pi[:]
|
| 2614 |
+
done = False
|
| 2615 |
+
m = n-1
|
| 2616 |
+
while m > 0 and not done:
|
| 2617 |
+
d = rho.index(m)
|
| 2618 |
+
for i in range(d, m):
|
| 2619 |
+
rho[i] = rho[i + 1]
|
| 2620 |
+
par = _af_parity(rho[:m])
|
| 2621 |
+
if par == 1:
|
| 2622 |
+
if d == m:
|
| 2623 |
+
m -= 1
|
| 2624 |
+
else:
|
| 2625 |
+
pi[st + d], pi[st + d + 1] = pi[st + d + 1], pi[st + d]
|
| 2626 |
+
done = True
|
| 2627 |
+
else:
|
| 2628 |
+
if d == 0:
|
| 2629 |
+
m -= 1
|
| 2630 |
+
st += 1
|
| 2631 |
+
else:
|
| 2632 |
+
pi[st + d], pi[st + d - 1] = pi[st + d - 1], pi[st + d]
|
| 2633 |
+
done = True
|
| 2634 |
+
if m == 0:
|
| 2635 |
+
return None
|
| 2636 |
+
return self._af_new(pi)
|
| 2637 |
+
|
| 2638 |
+
def get_precedence_matrix(self):
|
| 2639 |
+
"""
|
| 2640 |
+
Gets the precedence matrix. This is used for computing the
|
| 2641 |
+
distance between two permutations.
|
| 2642 |
+
|
| 2643 |
+
Examples
|
| 2644 |
+
========
|
| 2645 |
+
|
| 2646 |
+
>>> from sympy.combinatorics import Permutation
|
| 2647 |
+
>>> from sympy import init_printing
|
| 2648 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2649 |
+
>>> p = Permutation.josephus(3, 6, 1)
|
| 2650 |
+
>>> p
|
| 2651 |
+
Permutation([2, 5, 3, 1, 4, 0])
|
| 2652 |
+
>>> p.get_precedence_matrix()
|
| 2653 |
+
Matrix([
|
| 2654 |
+
[0, 0, 0, 0, 0, 0],
|
| 2655 |
+
[1, 0, 0, 0, 1, 0],
|
| 2656 |
+
[1, 1, 0, 1, 1, 1],
|
| 2657 |
+
[1, 1, 0, 0, 1, 0],
|
| 2658 |
+
[1, 0, 0, 0, 0, 0],
|
| 2659 |
+
[1, 1, 0, 1, 1, 0]])
|
| 2660 |
+
|
| 2661 |
+
See Also
|
| 2662 |
+
========
|
| 2663 |
+
|
| 2664 |
+
get_precedence_distance, get_adjacency_matrix, get_adjacency_distance
|
| 2665 |
+
"""
|
| 2666 |
+
m = zeros(self.size)
|
| 2667 |
+
perm = self.array_form
|
| 2668 |
+
for i in range(m.rows):
|
| 2669 |
+
for j in range(i + 1, m.cols):
|
| 2670 |
+
m[perm[i], perm[j]] = 1
|
| 2671 |
+
return m
|
| 2672 |
+
|
| 2673 |
+
def get_precedence_distance(self, other):
|
| 2674 |
+
"""
|
| 2675 |
+
Computes the precedence distance between two permutations.
|
| 2676 |
+
|
| 2677 |
+
Explanation
|
| 2678 |
+
===========
|
| 2679 |
+
|
| 2680 |
+
Suppose p and p' represent n jobs. The precedence metric
|
| 2681 |
+
counts the number of times a job j is preceded by job i
|
| 2682 |
+
in both p and p'. This metric is commutative.
|
| 2683 |
+
|
| 2684 |
+
Examples
|
| 2685 |
+
========
|
| 2686 |
+
|
| 2687 |
+
>>> from sympy.combinatorics import Permutation
|
| 2688 |
+
>>> p = Permutation([2, 0, 4, 3, 1])
|
| 2689 |
+
>>> q = Permutation([3, 1, 2, 4, 0])
|
| 2690 |
+
>>> p.get_precedence_distance(q)
|
| 2691 |
+
7
|
| 2692 |
+
>>> q.get_precedence_distance(p)
|
| 2693 |
+
7
|
| 2694 |
+
|
| 2695 |
+
See Also
|
| 2696 |
+
========
|
| 2697 |
+
|
| 2698 |
+
get_precedence_matrix, get_adjacency_matrix, get_adjacency_distance
|
| 2699 |
+
"""
|
| 2700 |
+
if self.size != other.size:
|
| 2701 |
+
raise ValueError("The permutations must be of equal size.")
|
| 2702 |
+
self_prec_mat = self.get_precedence_matrix()
|
| 2703 |
+
other_prec_mat = other.get_precedence_matrix()
|
| 2704 |
+
n_prec = 0
|
| 2705 |
+
for i in range(self.size):
|
| 2706 |
+
for j in range(self.size):
|
| 2707 |
+
if i == j:
|
| 2708 |
+
continue
|
| 2709 |
+
if self_prec_mat[i, j] * other_prec_mat[i, j] == 1:
|
| 2710 |
+
n_prec += 1
|
| 2711 |
+
d = self.size * (self.size - 1)//2 - n_prec
|
| 2712 |
+
return d
|
| 2713 |
+
|
| 2714 |
+
def get_adjacency_matrix(self):
|
| 2715 |
+
"""
|
| 2716 |
+
Computes the adjacency matrix of a permutation.
|
| 2717 |
+
|
| 2718 |
+
Explanation
|
| 2719 |
+
===========
|
| 2720 |
+
|
| 2721 |
+
If job i is adjacent to job j in a permutation p
|
| 2722 |
+
then we set m[i, j] = 1 where m is the adjacency
|
| 2723 |
+
matrix of p.
|
| 2724 |
+
|
| 2725 |
+
Examples
|
| 2726 |
+
========
|
| 2727 |
+
|
| 2728 |
+
>>> from sympy.combinatorics import Permutation
|
| 2729 |
+
>>> p = Permutation.josephus(3, 6, 1)
|
| 2730 |
+
>>> p.get_adjacency_matrix()
|
| 2731 |
+
Matrix([
|
| 2732 |
+
[0, 0, 0, 0, 0, 0],
|
| 2733 |
+
[0, 0, 0, 0, 1, 0],
|
| 2734 |
+
[0, 0, 0, 0, 0, 1],
|
| 2735 |
+
[0, 1, 0, 0, 0, 0],
|
| 2736 |
+
[1, 0, 0, 0, 0, 0],
|
| 2737 |
+
[0, 0, 0, 1, 0, 0]])
|
| 2738 |
+
>>> q = Permutation([0, 1, 2, 3])
|
| 2739 |
+
>>> q.get_adjacency_matrix()
|
| 2740 |
+
Matrix([
|
| 2741 |
+
[0, 1, 0, 0],
|
| 2742 |
+
[0, 0, 1, 0],
|
| 2743 |
+
[0, 0, 0, 1],
|
| 2744 |
+
[0, 0, 0, 0]])
|
| 2745 |
+
|
| 2746 |
+
See Also
|
| 2747 |
+
========
|
| 2748 |
+
|
| 2749 |
+
get_precedence_matrix, get_precedence_distance, get_adjacency_distance
|
| 2750 |
+
"""
|
| 2751 |
+
m = zeros(self.size)
|
| 2752 |
+
perm = self.array_form
|
| 2753 |
+
for i in range(self.size - 1):
|
| 2754 |
+
m[perm[i], perm[i + 1]] = 1
|
| 2755 |
+
return m
|
| 2756 |
+
|
| 2757 |
+
def get_adjacency_distance(self, other):
|
| 2758 |
+
"""
|
| 2759 |
+
Computes the adjacency distance between two permutations.
|
| 2760 |
+
|
| 2761 |
+
Explanation
|
| 2762 |
+
===========
|
| 2763 |
+
|
| 2764 |
+
This metric counts the number of times a pair i,j of jobs is
|
| 2765 |
+
adjacent in both p and p'. If n_adj is this quantity then
|
| 2766 |
+
the adjacency distance is n - n_adj - 1 [1]
|
| 2767 |
+
|
| 2768 |
+
[1] Reeves, Colin R. Landscapes, Operators and Heuristic search, Annals
|
| 2769 |
+
of Operational Research, 86, pp 473-490. (1999)
|
| 2770 |
+
|
| 2771 |
+
|
| 2772 |
+
Examples
|
| 2773 |
+
========
|
| 2774 |
+
|
| 2775 |
+
>>> from sympy.combinatorics import Permutation
|
| 2776 |
+
>>> p = Permutation([0, 3, 1, 2, 4])
|
| 2777 |
+
>>> q = Permutation.josephus(4, 5, 2)
|
| 2778 |
+
>>> p.get_adjacency_distance(q)
|
| 2779 |
+
3
|
| 2780 |
+
>>> r = Permutation([0, 2, 1, 4, 3])
|
| 2781 |
+
>>> p.get_adjacency_distance(r)
|
| 2782 |
+
4
|
| 2783 |
+
|
| 2784 |
+
See Also
|
| 2785 |
+
========
|
| 2786 |
+
|
| 2787 |
+
get_precedence_matrix, get_precedence_distance, get_adjacency_matrix
|
| 2788 |
+
"""
|
| 2789 |
+
if self.size != other.size:
|
| 2790 |
+
raise ValueError("The permutations must be of the same size.")
|
| 2791 |
+
self_adj_mat = self.get_adjacency_matrix()
|
| 2792 |
+
other_adj_mat = other.get_adjacency_matrix()
|
| 2793 |
+
n_adj = 0
|
| 2794 |
+
for i in range(self.size):
|
| 2795 |
+
for j in range(self.size):
|
| 2796 |
+
if i == j:
|
| 2797 |
+
continue
|
| 2798 |
+
if self_adj_mat[i, j] * other_adj_mat[i, j] == 1:
|
| 2799 |
+
n_adj += 1
|
| 2800 |
+
d = self.size - n_adj - 1
|
| 2801 |
+
return d
|
| 2802 |
+
|
| 2803 |
+
def get_positional_distance(self, other):
|
| 2804 |
+
"""
|
| 2805 |
+
Computes the positional distance between two permutations.
|
| 2806 |
+
|
| 2807 |
+
Examples
|
| 2808 |
+
========
|
| 2809 |
+
|
| 2810 |
+
>>> from sympy.combinatorics import Permutation
|
| 2811 |
+
>>> p = Permutation([0, 3, 1, 2, 4])
|
| 2812 |
+
>>> q = Permutation.josephus(4, 5, 2)
|
| 2813 |
+
>>> r = Permutation([3, 1, 4, 0, 2])
|
| 2814 |
+
>>> p.get_positional_distance(q)
|
| 2815 |
+
12
|
| 2816 |
+
>>> p.get_positional_distance(r)
|
| 2817 |
+
12
|
| 2818 |
+
|
| 2819 |
+
See Also
|
| 2820 |
+
========
|
| 2821 |
+
|
| 2822 |
+
get_precedence_distance, get_adjacency_distance
|
| 2823 |
+
"""
|
| 2824 |
+
a = self.array_form
|
| 2825 |
+
b = other.array_form
|
| 2826 |
+
if len(a) != len(b):
|
| 2827 |
+
raise ValueError("The permutations must be of the same size.")
|
| 2828 |
+
return sum(abs(a[i] - b[i]) for i in range(len(a)))
|
| 2829 |
+
|
| 2830 |
+
@classmethod
|
| 2831 |
+
def josephus(cls, m, n, s=1):
|
| 2832 |
+
"""Return as a permutation the shuffling of range(n) using the Josephus
|
| 2833 |
+
scheme in which every m-th item is selected until all have been chosen.
|
| 2834 |
+
The returned permutation has elements listed by the order in which they
|
| 2835 |
+
were selected.
|
| 2836 |
+
|
| 2837 |
+
The parameter ``s`` stops the selection process when there are ``s``
|
| 2838 |
+
items remaining and these are selected by continuing the selection,
|
| 2839 |
+
counting by 1 rather than by ``m``.
|
| 2840 |
+
|
| 2841 |
+
Consider selecting every 3rd item from 6 until only 2 remain::
|
| 2842 |
+
|
| 2843 |
+
choices chosen
|
| 2844 |
+
======== ======
|
| 2845 |
+
012345
|
| 2846 |
+
01 345 2
|
| 2847 |
+
01 34 25
|
| 2848 |
+
01 4 253
|
| 2849 |
+
0 4 2531
|
| 2850 |
+
0 25314
|
| 2851 |
+
253140
|
| 2852 |
+
|
| 2853 |
+
Examples
|
| 2854 |
+
========
|
| 2855 |
+
|
| 2856 |
+
>>> from sympy.combinatorics import Permutation
|
| 2857 |
+
>>> Permutation.josephus(3, 6, 2).array_form
|
| 2858 |
+
[2, 5, 3, 1, 4, 0]
|
| 2859 |
+
|
| 2860 |
+
References
|
| 2861 |
+
==========
|
| 2862 |
+
|
| 2863 |
+
.. [1] https://en.wikipedia.org/wiki/Flavius_Josephus
|
| 2864 |
+
.. [2] https://en.wikipedia.org/wiki/Josephus_problem
|
| 2865 |
+
.. [3] https://web.archive.org/web/20171008094331/http://www.wou.edu/~burtonl/josephus.html
|
| 2866 |
+
|
| 2867 |
+
"""
|
| 2868 |
+
from collections import deque
|
| 2869 |
+
m -= 1
|
| 2870 |
+
Q = deque(list(range(n)))
|
| 2871 |
+
perm = []
|
| 2872 |
+
while len(Q) > max(s, 1):
|
| 2873 |
+
for dp in range(m):
|
| 2874 |
+
Q.append(Q.popleft())
|
| 2875 |
+
perm.append(Q.popleft())
|
| 2876 |
+
perm.extend(list(Q))
|
| 2877 |
+
return cls(perm)
|
| 2878 |
+
|
| 2879 |
+
@classmethod
|
| 2880 |
+
def from_inversion_vector(cls, inversion):
|
| 2881 |
+
"""
|
| 2882 |
+
Calculates the permutation from the inversion vector.
|
| 2883 |
+
|
| 2884 |
+
Examples
|
| 2885 |
+
========
|
| 2886 |
+
|
| 2887 |
+
>>> from sympy.combinatorics import Permutation
|
| 2888 |
+
>>> from sympy import init_printing
|
| 2889 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2890 |
+
>>> Permutation.from_inversion_vector([3, 2, 1, 0, 0])
|
| 2891 |
+
Permutation([3, 2, 1, 0, 4, 5])
|
| 2892 |
+
|
| 2893 |
+
"""
|
| 2894 |
+
size = len(inversion)
|
| 2895 |
+
N = list(range(size + 1))
|
| 2896 |
+
perm = []
|
| 2897 |
+
try:
|
| 2898 |
+
for k in range(size):
|
| 2899 |
+
val = N[inversion[k]]
|
| 2900 |
+
perm.append(val)
|
| 2901 |
+
N.remove(val)
|
| 2902 |
+
except IndexError:
|
| 2903 |
+
raise ValueError("The inversion vector is not valid.")
|
| 2904 |
+
perm.extend(N)
|
| 2905 |
+
return cls._af_new(perm)
|
| 2906 |
+
|
| 2907 |
+
@classmethod
|
| 2908 |
+
def random(cls, n):
|
| 2909 |
+
"""
|
| 2910 |
+
Generates a random permutation of length ``n``.
|
| 2911 |
+
|
| 2912 |
+
Uses the underlying Python pseudo-random number generator.
|
| 2913 |
+
|
| 2914 |
+
Examples
|
| 2915 |
+
========
|
| 2916 |
+
|
| 2917 |
+
>>> from sympy.combinatorics import Permutation
|
| 2918 |
+
>>> Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1]))
|
| 2919 |
+
True
|
| 2920 |
+
|
| 2921 |
+
"""
|
| 2922 |
+
perm_array = list(range(n))
|
| 2923 |
+
random.shuffle(perm_array)
|
| 2924 |
+
return cls._af_new(perm_array)
|
| 2925 |
+
|
| 2926 |
+
@classmethod
|
| 2927 |
+
def unrank_lex(cls, size, rank):
|
| 2928 |
+
"""
|
| 2929 |
+
Lexicographic permutation unranking.
|
| 2930 |
+
|
| 2931 |
+
Examples
|
| 2932 |
+
========
|
| 2933 |
+
|
| 2934 |
+
>>> from sympy.combinatorics import Permutation
|
| 2935 |
+
>>> from sympy import init_printing
|
| 2936 |
+
>>> init_printing(perm_cyclic=False, pretty_print=False)
|
| 2937 |
+
>>> a = Permutation.unrank_lex(5, 10)
|
| 2938 |
+
>>> a.rank()
|
| 2939 |
+
10
|
| 2940 |
+
>>> a
|
| 2941 |
+
Permutation([0, 2, 4, 1, 3])
|
| 2942 |
+
|
| 2943 |
+
See Also
|
| 2944 |
+
========
|
| 2945 |
+
|
| 2946 |
+
rank, next_lex
|
| 2947 |
+
"""
|
| 2948 |
+
perm_array = [0] * size
|
| 2949 |
+
psize = 1
|
| 2950 |
+
for i in range(size):
|
| 2951 |
+
new_psize = psize*(i + 1)
|
| 2952 |
+
d = (rank % new_psize) // psize
|
| 2953 |
+
rank -= d*psize
|
| 2954 |
+
perm_array[size - i - 1] = d
|
| 2955 |
+
for j in range(size - i, size):
|
| 2956 |
+
if perm_array[j] > d - 1:
|
| 2957 |
+
perm_array[j] += 1
|
| 2958 |
+
psize = new_psize
|
| 2959 |
+
return cls._af_new(perm_array)
|
| 2960 |
+
|
| 2961 |
+
def resize(self, n):
|
| 2962 |
+
"""Resize the permutation to the new size ``n``.
|
| 2963 |
+
|
| 2964 |
+
Parameters
|
| 2965 |
+
==========
|
| 2966 |
+
|
| 2967 |
+
n : int
|
| 2968 |
+
The new size of the permutation.
|
| 2969 |
+
|
| 2970 |
+
Raises
|
| 2971 |
+
======
|
| 2972 |
+
|
| 2973 |
+
ValueError
|
| 2974 |
+
If the permutation cannot be resized to the given size.
|
| 2975 |
+
This may only happen when resized to a smaller size than
|
| 2976 |
+
the original.
|
| 2977 |
+
|
| 2978 |
+
Examples
|
| 2979 |
+
========
|
| 2980 |
+
|
| 2981 |
+
>>> from sympy.combinatorics import Permutation
|
| 2982 |
+
|
| 2983 |
+
Increasing the size of a permutation:
|
| 2984 |
+
|
| 2985 |
+
>>> p = Permutation(0, 1, 2)
|
| 2986 |
+
>>> p = p.resize(5)
|
| 2987 |
+
>>> p
|
| 2988 |
+
(4)(0 1 2)
|
| 2989 |
+
|
| 2990 |
+
Decreasing the size of the permutation:
|
| 2991 |
+
|
| 2992 |
+
>>> p = p.resize(4)
|
| 2993 |
+
>>> p
|
| 2994 |
+
(3)(0 1 2)
|
| 2995 |
+
|
| 2996 |
+
If resizing to the specific size breaks the cycles:
|
| 2997 |
+
|
| 2998 |
+
>>> p.resize(2)
|
| 2999 |
+
Traceback (most recent call last):
|
| 3000 |
+
...
|
| 3001 |
+
ValueError: The permutation cannot be resized to 2 because the
|
| 3002 |
+
cycle (0, 1, 2) may break.
|
| 3003 |
+
"""
|
| 3004 |
+
aform = self.array_form
|
| 3005 |
+
l = len(aform)
|
| 3006 |
+
if n > l:
|
| 3007 |
+
aform += list(range(l, n))
|
| 3008 |
+
return Permutation._af_new(aform)
|
| 3009 |
+
|
| 3010 |
+
elif n < l:
|
| 3011 |
+
cyclic_form = self.full_cyclic_form
|
| 3012 |
+
new_cyclic_form = []
|
| 3013 |
+
for cycle in cyclic_form:
|
| 3014 |
+
cycle_min = min(cycle)
|
| 3015 |
+
cycle_max = max(cycle)
|
| 3016 |
+
if cycle_min <= n-1:
|
| 3017 |
+
if cycle_max > n-1:
|
| 3018 |
+
raise ValueError(
|
| 3019 |
+
"The permutation cannot be resized to {} "
|
| 3020 |
+
"because the cycle {} may break."
|
| 3021 |
+
.format(n, tuple(cycle)))
|
| 3022 |
+
|
| 3023 |
+
new_cyclic_form.append(cycle)
|
| 3024 |
+
return Permutation(new_cyclic_form)
|
| 3025 |
+
|
| 3026 |
+
return self
|
| 3027 |
+
|
| 3028 |
+
# XXX Deprecated flag
|
| 3029 |
+
print_cyclic = None
|
| 3030 |
+
|
| 3031 |
+
|
| 3032 |
+
def _merge(arr, temp, left, mid, right):
|
| 3033 |
+
"""
|
| 3034 |
+
Merges two sorted arrays and calculates the inversion count.
|
| 3035 |
+
|
| 3036 |
+
Helper function for calculating inversions. This method is
|
| 3037 |
+
for internal use only.
|
| 3038 |
+
"""
|
| 3039 |
+
i = k = left
|
| 3040 |
+
j = mid
|
| 3041 |
+
inv_count = 0
|
| 3042 |
+
while i < mid and j <= right:
|
| 3043 |
+
if arr[i] < arr[j]:
|
| 3044 |
+
temp[k] = arr[i]
|
| 3045 |
+
k += 1
|
| 3046 |
+
i += 1
|
| 3047 |
+
else:
|
| 3048 |
+
temp[k] = arr[j]
|
| 3049 |
+
k += 1
|
| 3050 |
+
j += 1
|
| 3051 |
+
inv_count += (mid -i)
|
| 3052 |
+
while i < mid:
|
| 3053 |
+
temp[k] = arr[i]
|
| 3054 |
+
k += 1
|
| 3055 |
+
i += 1
|
| 3056 |
+
if j <= right:
|
| 3057 |
+
k += right - j + 1
|
| 3058 |
+
j += right - j + 1
|
| 3059 |
+
arr[left:k + 1] = temp[left:k + 1]
|
| 3060 |
+
else:
|
| 3061 |
+
arr[left:right + 1] = temp[left:right + 1]
|
| 3062 |
+
return inv_count
|
| 3063 |
+
|
| 3064 |
+
Perm = Permutation
|
| 3065 |
+
_af_new = Perm._af_new
|
| 3066 |
+
|
| 3067 |
+
|
| 3068 |
+
class AppliedPermutation(Expr):
|
| 3069 |
+
"""A permutation applied to a symbolic variable.
|
| 3070 |
+
|
| 3071 |
+
Parameters
|
| 3072 |
+
==========
|
| 3073 |
+
|
| 3074 |
+
perm : Permutation
|
| 3075 |
+
x : Expr
|
| 3076 |
+
|
| 3077 |
+
Examples
|
| 3078 |
+
========
|
| 3079 |
+
|
| 3080 |
+
>>> from sympy import Symbol
|
| 3081 |
+
>>> from sympy.combinatorics import Permutation
|
| 3082 |
+
|
| 3083 |
+
Creating a symbolic permutation function application:
|
| 3084 |
+
|
| 3085 |
+
>>> x = Symbol('x')
|
| 3086 |
+
>>> p = Permutation(0, 1, 2)
|
| 3087 |
+
>>> p.apply(x)
|
| 3088 |
+
AppliedPermutation((0 1 2), x)
|
| 3089 |
+
>>> _.subs(x, 1)
|
| 3090 |
+
2
|
| 3091 |
+
"""
|
| 3092 |
+
def __new__(cls, perm, x, evaluate=None):
|
| 3093 |
+
if evaluate is None:
|
| 3094 |
+
evaluate = global_parameters.evaluate
|
| 3095 |
+
|
| 3096 |
+
perm = _sympify(perm)
|
| 3097 |
+
x = _sympify(x)
|
| 3098 |
+
|
| 3099 |
+
if not isinstance(perm, Permutation):
|
| 3100 |
+
raise ValueError("{} must be a Permutation instance."
|
| 3101 |
+
.format(perm))
|
| 3102 |
+
|
| 3103 |
+
if evaluate:
|
| 3104 |
+
if x.is_Integer:
|
| 3105 |
+
return perm.apply(x)
|
| 3106 |
+
|
| 3107 |
+
obj = super().__new__(cls, perm, x)
|
| 3108 |
+
return obj
|
| 3109 |
+
|
| 3110 |
+
|
| 3111 |
+
@dispatch(Permutation, Permutation)
|
| 3112 |
+
def _eval_is_eq(lhs, rhs):
|
| 3113 |
+
if lhs._size != rhs._size:
|
| 3114 |
+
return None
|
| 3115 |
+
return lhs._array_form == rhs._array_form
|
lib/python3.12/site-packages/sympy/combinatorics/polyhedron.py
ADDED
|
@@ -0,0 +1,1019 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics import Permutation as Perm
|
| 2 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 3 |
+
from sympy.core import Basic, Tuple, default_sort_key
|
| 4 |
+
from sympy.sets import FiniteSet
|
| 5 |
+
from sympy.utilities.iterables import (minlex, unflatten, flatten)
|
| 6 |
+
from sympy.utilities.misc import as_int
|
| 7 |
+
|
| 8 |
+
rmul = Perm.rmul
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Polyhedron(Basic):
|
| 12 |
+
"""
|
| 13 |
+
Represents the polyhedral symmetry group (PSG).
|
| 14 |
+
|
| 15 |
+
Explanation
|
| 16 |
+
===========
|
| 17 |
+
|
| 18 |
+
The PSG is one of the symmetry groups of the Platonic solids.
|
| 19 |
+
There are three polyhedral groups: the tetrahedral group
|
| 20 |
+
of order 12, the octahedral group of order 24, and the
|
| 21 |
+
icosahedral group of order 60.
|
| 22 |
+
|
| 23 |
+
All doctests have been given in the docstring of the
|
| 24 |
+
constructor of the object.
|
| 25 |
+
|
| 26 |
+
References
|
| 27 |
+
==========
|
| 28 |
+
|
| 29 |
+
.. [1] https://mathworld.wolfram.com/PolyhedralGroup.html
|
| 30 |
+
|
| 31 |
+
"""
|
| 32 |
+
_edges = None
|
| 33 |
+
|
| 34 |
+
def __new__(cls, corners, faces=(), pgroup=()):
|
| 35 |
+
"""
|
| 36 |
+
The constructor of the Polyhedron group object.
|
| 37 |
+
|
| 38 |
+
Explanation
|
| 39 |
+
===========
|
| 40 |
+
|
| 41 |
+
It takes up to three parameters: the corners, faces, and
|
| 42 |
+
allowed transformations.
|
| 43 |
+
|
| 44 |
+
The corners/vertices are entered as a list of arbitrary
|
| 45 |
+
expressions that are used to identify each vertex.
|
| 46 |
+
|
| 47 |
+
The faces are entered as a list of tuples of indices; a tuple
|
| 48 |
+
of indices identifies the vertices which define the face. They
|
| 49 |
+
should be entered in a cw or ccw order; they will be standardized
|
| 50 |
+
by reversal and rotation to be give the lowest lexical ordering.
|
| 51 |
+
If no faces are given then no edges will be computed.
|
| 52 |
+
|
| 53 |
+
>>> from sympy.combinatorics.polyhedron import Polyhedron
|
| 54 |
+
>>> Polyhedron(list('abc'), [(1, 2, 0)]).faces
|
| 55 |
+
{(0, 1, 2)}
|
| 56 |
+
>>> Polyhedron(list('abc'), [(1, 0, 2)]).faces
|
| 57 |
+
{(0, 1, 2)}
|
| 58 |
+
|
| 59 |
+
The allowed transformations are entered as allowable permutations
|
| 60 |
+
of the vertices for the polyhedron. Instance of Permutations
|
| 61 |
+
(as with faces) should refer to the supplied vertices by index.
|
| 62 |
+
These permutation are stored as a PermutationGroup.
|
| 63 |
+
|
| 64 |
+
Examples
|
| 65 |
+
========
|
| 66 |
+
|
| 67 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 68 |
+
>>> from sympy import init_printing
|
| 69 |
+
>>> from sympy.abc import w, x, y, z
|
| 70 |
+
>>> init_printing(pretty_print=False, perm_cyclic=False)
|
| 71 |
+
|
| 72 |
+
Here we construct the Polyhedron object for a tetrahedron.
|
| 73 |
+
|
| 74 |
+
>>> corners = [w, x, y, z]
|
| 75 |
+
>>> faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)]
|
| 76 |
+
|
| 77 |
+
Next, allowed transformations of the polyhedron must be given. This
|
| 78 |
+
is given as permutations of vertices.
|
| 79 |
+
|
| 80 |
+
Although the vertices of a tetrahedron can be numbered in 24 (4!)
|
| 81 |
+
different ways, there are only 12 different orientations for a
|
| 82 |
+
physical tetrahedron. The following permutations, applied once or
|
| 83 |
+
twice, will generate all 12 of the orientations. (The identity
|
| 84 |
+
permutation, Permutation(range(4)), is not included since it does
|
| 85 |
+
not change the orientation of the vertices.)
|
| 86 |
+
|
| 87 |
+
>>> pgroup = [Permutation([[0, 1, 2], [3]]), \
|
| 88 |
+
Permutation([[0, 1, 3], [2]]), \
|
| 89 |
+
Permutation([[0, 2, 3], [1]]), \
|
| 90 |
+
Permutation([[1, 2, 3], [0]]), \
|
| 91 |
+
Permutation([[0, 1], [2, 3]]), \
|
| 92 |
+
Permutation([[0, 2], [1, 3]]), \
|
| 93 |
+
Permutation([[0, 3], [1, 2]])]
|
| 94 |
+
|
| 95 |
+
The Polyhedron is now constructed and demonstrated:
|
| 96 |
+
|
| 97 |
+
>>> tetra = Polyhedron(corners, faces, pgroup)
|
| 98 |
+
>>> tetra.size
|
| 99 |
+
4
|
| 100 |
+
>>> tetra.edges
|
| 101 |
+
{(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
|
| 102 |
+
>>> tetra.corners
|
| 103 |
+
(w, x, y, z)
|
| 104 |
+
|
| 105 |
+
It can be rotated with an arbitrary permutation of vertices, e.g.
|
| 106 |
+
the following permutation is not in the pgroup:
|
| 107 |
+
|
| 108 |
+
>>> tetra.rotate(Permutation([0, 1, 3, 2]))
|
| 109 |
+
>>> tetra.corners
|
| 110 |
+
(w, x, z, y)
|
| 111 |
+
|
| 112 |
+
An allowed permutation of the vertices can be constructed by
|
| 113 |
+
repeatedly applying permutations from the pgroup to the vertices.
|
| 114 |
+
Here is a demonstration that applying p and p**2 for every p in
|
| 115 |
+
pgroup generates all the orientations of a tetrahedron and no others:
|
| 116 |
+
|
| 117 |
+
>>> all = ( (w, x, y, z), \
|
| 118 |
+
(x, y, w, z), \
|
| 119 |
+
(y, w, x, z), \
|
| 120 |
+
(w, z, x, y), \
|
| 121 |
+
(z, w, y, x), \
|
| 122 |
+
(w, y, z, x), \
|
| 123 |
+
(y, z, w, x), \
|
| 124 |
+
(x, z, y, w), \
|
| 125 |
+
(z, y, x, w), \
|
| 126 |
+
(y, x, z, w), \
|
| 127 |
+
(x, w, z, y), \
|
| 128 |
+
(z, x, w, y) )
|
| 129 |
+
|
| 130 |
+
>>> got = []
|
| 131 |
+
>>> for p in (pgroup + [p**2 for p in pgroup]):
|
| 132 |
+
... h = Polyhedron(corners)
|
| 133 |
+
... h.rotate(p)
|
| 134 |
+
... got.append(h.corners)
|
| 135 |
+
...
|
| 136 |
+
>>> set(got) == set(all)
|
| 137 |
+
True
|
| 138 |
+
|
| 139 |
+
The make_perm method of a PermutationGroup will randomly pick
|
| 140 |
+
permutations, multiply them together, and return the permutation that
|
| 141 |
+
can be applied to the polyhedron to give the orientation produced
|
| 142 |
+
by those individual permutations.
|
| 143 |
+
|
| 144 |
+
Here, 3 permutations are used:
|
| 145 |
+
|
| 146 |
+
>>> tetra.pgroup.make_perm(3) # doctest: +SKIP
|
| 147 |
+
Permutation([0, 3, 1, 2])
|
| 148 |
+
|
| 149 |
+
To select the permutations that should be used, supply a list
|
| 150 |
+
of indices to the permutations in pgroup in the order they should
|
| 151 |
+
be applied:
|
| 152 |
+
|
| 153 |
+
>>> use = [0, 0, 2]
|
| 154 |
+
>>> p002 = tetra.pgroup.make_perm(3, use)
|
| 155 |
+
>>> p002
|
| 156 |
+
Permutation([1, 0, 3, 2])
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
Apply them one at a time:
|
| 160 |
+
|
| 161 |
+
>>> tetra.reset()
|
| 162 |
+
>>> for i in use:
|
| 163 |
+
... tetra.rotate(pgroup[i])
|
| 164 |
+
...
|
| 165 |
+
>>> tetra.vertices
|
| 166 |
+
(x, w, z, y)
|
| 167 |
+
>>> sequentially = tetra.vertices
|
| 168 |
+
|
| 169 |
+
Apply the composite permutation:
|
| 170 |
+
|
| 171 |
+
>>> tetra.reset()
|
| 172 |
+
>>> tetra.rotate(p002)
|
| 173 |
+
>>> tetra.corners
|
| 174 |
+
(x, w, z, y)
|
| 175 |
+
>>> tetra.corners in all and tetra.corners == sequentially
|
| 176 |
+
True
|
| 177 |
+
|
| 178 |
+
Notes
|
| 179 |
+
=====
|
| 180 |
+
|
| 181 |
+
Defining permutation groups
|
| 182 |
+
---------------------------
|
| 183 |
+
|
| 184 |
+
It is not necessary to enter any permutations, nor is necessary to
|
| 185 |
+
enter a complete set of transformations. In fact, for a polyhedron,
|
| 186 |
+
all configurations can be constructed from just two permutations.
|
| 187 |
+
For example, the orientations of a tetrahedron can be generated from
|
| 188 |
+
an axis passing through a vertex and face and another axis passing
|
| 189 |
+
through a different vertex or from an axis passing through the
|
| 190 |
+
midpoints of two edges opposite of each other.
|
| 191 |
+
|
| 192 |
+
For simplicity of presentation, consider a square --
|
| 193 |
+
not a cube -- with vertices 1, 2, 3, and 4:
|
| 194 |
+
|
| 195 |
+
1-----2 We could think of axes of rotation being:
|
| 196 |
+
| | 1) through the face
|
| 197 |
+
| | 2) from midpoint 1-2 to 3-4 or 1-3 to 2-4
|
| 198 |
+
3-----4 3) lines 1-4 or 2-3
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
To determine how to write the permutations, imagine 4 cameras,
|
| 202 |
+
one at each corner, labeled A-D:
|
| 203 |
+
|
| 204 |
+
A B A B
|
| 205 |
+
1-----2 1-----3 vertex index:
|
| 206 |
+
| | | | 1 0
|
| 207 |
+
| | | | 2 1
|
| 208 |
+
3-----4 2-----4 3 2
|
| 209 |
+
C D C D 4 3
|
| 210 |
+
|
| 211 |
+
original after rotation
|
| 212 |
+
along 1-4
|
| 213 |
+
|
| 214 |
+
A diagonal and a face axis will be chosen for the "permutation group"
|
| 215 |
+
from which any orientation can be constructed.
|
| 216 |
+
|
| 217 |
+
>>> pgroup = []
|
| 218 |
+
|
| 219 |
+
Imagine a clockwise rotation when viewing 1-4 from camera A. The new
|
| 220 |
+
orientation is (in camera-order): 1, 3, 2, 4 so the permutation is
|
| 221 |
+
given using the *indices* of the vertices as:
|
| 222 |
+
|
| 223 |
+
>>> pgroup.append(Permutation((0, 2, 1, 3)))
|
| 224 |
+
|
| 225 |
+
Now imagine rotating clockwise when looking down an axis entering the
|
| 226 |
+
center of the square as viewed. The new camera-order would be
|
| 227 |
+
3, 1, 4, 2 so the permutation is (using indices):
|
| 228 |
+
|
| 229 |
+
>>> pgroup.append(Permutation((2, 0, 3, 1)))
|
| 230 |
+
|
| 231 |
+
The square can now be constructed:
|
| 232 |
+
** use real-world labels for the vertices, entering them in
|
| 233 |
+
camera order
|
| 234 |
+
** for the faces we use zero-based indices of the vertices
|
| 235 |
+
in *edge-order* as the face is traversed; neither the
|
| 236 |
+
direction nor the starting point matter -- the faces are
|
| 237 |
+
only used to define edges (if so desired).
|
| 238 |
+
|
| 239 |
+
>>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup)
|
| 240 |
+
|
| 241 |
+
To rotate the square with a single permutation we can do:
|
| 242 |
+
|
| 243 |
+
>>> square.rotate(square.pgroup[0])
|
| 244 |
+
>>> square.corners
|
| 245 |
+
(1, 3, 2, 4)
|
| 246 |
+
|
| 247 |
+
To use more than one permutation (or to use one permutation more
|
| 248 |
+
than once) it is more convenient to use the make_perm method:
|
| 249 |
+
|
| 250 |
+
>>> p011 = square.pgroup.make_perm([0, 1, 1]) # diag flip + 2 rotations
|
| 251 |
+
>>> square.reset() # return to initial orientation
|
| 252 |
+
>>> square.rotate(p011)
|
| 253 |
+
>>> square.corners
|
| 254 |
+
(4, 2, 3, 1)
|
| 255 |
+
|
| 256 |
+
Thinking outside the box
|
| 257 |
+
------------------------
|
| 258 |
+
|
| 259 |
+
Although the Polyhedron object has a direct physical meaning, it
|
| 260 |
+
actually has broader application. In the most general sense it is
|
| 261 |
+
just a decorated PermutationGroup, allowing one to connect the
|
| 262 |
+
permutations to something physical. For example, a Rubik's cube is
|
| 263 |
+
not a proper polyhedron, but the Polyhedron class can be used to
|
| 264 |
+
represent it in a way that helps to visualize the Rubik's cube.
|
| 265 |
+
|
| 266 |
+
>>> from sympy import flatten, unflatten, symbols
|
| 267 |
+
>>> from sympy.combinatorics import RubikGroup
|
| 268 |
+
>>> facelets = flatten([symbols(s+'1:5') for s in 'UFRBLD'])
|
| 269 |
+
>>> def show():
|
| 270 |
+
... pairs = unflatten(r2.corners, 2)
|
| 271 |
+
... print(pairs[::2])
|
| 272 |
+
... print(pairs[1::2])
|
| 273 |
+
...
|
| 274 |
+
>>> r2 = Polyhedron(facelets, pgroup=RubikGroup(2))
|
| 275 |
+
>>> show()
|
| 276 |
+
[(U1, U2), (F1, F2), (R1, R2), (B1, B2), (L1, L2), (D1, D2)]
|
| 277 |
+
[(U3, U4), (F3, F4), (R3, R4), (B3, B4), (L3, L4), (D3, D4)]
|
| 278 |
+
>>> r2.rotate(0) # cw rotation of F
|
| 279 |
+
>>> show()
|
| 280 |
+
[(U1, U2), (F3, F1), (U3, R2), (B1, B2), (L1, D1), (R3, R1)]
|
| 281 |
+
[(L4, L2), (F4, F2), (U4, R4), (B3, B4), (L3, D2), (D3, D4)]
|
| 282 |
+
|
| 283 |
+
Predefined Polyhedra
|
| 284 |
+
====================
|
| 285 |
+
|
| 286 |
+
For convenience, the vertices and faces are defined for the following
|
| 287 |
+
standard solids along with a permutation group for transformations.
|
| 288 |
+
When the polyhedron is oriented as indicated below, the vertices in
|
| 289 |
+
a given horizontal plane are numbered in ccw direction, starting from
|
| 290 |
+
the vertex that will give the lowest indices in a given face. (In the
|
| 291 |
+
net of the vertices, indices preceded by "-" indicate replication of
|
| 292 |
+
the lhs index in the net.)
|
| 293 |
+
|
| 294 |
+
tetrahedron, tetrahedron_faces
|
| 295 |
+
------------------------------
|
| 296 |
+
|
| 297 |
+
4 vertices (vertex up) net:
|
| 298 |
+
|
| 299 |
+
0 0-0
|
| 300 |
+
1 2 3-1
|
| 301 |
+
|
| 302 |
+
4 faces:
|
| 303 |
+
|
| 304 |
+
(0, 1, 2) (0, 2, 3) (0, 3, 1) (1, 2, 3)
|
| 305 |
+
|
| 306 |
+
cube, cube_faces
|
| 307 |
+
----------------
|
| 308 |
+
|
| 309 |
+
8 vertices (face up) net:
|
| 310 |
+
|
| 311 |
+
0 1 2 3-0
|
| 312 |
+
4 5 6 7-4
|
| 313 |
+
|
| 314 |
+
6 faces:
|
| 315 |
+
|
| 316 |
+
(0, 1, 2, 3)
|
| 317 |
+
(0, 1, 5, 4) (1, 2, 6, 5) (2, 3, 7, 6) (0, 3, 7, 4)
|
| 318 |
+
(4, 5, 6, 7)
|
| 319 |
+
|
| 320 |
+
octahedron, octahedron_faces
|
| 321 |
+
----------------------------
|
| 322 |
+
|
| 323 |
+
6 vertices (vertex up) net:
|
| 324 |
+
|
| 325 |
+
0 0 0-0
|
| 326 |
+
1 2 3 4-1
|
| 327 |
+
5 5 5-5
|
| 328 |
+
|
| 329 |
+
8 faces:
|
| 330 |
+
|
| 331 |
+
(0, 1, 2) (0, 2, 3) (0, 3, 4) (0, 1, 4)
|
| 332 |
+
(1, 2, 5) (2, 3, 5) (3, 4, 5) (1, 4, 5)
|
| 333 |
+
|
| 334 |
+
dodecahedron, dodecahedron_faces
|
| 335 |
+
--------------------------------
|
| 336 |
+
|
| 337 |
+
20 vertices (vertex up) net:
|
| 338 |
+
|
| 339 |
+
0 1 2 3 4 -0
|
| 340 |
+
5 6 7 8 9 -5
|
| 341 |
+
14 10 11 12 13-14
|
| 342 |
+
15 16 17 18 19-15
|
| 343 |
+
|
| 344 |
+
12 faces:
|
| 345 |
+
|
| 346 |
+
(0, 1, 2, 3, 4) (0, 1, 6, 10, 5) (1, 2, 7, 11, 6)
|
| 347 |
+
(2, 3, 8, 12, 7) (3, 4, 9, 13, 8) (0, 4, 9, 14, 5)
|
| 348 |
+
(5, 10, 16, 15, 14) (6, 10, 16, 17, 11) (7, 11, 17, 18, 12)
|
| 349 |
+
(8, 12, 18, 19, 13) (9, 13, 19, 15, 14)(15, 16, 17, 18, 19)
|
| 350 |
+
|
| 351 |
+
icosahedron, icosahedron_faces
|
| 352 |
+
------------------------------
|
| 353 |
+
|
| 354 |
+
12 vertices (face up) net:
|
| 355 |
+
|
| 356 |
+
0 0 0 0 -0
|
| 357 |
+
1 2 3 4 5 -1
|
| 358 |
+
6 7 8 9 10 -6
|
| 359 |
+
11 11 11 11 -11
|
| 360 |
+
|
| 361 |
+
20 faces:
|
| 362 |
+
|
| 363 |
+
(0, 1, 2) (0, 2, 3) (0, 3, 4)
|
| 364 |
+
(0, 4, 5) (0, 1, 5) (1, 2, 6)
|
| 365 |
+
(2, 3, 7) (3, 4, 8) (4, 5, 9)
|
| 366 |
+
(1, 5, 10) (2, 6, 7) (3, 7, 8)
|
| 367 |
+
(4, 8, 9) (5, 9, 10) (1, 6, 10)
|
| 368 |
+
(6, 7, 11) (7, 8, 11) (8, 9, 11)
|
| 369 |
+
(9, 10, 11) (6, 10, 11)
|
| 370 |
+
|
| 371 |
+
>>> from sympy.combinatorics.polyhedron import cube
|
| 372 |
+
>>> cube.edges
|
| 373 |
+
{(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3), (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)}
|
| 374 |
+
|
| 375 |
+
If you want to use letters or other names for the corners you
|
| 376 |
+
can still use the pre-calculated faces:
|
| 377 |
+
|
| 378 |
+
>>> corners = list('abcdefgh')
|
| 379 |
+
>>> Polyhedron(corners, cube.faces).corners
|
| 380 |
+
(a, b, c, d, e, f, g, h)
|
| 381 |
+
|
| 382 |
+
References
|
| 383 |
+
==========
|
| 384 |
+
|
| 385 |
+
.. [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf
|
| 386 |
+
|
| 387 |
+
"""
|
| 388 |
+
faces = [minlex(f, directed=False, key=default_sort_key) for f in faces]
|
| 389 |
+
corners, faces, pgroup = args = \
|
| 390 |
+
[Tuple(*a) for a in (corners, faces, pgroup)]
|
| 391 |
+
obj = Basic.__new__(cls, *args)
|
| 392 |
+
obj._corners = tuple(corners) # in order given
|
| 393 |
+
obj._faces = FiniteSet(*faces)
|
| 394 |
+
if pgroup and pgroup[0].size != len(corners):
|
| 395 |
+
raise ValueError("Permutation size unequal to number of corners.")
|
| 396 |
+
# use the identity permutation if none are given
|
| 397 |
+
obj._pgroup = PermutationGroup(
|
| 398 |
+
pgroup or [Perm(range(len(corners)))] )
|
| 399 |
+
return obj
|
| 400 |
+
|
| 401 |
+
@property
|
| 402 |
+
def corners(self):
|
| 403 |
+
"""
|
| 404 |
+
Get the corners of the Polyhedron.
|
| 405 |
+
|
| 406 |
+
The method ``vertices`` is an alias for ``corners``.
|
| 407 |
+
|
| 408 |
+
Examples
|
| 409 |
+
========
|
| 410 |
+
|
| 411 |
+
>>> from sympy.combinatorics import Polyhedron
|
| 412 |
+
>>> from sympy.abc import a, b, c, d
|
| 413 |
+
>>> p = Polyhedron(list('abcd'))
|
| 414 |
+
>>> p.corners == p.vertices == (a, b, c, d)
|
| 415 |
+
True
|
| 416 |
+
|
| 417 |
+
See Also
|
| 418 |
+
========
|
| 419 |
+
|
| 420 |
+
array_form, cyclic_form
|
| 421 |
+
"""
|
| 422 |
+
return self._corners
|
| 423 |
+
vertices = corners
|
| 424 |
+
|
| 425 |
+
@property
|
| 426 |
+
def array_form(self):
|
| 427 |
+
"""Return the indices of the corners.
|
| 428 |
+
|
| 429 |
+
The indices are given relative to the original position of corners.
|
| 430 |
+
|
| 431 |
+
Examples
|
| 432 |
+
========
|
| 433 |
+
|
| 434 |
+
>>> from sympy.combinatorics.polyhedron import tetrahedron
|
| 435 |
+
>>> tetrahedron = tetrahedron.copy()
|
| 436 |
+
>>> tetrahedron.array_form
|
| 437 |
+
[0, 1, 2, 3]
|
| 438 |
+
|
| 439 |
+
>>> tetrahedron.rotate(0)
|
| 440 |
+
>>> tetrahedron.array_form
|
| 441 |
+
[0, 2, 3, 1]
|
| 442 |
+
>>> tetrahedron.pgroup[0].array_form
|
| 443 |
+
[0, 2, 3, 1]
|
| 444 |
+
|
| 445 |
+
See Also
|
| 446 |
+
========
|
| 447 |
+
|
| 448 |
+
corners, cyclic_form
|
| 449 |
+
"""
|
| 450 |
+
corners = list(self.args[0])
|
| 451 |
+
return [corners.index(c) for c in self.corners]
|
| 452 |
+
|
| 453 |
+
@property
|
| 454 |
+
def cyclic_form(self):
|
| 455 |
+
"""Return the indices of the corners in cyclic notation.
|
| 456 |
+
|
| 457 |
+
The indices are given relative to the original position of corners.
|
| 458 |
+
|
| 459 |
+
See Also
|
| 460 |
+
========
|
| 461 |
+
|
| 462 |
+
corners, array_form
|
| 463 |
+
"""
|
| 464 |
+
return Perm._af_new(self.array_form).cyclic_form
|
| 465 |
+
|
| 466 |
+
@property
|
| 467 |
+
def size(self):
|
| 468 |
+
"""
|
| 469 |
+
Get the number of corners of the Polyhedron.
|
| 470 |
+
"""
|
| 471 |
+
return len(self._corners)
|
| 472 |
+
|
| 473 |
+
@property
|
| 474 |
+
def faces(self):
|
| 475 |
+
"""
|
| 476 |
+
Get the faces of the Polyhedron.
|
| 477 |
+
"""
|
| 478 |
+
return self._faces
|
| 479 |
+
|
| 480 |
+
@property
|
| 481 |
+
def pgroup(self):
|
| 482 |
+
"""
|
| 483 |
+
Get the permutations of the Polyhedron.
|
| 484 |
+
"""
|
| 485 |
+
return self._pgroup
|
| 486 |
+
|
| 487 |
+
@property
|
| 488 |
+
def edges(self):
|
| 489 |
+
"""
|
| 490 |
+
Given the faces of the polyhedra we can get the edges.
|
| 491 |
+
|
| 492 |
+
Examples
|
| 493 |
+
========
|
| 494 |
+
|
| 495 |
+
>>> from sympy.combinatorics import Polyhedron
|
| 496 |
+
>>> from sympy.abc import a, b, c
|
| 497 |
+
>>> corners = (a, b, c)
|
| 498 |
+
>>> faces = [(0, 1, 2)]
|
| 499 |
+
>>> Polyhedron(corners, faces).edges
|
| 500 |
+
{(0, 1), (0, 2), (1, 2)}
|
| 501 |
+
|
| 502 |
+
"""
|
| 503 |
+
if self._edges is None:
|
| 504 |
+
output = set()
|
| 505 |
+
for face in self.faces:
|
| 506 |
+
for i in range(len(face)):
|
| 507 |
+
edge = tuple(sorted([face[i], face[i - 1]]))
|
| 508 |
+
output.add(edge)
|
| 509 |
+
self._edges = FiniteSet(*output)
|
| 510 |
+
return self._edges
|
| 511 |
+
|
| 512 |
+
def rotate(self, perm):
|
| 513 |
+
"""
|
| 514 |
+
Apply a permutation to the polyhedron *in place*. The permutation
|
| 515 |
+
may be given as a Permutation instance or an integer indicating
|
| 516 |
+
which permutation from pgroup of the Polyhedron should be
|
| 517 |
+
applied.
|
| 518 |
+
|
| 519 |
+
This is an operation that is analogous to rotation about
|
| 520 |
+
an axis by a fixed increment.
|
| 521 |
+
|
| 522 |
+
Notes
|
| 523 |
+
=====
|
| 524 |
+
|
| 525 |
+
When a Permutation is applied, no check is done to see if that
|
| 526 |
+
is a valid permutation for the Polyhedron. For example, a cube
|
| 527 |
+
could be given a permutation which effectively swaps only 2
|
| 528 |
+
vertices. A valid permutation (that rotates the object in a
|
| 529 |
+
physical way) will be obtained if one only uses
|
| 530 |
+
permutations from the ``pgroup`` of the Polyhedron. On the other
|
| 531 |
+
hand, allowing arbitrary rotations (applications of permutations)
|
| 532 |
+
gives a way to follow named elements rather than indices since
|
| 533 |
+
Polyhedron allows vertices to be named while Permutation works
|
| 534 |
+
only with indices.
|
| 535 |
+
|
| 536 |
+
Examples
|
| 537 |
+
========
|
| 538 |
+
|
| 539 |
+
>>> from sympy.combinatorics import Polyhedron, Permutation
|
| 540 |
+
>>> from sympy.combinatorics.polyhedron import cube
|
| 541 |
+
>>> cube = cube.copy()
|
| 542 |
+
>>> cube.corners
|
| 543 |
+
(0, 1, 2, 3, 4, 5, 6, 7)
|
| 544 |
+
>>> cube.rotate(0)
|
| 545 |
+
>>> cube.corners
|
| 546 |
+
(1, 2, 3, 0, 5, 6, 7, 4)
|
| 547 |
+
|
| 548 |
+
A non-physical "rotation" that is not prohibited by this method:
|
| 549 |
+
|
| 550 |
+
>>> cube.reset()
|
| 551 |
+
>>> cube.rotate(Permutation([[1, 2]], size=8))
|
| 552 |
+
>>> cube.corners
|
| 553 |
+
(0, 2, 1, 3, 4, 5, 6, 7)
|
| 554 |
+
|
| 555 |
+
Polyhedron can be used to follow elements of set that are
|
| 556 |
+
identified by letters instead of integers:
|
| 557 |
+
|
| 558 |
+
>>> shadow = h5 = Polyhedron(list('abcde'))
|
| 559 |
+
>>> p = Permutation([3, 0, 1, 2, 4])
|
| 560 |
+
>>> h5.rotate(p)
|
| 561 |
+
>>> h5.corners
|
| 562 |
+
(d, a, b, c, e)
|
| 563 |
+
>>> _ == shadow.corners
|
| 564 |
+
True
|
| 565 |
+
>>> copy = h5.copy()
|
| 566 |
+
>>> h5.rotate(p)
|
| 567 |
+
>>> h5.corners == copy.corners
|
| 568 |
+
False
|
| 569 |
+
"""
|
| 570 |
+
if not isinstance(perm, Perm):
|
| 571 |
+
perm = self.pgroup[perm]
|
| 572 |
+
# and we know it's valid
|
| 573 |
+
else:
|
| 574 |
+
if perm.size != self.size:
|
| 575 |
+
raise ValueError('Polyhedron and Permutation sizes differ.')
|
| 576 |
+
a = perm.array_form
|
| 577 |
+
corners = [self.corners[a[i]] for i in range(len(self.corners))]
|
| 578 |
+
self._corners = tuple(corners)
|
| 579 |
+
|
| 580 |
+
def reset(self):
|
| 581 |
+
"""Return corners to their original positions.
|
| 582 |
+
|
| 583 |
+
Examples
|
| 584 |
+
========
|
| 585 |
+
|
| 586 |
+
>>> from sympy.combinatorics.polyhedron import tetrahedron as T
|
| 587 |
+
>>> T = T.copy()
|
| 588 |
+
>>> T.corners
|
| 589 |
+
(0, 1, 2, 3)
|
| 590 |
+
>>> T.rotate(0)
|
| 591 |
+
>>> T.corners
|
| 592 |
+
(0, 2, 3, 1)
|
| 593 |
+
>>> T.reset()
|
| 594 |
+
>>> T.corners
|
| 595 |
+
(0, 1, 2, 3)
|
| 596 |
+
"""
|
| 597 |
+
self._corners = self.args[0]
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
def _pgroup_calcs():
|
| 601 |
+
"""Return the permutation groups for each of the polyhedra and the face
|
| 602 |
+
definitions: tetrahedron, cube, octahedron, dodecahedron, icosahedron,
|
| 603 |
+
tetrahedron_faces, cube_faces, octahedron_faces, dodecahedron_faces,
|
| 604 |
+
icosahedron_faces
|
| 605 |
+
|
| 606 |
+
Explanation
|
| 607 |
+
===========
|
| 608 |
+
|
| 609 |
+
(This author did not find and did not know of a better way to do it though
|
| 610 |
+
there likely is such a way.)
|
| 611 |
+
|
| 612 |
+
Although only 2 permutations are needed for a polyhedron in order to
|
| 613 |
+
generate all the possible orientations, a group of permutations is
|
| 614 |
+
provided instead. A set of permutations is called a "group" if::
|
| 615 |
+
|
| 616 |
+
a*b = c (for any pair of permutations in the group, a and b, their
|
| 617 |
+
product, c, is in the group)
|
| 618 |
+
|
| 619 |
+
a*(b*c) = (a*b)*c (for any 3 permutations in the group associativity holds)
|
| 620 |
+
|
| 621 |
+
there is an identity permutation, I, such that I*a = a*I for all elements
|
| 622 |
+
in the group
|
| 623 |
+
|
| 624 |
+
a*b = I (the inverse of each permutation is also in the group)
|
| 625 |
+
|
| 626 |
+
None of the polyhedron groups defined follow these definitions of a group.
|
| 627 |
+
Instead, they are selected to contain those permutations whose powers
|
| 628 |
+
alone will construct all orientations of the polyhedron, i.e. for
|
| 629 |
+
permutations ``a``, ``b``, etc... in the group, ``a, a**2, ..., a**o_a``,
|
| 630 |
+
``b, b**2, ..., b**o_b``, etc... (where ``o_i`` is the order of
|
| 631 |
+
permutation ``i``) generate all permutations of the polyhedron instead of
|
| 632 |
+
mixed products like ``a*b``, ``a*b**2``, etc....
|
| 633 |
+
|
| 634 |
+
Note that for a polyhedron with n vertices, the valid permutations of the
|
| 635 |
+
vertices exclude those that do not maintain its faces. e.g. the
|
| 636 |
+
permutation BCDE of a square's four corners, ABCD, is a valid
|
| 637 |
+
permutation while CBDE is not (because this would twist the square).
|
| 638 |
+
|
| 639 |
+
Examples
|
| 640 |
+
========
|
| 641 |
+
|
| 642 |
+
The is_group checks for: closure, the presence of the Identity permutation,
|
| 643 |
+
and the presence of the inverse for each of the elements in the group. This
|
| 644 |
+
confirms that none of the polyhedra are true groups:
|
| 645 |
+
|
| 646 |
+
>>> from sympy.combinatorics.polyhedron import (
|
| 647 |
+
... tetrahedron, cube, octahedron, dodecahedron, icosahedron)
|
| 648 |
+
...
|
| 649 |
+
>>> polyhedra = (tetrahedron, cube, octahedron, dodecahedron, icosahedron)
|
| 650 |
+
>>> [h.pgroup.is_group for h in polyhedra]
|
| 651 |
+
...
|
| 652 |
+
[True, True, True, True, True]
|
| 653 |
+
|
| 654 |
+
Although tests in polyhedron's test suite check that powers of the
|
| 655 |
+
permutations in the groups generate all permutations of the vertices
|
| 656 |
+
of the polyhedron, here we also demonstrate the powers of the given
|
| 657 |
+
permutations create a complete group for the tetrahedron:
|
| 658 |
+
|
| 659 |
+
>>> from sympy.combinatorics import Permutation, PermutationGroup
|
| 660 |
+
>>> for h in polyhedra[:1]:
|
| 661 |
+
... G = h.pgroup
|
| 662 |
+
... perms = set()
|
| 663 |
+
... for g in G:
|
| 664 |
+
... for e in range(g.order()):
|
| 665 |
+
... p = tuple((g**e).array_form)
|
| 666 |
+
... perms.add(p)
|
| 667 |
+
...
|
| 668 |
+
... perms = [Permutation(p) for p in perms]
|
| 669 |
+
... assert PermutationGroup(perms).is_group
|
| 670 |
+
|
| 671 |
+
In addition to doing the above, the tests in the suite confirm that the
|
| 672 |
+
faces are all present after the application of each permutation.
|
| 673 |
+
|
| 674 |
+
References
|
| 675 |
+
==========
|
| 676 |
+
|
| 677 |
+
.. [1] https://dogschool.tripod.com/trianglegroup.html
|
| 678 |
+
|
| 679 |
+
"""
|
| 680 |
+
def _pgroup_of_double(polyh, ordered_faces, pgroup):
|
| 681 |
+
n = len(ordered_faces[0])
|
| 682 |
+
# the vertices of the double which sits inside a give polyhedron
|
| 683 |
+
# can be found by tracking the faces of the outer polyhedron.
|
| 684 |
+
# A map between face and the vertex of the double is made so that
|
| 685 |
+
# after rotation the position of the vertices can be located
|
| 686 |
+
fmap = dict(zip(ordered_faces,
|
| 687 |
+
range(len(ordered_faces))))
|
| 688 |
+
flat_faces = flatten(ordered_faces)
|
| 689 |
+
new_pgroup = []
|
| 690 |
+
for p in pgroup:
|
| 691 |
+
h = polyh.copy()
|
| 692 |
+
h.rotate(p)
|
| 693 |
+
c = h.corners
|
| 694 |
+
# reorder corners in the order they should appear when
|
| 695 |
+
# enumerating the faces
|
| 696 |
+
reorder = unflatten([c[j] for j in flat_faces], n)
|
| 697 |
+
# make them canonical
|
| 698 |
+
reorder = [tuple(map(as_int,
|
| 699 |
+
minlex(f, directed=False)))
|
| 700 |
+
for f in reorder]
|
| 701 |
+
# map face to vertex: the resulting list of vertices are the
|
| 702 |
+
# permutation that we seek for the double
|
| 703 |
+
new_pgroup.append(Perm([fmap[f] for f in reorder]))
|
| 704 |
+
return new_pgroup
|
| 705 |
+
|
| 706 |
+
tetrahedron_faces = [
|
| 707 |
+
(0, 1, 2), (0, 2, 3), (0, 3, 1), # upper 3
|
| 708 |
+
(1, 2, 3), # bottom
|
| 709 |
+
]
|
| 710 |
+
|
| 711 |
+
# cw from top
|
| 712 |
+
#
|
| 713 |
+
_t_pgroup = [
|
| 714 |
+
Perm([[1, 2, 3], [0]]), # cw from top
|
| 715 |
+
Perm([[0, 1, 2], [3]]), # cw from front face
|
| 716 |
+
Perm([[0, 3, 2], [1]]), # cw from back right face
|
| 717 |
+
Perm([[0, 3, 1], [2]]), # cw from back left face
|
| 718 |
+
Perm([[0, 1], [2, 3]]), # through front left edge
|
| 719 |
+
Perm([[0, 2], [1, 3]]), # through front right edge
|
| 720 |
+
Perm([[0, 3], [1, 2]]), # through back edge
|
| 721 |
+
]
|
| 722 |
+
|
| 723 |
+
tetrahedron = Polyhedron(
|
| 724 |
+
range(4),
|
| 725 |
+
tetrahedron_faces,
|
| 726 |
+
_t_pgroup)
|
| 727 |
+
|
| 728 |
+
cube_faces = [
|
| 729 |
+
(0, 1, 2, 3), # upper
|
| 730 |
+
(0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4), # middle 4
|
| 731 |
+
(4, 5, 6, 7), # lower
|
| 732 |
+
]
|
| 733 |
+
|
| 734 |
+
# U, D, F, B, L, R = up, down, front, back, left, right
|
| 735 |
+
_c_pgroup = [Perm(p) for p in
|
| 736 |
+
[
|
| 737 |
+
[1, 2, 3, 0, 5, 6, 7, 4], # cw from top, U
|
| 738 |
+
[4, 0, 3, 7, 5, 1, 2, 6], # cw from F face
|
| 739 |
+
[4, 5, 1, 0, 7, 6, 2, 3], # cw from R face
|
| 740 |
+
|
| 741 |
+
[1, 0, 4, 5, 2, 3, 7, 6], # cw through UF edge
|
| 742 |
+
[6, 2, 1, 5, 7, 3, 0, 4], # cw through UR edge
|
| 743 |
+
[6, 7, 3, 2, 5, 4, 0, 1], # cw through UB edge
|
| 744 |
+
[3, 7, 4, 0, 2, 6, 5, 1], # cw through UL edge
|
| 745 |
+
[4, 7, 6, 5, 0, 3, 2, 1], # cw through FL edge
|
| 746 |
+
[6, 5, 4, 7, 2, 1, 0, 3], # cw through FR edge
|
| 747 |
+
|
| 748 |
+
[0, 3, 7, 4, 1, 2, 6, 5], # cw through UFL vertex
|
| 749 |
+
[5, 1, 0, 4, 6, 2, 3, 7], # cw through UFR vertex
|
| 750 |
+
[5, 6, 2, 1, 4, 7, 3, 0], # cw through UBR vertex
|
| 751 |
+
[7, 4, 0, 3, 6, 5, 1, 2], # cw through UBL
|
| 752 |
+
]]
|
| 753 |
+
|
| 754 |
+
cube = Polyhedron(
|
| 755 |
+
range(8),
|
| 756 |
+
cube_faces,
|
| 757 |
+
_c_pgroup)
|
| 758 |
+
|
| 759 |
+
octahedron_faces = [
|
| 760 |
+
(0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4), # top 4
|
| 761 |
+
(1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5), # bottom 4
|
| 762 |
+
]
|
| 763 |
+
|
| 764 |
+
octahedron = Polyhedron(
|
| 765 |
+
range(6),
|
| 766 |
+
octahedron_faces,
|
| 767 |
+
_pgroup_of_double(cube, cube_faces, _c_pgroup))
|
| 768 |
+
|
| 769 |
+
dodecahedron_faces = [
|
| 770 |
+
(0, 1, 2, 3, 4), # top
|
| 771 |
+
(0, 1, 6, 10, 5), (1, 2, 7, 11, 6), (2, 3, 8, 12, 7), # upper 5
|
| 772 |
+
(3, 4, 9, 13, 8), (0, 4, 9, 14, 5),
|
| 773 |
+
(5, 10, 16, 15, 14), (6, 10, 16, 17, 11), (7, 11, 17, 18,
|
| 774 |
+
12), # lower 5
|
| 775 |
+
(8, 12, 18, 19, 13), (9, 13, 19, 15, 14),
|
| 776 |
+
(15, 16, 17, 18, 19) # bottom
|
| 777 |
+
]
|
| 778 |
+
|
| 779 |
+
def _string_to_perm(s):
|
| 780 |
+
rv = [Perm(range(20))]
|
| 781 |
+
p = None
|
| 782 |
+
for si in s:
|
| 783 |
+
if si not in '01':
|
| 784 |
+
count = int(si) - 1
|
| 785 |
+
else:
|
| 786 |
+
count = 1
|
| 787 |
+
if si == '0':
|
| 788 |
+
p = _f0
|
| 789 |
+
elif si == '1':
|
| 790 |
+
p = _f1
|
| 791 |
+
rv.extend([p]*count)
|
| 792 |
+
return Perm.rmul(*rv)
|
| 793 |
+
|
| 794 |
+
# top face cw
|
| 795 |
+
_f0 = Perm([
|
| 796 |
+
1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11,
|
| 797 |
+
12, 13, 14, 10, 16, 17, 18, 19, 15])
|
| 798 |
+
# front face cw
|
| 799 |
+
_f1 = Perm([
|
| 800 |
+
5, 0, 4, 9, 14, 10, 1, 3, 13, 15,
|
| 801 |
+
6, 2, 8, 19, 16, 17, 11, 7, 12, 18])
|
| 802 |
+
# the strings below, like 0104 are shorthand for F0*F1*F0**4 and are
|
| 803 |
+
# the remaining 4 face rotations, 15 edge permutations, and the
|
| 804 |
+
# 10 vertex rotations.
|
| 805 |
+
_dodeca_pgroup = [_f0, _f1] + [_string_to_perm(s) for s in '''
|
| 806 |
+
0104 140 014 0410
|
| 807 |
+
010 1403 03104 04103 102
|
| 808 |
+
120 1304 01303 021302 03130
|
| 809 |
+
0412041 041204103 04120410 041204104 041204102
|
| 810 |
+
10 01 1402 0140 04102 0412 1204 1302 0130 03120'''.strip().split()]
|
| 811 |
+
|
| 812 |
+
dodecahedron = Polyhedron(
|
| 813 |
+
range(20),
|
| 814 |
+
dodecahedron_faces,
|
| 815 |
+
_dodeca_pgroup)
|
| 816 |
+
|
| 817 |
+
icosahedron_faces = [
|
| 818 |
+
(0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 1, 5),
|
| 819 |
+
(1, 6, 7), (1, 2, 7), (2, 7, 8), (2, 3, 8), (3, 8, 9),
|
| 820 |
+
(3, 4, 9), (4, 9, 10), (4, 5, 10), (5, 6, 10), (1, 5, 6),
|
| 821 |
+
(6, 7, 11), (7, 8, 11), (8, 9, 11), (9, 10, 11), (6, 10, 11)]
|
| 822 |
+
|
| 823 |
+
icosahedron = Polyhedron(
|
| 824 |
+
range(12),
|
| 825 |
+
icosahedron_faces,
|
| 826 |
+
_pgroup_of_double(
|
| 827 |
+
dodecahedron, dodecahedron_faces, _dodeca_pgroup))
|
| 828 |
+
|
| 829 |
+
return (tetrahedron, cube, octahedron, dodecahedron, icosahedron,
|
| 830 |
+
tetrahedron_faces, cube_faces, octahedron_faces,
|
| 831 |
+
dodecahedron_faces, icosahedron_faces)
|
| 832 |
+
|
| 833 |
+
# -----------------------------------------------------------------------
|
| 834 |
+
# Standard Polyhedron groups
|
| 835 |
+
#
|
| 836 |
+
# These are generated using _pgroup_calcs() above. However to save
|
| 837 |
+
# import time we encode them explicitly here.
|
| 838 |
+
# -----------------------------------------------------------------------
|
| 839 |
+
|
| 840 |
+
tetrahedron = Polyhedron(
|
| 841 |
+
Tuple(0, 1, 2, 3),
|
| 842 |
+
Tuple(
|
| 843 |
+
Tuple(0, 1, 2),
|
| 844 |
+
Tuple(0, 2, 3),
|
| 845 |
+
Tuple(0, 1, 3),
|
| 846 |
+
Tuple(1, 2, 3)),
|
| 847 |
+
Tuple(
|
| 848 |
+
Perm(1, 2, 3),
|
| 849 |
+
Perm(3)(0, 1, 2),
|
| 850 |
+
Perm(0, 3, 2),
|
| 851 |
+
Perm(0, 3, 1),
|
| 852 |
+
Perm(0, 1)(2, 3),
|
| 853 |
+
Perm(0, 2)(1, 3),
|
| 854 |
+
Perm(0, 3)(1, 2)
|
| 855 |
+
))
|
| 856 |
+
|
| 857 |
+
cube = Polyhedron(
|
| 858 |
+
Tuple(0, 1, 2, 3, 4, 5, 6, 7),
|
| 859 |
+
Tuple(
|
| 860 |
+
Tuple(0, 1, 2, 3),
|
| 861 |
+
Tuple(0, 1, 5, 4),
|
| 862 |
+
Tuple(1, 2, 6, 5),
|
| 863 |
+
Tuple(2, 3, 7, 6),
|
| 864 |
+
Tuple(0, 3, 7, 4),
|
| 865 |
+
Tuple(4, 5, 6, 7)),
|
| 866 |
+
Tuple(
|
| 867 |
+
Perm(0, 1, 2, 3)(4, 5, 6, 7),
|
| 868 |
+
Perm(0, 4, 5, 1)(2, 3, 7, 6),
|
| 869 |
+
Perm(0, 4, 7, 3)(1, 5, 6, 2),
|
| 870 |
+
Perm(0, 1)(2, 4)(3, 5)(6, 7),
|
| 871 |
+
Perm(0, 6)(1, 2)(3, 5)(4, 7),
|
| 872 |
+
Perm(0, 6)(1, 7)(2, 3)(4, 5),
|
| 873 |
+
Perm(0, 3)(1, 7)(2, 4)(5, 6),
|
| 874 |
+
Perm(0, 4)(1, 7)(2, 6)(3, 5),
|
| 875 |
+
Perm(0, 6)(1, 5)(2, 4)(3, 7),
|
| 876 |
+
Perm(1, 3, 4)(2, 7, 5),
|
| 877 |
+
Perm(7)(0, 5, 2)(3, 4, 6),
|
| 878 |
+
Perm(0, 5, 7)(1, 6, 3),
|
| 879 |
+
Perm(0, 7, 2)(1, 4, 6)))
|
| 880 |
+
|
| 881 |
+
octahedron = Polyhedron(
|
| 882 |
+
Tuple(0, 1, 2, 3, 4, 5),
|
| 883 |
+
Tuple(
|
| 884 |
+
Tuple(0, 1, 2),
|
| 885 |
+
Tuple(0, 2, 3),
|
| 886 |
+
Tuple(0, 3, 4),
|
| 887 |
+
Tuple(0, 1, 4),
|
| 888 |
+
Tuple(1, 2, 5),
|
| 889 |
+
Tuple(2, 3, 5),
|
| 890 |
+
Tuple(3, 4, 5),
|
| 891 |
+
Tuple(1, 4, 5)),
|
| 892 |
+
Tuple(
|
| 893 |
+
Perm(5)(1, 2, 3, 4),
|
| 894 |
+
Perm(0, 4, 5, 2),
|
| 895 |
+
Perm(0, 1, 5, 3),
|
| 896 |
+
Perm(0, 1)(2, 4)(3, 5),
|
| 897 |
+
Perm(0, 2)(1, 3)(4, 5),
|
| 898 |
+
Perm(0, 3)(1, 5)(2, 4),
|
| 899 |
+
Perm(0, 4)(1, 3)(2, 5),
|
| 900 |
+
Perm(0, 5)(1, 4)(2, 3),
|
| 901 |
+
Perm(0, 5)(1, 2)(3, 4),
|
| 902 |
+
Perm(0, 4, 1)(2, 3, 5),
|
| 903 |
+
Perm(0, 1, 2)(3, 4, 5),
|
| 904 |
+
Perm(0, 2, 3)(1, 5, 4),
|
| 905 |
+
Perm(0, 4, 3)(1, 5, 2)))
|
| 906 |
+
|
| 907 |
+
dodecahedron = Polyhedron(
|
| 908 |
+
Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19),
|
| 909 |
+
Tuple(
|
| 910 |
+
Tuple(0, 1, 2, 3, 4),
|
| 911 |
+
Tuple(0, 1, 6, 10, 5),
|
| 912 |
+
Tuple(1, 2, 7, 11, 6),
|
| 913 |
+
Tuple(2, 3, 8, 12, 7),
|
| 914 |
+
Tuple(3, 4, 9, 13, 8),
|
| 915 |
+
Tuple(0, 4, 9, 14, 5),
|
| 916 |
+
Tuple(5, 10, 16, 15, 14),
|
| 917 |
+
Tuple(6, 10, 16, 17, 11),
|
| 918 |
+
Tuple(7, 11, 17, 18, 12),
|
| 919 |
+
Tuple(8, 12, 18, 19, 13),
|
| 920 |
+
Tuple(9, 13, 19, 15, 14),
|
| 921 |
+
Tuple(15, 16, 17, 18, 19)),
|
| 922 |
+
Tuple(
|
| 923 |
+
Perm(0, 1, 2, 3, 4)(5, 6, 7, 8, 9)(10, 11, 12, 13, 14)(15, 16, 17, 18, 19),
|
| 924 |
+
Perm(0, 5, 10, 6, 1)(2, 4, 14, 16, 11)(3, 9, 15, 17, 7)(8, 13, 19, 18, 12),
|
| 925 |
+
Perm(0, 10, 17, 12, 3)(1, 6, 11, 7, 2)(4, 5, 16, 18, 8)(9, 14, 15, 19, 13),
|
| 926 |
+
Perm(0, 6, 17, 19, 9)(1, 11, 18, 13, 4)(2, 7, 12, 8, 3)(5, 10, 16, 15, 14),
|
| 927 |
+
Perm(0, 2, 12, 19, 14)(1, 7, 18, 15, 5)(3, 8, 13, 9, 4)(6, 11, 17, 16, 10),
|
| 928 |
+
Perm(0, 4, 9, 14, 5)(1, 3, 13, 15, 10)(2, 8, 19, 16, 6)(7, 12, 18, 17, 11),
|
| 929 |
+
Perm(0, 1)(2, 5)(3, 10)(4, 6)(7, 14)(8, 16)(9, 11)(12, 15)(13, 17)(18, 19),
|
| 930 |
+
Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 12)(8, 10)(9, 17)(13, 16)(14, 18)(15, 19),
|
| 931 |
+
Perm(0, 12)(1, 8)(2, 3)(4, 7)(5, 18)(6, 13)(9, 11)(10, 19)(14, 17)(15, 16),
|
| 932 |
+
Perm(0, 8)(1, 13)(2, 9)(3, 4)(5, 12)(6, 19)(7, 14)(10, 18)(11, 15)(16, 17),
|
| 933 |
+
Perm(0, 4)(1, 9)(2, 14)(3, 5)(6, 13)(7, 15)(8, 10)(11, 19)(12, 16)(17, 18),
|
| 934 |
+
Perm(0, 5)(1, 14)(2, 15)(3, 16)(4, 10)(6, 9)(7, 19)(8, 17)(11, 13)(12, 18),
|
| 935 |
+
Perm(0, 11)(1, 6)(2, 10)(3, 16)(4, 17)(5, 7)(8, 15)(9, 18)(12, 14)(13, 19),
|
| 936 |
+
Perm(0, 18)(1, 12)(2, 7)(3, 11)(4, 17)(5, 19)(6, 8)(9, 16)(10, 13)(14, 15),
|
| 937 |
+
Perm(0, 18)(1, 19)(2, 13)(3, 8)(4, 12)(5, 17)(6, 15)(7, 9)(10, 16)(11, 14),
|
| 938 |
+
Perm(0, 13)(1, 19)(2, 15)(3, 14)(4, 9)(5, 8)(6, 18)(7, 16)(10, 12)(11, 17),
|
| 939 |
+
Perm(0, 16)(1, 15)(2, 19)(3, 18)(4, 17)(5, 10)(6, 14)(7, 13)(8, 12)(9, 11),
|
| 940 |
+
Perm(0, 18)(1, 17)(2, 16)(3, 15)(4, 19)(5, 12)(6, 11)(7, 10)(8, 14)(9, 13),
|
| 941 |
+
Perm(0, 15)(1, 19)(2, 18)(3, 17)(4, 16)(5, 14)(6, 13)(7, 12)(8, 11)(9, 10),
|
| 942 |
+
Perm(0, 17)(1, 16)(2, 15)(3, 19)(4, 18)(5, 11)(6, 10)(7, 14)(8, 13)(9, 12),
|
| 943 |
+
Perm(0, 19)(1, 18)(2, 17)(3, 16)(4, 15)(5, 13)(6, 12)(7, 11)(8, 10)(9, 14),
|
| 944 |
+
Perm(1, 4, 5)(2, 9, 10)(3, 14, 6)(7, 13, 16)(8, 15, 11)(12, 19, 17),
|
| 945 |
+
Perm(19)(0, 6, 2)(3, 5, 11)(4, 10, 7)(8, 14, 17)(9, 16, 12)(13, 15, 18),
|
| 946 |
+
Perm(0, 11, 8)(1, 7, 3)(4, 6, 12)(5, 17, 13)(9, 10, 18)(14, 16, 19),
|
| 947 |
+
Perm(0, 7, 13)(1, 12, 9)(2, 8, 4)(5, 11, 19)(6, 18, 14)(10, 17, 15),
|
| 948 |
+
Perm(0, 3, 9)(1, 8, 14)(2, 13, 5)(6, 12, 15)(7, 19, 10)(11, 18, 16),
|
| 949 |
+
Perm(0, 14, 10)(1, 9, 16)(2, 13, 17)(3, 19, 11)(4, 15, 6)(7, 8, 18),
|
| 950 |
+
Perm(0, 16, 7)(1, 10, 11)(2, 5, 17)(3, 14, 18)(4, 15, 12)(8, 9, 19),
|
| 951 |
+
Perm(0, 16, 13)(1, 17, 8)(2, 11, 12)(3, 6, 18)(4, 10, 19)(5, 15, 9),
|
| 952 |
+
Perm(0, 11, 15)(1, 17, 14)(2, 18, 9)(3, 12, 13)(4, 7, 19)(5, 6, 16),
|
| 953 |
+
Perm(0, 8, 15)(1, 12, 16)(2, 18, 10)(3, 19, 5)(4, 13, 14)(6, 7, 17)))
|
| 954 |
+
|
| 955 |
+
icosahedron = Polyhedron(
|
| 956 |
+
Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
|
| 957 |
+
Tuple(
|
| 958 |
+
Tuple(0, 1, 2),
|
| 959 |
+
Tuple(0, 2, 3),
|
| 960 |
+
Tuple(0, 3, 4),
|
| 961 |
+
Tuple(0, 4, 5),
|
| 962 |
+
Tuple(0, 1, 5),
|
| 963 |
+
Tuple(1, 6, 7),
|
| 964 |
+
Tuple(1, 2, 7),
|
| 965 |
+
Tuple(2, 7, 8),
|
| 966 |
+
Tuple(2, 3, 8),
|
| 967 |
+
Tuple(3, 8, 9),
|
| 968 |
+
Tuple(3, 4, 9),
|
| 969 |
+
Tuple(4, 9, 10),
|
| 970 |
+
Tuple(4, 5, 10),
|
| 971 |
+
Tuple(5, 6, 10),
|
| 972 |
+
Tuple(1, 5, 6),
|
| 973 |
+
Tuple(6, 7, 11),
|
| 974 |
+
Tuple(7, 8, 11),
|
| 975 |
+
Tuple(8, 9, 11),
|
| 976 |
+
Tuple(9, 10, 11),
|
| 977 |
+
Tuple(6, 10, 11)),
|
| 978 |
+
Tuple(
|
| 979 |
+
Perm(11)(1, 2, 3, 4, 5)(6, 7, 8, 9, 10),
|
| 980 |
+
Perm(0, 5, 6, 7, 2)(3, 4, 10, 11, 8),
|
| 981 |
+
Perm(0, 1, 7, 8, 3)(4, 5, 6, 11, 9),
|
| 982 |
+
Perm(0, 2, 8, 9, 4)(1, 7, 11, 10, 5),
|
| 983 |
+
Perm(0, 3, 9, 10, 5)(1, 2, 8, 11, 6),
|
| 984 |
+
Perm(0, 4, 10, 6, 1)(2, 3, 9, 11, 7),
|
| 985 |
+
Perm(0, 1)(2, 5)(3, 6)(4, 7)(8, 10)(9, 11),
|
| 986 |
+
Perm(0, 2)(1, 3)(4, 7)(5, 8)(6, 9)(10, 11),
|
| 987 |
+
Perm(0, 3)(1, 9)(2, 4)(5, 8)(6, 11)(7, 10),
|
| 988 |
+
Perm(0, 4)(1, 9)(2, 10)(3, 5)(6, 8)(7, 11),
|
| 989 |
+
Perm(0, 5)(1, 4)(2, 10)(3, 6)(7, 9)(8, 11),
|
| 990 |
+
Perm(0, 6)(1, 5)(2, 10)(3, 11)(4, 7)(8, 9),
|
| 991 |
+
Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 8)(9, 10),
|
| 992 |
+
Perm(0, 8)(1, 9)(2, 3)(4, 7)(5, 11)(6, 10),
|
| 993 |
+
Perm(0, 9)(1, 11)(2, 10)(3, 4)(5, 8)(6, 7),
|
| 994 |
+
Perm(0, 10)(1, 9)(2, 11)(3, 6)(4, 5)(7, 8),
|
| 995 |
+
Perm(0, 11)(1, 6)(2, 10)(3, 9)(4, 8)(5, 7),
|
| 996 |
+
Perm(0, 11)(1, 8)(2, 7)(3, 6)(4, 10)(5, 9),
|
| 997 |
+
Perm(0, 11)(1, 10)(2, 9)(3, 8)(4, 7)(5, 6),
|
| 998 |
+
Perm(0, 11)(1, 7)(2, 6)(3, 10)(4, 9)(5, 8),
|
| 999 |
+
Perm(0, 11)(1, 9)(2, 8)(3, 7)(4, 6)(5, 10),
|
| 1000 |
+
Perm(0, 5, 1)(2, 4, 6)(3, 10, 7)(8, 9, 11),
|
| 1001 |
+
Perm(0, 1, 2)(3, 5, 7)(4, 6, 8)(9, 10, 11),
|
| 1002 |
+
Perm(0, 2, 3)(1, 8, 4)(5, 7, 9)(6, 11, 10),
|
| 1003 |
+
Perm(0, 3, 4)(1, 8, 10)(2, 9, 5)(6, 7, 11),
|
| 1004 |
+
Perm(0, 4, 5)(1, 3, 10)(2, 9, 6)(7, 8, 11),
|
| 1005 |
+
Perm(0, 10, 7)(1, 5, 6)(2, 4, 11)(3, 9, 8),
|
| 1006 |
+
Perm(0, 6, 8)(1, 7, 2)(3, 5, 11)(4, 10, 9),
|
| 1007 |
+
Perm(0, 7, 9)(1, 11, 4)(2, 8, 3)(5, 6, 10),
|
| 1008 |
+
Perm(0, 8, 10)(1, 7, 6)(2, 11, 5)(3, 9, 4),
|
| 1009 |
+
Perm(0, 9, 6)(1, 3, 11)(2, 8, 7)(4, 10, 5)))
|
| 1010 |
+
|
| 1011 |
+
tetrahedron_faces = [tuple(arg) for arg in tetrahedron.faces]
|
| 1012 |
+
|
| 1013 |
+
cube_faces = [tuple(arg) for arg in cube.faces]
|
| 1014 |
+
|
| 1015 |
+
octahedron_faces = [tuple(arg) for arg in octahedron.faces]
|
| 1016 |
+
|
| 1017 |
+
dodecahedron_faces = [tuple(arg) for arg in dodecahedron.faces]
|
| 1018 |
+
|
| 1019 |
+
icosahedron_faces = [tuple(arg) for arg in icosahedron.faces]
|
lib/python3.12/site-packages/sympy/combinatorics/prufer.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core import Basic
|
| 2 |
+
from sympy.core.containers import Tuple
|
| 3 |
+
from sympy.tensor.array import Array
|
| 4 |
+
from sympy.core.sympify import _sympify
|
| 5 |
+
from sympy.utilities.iterables import flatten, iterable
|
| 6 |
+
from sympy.utilities.misc import as_int
|
| 7 |
+
|
| 8 |
+
from collections import defaultdict
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Prufer(Basic):
|
| 12 |
+
"""
|
| 13 |
+
The Prufer correspondence is an algorithm that describes the
|
| 14 |
+
bijection between labeled trees and the Prufer code. A Prufer
|
| 15 |
+
code of a labeled tree is unique up to isomorphism and has
|
| 16 |
+
a length of n - 2.
|
| 17 |
+
|
| 18 |
+
Prufer sequences were first used by Heinz Prufer to give a
|
| 19 |
+
proof of Cayley's formula.
|
| 20 |
+
|
| 21 |
+
References
|
| 22 |
+
==========
|
| 23 |
+
|
| 24 |
+
.. [1] https://mathworld.wolfram.com/LabeledTree.html
|
| 25 |
+
|
| 26 |
+
"""
|
| 27 |
+
_prufer_repr = None
|
| 28 |
+
_tree_repr = None
|
| 29 |
+
_nodes = None
|
| 30 |
+
_rank = None
|
| 31 |
+
|
| 32 |
+
@property
|
| 33 |
+
def prufer_repr(self):
|
| 34 |
+
"""Returns Prufer sequence for the Prufer object.
|
| 35 |
+
|
| 36 |
+
This sequence is found by removing the highest numbered vertex,
|
| 37 |
+
recording the node it was attached to, and continuing until only
|
| 38 |
+
two vertices remain. The Prufer sequence is the list of recorded nodes.
|
| 39 |
+
|
| 40 |
+
Examples
|
| 41 |
+
========
|
| 42 |
+
|
| 43 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 44 |
+
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).prufer_repr
|
| 45 |
+
[3, 3, 3, 4]
|
| 46 |
+
>>> Prufer([1, 0, 0]).prufer_repr
|
| 47 |
+
[1, 0, 0]
|
| 48 |
+
|
| 49 |
+
See Also
|
| 50 |
+
========
|
| 51 |
+
|
| 52 |
+
to_prufer
|
| 53 |
+
|
| 54 |
+
"""
|
| 55 |
+
if self._prufer_repr is None:
|
| 56 |
+
self._prufer_repr = self.to_prufer(self._tree_repr[:], self.nodes)
|
| 57 |
+
return self._prufer_repr
|
| 58 |
+
|
| 59 |
+
@property
|
| 60 |
+
def tree_repr(self):
|
| 61 |
+
"""Returns the tree representation of the Prufer object.
|
| 62 |
+
|
| 63 |
+
Examples
|
| 64 |
+
========
|
| 65 |
+
|
| 66 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 67 |
+
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).tree_repr
|
| 68 |
+
[[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]
|
| 69 |
+
>>> Prufer([1, 0, 0]).tree_repr
|
| 70 |
+
[[1, 2], [0, 1], [0, 3], [0, 4]]
|
| 71 |
+
|
| 72 |
+
See Also
|
| 73 |
+
========
|
| 74 |
+
|
| 75 |
+
to_tree
|
| 76 |
+
|
| 77 |
+
"""
|
| 78 |
+
if self._tree_repr is None:
|
| 79 |
+
self._tree_repr = self.to_tree(self._prufer_repr[:])
|
| 80 |
+
return self._tree_repr
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def nodes(self):
|
| 84 |
+
"""Returns the number of nodes in the tree.
|
| 85 |
+
|
| 86 |
+
Examples
|
| 87 |
+
========
|
| 88 |
+
|
| 89 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 90 |
+
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).nodes
|
| 91 |
+
6
|
| 92 |
+
>>> Prufer([1, 0, 0]).nodes
|
| 93 |
+
5
|
| 94 |
+
|
| 95 |
+
"""
|
| 96 |
+
return self._nodes
|
| 97 |
+
|
| 98 |
+
@property
|
| 99 |
+
def rank(self):
|
| 100 |
+
"""Returns the rank of the Prufer sequence.
|
| 101 |
+
|
| 102 |
+
Examples
|
| 103 |
+
========
|
| 104 |
+
|
| 105 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 106 |
+
>>> p = Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]])
|
| 107 |
+
>>> p.rank
|
| 108 |
+
778
|
| 109 |
+
>>> p.next(1).rank
|
| 110 |
+
779
|
| 111 |
+
>>> p.prev().rank
|
| 112 |
+
777
|
| 113 |
+
|
| 114 |
+
See Also
|
| 115 |
+
========
|
| 116 |
+
|
| 117 |
+
prufer_rank, next, prev, size
|
| 118 |
+
|
| 119 |
+
"""
|
| 120 |
+
if self._rank is None:
|
| 121 |
+
self._rank = self.prufer_rank()
|
| 122 |
+
return self._rank
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def size(self):
|
| 126 |
+
"""Return the number of possible trees of this Prufer object.
|
| 127 |
+
|
| 128 |
+
Examples
|
| 129 |
+
========
|
| 130 |
+
|
| 131 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 132 |
+
>>> Prufer([0]*4).size == Prufer([6]*4).size == 1296
|
| 133 |
+
True
|
| 134 |
+
|
| 135 |
+
See Also
|
| 136 |
+
========
|
| 137 |
+
|
| 138 |
+
prufer_rank, rank, next, prev
|
| 139 |
+
|
| 140 |
+
"""
|
| 141 |
+
return self.prev(self.rank).prev().rank + 1
|
| 142 |
+
|
| 143 |
+
@staticmethod
|
| 144 |
+
def to_prufer(tree, n):
|
| 145 |
+
"""Return the Prufer sequence for a tree given as a list of edges where
|
| 146 |
+
``n`` is the number of nodes in the tree.
|
| 147 |
+
|
| 148 |
+
Examples
|
| 149 |
+
========
|
| 150 |
+
|
| 151 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 152 |
+
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
| 153 |
+
>>> a.prufer_repr
|
| 154 |
+
[0, 0]
|
| 155 |
+
>>> Prufer.to_prufer([[0, 1], [0, 2], [0, 3]], 4)
|
| 156 |
+
[0, 0]
|
| 157 |
+
|
| 158 |
+
See Also
|
| 159 |
+
========
|
| 160 |
+
prufer_repr: returns Prufer sequence of a Prufer object.
|
| 161 |
+
|
| 162 |
+
"""
|
| 163 |
+
d = defaultdict(int)
|
| 164 |
+
L = []
|
| 165 |
+
for edge in tree:
|
| 166 |
+
# Increment the value of the corresponding
|
| 167 |
+
# node in the degree list as we encounter an
|
| 168 |
+
# edge involving it.
|
| 169 |
+
d[edge[0]] += 1
|
| 170 |
+
d[edge[1]] += 1
|
| 171 |
+
for i in range(n - 2):
|
| 172 |
+
# find the smallest leaf
|
| 173 |
+
for x in range(n):
|
| 174 |
+
if d[x] == 1:
|
| 175 |
+
break
|
| 176 |
+
# find the node it was connected to
|
| 177 |
+
y = None
|
| 178 |
+
for edge in tree:
|
| 179 |
+
if x == edge[0]:
|
| 180 |
+
y = edge[1]
|
| 181 |
+
elif x == edge[1]:
|
| 182 |
+
y = edge[0]
|
| 183 |
+
if y is not None:
|
| 184 |
+
break
|
| 185 |
+
# record and update
|
| 186 |
+
L.append(y)
|
| 187 |
+
for j in (x, y):
|
| 188 |
+
d[j] -= 1
|
| 189 |
+
if not d[j]:
|
| 190 |
+
d.pop(j)
|
| 191 |
+
tree.remove(edge)
|
| 192 |
+
return L
|
| 193 |
+
|
| 194 |
+
@staticmethod
|
| 195 |
+
def to_tree(prufer):
|
| 196 |
+
"""Return the tree (as a list of edges) of the given Prufer sequence.
|
| 197 |
+
|
| 198 |
+
Examples
|
| 199 |
+
========
|
| 200 |
+
|
| 201 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 202 |
+
>>> a = Prufer([0, 2], 4)
|
| 203 |
+
>>> a.tree_repr
|
| 204 |
+
[[0, 1], [0, 2], [2, 3]]
|
| 205 |
+
>>> Prufer.to_tree([0, 2])
|
| 206 |
+
[[0, 1], [0, 2], [2, 3]]
|
| 207 |
+
|
| 208 |
+
References
|
| 209 |
+
==========
|
| 210 |
+
|
| 211 |
+
.. [1] https://hamberg.no/erlend/posts/2010-11-06-prufer-sequence-compact-tree-representation.html
|
| 212 |
+
|
| 213 |
+
See Also
|
| 214 |
+
========
|
| 215 |
+
tree_repr: returns tree representation of a Prufer object.
|
| 216 |
+
|
| 217 |
+
"""
|
| 218 |
+
tree = []
|
| 219 |
+
last = []
|
| 220 |
+
n = len(prufer) + 2
|
| 221 |
+
d = defaultdict(lambda: 1)
|
| 222 |
+
for p in prufer:
|
| 223 |
+
d[p] += 1
|
| 224 |
+
for i in prufer:
|
| 225 |
+
for j in range(n):
|
| 226 |
+
# find the smallest leaf (degree = 1)
|
| 227 |
+
if d[j] == 1:
|
| 228 |
+
break
|
| 229 |
+
# (i, j) is the new edge that we append to the tree
|
| 230 |
+
# and remove from the degree dictionary
|
| 231 |
+
d[i] -= 1
|
| 232 |
+
d[j] -= 1
|
| 233 |
+
tree.append(sorted([i, j]))
|
| 234 |
+
last = [i for i in range(n) if d[i] == 1] or [0, 1]
|
| 235 |
+
tree.append(last)
|
| 236 |
+
|
| 237 |
+
return tree
|
| 238 |
+
|
| 239 |
+
@staticmethod
|
| 240 |
+
def edges(*runs):
|
| 241 |
+
"""Return a list of edges and the number of nodes from the given runs
|
| 242 |
+
that connect nodes in an integer-labelled tree.
|
| 243 |
+
|
| 244 |
+
All node numbers will be shifted so that the minimum node is 0. It is
|
| 245 |
+
not a problem if edges are repeated in the runs; only unique edges are
|
| 246 |
+
returned. There is no assumption made about what the range of the node
|
| 247 |
+
labels should be, but all nodes from the smallest through the largest
|
| 248 |
+
must be present.
|
| 249 |
+
|
| 250 |
+
Examples
|
| 251 |
+
========
|
| 252 |
+
|
| 253 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 254 |
+
>>> Prufer.edges([1, 2, 3], [2, 4, 5]) # a T
|
| 255 |
+
([[0, 1], [1, 2], [1, 3], [3, 4]], 5)
|
| 256 |
+
|
| 257 |
+
Duplicate edges are removed:
|
| 258 |
+
|
| 259 |
+
>>> Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) # a K
|
| 260 |
+
([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7)
|
| 261 |
+
|
| 262 |
+
"""
|
| 263 |
+
e = set()
|
| 264 |
+
nmin = runs[0][0]
|
| 265 |
+
for r in runs:
|
| 266 |
+
for i in range(len(r) - 1):
|
| 267 |
+
a, b = r[i: i + 2]
|
| 268 |
+
if b < a:
|
| 269 |
+
a, b = b, a
|
| 270 |
+
e.add((a, b))
|
| 271 |
+
rv = []
|
| 272 |
+
got = set()
|
| 273 |
+
nmin = nmax = None
|
| 274 |
+
for ei in e:
|
| 275 |
+
got.update(ei)
|
| 276 |
+
nmin = min(ei[0], nmin) if nmin is not None else ei[0]
|
| 277 |
+
nmax = max(ei[1], nmax) if nmax is not None else ei[1]
|
| 278 |
+
rv.append(list(ei))
|
| 279 |
+
missing = set(range(nmin, nmax + 1)) - got
|
| 280 |
+
if missing:
|
| 281 |
+
missing = [i + nmin for i in missing]
|
| 282 |
+
if len(missing) == 1:
|
| 283 |
+
msg = 'Node %s is missing.' % missing.pop()
|
| 284 |
+
else:
|
| 285 |
+
msg = 'Nodes %s are missing.' % sorted(missing)
|
| 286 |
+
raise ValueError(msg)
|
| 287 |
+
if nmin != 0:
|
| 288 |
+
for i, ei in enumerate(rv):
|
| 289 |
+
rv[i] = [n - nmin for n in ei]
|
| 290 |
+
nmax -= nmin
|
| 291 |
+
return sorted(rv), nmax + 1
|
| 292 |
+
|
| 293 |
+
def prufer_rank(self):
|
| 294 |
+
"""Computes the rank of a Prufer sequence.
|
| 295 |
+
|
| 296 |
+
Examples
|
| 297 |
+
========
|
| 298 |
+
|
| 299 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 300 |
+
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
| 301 |
+
>>> a.prufer_rank()
|
| 302 |
+
0
|
| 303 |
+
|
| 304 |
+
See Also
|
| 305 |
+
========
|
| 306 |
+
|
| 307 |
+
rank, next, prev, size
|
| 308 |
+
|
| 309 |
+
"""
|
| 310 |
+
r = 0
|
| 311 |
+
p = 1
|
| 312 |
+
for i in range(self.nodes - 3, -1, -1):
|
| 313 |
+
r += p*self.prufer_repr[i]
|
| 314 |
+
p *= self.nodes
|
| 315 |
+
return r
|
| 316 |
+
|
| 317 |
+
@classmethod
|
| 318 |
+
def unrank(self, rank, n):
|
| 319 |
+
"""Finds the unranked Prufer sequence.
|
| 320 |
+
|
| 321 |
+
Examples
|
| 322 |
+
========
|
| 323 |
+
|
| 324 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 325 |
+
>>> Prufer.unrank(0, 4)
|
| 326 |
+
Prufer([0, 0])
|
| 327 |
+
|
| 328 |
+
"""
|
| 329 |
+
n, rank = as_int(n), as_int(rank)
|
| 330 |
+
L = defaultdict(int)
|
| 331 |
+
for i in range(n - 3, -1, -1):
|
| 332 |
+
L[i] = rank % n
|
| 333 |
+
rank = (rank - L[i])//n
|
| 334 |
+
return Prufer([L[i] for i in range(len(L))])
|
| 335 |
+
|
| 336 |
+
def __new__(cls, *args, **kw_args):
|
| 337 |
+
"""The constructor for the Prufer object.
|
| 338 |
+
|
| 339 |
+
Examples
|
| 340 |
+
========
|
| 341 |
+
|
| 342 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 343 |
+
|
| 344 |
+
A Prufer object can be constructed from a list of edges:
|
| 345 |
+
|
| 346 |
+
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
| 347 |
+
>>> a.prufer_repr
|
| 348 |
+
[0, 0]
|
| 349 |
+
|
| 350 |
+
If the number of nodes is given, no checking of the nodes will
|
| 351 |
+
be performed; it will be assumed that nodes 0 through n - 1 are
|
| 352 |
+
present:
|
| 353 |
+
|
| 354 |
+
>>> Prufer([[0, 1], [0, 2], [0, 3]], 4)
|
| 355 |
+
Prufer([[0, 1], [0, 2], [0, 3]], 4)
|
| 356 |
+
|
| 357 |
+
A Prufer object can be constructed from a Prufer sequence:
|
| 358 |
+
|
| 359 |
+
>>> b = Prufer([1, 3])
|
| 360 |
+
>>> b.tree_repr
|
| 361 |
+
[[0, 1], [1, 3], [2, 3]]
|
| 362 |
+
|
| 363 |
+
"""
|
| 364 |
+
arg0 = Array(args[0]) if args[0] else Tuple()
|
| 365 |
+
args = (arg0,) + tuple(_sympify(arg) for arg in args[1:])
|
| 366 |
+
ret_obj = Basic.__new__(cls, *args, **kw_args)
|
| 367 |
+
args = [list(args[0])]
|
| 368 |
+
if args[0] and iterable(args[0][0]):
|
| 369 |
+
if not args[0][0]:
|
| 370 |
+
raise ValueError(
|
| 371 |
+
'Prufer expects at least one edge in the tree.')
|
| 372 |
+
if len(args) > 1:
|
| 373 |
+
nnodes = args[1]
|
| 374 |
+
else:
|
| 375 |
+
nodes = set(flatten(args[0]))
|
| 376 |
+
nnodes = max(nodes) + 1
|
| 377 |
+
if nnodes != len(nodes):
|
| 378 |
+
missing = set(range(nnodes)) - nodes
|
| 379 |
+
if len(missing) == 1:
|
| 380 |
+
msg = 'Node %s is missing.' % missing.pop()
|
| 381 |
+
else:
|
| 382 |
+
msg = 'Nodes %s are missing.' % sorted(missing)
|
| 383 |
+
raise ValueError(msg)
|
| 384 |
+
ret_obj._tree_repr = [list(i) for i in args[0]]
|
| 385 |
+
ret_obj._nodes = nnodes
|
| 386 |
+
else:
|
| 387 |
+
ret_obj._prufer_repr = args[0]
|
| 388 |
+
ret_obj._nodes = len(ret_obj._prufer_repr) + 2
|
| 389 |
+
return ret_obj
|
| 390 |
+
|
| 391 |
+
def next(self, delta=1):
|
| 392 |
+
"""Generates the Prufer sequence that is delta beyond the current one.
|
| 393 |
+
|
| 394 |
+
Examples
|
| 395 |
+
========
|
| 396 |
+
|
| 397 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 398 |
+
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
| 399 |
+
>>> b = a.next(1) # == a.next()
|
| 400 |
+
>>> b.tree_repr
|
| 401 |
+
[[0, 2], [0, 1], [1, 3]]
|
| 402 |
+
>>> b.rank
|
| 403 |
+
1
|
| 404 |
+
|
| 405 |
+
See Also
|
| 406 |
+
========
|
| 407 |
+
|
| 408 |
+
prufer_rank, rank, prev, size
|
| 409 |
+
|
| 410 |
+
"""
|
| 411 |
+
return Prufer.unrank(self.rank + delta, self.nodes)
|
| 412 |
+
|
| 413 |
+
def prev(self, delta=1):
|
| 414 |
+
"""Generates the Prufer sequence that is -delta before the current one.
|
| 415 |
+
|
| 416 |
+
Examples
|
| 417 |
+
========
|
| 418 |
+
|
| 419 |
+
>>> from sympy.combinatorics.prufer import Prufer
|
| 420 |
+
>>> a = Prufer([[0, 1], [1, 2], [2, 3], [1, 4]])
|
| 421 |
+
>>> a.rank
|
| 422 |
+
36
|
| 423 |
+
>>> b = a.prev()
|
| 424 |
+
>>> b
|
| 425 |
+
Prufer([1, 2, 0])
|
| 426 |
+
>>> b.rank
|
| 427 |
+
35
|
| 428 |
+
|
| 429 |
+
See Also
|
| 430 |
+
========
|
| 431 |
+
|
| 432 |
+
prufer_rank, rank, next, size
|
| 433 |
+
|
| 434 |
+
"""
|
| 435 |
+
return Prufer.unrank(self.rank -delta, self.nodes)
|
lib/python3.12/site-packages/sympy/combinatorics/rewritingsystem_fsm.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class State:
|
| 2 |
+
'''
|
| 3 |
+
A representation of a state managed by a ``StateMachine``.
|
| 4 |
+
|
| 5 |
+
Attributes:
|
| 6 |
+
name (instance of FreeGroupElement or string) -- State name which is also assigned to the Machine.
|
| 7 |
+
transisitons (OrderedDict) -- Represents all the transitions of the state object.
|
| 8 |
+
state_type (string) -- Denotes the type (accept/start/dead) of the state.
|
| 9 |
+
rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.
|
| 10 |
+
state_machine (instance of StateMachine object) -- The finite state machine that the state belongs to.
|
| 11 |
+
'''
|
| 12 |
+
|
| 13 |
+
def __init__(self, name, state_machine, state_type=None, rh_rule=None):
|
| 14 |
+
self.name = name
|
| 15 |
+
self.transitions = {}
|
| 16 |
+
self.state_machine = state_machine
|
| 17 |
+
self.state_type = state_type[0]
|
| 18 |
+
self.rh_rule = rh_rule
|
| 19 |
+
|
| 20 |
+
def add_transition(self, letter, state):
|
| 21 |
+
'''
|
| 22 |
+
Add a transition from the current state to a new state.
|
| 23 |
+
|
| 24 |
+
Keyword Arguments:
|
| 25 |
+
letter -- The alphabet element the current state reads to make the state transition.
|
| 26 |
+
state -- This will be an instance of the State object which represents a new state after in the transition after the alphabet is read.
|
| 27 |
+
|
| 28 |
+
'''
|
| 29 |
+
self.transitions[letter] = state
|
| 30 |
+
|
| 31 |
+
class StateMachine:
|
| 32 |
+
'''
|
| 33 |
+
Representation of a finite state machine the manages the states and the transitions of the automaton.
|
| 34 |
+
|
| 35 |
+
Attributes:
|
| 36 |
+
states (dictionary) -- Collection of all registered `State` objects.
|
| 37 |
+
name (str) -- Name of the state machine.
|
| 38 |
+
'''
|
| 39 |
+
|
| 40 |
+
def __init__(self, name, automaton_alphabet):
|
| 41 |
+
self.name = name
|
| 42 |
+
self.automaton_alphabet = automaton_alphabet
|
| 43 |
+
self.states = {} # Contains all the states in the machine.
|
| 44 |
+
self.add_state('start', state_type='s')
|
| 45 |
+
|
| 46 |
+
def add_state(self, state_name, state_type=None, rh_rule=None):
|
| 47 |
+
'''
|
| 48 |
+
Instantiate a state object and stores it in the 'states' dictionary.
|
| 49 |
+
|
| 50 |
+
Arguments:
|
| 51 |
+
state_name (instance of FreeGroupElement or string) -- name of the new states.
|
| 52 |
+
state_type (string) -- Denotes the type (accept/start/dead) of the state added.
|
| 53 |
+
rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.
|
| 54 |
+
|
| 55 |
+
'''
|
| 56 |
+
new_state = State(state_name, self, state_type, rh_rule)
|
| 57 |
+
self.states[state_name] = new_state
|
| 58 |
+
|
| 59 |
+
def __repr__(self):
|
| 60 |
+
return "%s" % (self.name)
|
lib/python3.12/site-packages/sympy/combinatorics/schur_number.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
The Schur number S(k) is the largest integer n for which the interval [1,n]
|
| 3 |
+
can be partitioned into k sum-free sets.(https://mathworld.wolfram.com/SchurNumber.html)
|
| 4 |
+
"""
|
| 5 |
+
import math
|
| 6 |
+
from sympy.core import S
|
| 7 |
+
from sympy.core.basic import Basic
|
| 8 |
+
from sympy.core.function import Function
|
| 9 |
+
from sympy.core.numbers import Integer
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class SchurNumber(Function):
|
| 13 |
+
r"""
|
| 14 |
+
This function creates a SchurNumber object
|
| 15 |
+
which is evaluated for `k \le 5` otherwise only
|
| 16 |
+
the lower bound information can be retrieved.
|
| 17 |
+
|
| 18 |
+
Examples
|
| 19 |
+
========
|
| 20 |
+
|
| 21 |
+
>>> from sympy.combinatorics.schur_number import SchurNumber
|
| 22 |
+
|
| 23 |
+
Since S(3) = 13, hence the output is a number
|
| 24 |
+
>>> SchurNumber(3)
|
| 25 |
+
13
|
| 26 |
+
|
| 27 |
+
We do not know the Schur number for values greater than 5, hence
|
| 28 |
+
only the object is returned
|
| 29 |
+
>>> SchurNumber(6)
|
| 30 |
+
SchurNumber(6)
|
| 31 |
+
|
| 32 |
+
Now, the lower bound information can be retrieved using lower_bound()
|
| 33 |
+
method
|
| 34 |
+
>>> SchurNumber(6).lower_bound()
|
| 35 |
+
536
|
| 36 |
+
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
@classmethod
|
| 40 |
+
def eval(cls, k):
|
| 41 |
+
if k.is_Number:
|
| 42 |
+
if k is S.Infinity:
|
| 43 |
+
return S.Infinity
|
| 44 |
+
if k.is_zero:
|
| 45 |
+
return S.Zero
|
| 46 |
+
if not k.is_integer or k.is_negative:
|
| 47 |
+
raise ValueError("k should be a positive integer")
|
| 48 |
+
first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160}
|
| 49 |
+
if k <= 5:
|
| 50 |
+
return Integer(first_known_schur_numbers[k])
|
| 51 |
+
|
| 52 |
+
def lower_bound(self):
|
| 53 |
+
f_ = self.args[0]
|
| 54 |
+
# Improved lower bounds known for S(6) and S(7)
|
| 55 |
+
if f_ == 6:
|
| 56 |
+
return Integer(536)
|
| 57 |
+
if f_ == 7:
|
| 58 |
+
return Integer(1680)
|
| 59 |
+
# For other cases, use general expression
|
| 60 |
+
if f_.is_Integer:
|
| 61 |
+
return 3*self.func(f_ - 1).lower_bound() - 1
|
| 62 |
+
return (3**f_ - 1)/2
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _schur_subsets_number(n):
|
| 66 |
+
|
| 67 |
+
if n is S.Infinity:
|
| 68 |
+
raise ValueError("Input must be finite")
|
| 69 |
+
if n <= 0:
|
| 70 |
+
raise ValueError("n must be a non-zero positive integer.")
|
| 71 |
+
elif n <= 3:
|
| 72 |
+
min_k = 1
|
| 73 |
+
else:
|
| 74 |
+
min_k = math.ceil(math.log(2*n + 1, 3))
|
| 75 |
+
|
| 76 |
+
return Integer(min_k)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def schur_partition(n):
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
This function returns the partition in the minimum number of sum-free subsets
|
| 83 |
+
according to the lower bound given by the Schur Number.
|
| 84 |
+
|
| 85 |
+
Parameters
|
| 86 |
+
==========
|
| 87 |
+
|
| 88 |
+
n: a number
|
| 89 |
+
n is the upper limit of the range [1, n] for which we need to find and
|
| 90 |
+
return the minimum number of free subsets according to the lower bound
|
| 91 |
+
of schur number
|
| 92 |
+
|
| 93 |
+
Returns
|
| 94 |
+
=======
|
| 95 |
+
|
| 96 |
+
List of lists
|
| 97 |
+
List of the minimum number of sum-free subsets
|
| 98 |
+
|
| 99 |
+
Notes
|
| 100 |
+
=====
|
| 101 |
+
|
| 102 |
+
It is possible for some n to make the partition into less
|
| 103 |
+
subsets since the only known Schur numbers are:
|
| 104 |
+
S(1) = 1, S(2) = 4, S(3) = 13, S(4) = 44.
|
| 105 |
+
e.g for n = 44 the lower bound from the function above is 5 subsets but it has been proven
|
| 106 |
+
that can be done with 4 subsets.
|
| 107 |
+
|
| 108 |
+
Examples
|
| 109 |
+
========
|
| 110 |
+
|
| 111 |
+
For n = 1, 2, 3 the answer is the set itself
|
| 112 |
+
|
| 113 |
+
>>> from sympy.combinatorics.schur_number import schur_partition
|
| 114 |
+
>>> schur_partition(2)
|
| 115 |
+
[[1, 2]]
|
| 116 |
+
|
| 117 |
+
For n > 3, the answer is the minimum number of sum-free subsets:
|
| 118 |
+
|
| 119 |
+
>>> schur_partition(5)
|
| 120 |
+
[[3, 2], [5], [1, 4]]
|
| 121 |
+
|
| 122 |
+
>>> schur_partition(8)
|
| 123 |
+
[[3, 2], [6, 5, 8], [1, 4, 7]]
|
| 124 |
+
"""
|
| 125 |
+
|
| 126 |
+
if isinstance(n, Basic) and not n.is_Number:
|
| 127 |
+
raise ValueError("Input value must be a number")
|
| 128 |
+
|
| 129 |
+
number_of_subsets = _schur_subsets_number(n)
|
| 130 |
+
if n == 1:
|
| 131 |
+
sum_free_subsets = [[1]]
|
| 132 |
+
elif n == 2:
|
| 133 |
+
sum_free_subsets = [[1, 2]]
|
| 134 |
+
elif n == 3:
|
| 135 |
+
sum_free_subsets = [[1, 2, 3]]
|
| 136 |
+
else:
|
| 137 |
+
sum_free_subsets = [[1, 4], [2, 3]]
|
| 138 |
+
|
| 139 |
+
while len(sum_free_subsets) < number_of_subsets:
|
| 140 |
+
sum_free_subsets = _generate_next_list(sum_free_subsets, n)
|
| 141 |
+
missed_elements = [3*k + 1 for k in range(len(sum_free_subsets), (n-1)//3 + 1)]
|
| 142 |
+
sum_free_subsets[-1] += missed_elements
|
| 143 |
+
|
| 144 |
+
return sum_free_subsets
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _generate_next_list(current_list, n):
|
| 148 |
+
new_list = []
|
| 149 |
+
|
| 150 |
+
for item in current_list:
|
| 151 |
+
temp_1 = [number*3 for number in item if number*3 <= n]
|
| 152 |
+
temp_2 = [number*3 - 1 for number in item if number*3 - 1 <= n]
|
| 153 |
+
new_item = temp_1 + temp_2
|
| 154 |
+
new_list.append(new_item)
|
| 155 |
+
|
| 156 |
+
last_list = [3*k + 1 for k in range(len(current_list)+1) if 3*k + 1 <= n]
|
| 157 |
+
new_list.append(last_list)
|
| 158 |
+
current_list = new_list
|
| 159 |
+
|
| 160 |
+
return current_list
|
lib/python3.12/site-packages/sympy/combinatorics/subsets.py
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import combinations
|
| 2 |
+
|
| 3 |
+
from sympy.combinatorics.graycode import GrayCode
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Subset():
|
| 7 |
+
"""
|
| 8 |
+
Represents a basic subset object.
|
| 9 |
+
|
| 10 |
+
Explanation
|
| 11 |
+
===========
|
| 12 |
+
|
| 13 |
+
We generate subsets using essentially two techniques,
|
| 14 |
+
binary enumeration and lexicographic enumeration.
|
| 15 |
+
The Subset class takes two arguments, the first one
|
| 16 |
+
describes the initial subset to consider and the second
|
| 17 |
+
describes the superset.
|
| 18 |
+
|
| 19 |
+
Examples
|
| 20 |
+
========
|
| 21 |
+
|
| 22 |
+
>>> from sympy.combinatorics import Subset
|
| 23 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 24 |
+
>>> a.next_binary().subset
|
| 25 |
+
['b']
|
| 26 |
+
>>> a.prev_binary().subset
|
| 27 |
+
['c']
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
_rank_binary = None
|
| 31 |
+
_rank_lex = None
|
| 32 |
+
_rank_graycode = None
|
| 33 |
+
_subset = None
|
| 34 |
+
_superset = None
|
| 35 |
+
|
| 36 |
+
def __new__(cls, subset, superset):
|
| 37 |
+
"""
|
| 38 |
+
Default constructor.
|
| 39 |
+
|
| 40 |
+
It takes the ``subset`` and its ``superset`` as its parameters.
|
| 41 |
+
|
| 42 |
+
Examples
|
| 43 |
+
========
|
| 44 |
+
|
| 45 |
+
>>> from sympy.combinatorics import Subset
|
| 46 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 47 |
+
>>> a.subset
|
| 48 |
+
['c', 'd']
|
| 49 |
+
>>> a.superset
|
| 50 |
+
['a', 'b', 'c', 'd']
|
| 51 |
+
>>> a.size
|
| 52 |
+
2
|
| 53 |
+
"""
|
| 54 |
+
if len(subset) > len(superset):
|
| 55 |
+
raise ValueError('Invalid arguments have been provided. The '
|
| 56 |
+
'superset must be larger than the subset.')
|
| 57 |
+
for elem in subset:
|
| 58 |
+
if elem not in superset:
|
| 59 |
+
raise ValueError('The superset provided is invalid as it does '
|
| 60 |
+
'not contain the element {}'.format(elem))
|
| 61 |
+
obj = object.__new__(cls)
|
| 62 |
+
obj._subset = subset
|
| 63 |
+
obj._superset = superset
|
| 64 |
+
return obj
|
| 65 |
+
|
| 66 |
+
def __eq__(self, other):
|
| 67 |
+
"""Return a boolean indicating whether a == b on the basis of
|
| 68 |
+
whether both objects are of the class Subset and if the values
|
| 69 |
+
of the subset and superset attributes are the same.
|
| 70 |
+
"""
|
| 71 |
+
if not isinstance(other, Subset):
|
| 72 |
+
return NotImplemented
|
| 73 |
+
return self.subset == other.subset and self.superset == other.superset
|
| 74 |
+
|
| 75 |
+
def iterate_binary(self, k):
|
| 76 |
+
"""
|
| 77 |
+
This is a helper function. It iterates over the
|
| 78 |
+
binary subsets by ``k`` steps. This variable can be
|
| 79 |
+
both positive or negative.
|
| 80 |
+
|
| 81 |
+
Examples
|
| 82 |
+
========
|
| 83 |
+
|
| 84 |
+
>>> from sympy.combinatorics import Subset
|
| 85 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 86 |
+
>>> a.iterate_binary(-2).subset
|
| 87 |
+
['d']
|
| 88 |
+
>>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd'])
|
| 89 |
+
>>> a.iterate_binary(2).subset
|
| 90 |
+
[]
|
| 91 |
+
|
| 92 |
+
See Also
|
| 93 |
+
========
|
| 94 |
+
|
| 95 |
+
next_binary, prev_binary
|
| 96 |
+
"""
|
| 97 |
+
bin_list = Subset.bitlist_from_subset(self.subset, self.superset)
|
| 98 |
+
n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size
|
| 99 |
+
bits = bin(n)[2:].rjust(self.superset_size, '0')
|
| 100 |
+
return Subset.subset_from_bitlist(self.superset, bits)
|
| 101 |
+
|
| 102 |
+
def next_binary(self):
|
| 103 |
+
"""
|
| 104 |
+
Generates the next binary ordered subset.
|
| 105 |
+
|
| 106 |
+
Examples
|
| 107 |
+
========
|
| 108 |
+
|
| 109 |
+
>>> from sympy.combinatorics import Subset
|
| 110 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 111 |
+
>>> a.next_binary().subset
|
| 112 |
+
['b']
|
| 113 |
+
>>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd'])
|
| 114 |
+
>>> a.next_binary().subset
|
| 115 |
+
[]
|
| 116 |
+
|
| 117 |
+
See Also
|
| 118 |
+
========
|
| 119 |
+
|
| 120 |
+
prev_binary, iterate_binary
|
| 121 |
+
"""
|
| 122 |
+
return self.iterate_binary(1)
|
| 123 |
+
|
| 124 |
+
def prev_binary(self):
|
| 125 |
+
"""
|
| 126 |
+
Generates the previous binary ordered subset.
|
| 127 |
+
|
| 128 |
+
Examples
|
| 129 |
+
========
|
| 130 |
+
|
| 131 |
+
>>> from sympy.combinatorics import Subset
|
| 132 |
+
>>> a = Subset([], ['a', 'b', 'c', 'd'])
|
| 133 |
+
>>> a.prev_binary().subset
|
| 134 |
+
['a', 'b', 'c', 'd']
|
| 135 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 136 |
+
>>> a.prev_binary().subset
|
| 137 |
+
['c']
|
| 138 |
+
|
| 139 |
+
See Also
|
| 140 |
+
========
|
| 141 |
+
|
| 142 |
+
next_binary, iterate_binary
|
| 143 |
+
"""
|
| 144 |
+
return self.iterate_binary(-1)
|
| 145 |
+
|
| 146 |
+
def next_lexicographic(self):
|
| 147 |
+
"""
|
| 148 |
+
Generates the next lexicographically ordered subset.
|
| 149 |
+
|
| 150 |
+
Examples
|
| 151 |
+
========
|
| 152 |
+
|
| 153 |
+
>>> from sympy.combinatorics import Subset
|
| 154 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 155 |
+
>>> a.next_lexicographic().subset
|
| 156 |
+
['d']
|
| 157 |
+
>>> a = Subset(['d'], ['a', 'b', 'c', 'd'])
|
| 158 |
+
>>> a.next_lexicographic().subset
|
| 159 |
+
[]
|
| 160 |
+
|
| 161 |
+
See Also
|
| 162 |
+
========
|
| 163 |
+
|
| 164 |
+
prev_lexicographic
|
| 165 |
+
"""
|
| 166 |
+
i = self.superset_size - 1
|
| 167 |
+
indices = Subset.subset_indices(self.subset, self.superset)
|
| 168 |
+
|
| 169 |
+
if i in indices:
|
| 170 |
+
if i - 1 in indices:
|
| 171 |
+
indices.remove(i - 1)
|
| 172 |
+
else:
|
| 173 |
+
indices.remove(i)
|
| 174 |
+
i = i - 1
|
| 175 |
+
while i >= 0 and i not in indices:
|
| 176 |
+
i = i - 1
|
| 177 |
+
if i >= 0:
|
| 178 |
+
indices.remove(i)
|
| 179 |
+
indices.append(i+1)
|
| 180 |
+
else:
|
| 181 |
+
while i not in indices and i >= 0:
|
| 182 |
+
i = i - 1
|
| 183 |
+
indices.append(i + 1)
|
| 184 |
+
|
| 185 |
+
ret_set = []
|
| 186 |
+
super_set = self.superset
|
| 187 |
+
for i in indices:
|
| 188 |
+
ret_set.append(super_set[i])
|
| 189 |
+
return Subset(ret_set, super_set)
|
| 190 |
+
|
| 191 |
+
def prev_lexicographic(self):
|
| 192 |
+
"""
|
| 193 |
+
Generates the previous lexicographically ordered subset.
|
| 194 |
+
|
| 195 |
+
Examples
|
| 196 |
+
========
|
| 197 |
+
|
| 198 |
+
>>> from sympy.combinatorics import Subset
|
| 199 |
+
>>> a = Subset([], ['a', 'b', 'c', 'd'])
|
| 200 |
+
>>> a.prev_lexicographic().subset
|
| 201 |
+
['d']
|
| 202 |
+
>>> a = Subset(['c','d'], ['a', 'b', 'c', 'd'])
|
| 203 |
+
>>> a.prev_lexicographic().subset
|
| 204 |
+
['c']
|
| 205 |
+
|
| 206 |
+
See Also
|
| 207 |
+
========
|
| 208 |
+
|
| 209 |
+
next_lexicographic
|
| 210 |
+
"""
|
| 211 |
+
i = self.superset_size - 1
|
| 212 |
+
indices = Subset.subset_indices(self.subset, self.superset)
|
| 213 |
+
|
| 214 |
+
while i >= 0 and i not in indices:
|
| 215 |
+
i = i - 1
|
| 216 |
+
|
| 217 |
+
if i == 0 or i - 1 in indices:
|
| 218 |
+
indices.remove(i)
|
| 219 |
+
else:
|
| 220 |
+
if i >= 0:
|
| 221 |
+
indices.remove(i)
|
| 222 |
+
indices.append(i - 1)
|
| 223 |
+
indices.append(self.superset_size - 1)
|
| 224 |
+
|
| 225 |
+
ret_set = []
|
| 226 |
+
super_set = self.superset
|
| 227 |
+
for i in indices:
|
| 228 |
+
ret_set.append(super_set[i])
|
| 229 |
+
return Subset(ret_set, super_set)
|
| 230 |
+
|
| 231 |
+
def iterate_graycode(self, k):
|
| 232 |
+
"""
|
| 233 |
+
Helper function used for prev_gray and next_gray.
|
| 234 |
+
It performs ``k`` step overs to get the respective Gray codes.
|
| 235 |
+
|
| 236 |
+
Examples
|
| 237 |
+
========
|
| 238 |
+
|
| 239 |
+
>>> from sympy.combinatorics import Subset
|
| 240 |
+
>>> a = Subset([1, 2, 3], [1, 2, 3, 4])
|
| 241 |
+
>>> a.iterate_graycode(3).subset
|
| 242 |
+
[1, 4]
|
| 243 |
+
>>> a.iterate_graycode(-2).subset
|
| 244 |
+
[1, 2, 4]
|
| 245 |
+
|
| 246 |
+
See Also
|
| 247 |
+
========
|
| 248 |
+
|
| 249 |
+
next_gray, prev_gray
|
| 250 |
+
"""
|
| 251 |
+
unranked_code = GrayCode.unrank(self.superset_size,
|
| 252 |
+
(self.rank_gray + k) % self.cardinality)
|
| 253 |
+
return Subset.subset_from_bitlist(self.superset,
|
| 254 |
+
unranked_code)
|
| 255 |
+
|
| 256 |
+
def next_gray(self):
|
| 257 |
+
"""
|
| 258 |
+
Generates the next Gray code ordered subset.
|
| 259 |
+
|
| 260 |
+
Examples
|
| 261 |
+
========
|
| 262 |
+
|
| 263 |
+
>>> from sympy.combinatorics import Subset
|
| 264 |
+
>>> a = Subset([1, 2, 3], [1, 2, 3, 4])
|
| 265 |
+
>>> a.next_gray().subset
|
| 266 |
+
[1, 3]
|
| 267 |
+
|
| 268 |
+
See Also
|
| 269 |
+
========
|
| 270 |
+
|
| 271 |
+
iterate_graycode, prev_gray
|
| 272 |
+
"""
|
| 273 |
+
return self.iterate_graycode(1)
|
| 274 |
+
|
| 275 |
+
def prev_gray(self):
|
| 276 |
+
"""
|
| 277 |
+
Generates the previous Gray code ordered subset.
|
| 278 |
+
|
| 279 |
+
Examples
|
| 280 |
+
========
|
| 281 |
+
|
| 282 |
+
>>> from sympy.combinatorics import Subset
|
| 283 |
+
>>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5])
|
| 284 |
+
>>> a.prev_gray().subset
|
| 285 |
+
[2, 3, 4, 5]
|
| 286 |
+
|
| 287 |
+
See Also
|
| 288 |
+
========
|
| 289 |
+
|
| 290 |
+
iterate_graycode, next_gray
|
| 291 |
+
"""
|
| 292 |
+
return self.iterate_graycode(-1)
|
| 293 |
+
|
| 294 |
+
@property
|
| 295 |
+
def rank_binary(self):
|
| 296 |
+
"""
|
| 297 |
+
Computes the binary ordered rank.
|
| 298 |
+
|
| 299 |
+
Examples
|
| 300 |
+
========
|
| 301 |
+
|
| 302 |
+
>>> from sympy.combinatorics import Subset
|
| 303 |
+
>>> a = Subset([], ['a','b','c','d'])
|
| 304 |
+
>>> a.rank_binary
|
| 305 |
+
0
|
| 306 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 307 |
+
>>> a.rank_binary
|
| 308 |
+
3
|
| 309 |
+
|
| 310 |
+
See Also
|
| 311 |
+
========
|
| 312 |
+
|
| 313 |
+
iterate_binary, unrank_binary
|
| 314 |
+
"""
|
| 315 |
+
if self._rank_binary is None:
|
| 316 |
+
self._rank_binary = int("".join(
|
| 317 |
+
Subset.bitlist_from_subset(self.subset,
|
| 318 |
+
self.superset)), 2)
|
| 319 |
+
return self._rank_binary
|
| 320 |
+
|
| 321 |
+
@property
|
| 322 |
+
def rank_lexicographic(self):
|
| 323 |
+
"""
|
| 324 |
+
Computes the lexicographic ranking of the subset.
|
| 325 |
+
|
| 326 |
+
Examples
|
| 327 |
+
========
|
| 328 |
+
|
| 329 |
+
>>> from sympy.combinatorics import Subset
|
| 330 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 331 |
+
>>> a.rank_lexicographic
|
| 332 |
+
14
|
| 333 |
+
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
|
| 334 |
+
>>> a.rank_lexicographic
|
| 335 |
+
43
|
| 336 |
+
"""
|
| 337 |
+
if self._rank_lex is None:
|
| 338 |
+
def _ranklex(self, subset_index, i, n):
|
| 339 |
+
if subset_index == [] or i > n:
|
| 340 |
+
return 0
|
| 341 |
+
if i in subset_index:
|
| 342 |
+
subset_index.remove(i)
|
| 343 |
+
return 1 + _ranklex(self, subset_index, i + 1, n)
|
| 344 |
+
return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n)
|
| 345 |
+
indices = Subset.subset_indices(self.subset, self.superset)
|
| 346 |
+
self._rank_lex = _ranklex(self, indices, 0, self.superset_size)
|
| 347 |
+
return self._rank_lex
|
| 348 |
+
|
| 349 |
+
@property
|
| 350 |
+
def rank_gray(self):
|
| 351 |
+
"""
|
| 352 |
+
Computes the Gray code ranking of the subset.
|
| 353 |
+
|
| 354 |
+
Examples
|
| 355 |
+
========
|
| 356 |
+
|
| 357 |
+
>>> from sympy.combinatorics import Subset
|
| 358 |
+
>>> a = Subset(['c','d'], ['a','b','c','d'])
|
| 359 |
+
>>> a.rank_gray
|
| 360 |
+
2
|
| 361 |
+
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
|
| 362 |
+
>>> a.rank_gray
|
| 363 |
+
27
|
| 364 |
+
|
| 365 |
+
See Also
|
| 366 |
+
========
|
| 367 |
+
|
| 368 |
+
iterate_graycode, unrank_gray
|
| 369 |
+
"""
|
| 370 |
+
if self._rank_graycode is None:
|
| 371 |
+
bits = Subset.bitlist_from_subset(self.subset, self.superset)
|
| 372 |
+
self._rank_graycode = GrayCode(len(bits), start=bits).rank
|
| 373 |
+
return self._rank_graycode
|
| 374 |
+
|
| 375 |
+
@property
|
| 376 |
+
def subset(self):
|
| 377 |
+
"""
|
| 378 |
+
Gets the subset represented by the current instance.
|
| 379 |
+
|
| 380 |
+
Examples
|
| 381 |
+
========
|
| 382 |
+
|
| 383 |
+
>>> from sympy.combinatorics import Subset
|
| 384 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 385 |
+
>>> a.subset
|
| 386 |
+
['c', 'd']
|
| 387 |
+
|
| 388 |
+
See Also
|
| 389 |
+
========
|
| 390 |
+
|
| 391 |
+
superset, size, superset_size, cardinality
|
| 392 |
+
"""
|
| 393 |
+
return self._subset
|
| 394 |
+
|
| 395 |
+
@property
|
| 396 |
+
def size(self):
|
| 397 |
+
"""
|
| 398 |
+
Gets the size of the subset.
|
| 399 |
+
|
| 400 |
+
Examples
|
| 401 |
+
========
|
| 402 |
+
|
| 403 |
+
>>> from sympy.combinatorics import Subset
|
| 404 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 405 |
+
>>> a.size
|
| 406 |
+
2
|
| 407 |
+
|
| 408 |
+
See Also
|
| 409 |
+
========
|
| 410 |
+
|
| 411 |
+
subset, superset, superset_size, cardinality
|
| 412 |
+
"""
|
| 413 |
+
return len(self.subset)
|
| 414 |
+
|
| 415 |
+
@property
|
| 416 |
+
def superset(self):
|
| 417 |
+
"""
|
| 418 |
+
Gets the superset of the subset.
|
| 419 |
+
|
| 420 |
+
Examples
|
| 421 |
+
========
|
| 422 |
+
|
| 423 |
+
>>> from sympy.combinatorics import Subset
|
| 424 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 425 |
+
>>> a.superset
|
| 426 |
+
['a', 'b', 'c', 'd']
|
| 427 |
+
|
| 428 |
+
See Also
|
| 429 |
+
========
|
| 430 |
+
|
| 431 |
+
subset, size, superset_size, cardinality
|
| 432 |
+
"""
|
| 433 |
+
return self._superset
|
| 434 |
+
|
| 435 |
+
@property
|
| 436 |
+
def superset_size(self):
|
| 437 |
+
"""
|
| 438 |
+
Returns the size of the superset.
|
| 439 |
+
|
| 440 |
+
Examples
|
| 441 |
+
========
|
| 442 |
+
|
| 443 |
+
>>> from sympy.combinatorics import Subset
|
| 444 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 445 |
+
>>> a.superset_size
|
| 446 |
+
4
|
| 447 |
+
|
| 448 |
+
See Also
|
| 449 |
+
========
|
| 450 |
+
|
| 451 |
+
subset, superset, size, cardinality
|
| 452 |
+
"""
|
| 453 |
+
return len(self.superset)
|
| 454 |
+
|
| 455 |
+
@property
|
| 456 |
+
def cardinality(self):
|
| 457 |
+
"""
|
| 458 |
+
Returns the number of all possible subsets.
|
| 459 |
+
|
| 460 |
+
Examples
|
| 461 |
+
========
|
| 462 |
+
|
| 463 |
+
>>> from sympy.combinatorics import Subset
|
| 464 |
+
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 465 |
+
>>> a.cardinality
|
| 466 |
+
16
|
| 467 |
+
|
| 468 |
+
See Also
|
| 469 |
+
========
|
| 470 |
+
|
| 471 |
+
subset, superset, size, superset_size
|
| 472 |
+
"""
|
| 473 |
+
return 2**(self.superset_size)
|
| 474 |
+
|
| 475 |
+
@classmethod
|
| 476 |
+
def subset_from_bitlist(self, super_set, bitlist):
|
| 477 |
+
"""
|
| 478 |
+
Gets the subset defined by the bitlist.
|
| 479 |
+
|
| 480 |
+
Examples
|
| 481 |
+
========
|
| 482 |
+
|
| 483 |
+
>>> from sympy.combinatorics import Subset
|
| 484 |
+
>>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset
|
| 485 |
+
['c', 'd']
|
| 486 |
+
|
| 487 |
+
See Also
|
| 488 |
+
========
|
| 489 |
+
|
| 490 |
+
bitlist_from_subset
|
| 491 |
+
"""
|
| 492 |
+
if len(super_set) != len(bitlist):
|
| 493 |
+
raise ValueError("The sizes of the lists are not equal")
|
| 494 |
+
ret_set = []
|
| 495 |
+
for i in range(len(bitlist)):
|
| 496 |
+
if bitlist[i] == '1':
|
| 497 |
+
ret_set.append(super_set[i])
|
| 498 |
+
return Subset(ret_set, super_set)
|
| 499 |
+
|
| 500 |
+
@classmethod
|
| 501 |
+
def bitlist_from_subset(self, subset, superset):
|
| 502 |
+
"""
|
| 503 |
+
Gets the bitlist corresponding to a subset.
|
| 504 |
+
|
| 505 |
+
Examples
|
| 506 |
+
========
|
| 507 |
+
|
| 508 |
+
>>> from sympy.combinatorics import Subset
|
| 509 |
+
>>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
| 510 |
+
'0011'
|
| 511 |
+
|
| 512 |
+
See Also
|
| 513 |
+
========
|
| 514 |
+
|
| 515 |
+
subset_from_bitlist
|
| 516 |
+
"""
|
| 517 |
+
bitlist = ['0'] * len(superset)
|
| 518 |
+
if isinstance(subset, Subset):
|
| 519 |
+
subset = subset.subset
|
| 520 |
+
for i in Subset.subset_indices(subset, superset):
|
| 521 |
+
bitlist[i] = '1'
|
| 522 |
+
return ''.join(bitlist)
|
| 523 |
+
|
| 524 |
+
@classmethod
|
| 525 |
+
def unrank_binary(self, rank, superset):
|
| 526 |
+
"""
|
| 527 |
+
Gets the binary ordered subset of the specified rank.
|
| 528 |
+
|
| 529 |
+
Examples
|
| 530 |
+
========
|
| 531 |
+
|
| 532 |
+
>>> from sympy.combinatorics import Subset
|
| 533 |
+
>>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset
|
| 534 |
+
['b']
|
| 535 |
+
|
| 536 |
+
See Also
|
| 537 |
+
========
|
| 538 |
+
|
| 539 |
+
iterate_binary, rank_binary
|
| 540 |
+
"""
|
| 541 |
+
bits = bin(rank)[2:].rjust(len(superset), '0')
|
| 542 |
+
return Subset.subset_from_bitlist(superset, bits)
|
| 543 |
+
|
| 544 |
+
@classmethod
|
| 545 |
+
def unrank_gray(self, rank, superset):
|
| 546 |
+
"""
|
| 547 |
+
Gets the Gray code ordered subset of the specified rank.
|
| 548 |
+
|
| 549 |
+
Examples
|
| 550 |
+
========
|
| 551 |
+
|
| 552 |
+
>>> from sympy.combinatorics import Subset
|
| 553 |
+
>>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset
|
| 554 |
+
['a', 'b']
|
| 555 |
+
>>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset
|
| 556 |
+
[]
|
| 557 |
+
|
| 558 |
+
See Also
|
| 559 |
+
========
|
| 560 |
+
|
| 561 |
+
iterate_graycode, rank_gray
|
| 562 |
+
"""
|
| 563 |
+
graycode_bitlist = GrayCode.unrank(len(superset), rank)
|
| 564 |
+
return Subset.subset_from_bitlist(superset, graycode_bitlist)
|
| 565 |
+
|
| 566 |
+
@classmethod
|
| 567 |
+
def subset_indices(self, subset, superset):
|
| 568 |
+
"""Return indices of subset in superset in a list; the list is empty
|
| 569 |
+
if all elements of ``subset`` are not in ``superset``.
|
| 570 |
+
|
| 571 |
+
Examples
|
| 572 |
+
========
|
| 573 |
+
|
| 574 |
+
>>> from sympy.combinatorics import Subset
|
| 575 |
+
>>> superset = [1, 3, 2, 5, 4]
|
| 576 |
+
>>> Subset.subset_indices([3, 2, 1], superset)
|
| 577 |
+
[1, 2, 0]
|
| 578 |
+
>>> Subset.subset_indices([1, 6], superset)
|
| 579 |
+
[]
|
| 580 |
+
>>> Subset.subset_indices([], superset)
|
| 581 |
+
[]
|
| 582 |
+
|
| 583 |
+
"""
|
| 584 |
+
a, b = superset, subset
|
| 585 |
+
sb = set(b)
|
| 586 |
+
d = {}
|
| 587 |
+
for i, ai in enumerate(a):
|
| 588 |
+
if ai in sb:
|
| 589 |
+
d[ai] = i
|
| 590 |
+
sb.remove(ai)
|
| 591 |
+
if not sb:
|
| 592 |
+
break
|
| 593 |
+
else:
|
| 594 |
+
return []
|
| 595 |
+
return [d[bi] for bi in b]
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def ksubsets(superset, k):
|
| 599 |
+
"""
|
| 600 |
+
Finds the subsets of size ``k`` in lexicographic order.
|
| 601 |
+
|
| 602 |
+
This uses the itertools generator.
|
| 603 |
+
|
| 604 |
+
Examples
|
| 605 |
+
========
|
| 606 |
+
|
| 607 |
+
>>> from sympy.combinatorics.subsets import ksubsets
|
| 608 |
+
>>> list(ksubsets([1, 2, 3], 2))
|
| 609 |
+
[(1, 2), (1, 3), (2, 3)]
|
| 610 |
+
>>> list(ksubsets([1, 2, 3, 4, 5], 2))
|
| 611 |
+
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \
|
| 612 |
+
(2, 5), (3, 4), (3, 5), (4, 5)]
|
| 613 |
+
|
| 614 |
+
See Also
|
| 615 |
+
========
|
| 616 |
+
|
| 617 |
+
Subset
|
| 618 |
+
"""
|
| 619 |
+
return combinations(superset, k)
|
lib/python3.12/site-packages/sympy/combinatorics/tensor_can.py
ADDED
|
@@ -0,0 +1,1190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.permutations import Permutation, _af_rmul, \
|
| 2 |
+
_af_invert, _af_new
|
| 3 |
+
from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \
|
| 4 |
+
_orbit_transversal
|
| 5 |
+
from sympy.combinatorics.util import _distribute_gens_by_base, \
|
| 6 |
+
_orbits_transversals_from_bsgs
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
References for tensor canonicalization:
|
| 10 |
+
|
| 11 |
+
[1] R. Portugal "Algorithmic simplification of tensor expressions",
|
| 12 |
+
J. Phys. A 32 (1999) 7779-7789
|
| 13 |
+
|
| 14 |
+
[2] R. Portugal, B.F. Svaiter "Group-theoretic Approach for Symbolic
|
| 15 |
+
Tensor Manipulation: I. Free Indices"
|
| 16 |
+
arXiv:math-ph/0107031v1
|
| 17 |
+
|
| 18 |
+
[3] L.R.U. Manssur, R. Portugal "Group-theoretic Approach for Symbolic
|
| 19 |
+
Tensor Manipulation: II. Dummy Indices"
|
| 20 |
+
arXiv:math-ph/0107032v1
|
| 21 |
+
|
| 22 |
+
[4] xperm.c part of XPerm written by J. M. Martin-Garcia
|
| 23 |
+
http://www.xact.es/index.html
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def dummy_sgs(dummies, sym, n):
|
| 28 |
+
"""
|
| 29 |
+
Return the strong generators for dummy indices.
|
| 30 |
+
|
| 31 |
+
Parameters
|
| 32 |
+
==========
|
| 33 |
+
|
| 34 |
+
dummies : List of dummy indices.
|
| 35 |
+
`dummies[2k], dummies[2k+1]` are paired indices.
|
| 36 |
+
In base form, the dummy indices are always in
|
| 37 |
+
consecutive positions.
|
| 38 |
+
sym : symmetry under interchange of contracted dummies::
|
| 39 |
+
* None no symmetry
|
| 40 |
+
* 0 commuting
|
| 41 |
+
* 1 anticommuting
|
| 42 |
+
|
| 43 |
+
n : number of indices
|
| 44 |
+
|
| 45 |
+
Examples
|
| 46 |
+
========
|
| 47 |
+
|
| 48 |
+
>>> from sympy.combinatorics.tensor_can import dummy_sgs
|
| 49 |
+
>>> dummy_sgs(list(range(2, 8)), 0, 8)
|
| 50 |
+
[[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9],
|
| 51 |
+
[0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9],
|
| 52 |
+
[0, 1, 2, 3, 6, 7, 4, 5, 8, 9]]
|
| 53 |
+
"""
|
| 54 |
+
if len(dummies) > n:
|
| 55 |
+
raise ValueError("List too large")
|
| 56 |
+
res = []
|
| 57 |
+
# exchange of contravariant and covariant indices
|
| 58 |
+
if sym is not None:
|
| 59 |
+
for j in dummies[::2]:
|
| 60 |
+
a = list(range(n + 2))
|
| 61 |
+
if sym == 1:
|
| 62 |
+
a[n] = n + 1
|
| 63 |
+
a[n + 1] = n
|
| 64 |
+
a[j], a[j + 1] = a[j + 1], a[j]
|
| 65 |
+
res.append(a)
|
| 66 |
+
# rename dummy indices
|
| 67 |
+
for j in dummies[:-3:2]:
|
| 68 |
+
a = list(range(n + 2))
|
| 69 |
+
a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1]
|
| 70 |
+
res.append(a)
|
| 71 |
+
return res
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _min_dummies(dummies, sym, indices):
|
| 75 |
+
"""
|
| 76 |
+
Return list of minima of the orbits of indices in group of dummies.
|
| 77 |
+
See ``double_coset_can_rep`` for the description of ``dummies`` and ``sym``.
|
| 78 |
+
``indices`` is the initial list of dummy indices.
|
| 79 |
+
|
| 80 |
+
Examples
|
| 81 |
+
========
|
| 82 |
+
|
| 83 |
+
>>> from sympy.combinatorics.tensor_can import _min_dummies
|
| 84 |
+
>>> _min_dummies([list(range(2, 8))], [0], list(range(10)))
|
| 85 |
+
[0, 1, 2, 2, 2, 2, 2, 2, 8, 9]
|
| 86 |
+
"""
|
| 87 |
+
num_types = len(sym)
|
| 88 |
+
m = [min(dx) if dx else None for dx in dummies]
|
| 89 |
+
res = indices[:]
|
| 90 |
+
for i in range(num_types):
|
| 91 |
+
for c, i in enumerate(indices):
|
| 92 |
+
for j in range(num_types):
|
| 93 |
+
if i in dummies[j]:
|
| 94 |
+
res[c] = m[j]
|
| 95 |
+
break
|
| 96 |
+
return res
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _trace_S(s, j, b, S_cosets):
|
| 100 |
+
"""
|
| 101 |
+
Return the representative h satisfying s[h[b]] == j
|
| 102 |
+
|
| 103 |
+
If there is not such a representative return None
|
| 104 |
+
"""
|
| 105 |
+
for h in S_cosets[b]:
|
| 106 |
+
if s[h[b]] == j:
|
| 107 |
+
return h
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _trace_D(gj, p_i, Dxtrav):
|
| 112 |
+
"""
|
| 113 |
+
Return the representative h satisfying h[gj] == p_i
|
| 114 |
+
|
| 115 |
+
If there is not such a representative return None
|
| 116 |
+
"""
|
| 117 |
+
for h in Dxtrav:
|
| 118 |
+
if h[gj] == p_i:
|
| 119 |
+
return h
|
| 120 |
+
return None
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _dumx_remove(dumx, dumx_flat, p0):
|
| 124 |
+
"""
|
| 125 |
+
remove p0 from dumx
|
| 126 |
+
"""
|
| 127 |
+
res = []
|
| 128 |
+
for dx in dumx:
|
| 129 |
+
if p0 not in dx:
|
| 130 |
+
res.append(dx)
|
| 131 |
+
continue
|
| 132 |
+
k = dx.index(p0)
|
| 133 |
+
if k % 2 == 0:
|
| 134 |
+
p0_paired = dx[k + 1]
|
| 135 |
+
else:
|
| 136 |
+
p0_paired = dx[k - 1]
|
| 137 |
+
dx.remove(p0)
|
| 138 |
+
dx.remove(p0_paired)
|
| 139 |
+
dumx_flat.remove(p0)
|
| 140 |
+
dumx_flat.remove(p0_paired)
|
| 141 |
+
res.append(dx)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def transversal2coset(size, base, transversal):
|
| 145 |
+
a = []
|
| 146 |
+
j = 0
|
| 147 |
+
for i in range(size):
|
| 148 |
+
if i in base:
|
| 149 |
+
a.append(sorted(transversal[j].values()))
|
| 150 |
+
j += 1
|
| 151 |
+
else:
|
| 152 |
+
a.append([list(range(size))])
|
| 153 |
+
j = len(a) - 1
|
| 154 |
+
while a[j] == [list(range(size))]:
|
| 155 |
+
j -= 1
|
| 156 |
+
return a[:j + 1]
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g):
|
| 160 |
+
r"""
|
| 161 |
+
Butler-Portugal algorithm for tensor canonicalization with dummy indices.
|
| 162 |
+
|
| 163 |
+
Parameters
|
| 164 |
+
==========
|
| 165 |
+
|
| 166 |
+
dummies
|
| 167 |
+
list of lists of dummy indices,
|
| 168 |
+
one list for each type of index;
|
| 169 |
+
the dummy indices are put in order contravariant, covariant
|
| 170 |
+
[d0, -d0, d1, -d1, ...].
|
| 171 |
+
|
| 172 |
+
sym
|
| 173 |
+
list of the symmetries of the index metric for each type.
|
| 174 |
+
|
| 175 |
+
possible symmetries of the metrics
|
| 176 |
+
* 0 symmetric
|
| 177 |
+
* 1 antisymmetric
|
| 178 |
+
* None no symmetry
|
| 179 |
+
|
| 180 |
+
b_S
|
| 181 |
+
base of a minimal slot symmetry BSGS.
|
| 182 |
+
|
| 183 |
+
sgens
|
| 184 |
+
generators of the slot symmetry BSGS.
|
| 185 |
+
|
| 186 |
+
S_transversals
|
| 187 |
+
transversals for the slot BSGS.
|
| 188 |
+
|
| 189 |
+
g
|
| 190 |
+
permutation representing the tensor.
|
| 191 |
+
|
| 192 |
+
Returns
|
| 193 |
+
=======
|
| 194 |
+
|
| 195 |
+
Return 0 if the tensor is zero, else return the array form of
|
| 196 |
+
the permutation representing the canonical form of the tensor.
|
| 197 |
+
|
| 198 |
+
Notes
|
| 199 |
+
=====
|
| 200 |
+
|
| 201 |
+
A tensor with dummy indices can be represented in a number
|
| 202 |
+
of equivalent ways which typically grows exponentially with
|
| 203 |
+
the number of indices. To be able to establish if two tensors
|
| 204 |
+
with many indices are equal becomes computationally very slow
|
| 205 |
+
in absence of an efficient algorithm.
|
| 206 |
+
|
| 207 |
+
The Butler-Portugal algorithm [3] is an efficient algorithm to
|
| 208 |
+
put tensors in canonical form, solving the above problem.
|
| 209 |
+
|
| 210 |
+
Portugal observed that a tensor can be represented by a permutation,
|
| 211 |
+
and that the class of tensors equivalent to it under slot and dummy
|
| 212 |
+
symmetries is equivalent to the double coset `D*g*S`
|
| 213 |
+
(Note: in this documentation we use the conventions for multiplication
|
| 214 |
+
of permutations p, q with (p*q)(i) = p[q[i]] which is opposite
|
| 215 |
+
to the one used in the Permutation class)
|
| 216 |
+
|
| 217 |
+
Using the algorithm by Butler to find a representative of the
|
| 218 |
+
double coset one can find a canonical form for the tensor.
|
| 219 |
+
|
| 220 |
+
To see this correspondence,
|
| 221 |
+
let `g` be a permutation in array form; a tensor with indices `ind`
|
| 222 |
+
(the indices including both the contravariant and the covariant ones)
|
| 223 |
+
can be written as
|
| 224 |
+
|
| 225 |
+
`t = T(ind[g[0]], \dots, ind[g[n-1]])`,
|
| 226 |
+
|
| 227 |
+
where `n = len(ind)`;
|
| 228 |
+
`g` has size `n + 2`, the last two indices for the sign of the tensor
|
| 229 |
+
(trick introduced in [4]).
|
| 230 |
+
|
| 231 |
+
A slot symmetry transformation `s` is a permutation acting on the slots
|
| 232 |
+
`t \rightarrow T(ind[(g*s)[0]], \dots, ind[(g*s)[n-1]])`
|
| 233 |
+
|
| 234 |
+
A dummy symmetry transformation acts on `ind`
|
| 235 |
+
`t \rightarrow T(ind[(d*g)[0]], \dots, ind[(d*g)[n-1]])`
|
| 236 |
+
|
| 237 |
+
Being interested only in the transformations of the tensor under
|
| 238 |
+
these symmetries, one can represent the tensor by `g`, which transforms
|
| 239 |
+
as
|
| 240 |
+
|
| 241 |
+
`g -> d*g*s`, so it belongs to the coset `D*g*S`, or in other words
|
| 242 |
+
to the set of all permutations allowed by the slot and dummy symmetries.
|
| 243 |
+
|
| 244 |
+
Let us explain the conventions by an example.
|
| 245 |
+
|
| 246 |
+
Given a tensor `T^{d3 d2 d1}{}_{d1 d2 d3}` with the slot symmetries
|
| 247 |
+
`T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
|
| 248 |
+
|
| 249 |
+
`T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
|
| 250 |
+
|
| 251 |
+
and symmetric metric, find the tensor equivalent to it which
|
| 252 |
+
is the lowest under the ordering of indices:
|
| 253 |
+
lexicographic ordering `d1, d2, d3` and then contravariant
|
| 254 |
+
before covariant index; that is the canonical form of the tensor.
|
| 255 |
+
|
| 256 |
+
The canonical form is `-T^{d1 d2 d3}{}_{d1 d2 d3}`
|
| 257 |
+
obtained using `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`.
|
| 258 |
+
|
| 259 |
+
To convert this problem in the input for this function,
|
| 260 |
+
use the following ordering of the index names
|
| 261 |
+
(- for covariant for short) `d1, -d1, d2, -d2, d3, -d3`
|
| 262 |
+
|
| 263 |
+
`T^{d3 d2 d1}{}_{d1 d2 d3}` corresponds to `g = [4, 2, 0, 1, 3, 5, 6, 7]`
|
| 264 |
+
where the last two indices are for the sign
|
| 265 |
+
|
| 266 |
+
`sgens = [Permutation(0, 2)(6, 7), Permutation(0, 4)(6, 7)]`
|
| 267 |
+
|
| 268 |
+
sgens[0] is the slot symmetry `-(0, 2)`
|
| 269 |
+
`T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
|
| 270 |
+
|
| 271 |
+
sgens[1] is the slot symmetry `-(0, 4)`
|
| 272 |
+
`T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
|
| 273 |
+
|
| 274 |
+
The dummy symmetry group D is generated by the strong base generators
|
| 275 |
+
`[(0, 1), (2, 3), (4, 5), (0, 2)(1, 3), (0, 4)(1, 5)]`
|
| 276 |
+
where the first three interchange covariant and contravariant
|
| 277 |
+
positions of the same index (d1 <-> -d1) and the last two interchange
|
| 278 |
+
the dummy indices themselves (d1 <-> d2).
|
| 279 |
+
|
| 280 |
+
The dummy symmetry acts from the left
|
| 281 |
+
`d = [1, 0, 2, 3, 4, 5, 6, 7]` exchange `d1 \leftrightarrow -d1`
|
| 282 |
+
`T^{d3 d2 d1}{}_{d1 d2 d3} == T^{d3 d2}{}_{d1}{}^{d1}{}_{d2 d3}`
|
| 283 |
+
|
| 284 |
+
`g=[4, 2, 0, 1, 3, 5, 6, 7] -> [4, 2, 1, 0, 3, 5, 6, 7] = _af_rmul(d, g)`
|
| 285 |
+
which differs from `_af_rmul(g, d)`.
|
| 286 |
+
|
| 287 |
+
The slot symmetry acts from the right
|
| 288 |
+
`s = [2, 1, 0, 3, 4, 5, 7, 6]` exchanges slots 0 and 2 and changes sign
|
| 289 |
+
`T^{d3 d2 d1}{}_{d1 d2 d3} == -T^{d1 d2 d3}{}_{d1 d2 d3}`
|
| 290 |
+
|
| 291 |
+
`g=[4,2,0,1,3,5,6,7] -> [0, 2, 4, 1, 3, 5, 7, 6] = _af_rmul(g, s)`
|
| 292 |
+
|
| 293 |
+
Example in which the tensor is zero, same slot symmetries as above:
|
| 294 |
+
`T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}`
|
| 295 |
+
|
| 296 |
+
`= -T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,4)`;
|
| 297 |
+
|
| 298 |
+
`= T_{d3 d1}{}^{d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,2)`;
|
| 299 |
+
|
| 300 |
+
`= T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` symmetric metric;
|
| 301 |
+
|
| 302 |
+
`= 0` since two of these lines have tensors differ only for the sign.
|
| 303 |
+
|
| 304 |
+
The double coset D*g*S consists of permutations `h = d*g*s` corresponding
|
| 305 |
+
to equivalent tensors; if there are two `h` which are the same apart
|
| 306 |
+
from the sign, return zero; otherwise
|
| 307 |
+
choose as representative the tensor with indices
|
| 308 |
+
ordered lexicographically according to `[d1, -d1, d2, -d2, d3, -d3]`
|
| 309 |
+
that is ``rep = min(D*g*S) = min([d*g*s for d in D for s in S])``
|
| 310 |
+
|
| 311 |
+
The indices are fixed one by one; first choose the lowest index
|
| 312 |
+
for slot 0, then the lowest remaining index for slot 1, etc.
|
| 313 |
+
Doing this one obtains a chain of stabilizers
|
| 314 |
+
|
| 315 |
+
`S \rightarrow S_{b0} \rightarrow S_{b0,b1} \rightarrow \dots` and
|
| 316 |
+
`D \rightarrow D_{p0} \rightarrow D_{p0,p1} \rightarrow \dots`
|
| 317 |
+
|
| 318 |
+
where ``[b0, b1, ...] = range(b)`` is a base of the symmetric group;
|
| 319 |
+
the strong base `b_S` of S is an ordered sublist of it;
|
| 320 |
+
therefore it is sufficient to compute once the
|
| 321 |
+
strong base generators of S using the Schreier-Sims algorithm;
|
| 322 |
+
the stabilizers of the strong base generators are the
|
| 323 |
+
strong base generators of the stabilizer subgroup.
|
| 324 |
+
|
| 325 |
+
``dbase = [p0, p1, ...]`` is not in general in lexicographic order,
|
| 326 |
+
so that one must recompute the strong base generators each time;
|
| 327 |
+
however this is trivial, there is no need to use the Schreier-Sims
|
| 328 |
+
algorithm for D.
|
| 329 |
+
|
| 330 |
+
The algorithm keeps a TAB of elements `(s_i, d_i, h_i)`
|
| 331 |
+
where `h_i = d_i \times g \times s_i` satisfying `h_i[j] = p_j` for `0 \le j < i`
|
| 332 |
+
starting from `s_0 = id, d_0 = id, h_0 = g`.
|
| 333 |
+
|
| 334 |
+
The equations `h_0[0] = p_0, h_1[1] = p_1, \dots` are solved in this order,
|
| 335 |
+
choosing each time the lowest possible value of p_i
|
| 336 |
+
|
| 337 |
+
For `j < i`
|
| 338 |
+
`d_i*g*s_i*S_{b_0, \dots, b_{i-1}}*b_j = D_{p_0, \dots, p_{i-1}}*p_j`
|
| 339 |
+
so that for dx in `D_{p_0,\dots,p_{i-1}}` and sx in
|
| 340 |
+
`S_{base[0], \dots, base[i-1]}` one has `dx*d_i*g*s_i*sx*b_j = p_j`
|
| 341 |
+
|
| 342 |
+
Search for dx, sx such that this equation holds for `j = i`;
|
| 343 |
+
it can be written as `s_i*sx*b_j = J, dx*d_i*g*J = p_j`
|
| 344 |
+
`sx*b_j = s_i**-1*J; sx = trace(s_i**-1, S_{b_0,...,b_{i-1}})`
|
| 345 |
+
`dx**-1*p_j = d_i*g*J; dx = trace(d_i*g*J, D_{p_0,...,p_{i-1}})`
|
| 346 |
+
|
| 347 |
+
`s_{i+1} = s_i*trace(s_i**-1*J, S_{b_0,...,b_{i-1}})`
|
| 348 |
+
`d_{i+1} = trace(d_i*g*J, D_{p_0,...,p_{i-1}})**-1*d_i`
|
| 349 |
+
`h_{i+1}*b_i = d_{i+1}*g*s_{i+1}*b_i = p_i`
|
| 350 |
+
|
| 351 |
+
`h_n*b_j = p_j` for all j, so that `h_n` is the solution.
|
| 352 |
+
|
| 353 |
+
Add the found `(s, d, h)` to TAB1.
|
| 354 |
+
|
| 355 |
+
At the end of the iteration sort TAB1 with respect to the `h`;
|
| 356 |
+
if there are two consecutive `h` in TAB1 which differ only for the
|
| 357 |
+
sign, the tensor is zero, so return 0;
|
| 358 |
+
if there are two consecutive `h` which are equal, keep only one.
|
| 359 |
+
|
| 360 |
+
Then stabilize the slot generators under `i` and the dummy generators
|
| 361 |
+
under `p_i`.
|
| 362 |
+
|
| 363 |
+
Assign `TAB = TAB1` at the end of the iteration step.
|
| 364 |
+
|
| 365 |
+
At the end `TAB` contains a unique `(s, d, h)`, since all the slots
|
| 366 |
+
of the tensor `h` have been fixed to have the minimum value according
|
| 367 |
+
to the symmetries. The algorithm returns `h`.
|
| 368 |
+
|
| 369 |
+
It is important that the slot BSGS has lexicographic minimal base,
|
| 370 |
+
otherwise there is an `i` which does not belong to the slot base
|
| 371 |
+
for which `p_i` is fixed by the dummy symmetry only, while `i`
|
| 372 |
+
is not invariant from the slot stabilizer, so `p_i` is not in
|
| 373 |
+
general the minimal value.
|
| 374 |
+
|
| 375 |
+
This algorithm differs slightly from the original algorithm [3]:
|
| 376 |
+
the canonical form is minimal lexicographically, and
|
| 377 |
+
the BSGS has minimal base under lexicographic order.
|
| 378 |
+
Equal tensors `h` are eliminated from TAB.
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
Examples
|
| 382 |
+
========
|
| 383 |
+
|
| 384 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 385 |
+
>>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals
|
| 386 |
+
>>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]]
|
| 387 |
+
>>> base = [0, 2]
|
| 388 |
+
>>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7])
|
| 389 |
+
>>> transversals = get_transversals(base, gens)
|
| 390 |
+
>>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
|
| 391 |
+
[0, 1, 2, 3, 4, 5, 7, 6]
|
| 392 |
+
|
| 393 |
+
>>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7])
|
| 394 |
+
>>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
|
| 395 |
+
0
|
| 396 |
+
"""
|
| 397 |
+
size = g.size
|
| 398 |
+
g = g.array_form
|
| 399 |
+
num_dummies = size - 2
|
| 400 |
+
indices = list(range(num_dummies))
|
| 401 |
+
all_metrics_with_sym = not any(_ is None for _ in sym)
|
| 402 |
+
num_types = len(sym)
|
| 403 |
+
dumx = dummies[:]
|
| 404 |
+
dumx_flat = []
|
| 405 |
+
for dx in dumx:
|
| 406 |
+
dumx_flat.extend(dx)
|
| 407 |
+
b_S = b_S[:]
|
| 408 |
+
sgensx = [h._array_form for h in sgens]
|
| 409 |
+
if b_S:
|
| 410 |
+
S_transversals = transversal2coset(size, b_S, S_transversals)
|
| 411 |
+
# strong generating set for D
|
| 412 |
+
dsgsx = []
|
| 413 |
+
for i in range(num_types):
|
| 414 |
+
dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
|
| 415 |
+
idn = list(range(size))
|
| 416 |
+
# TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s)
|
| 417 |
+
# for short, in the following d*g*s means _af_rmuln(d,g,s)
|
| 418 |
+
TAB = [(idn, idn, g)]
|
| 419 |
+
for i in range(size - 2):
|
| 420 |
+
b = i
|
| 421 |
+
testb = b in b_S and sgensx
|
| 422 |
+
if testb:
|
| 423 |
+
sgensx1 = [_af_new(_) for _ in sgensx]
|
| 424 |
+
deltab = _orbit(size, sgensx1, b)
|
| 425 |
+
else:
|
| 426 |
+
deltab = {b}
|
| 427 |
+
# p1 = min(IMAGES) = min(Union D_p*h*deltab for h in TAB)
|
| 428 |
+
if all_metrics_with_sym:
|
| 429 |
+
md = _min_dummies(dumx, sym, indices)
|
| 430 |
+
else:
|
| 431 |
+
md = [min(_orbit(size, [_af_new(
|
| 432 |
+
ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)]
|
| 433 |
+
|
| 434 |
+
p_i = min(min(md[h[x]] for x in deltab) for s, d, h in TAB)
|
| 435 |
+
dsgsx1 = [_af_new(_) for _ in dsgsx]
|
| 436 |
+
Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \
|
| 437 |
+
if dsgsx else None
|
| 438 |
+
if Dxtrav:
|
| 439 |
+
Dxtrav = [_af_invert(x) for x in Dxtrav]
|
| 440 |
+
# compute the orbit of p_i
|
| 441 |
+
for ii in range(num_types):
|
| 442 |
+
if p_i in dumx[ii]:
|
| 443 |
+
# the orbit is made by all the indices in dum[ii]
|
| 444 |
+
if sym[ii] is not None:
|
| 445 |
+
deltap = dumx[ii]
|
| 446 |
+
else:
|
| 447 |
+
# the orbit is made by all the even indices if p_i
|
| 448 |
+
# is even, by all the odd indices if p_i is odd
|
| 449 |
+
p_i_index = dumx[ii].index(p_i) % 2
|
| 450 |
+
deltap = dumx[ii][p_i_index::2]
|
| 451 |
+
break
|
| 452 |
+
else:
|
| 453 |
+
deltap = [p_i]
|
| 454 |
+
TAB1 = []
|
| 455 |
+
while TAB:
|
| 456 |
+
s, d, h = TAB.pop()
|
| 457 |
+
if min(md[h[x]] for x in deltab) != p_i:
|
| 458 |
+
continue
|
| 459 |
+
deltab1 = [x for x in deltab if md[h[x]] == p_i]
|
| 460 |
+
# NEXT = s*deltab1 intersection (d*g)**-1*deltap
|
| 461 |
+
dg = _af_rmul(d, g)
|
| 462 |
+
dginv = _af_invert(dg)
|
| 463 |
+
sdeltab = [s[x] for x in deltab1]
|
| 464 |
+
gdeltap = [dginv[x] for x in deltap]
|
| 465 |
+
NEXT = [x for x in sdeltab if x in gdeltap]
|
| 466 |
+
# d, s satisfy
|
| 467 |
+
# d*g*s*base[i-1] = p_{i-1}; using the stabilizers
|
| 468 |
+
# d*g*s*S_{base[0],...,base[i-1]}*base[i-1] =
|
| 469 |
+
# D_{p_0,...,p_{i-1}}*p_{i-1}
|
| 470 |
+
# so that to find d1, s1 satisfying d1*g*s1*b = p_i
|
| 471 |
+
# one can look for dx in D_{p_0,...,p_{i-1}} and
|
| 472 |
+
# sx in S_{base[0],...,base[i-1]}
|
| 473 |
+
# d1 = dx*d; s1 = s*sx
|
| 474 |
+
# d1*g*s1*b = dx*d*g*s*sx*b = p_i
|
| 475 |
+
for j in NEXT:
|
| 476 |
+
if testb:
|
| 477 |
+
# solve s1*b = j with s1 = s*sx for some element sx
|
| 478 |
+
# of the stabilizer of ..., base[i-1]
|
| 479 |
+
# sx*b = s**-1*j; sx = _trace_S(s, j,...)
|
| 480 |
+
# s1 = s*trace_S(s**-1*j,...)
|
| 481 |
+
s1 = _trace_S(s, j, b, S_transversals)
|
| 482 |
+
if not s1:
|
| 483 |
+
continue
|
| 484 |
+
else:
|
| 485 |
+
s1 = [s[ix] for ix in s1]
|
| 486 |
+
else:
|
| 487 |
+
s1 = s
|
| 488 |
+
# assert s1[b] == j # invariant
|
| 489 |
+
# solve d1*g*j = p_i with d1 = dx*d for some element dg
|
| 490 |
+
# of the stabilizer of ..., p_{i-1}
|
| 491 |
+
# dx**-1*p_i = d*g*j; dx**-1 = trace_D(d*g*j,...)
|
| 492 |
+
# d1 = trace_D(d*g*j,...)**-1*d
|
| 493 |
+
# to save an inversion in the inner loop; notice we did
|
| 494 |
+
# Dxtrav = [perm_af_invert(x) for x in Dxtrav] out of the loop
|
| 495 |
+
if Dxtrav:
|
| 496 |
+
d1 = _trace_D(dg[j], p_i, Dxtrav)
|
| 497 |
+
if not d1:
|
| 498 |
+
continue
|
| 499 |
+
else:
|
| 500 |
+
if p_i != dg[j]:
|
| 501 |
+
continue
|
| 502 |
+
d1 = idn
|
| 503 |
+
assert d1[dg[j]] == p_i # invariant
|
| 504 |
+
d1 = [d1[ix] for ix in d]
|
| 505 |
+
h1 = [d1[g[ix]] for ix in s1]
|
| 506 |
+
# assert h1[b] == p_i # invariant
|
| 507 |
+
TAB1.append((s1, d1, h1))
|
| 508 |
+
|
| 509 |
+
# if TAB contains equal permutations, keep only one of them;
|
| 510 |
+
# if TAB contains equal permutations up to the sign, return 0
|
| 511 |
+
TAB1.sort(key=lambda x: x[-1])
|
| 512 |
+
prev = [0] * size
|
| 513 |
+
while TAB1:
|
| 514 |
+
s, d, h = TAB1.pop()
|
| 515 |
+
if h[:-2] == prev[:-2]:
|
| 516 |
+
if h[-1] != prev[-1]:
|
| 517 |
+
return 0
|
| 518 |
+
else:
|
| 519 |
+
TAB.append((s, d, h))
|
| 520 |
+
prev = h
|
| 521 |
+
|
| 522 |
+
# stabilize the SGS
|
| 523 |
+
sgensx = [h for h in sgensx if h[b] == b]
|
| 524 |
+
if b in b_S:
|
| 525 |
+
b_S.remove(b)
|
| 526 |
+
_dumx_remove(dumx, dumx_flat, p_i)
|
| 527 |
+
dsgsx = []
|
| 528 |
+
for i in range(num_types):
|
| 529 |
+
dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
|
| 530 |
+
return TAB[0][-1]
|
| 531 |
+
|
| 532 |
+
|
| 533 |
+
def canonical_free(base, gens, g, num_free):
|
| 534 |
+
"""
|
| 535 |
+
Canonicalization of a tensor with respect to free indices
|
| 536 |
+
choosing the minimum with respect to lexicographical ordering
|
| 537 |
+
in the free indices.
|
| 538 |
+
|
| 539 |
+
Explanation
|
| 540 |
+
===========
|
| 541 |
+
|
| 542 |
+
``base``, ``gens`` BSGS for slot permutation group
|
| 543 |
+
``g`` permutation representing the tensor
|
| 544 |
+
``num_free`` number of free indices
|
| 545 |
+
The indices must be ordered with first the free indices
|
| 546 |
+
|
| 547 |
+
See explanation in double_coset_can_rep
|
| 548 |
+
The algorithm is a variation of the one given in [2].
|
| 549 |
+
|
| 550 |
+
Examples
|
| 551 |
+
========
|
| 552 |
+
|
| 553 |
+
>>> from sympy.combinatorics import Permutation
|
| 554 |
+
>>> from sympy.combinatorics.tensor_can import canonical_free
|
| 555 |
+
>>> gens = [[1, 0, 2, 3, 5, 4], [2, 3, 0, 1, 4, 5],[0, 1, 3, 2, 5, 4]]
|
| 556 |
+
>>> gens = [Permutation(h) for h in gens]
|
| 557 |
+
>>> base = [0, 2]
|
| 558 |
+
>>> g = Permutation([2, 1, 0, 3, 4, 5])
|
| 559 |
+
>>> canonical_free(base, gens, g, 4)
|
| 560 |
+
[0, 3, 1, 2, 5, 4]
|
| 561 |
+
|
| 562 |
+
Consider the product of Riemann tensors
|
| 563 |
+
``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}``
|
| 564 |
+
The order of the indices is ``[a, b, d0, -d0, d1, -d1, d2, -d2]``
|
| 565 |
+
The permutation corresponding to the tensor is
|
| 566 |
+
``g = [0, 3, 4, 6, 7, 5, 2, 1, 8, 9]``
|
| 567 |
+
|
| 568 |
+
In particular ``a`` is position ``0``, ``b`` is in position ``9``.
|
| 569 |
+
Use the slot symmetries to get `T` is a form which is the minimal
|
| 570 |
+
in lexicographic order in the free indices ``a`` and ``b``, e.g.
|
| 571 |
+
``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to
|
| 572 |
+
``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]``
|
| 573 |
+
|
| 574 |
+
>>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens
|
| 575 |
+
>>> base, gens = riemann_bsgs
|
| 576 |
+
>>> size, sbase, sgens = tensor_gens(base, gens, [[], []], 0)
|
| 577 |
+
>>> g = Permutation([0, 3, 4, 6, 7, 5, 2, 1, 8, 9])
|
| 578 |
+
>>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2)
|
| 579 |
+
[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]
|
| 580 |
+
"""
|
| 581 |
+
g = g.array_form
|
| 582 |
+
size = len(g)
|
| 583 |
+
if not base:
|
| 584 |
+
return g[:]
|
| 585 |
+
|
| 586 |
+
transversals = get_transversals(base, gens)
|
| 587 |
+
for x in sorted(g[:-2]):
|
| 588 |
+
if x not in base:
|
| 589 |
+
base.append(x)
|
| 590 |
+
h = g
|
| 591 |
+
for transv in transversals:
|
| 592 |
+
h_i = [size]*num_free
|
| 593 |
+
# find the element s in transversals[i] such that
|
| 594 |
+
# _af_rmul(h, s) has its free elements with the lowest position in h
|
| 595 |
+
s = None
|
| 596 |
+
for sk in transv.values():
|
| 597 |
+
h1 = _af_rmul(h, sk)
|
| 598 |
+
hi = [h1.index(ix) for ix in range(num_free)]
|
| 599 |
+
if hi < h_i:
|
| 600 |
+
h_i = hi
|
| 601 |
+
s = sk
|
| 602 |
+
if s:
|
| 603 |
+
h = _af_rmul(h, s)
|
| 604 |
+
return h
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def _get_map_slots(size, fixed_slots):
|
| 608 |
+
res = list(range(size))
|
| 609 |
+
pos = 0
|
| 610 |
+
for i in range(size):
|
| 611 |
+
if i in fixed_slots:
|
| 612 |
+
continue
|
| 613 |
+
res[i] = pos
|
| 614 |
+
pos += 1
|
| 615 |
+
return res
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def _lift_sgens(size, fixed_slots, free, s):
|
| 619 |
+
a = []
|
| 620 |
+
j = k = 0
|
| 621 |
+
fd = list(zip(fixed_slots, free))
|
| 622 |
+
fd = [y for x, y in sorted(fd)]
|
| 623 |
+
num_free = len(free)
|
| 624 |
+
for i in range(size):
|
| 625 |
+
if i in fixed_slots:
|
| 626 |
+
a.append(fd[k])
|
| 627 |
+
k += 1
|
| 628 |
+
else:
|
| 629 |
+
a.append(s[j] + num_free)
|
| 630 |
+
j += 1
|
| 631 |
+
return a
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
def canonicalize(g, dummies, msym, *v):
|
| 635 |
+
"""
|
| 636 |
+
canonicalize tensor formed by tensors
|
| 637 |
+
|
| 638 |
+
Parameters
|
| 639 |
+
==========
|
| 640 |
+
|
| 641 |
+
g : permutation representing the tensor
|
| 642 |
+
|
| 643 |
+
dummies : list representing the dummy indices
|
| 644 |
+
it can be a list of dummy indices of the same type
|
| 645 |
+
or a list of lists of dummy indices, one list for each
|
| 646 |
+
type of index;
|
| 647 |
+
the dummy indices must come after the free indices,
|
| 648 |
+
and put in order contravariant, covariant
|
| 649 |
+
[d0, -d0, d1,-d1,...]
|
| 650 |
+
|
| 651 |
+
msym : symmetry of the metric(s)
|
| 652 |
+
it can be an integer or a list;
|
| 653 |
+
in the first case it is the symmetry of the dummy index metric;
|
| 654 |
+
in the second case it is the list of the symmetries of the
|
| 655 |
+
index metric for each type
|
| 656 |
+
|
| 657 |
+
v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i`
|
| 658 |
+
|
| 659 |
+
base_i, gens_i : BSGS for tensors of this type.
|
| 660 |
+
The BSGS should have minimal base under lexicographic ordering;
|
| 661 |
+
if not, an attempt is made do get the minimal BSGS;
|
| 662 |
+
in case of failure,
|
| 663 |
+
canonicalize_naive is used, which is much slower.
|
| 664 |
+
|
| 665 |
+
n_i : number of tensors of type `i`.
|
| 666 |
+
|
| 667 |
+
sym_i : symmetry under exchange of component tensors of type `i`.
|
| 668 |
+
|
| 669 |
+
Both for msym and sym_i the cases are
|
| 670 |
+
* None no symmetry
|
| 671 |
+
* 0 commuting
|
| 672 |
+
* 1 anticommuting
|
| 673 |
+
|
| 674 |
+
Returns
|
| 675 |
+
=======
|
| 676 |
+
|
| 677 |
+
0 if the tensor is zero, else return the array form of
|
| 678 |
+
the permutation representing the canonical form of the tensor.
|
| 679 |
+
|
| 680 |
+
Algorithm
|
| 681 |
+
=========
|
| 682 |
+
|
| 683 |
+
First one uses canonical_free to get the minimum tensor under
|
| 684 |
+
lexicographic order, using only the slot symmetries.
|
| 685 |
+
If the component tensors have not minimal BSGS, it is attempted
|
| 686 |
+
to find it; if the attempt fails canonicalize_naive
|
| 687 |
+
is used instead.
|
| 688 |
+
|
| 689 |
+
Compute the residual slot symmetry keeping fixed the free indices
|
| 690 |
+
using tensor_gens(base, gens, list_free_indices, sym).
|
| 691 |
+
|
| 692 |
+
Reduce the problem eliminating the free indices.
|
| 693 |
+
|
| 694 |
+
Then use double_coset_can_rep and lift back the result reintroducing
|
| 695 |
+
the free indices.
|
| 696 |
+
|
| 697 |
+
Examples
|
| 698 |
+
========
|
| 699 |
+
|
| 700 |
+
one type of index with commuting metric;
|
| 701 |
+
|
| 702 |
+
`A_{a b}` and `B_{a b}` antisymmetric and commuting
|
| 703 |
+
|
| 704 |
+
`T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}`
|
| 705 |
+
|
| 706 |
+
`ord = [d0,-d0,d1,-d1,d2,-d2]` order of the indices
|
| 707 |
+
|
| 708 |
+
g = [1, 3, 0, 5, 4, 2, 6, 7]
|
| 709 |
+
|
| 710 |
+
`T_c = 0`
|
| 711 |
+
|
| 712 |
+
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product
|
| 713 |
+
>>> from sympy.combinatorics import Permutation
|
| 714 |
+
>>> base2a, gens2a = get_symmetric_group_sgs(2, 1)
|
| 715 |
+
>>> t0 = (base2a, gens2a, 1, 0)
|
| 716 |
+
>>> t1 = (base2a, gens2a, 2, 0)
|
| 717 |
+
>>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7])
|
| 718 |
+
>>> canonicalize(g, range(6), 0, t0, t1)
|
| 719 |
+
0
|
| 720 |
+
|
| 721 |
+
same as above, but with `B_{a b}` anticommuting
|
| 722 |
+
|
| 723 |
+
`T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}`
|
| 724 |
+
|
| 725 |
+
can = [0,2,1,4,3,5,7,6]
|
| 726 |
+
|
| 727 |
+
>>> t1 = (base2a, gens2a, 2, 1)
|
| 728 |
+
>>> canonicalize(g, range(6), 0, t0, t1)
|
| 729 |
+
[0, 2, 1, 4, 3, 5, 7, 6]
|
| 730 |
+
|
| 731 |
+
two types of indices `[a,b,c,d,e,f]` and `[m,n]`, in this order,
|
| 732 |
+
both with commuting metric
|
| 733 |
+
|
| 734 |
+
`f^{a b c}` antisymmetric, commuting
|
| 735 |
+
|
| 736 |
+
`A_{m a}` no symmetry, commuting
|
| 737 |
+
|
| 738 |
+
`T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}`
|
| 739 |
+
|
| 740 |
+
ord = [c,f,a,-a,b,-b,d,-d,e,-e,m,-m,n,-n]
|
| 741 |
+
|
| 742 |
+
g = [0,7,3, 1,9,5, 11,6, 10,4, 13,2, 12,8, 14,15]
|
| 743 |
+
|
| 744 |
+
The canonical tensor is
|
| 745 |
+
`T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}`
|
| 746 |
+
|
| 747 |
+
can = [0,2,4, 1,6,8, 10,3, 11,7, 12,5, 13,9, 15,14]
|
| 748 |
+
|
| 749 |
+
>>> base_f, gens_f = get_symmetric_group_sgs(3, 1)
|
| 750 |
+
>>> base1, gens1 = get_symmetric_group_sgs(1)
|
| 751 |
+
>>> base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1)
|
| 752 |
+
>>> t0 = (base_f, gens_f, 2, 0)
|
| 753 |
+
>>> t1 = (base_A, gens_A, 4, 0)
|
| 754 |
+
>>> dummies = [range(2, 10), range(10, 14)]
|
| 755 |
+
>>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15])
|
| 756 |
+
>>> canonicalize(g, dummies, [0, 0], t0, t1)
|
| 757 |
+
[0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14]
|
| 758 |
+
"""
|
| 759 |
+
from sympy.combinatorics.testutil import canonicalize_naive
|
| 760 |
+
if not isinstance(msym, list):
|
| 761 |
+
if msym not in (0, 1, None):
|
| 762 |
+
raise ValueError('msym must be 0, 1 or None')
|
| 763 |
+
num_types = 1
|
| 764 |
+
else:
|
| 765 |
+
num_types = len(msym)
|
| 766 |
+
if not all(msymx in (0, 1, None) for msymx in msym):
|
| 767 |
+
raise ValueError('msym entries must be 0, 1 or None')
|
| 768 |
+
if len(dummies) != num_types:
|
| 769 |
+
raise ValueError(
|
| 770 |
+
'dummies and msym must have the same number of elements')
|
| 771 |
+
size = g.size
|
| 772 |
+
num_tensors = 0
|
| 773 |
+
v1 = []
|
| 774 |
+
for base_i, gens_i, n_i, sym_i in v:
|
| 775 |
+
# check that the BSGS is minimal;
|
| 776 |
+
# this property is used in double_coset_can_rep;
|
| 777 |
+
# if it is not minimal use canonicalize_naive
|
| 778 |
+
if not _is_minimal_bsgs(base_i, gens_i):
|
| 779 |
+
mbsgs = get_minimal_bsgs(base_i, gens_i)
|
| 780 |
+
if not mbsgs:
|
| 781 |
+
can = canonicalize_naive(g, dummies, msym, *v)
|
| 782 |
+
return can
|
| 783 |
+
base_i, gens_i = mbsgs
|
| 784 |
+
v1.append((base_i, gens_i, [[]] * n_i, sym_i))
|
| 785 |
+
num_tensors += n_i
|
| 786 |
+
|
| 787 |
+
if num_types == 1 and not isinstance(msym, list):
|
| 788 |
+
dummies = [dummies]
|
| 789 |
+
msym = [msym]
|
| 790 |
+
flat_dummies = []
|
| 791 |
+
for dumx in dummies:
|
| 792 |
+
flat_dummies.extend(dumx)
|
| 793 |
+
|
| 794 |
+
if flat_dummies and flat_dummies != list(range(flat_dummies[0], flat_dummies[-1] + 1)):
|
| 795 |
+
raise ValueError('dummies is not valid')
|
| 796 |
+
|
| 797 |
+
# slot symmetry of the tensor
|
| 798 |
+
size1, sbase, sgens = gens_products(*v1)
|
| 799 |
+
if size != size1:
|
| 800 |
+
raise ValueError(
|
| 801 |
+
'g has size %d, generators have size %d' % (size, size1))
|
| 802 |
+
free = [i for i in range(size - 2) if i not in flat_dummies]
|
| 803 |
+
num_free = len(free)
|
| 804 |
+
|
| 805 |
+
# g1 minimal tensor under slot symmetry
|
| 806 |
+
g1 = canonical_free(sbase, sgens, g, num_free)
|
| 807 |
+
if not flat_dummies:
|
| 808 |
+
return g1
|
| 809 |
+
# save the sign of g1
|
| 810 |
+
sign = 0 if g1[-1] == size - 1 else 1
|
| 811 |
+
|
| 812 |
+
# the free indices are kept fixed.
|
| 813 |
+
# Determine free_i, the list of slots of tensors which are fixed
|
| 814 |
+
# since they are occupied by free indices, which are fixed.
|
| 815 |
+
start = 0
|
| 816 |
+
for i, (base_i, gens_i, n_i, sym_i) in enumerate(v):
|
| 817 |
+
free_i = []
|
| 818 |
+
len_tens = gens_i[0].size - 2
|
| 819 |
+
# for each component tensor get a list od fixed islots
|
| 820 |
+
for j in range(n_i):
|
| 821 |
+
# get the elements corresponding to the component tensor
|
| 822 |
+
h = g1[start:(start + len_tens)]
|
| 823 |
+
fr = []
|
| 824 |
+
# get the positions of the fixed elements in h
|
| 825 |
+
for k in free:
|
| 826 |
+
if k in h:
|
| 827 |
+
fr.append(h.index(k))
|
| 828 |
+
free_i.append(fr)
|
| 829 |
+
start += len_tens
|
| 830 |
+
v1[i] = (base_i, gens_i, free_i, sym_i)
|
| 831 |
+
# BSGS of the tensor with fixed free indices
|
| 832 |
+
# if tensor_gens fails in gens_product, use canonicalize_naive
|
| 833 |
+
size, sbase, sgens = gens_products(*v1)
|
| 834 |
+
|
| 835 |
+
# reduce the permutations getting rid of the free indices
|
| 836 |
+
pos_free = [g1.index(x) for x in range(num_free)]
|
| 837 |
+
size_red = size - num_free
|
| 838 |
+
g1_red = [x - num_free for x in g1 if x in flat_dummies]
|
| 839 |
+
if sign:
|
| 840 |
+
g1_red.extend([size_red - 1, size_red - 2])
|
| 841 |
+
else:
|
| 842 |
+
g1_red.extend([size_red - 2, size_red - 1])
|
| 843 |
+
map_slots = _get_map_slots(size, pos_free)
|
| 844 |
+
sbase_red = [map_slots[i] for i in sbase if i not in pos_free]
|
| 845 |
+
sgens_red = [_af_new([map_slots[i] for i in y._array_form if i not in pos_free]) for y in sgens]
|
| 846 |
+
dummies_red = [[x - num_free for x in y] for y in dummies]
|
| 847 |
+
transv_red = get_transversals(sbase_red, sgens_red)
|
| 848 |
+
g1_red = _af_new(g1_red)
|
| 849 |
+
g2 = double_coset_can_rep(
|
| 850 |
+
dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red)
|
| 851 |
+
if g2 == 0:
|
| 852 |
+
return 0
|
| 853 |
+
# lift to the case with the free indices
|
| 854 |
+
g3 = _lift_sgens(size, pos_free, free, g2)
|
| 855 |
+
return g3
|
| 856 |
+
|
| 857 |
+
|
| 858 |
+
def perm_af_direct_product(gens1, gens2, signed=True):
|
| 859 |
+
"""
|
| 860 |
+
Direct products of the generators gens1 and gens2.
|
| 861 |
+
|
| 862 |
+
Examples
|
| 863 |
+
========
|
| 864 |
+
|
| 865 |
+
>>> from sympy.combinatorics.tensor_can import perm_af_direct_product
|
| 866 |
+
>>> gens1 = [[1, 0, 2, 3], [0, 1, 3, 2]]
|
| 867 |
+
>>> gens2 = [[1, 0]]
|
| 868 |
+
>>> perm_af_direct_product(gens1, gens2, False)
|
| 869 |
+
[[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]]
|
| 870 |
+
>>> gens1 = [[1, 0, 2, 3, 5, 4], [0, 1, 3, 2, 4, 5]]
|
| 871 |
+
>>> gens2 = [[1, 0, 2, 3]]
|
| 872 |
+
>>> perm_af_direct_product(gens1, gens2, True)
|
| 873 |
+
[[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]]
|
| 874 |
+
"""
|
| 875 |
+
gens1 = [list(x) for x in gens1]
|
| 876 |
+
gens2 = [list(x) for x in gens2]
|
| 877 |
+
s = 2 if signed else 0
|
| 878 |
+
n1 = len(gens1[0]) - s
|
| 879 |
+
n2 = len(gens2[0]) - s
|
| 880 |
+
start = list(range(n1))
|
| 881 |
+
end = list(range(n1, n1 + n2))
|
| 882 |
+
if signed:
|
| 883 |
+
gens1 = [gen[:-2] + end + [gen[-2] + n2, gen[-1] + n2]
|
| 884 |
+
for gen in gens1]
|
| 885 |
+
gens2 = [start + [x + n1 for x in gen] for gen in gens2]
|
| 886 |
+
else:
|
| 887 |
+
gens1 = [gen + end for gen in gens1]
|
| 888 |
+
gens2 = [start + [x + n1 for x in gen] for gen in gens2]
|
| 889 |
+
|
| 890 |
+
res = gens1 + gens2
|
| 891 |
+
|
| 892 |
+
return res
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
def bsgs_direct_product(base1, gens1, base2, gens2, signed=True):
|
| 896 |
+
"""
|
| 897 |
+
Direct product of two BSGS.
|
| 898 |
+
|
| 899 |
+
Parameters
|
| 900 |
+
==========
|
| 901 |
+
|
| 902 |
+
base1 : base of the first BSGS.
|
| 903 |
+
|
| 904 |
+
gens1 : strong generating sequence of the first BSGS.
|
| 905 |
+
|
| 906 |
+
base2, gens2 : similarly for the second BSGS.
|
| 907 |
+
|
| 908 |
+
signed : flag for signed permutations.
|
| 909 |
+
|
| 910 |
+
Examples
|
| 911 |
+
========
|
| 912 |
+
|
| 913 |
+
>>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product)
|
| 914 |
+
>>> base1, gens1 = get_symmetric_group_sgs(1)
|
| 915 |
+
>>> base2, gens2 = get_symmetric_group_sgs(2)
|
| 916 |
+
>>> bsgs_direct_product(base1, gens1, base2, gens2)
|
| 917 |
+
([1], [(4)(1 2)])
|
| 918 |
+
"""
|
| 919 |
+
s = 2 if signed else 0
|
| 920 |
+
n1 = gens1[0].size - s
|
| 921 |
+
base = list(base1)
|
| 922 |
+
base += [x + n1 for x in base2]
|
| 923 |
+
gens1 = [h._array_form for h in gens1]
|
| 924 |
+
gens2 = [h._array_form for h in gens2]
|
| 925 |
+
gens = perm_af_direct_product(gens1, gens2, signed)
|
| 926 |
+
size = len(gens[0])
|
| 927 |
+
id_af = list(range(size))
|
| 928 |
+
gens = [h for h in gens if h != id_af]
|
| 929 |
+
if not gens:
|
| 930 |
+
gens = [id_af]
|
| 931 |
+
return base, [_af_new(h) for h in gens]
|
| 932 |
+
|
| 933 |
+
|
| 934 |
+
def get_symmetric_group_sgs(n, antisym=False):
|
| 935 |
+
"""
|
| 936 |
+
Return base, gens of the minimal BSGS for (anti)symmetric tensor
|
| 937 |
+
|
| 938 |
+
Parameters
|
| 939 |
+
==========
|
| 940 |
+
|
| 941 |
+
n : rank of the tensor
|
| 942 |
+
antisym : bool
|
| 943 |
+
``antisym = False`` symmetric tensor
|
| 944 |
+
``antisym = True`` antisymmetric tensor
|
| 945 |
+
|
| 946 |
+
Examples
|
| 947 |
+
========
|
| 948 |
+
|
| 949 |
+
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs
|
| 950 |
+
>>> get_symmetric_group_sgs(3)
|
| 951 |
+
([0, 1], [(4)(0 1), (4)(1 2)])
|
| 952 |
+
"""
|
| 953 |
+
if n == 1:
|
| 954 |
+
return [], [_af_new(list(range(3)))]
|
| 955 |
+
gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)]
|
| 956 |
+
if antisym == 0:
|
| 957 |
+
gens = [x + [n, n + 1] for x in gens]
|
| 958 |
+
else:
|
| 959 |
+
gens = [x + [n + 1, n] for x in gens]
|
| 960 |
+
base = list(range(n - 1))
|
| 961 |
+
return base, [_af_new(h) for h in gens]
|
| 962 |
+
|
| 963 |
+
riemann_bsgs = [0, 2], [Permutation(0, 1)(4, 5), Permutation(2, 3)(4, 5),
|
| 964 |
+
Permutation(5)(0, 2)(1, 3)]
|
| 965 |
+
|
| 966 |
+
|
| 967 |
+
def get_transversals(base, gens):
|
| 968 |
+
"""
|
| 969 |
+
Return transversals for the group with BSGS base, gens
|
| 970 |
+
"""
|
| 971 |
+
if not base:
|
| 972 |
+
return []
|
| 973 |
+
stabs = _distribute_gens_by_base(base, gens)
|
| 974 |
+
orbits, transversals = _orbits_transversals_from_bsgs(base, stabs)
|
| 975 |
+
transversals = [{x: h._array_form for x, h in y.items()} for y in
|
| 976 |
+
transversals]
|
| 977 |
+
return transversals
|
| 978 |
+
|
| 979 |
+
|
| 980 |
+
def _is_minimal_bsgs(base, gens):
|
| 981 |
+
"""
|
| 982 |
+
Check if the BSGS has minimal base under lexigographic order.
|
| 983 |
+
|
| 984 |
+
base, gens BSGS
|
| 985 |
+
|
| 986 |
+
Examples
|
| 987 |
+
========
|
| 988 |
+
|
| 989 |
+
>>> from sympy.combinatorics import Permutation
|
| 990 |
+
>>> from sympy.combinatorics.tensor_can import riemann_bsgs, _is_minimal_bsgs
|
| 991 |
+
>>> _is_minimal_bsgs(*riemann_bsgs)
|
| 992 |
+
True
|
| 993 |
+
>>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
|
| 994 |
+
>>> _is_minimal_bsgs(*riemann_bsgs1)
|
| 995 |
+
False
|
| 996 |
+
"""
|
| 997 |
+
base1 = []
|
| 998 |
+
sgs1 = gens[:]
|
| 999 |
+
size = gens[0].size
|
| 1000 |
+
for i in range(size):
|
| 1001 |
+
if not all(h._array_form[i] == i for h in sgs1):
|
| 1002 |
+
base1.append(i)
|
| 1003 |
+
sgs1 = [h for h in sgs1 if h._array_form[i] == i]
|
| 1004 |
+
return base1 == base
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
def get_minimal_bsgs(base, gens):
|
| 1008 |
+
"""
|
| 1009 |
+
Compute a minimal GSGS
|
| 1010 |
+
|
| 1011 |
+
base, gens BSGS
|
| 1012 |
+
|
| 1013 |
+
If base, gens is a minimal BSGS return it; else return a minimal BSGS
|
| 1014 |
+
if it fails in finding one, it returns None
|
| 1015 |
+
|
| 1016 |
+
TODO: use baseswap in the case in which if it fails in finding a
|
| 1017 |
+
minimal BSGS
|
| 1018 |
+
|
| 1019 |
+
Examples
|
| 1020 |
+
========
|
| 1021 |
+
|
| 1022 |
+
>>> from sympy.combinatorics import Permutation
|
| 1023 |
+
>>> from sympy.combinatorics.tensor_can import get_minimal_bsgs
|
| 1024 |
+
>>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
|
| 1025 |
+
>>> get_minimal_bsgs(*riemann_bsgs1)
|
| 1026 |
+
([0, 2], [(0 1)(4 5), (5)(0 2)(1 3), (2 3)(4 5)])
|
| 1027 |
+
"""
|
| 1028 |
+
G = PermutationGroup(gens)
|
| 1029 |
+
base, gens = G.schreier_sims_incremental()
|
| 1030 |
+
if not _is_minimal_bsgs(base, gens):
|
| 1031 |
+
return None
|
| 1032 |
+
return base, gens
|
| 1033 |
+
|
| 1034 |
+
|
| 1035 |
+
def tensor_gens(base, gens, list_free_indices, sym=0):
|
| 1036 |
+
"""
|
| 1037 |
+
Returns size, res_base, res_gens BSGS for n tensors of the
|
| 1038 |
+
same type.
|
| 1039 |
+
|
| 1040 |
+
Explanation
|
| 1041 |
+
===========
|
| 1042 |
+
|
| 1043 |
+
base, gens BSGS for tensors of this type
|
| 1044 |
+
list_free_indices list of the slots occupied by fixed indices
|
| 1045 |
+
for each of the tensors
|
| 1046 |
+
|
| 1047 |
+
sym symmetry under commutation of two tensors
|
| 1048 |
+
sym None no symmetry
|
| 1049 |
+
sym 0 commuting
|
| 1050 |
+
sym 1 anticommuting
|
| 1051 |
+
|
| 1052 |
+
Examples
|
| 1053 |
+
========
|
| 1054 |
+
|
| 1055 |
+
>>> from sympy.combinatorics.tensor_can import tensor_gens, get_symmetric_group_sgs
|
| 1056 |
+
|
| 1057 |
+
two symmetric tensors with 3 indices without free indices
|
| 1058 |
+
|
| 1059 |
+
>>> base, gens = get_symmetric_group_sgs(3)
|
| 1060 |
+
>>> tensor_gens(base, gens, [[], []])
|
| 1061 |
+
(8, [0, 1, 3, 4], [(7)(0 1), (7)(1 2), (7)(3 4), (7)(4 5), (7)(0 3)(1 4)(2 5)])
|
| 1062 |
+
|
| 1063 |
+
two symmetric tensors with 3 indices with free indices in slot 1 and 0
|
| 1064 |
+
|
| 1065 |
+
>>> tensor_gens(base, gens, [[1], [0]])
|
| 1066 |
+
(8, [0, 4], [(7)(0 2), (7)(4 5)])
|
| 1067 |
+
|
| 1068 |
+
four symmetric tensors with 3 indices, two of which with free indices
|
| 1069 |
+
|
| 1070 |
+
"""
|
| 1071 |
+
def _get_bsgs(G, base, gens, free_indices):
|
| 1072 |
+
"""
|
| 1073 |
+
return the BSGS for G.pointwise_stabilizer(free_indices)
|
| 1074 |
+
"""
|
| 1075 |
+
if not free_indices:
|
| 1076 |
+
return base[:], gens[:]
|
| 1077 |
+
else:
|
| 1078 |
+
H = G.pointwise_stabilizer(free_indices)
|
| 1079 |
+
base, sgs = H.schreier_sims_incremental()
|
| 1080 |
+
return base, sgs
|
| 1081 |
+
|
| 1082 |
+
# if not base there is no slot symmetry for the component tensors
|
| 1083 |
+
# if list_free_indices.count([]) < 2 there is no commutation symmetry
|
| 1084 |
+
# so there is no resulting slot symmetry
|
| 1085 |
+
if not base and list_free_indices.count([]) < 2:
|
| 1086 |
+
n = len(list_free_indices)
|
| 1087 |
+
size = gens[0].size
|
| 1088 |
+
size = n * (size - 2) + 2
|
| 1089 |
+
return size, [], [_af_new(list(range(size)))]
|
| 1090 |
+
|
| 1091 |
+
# if any(list_free_indices) one needs to compute the pointwise
|
| 1092 |
+
# stabilizer, so G is needed
|
| 1093 |
+
if any(list_free_indices):
|
| 1094 |
+
G = PermutationGroup(gens)
|
| 1095 |
+
else:
|
| 1096 |
+
G = None
|
| 1097 |
+
|
| 1098 |
+
# no_free list of lists of indices for component tensors without fixed
|
| 1099 |
+
# indices
|
| 1100 |
+
no_free = []
|
| 1101 |
+
size = gens[0].size
|
| 1102 |
+
id_af = list(range(size))
|
| 1103 |
+
num_indices = size - 2
|
| 1104 |
+
if not list_free_indices[0]:
|
| 1105 |
+
no_free.append(list(range(num_indices)))
|
| 1106 |
+
res_base, res_gens = _get_bsgs(G, base, gens, list_free_indices[0])
|
| 1107 |
+
for i in range(1, len(list_free_indices)):
|
| 1108 |
+
base1, gens1 = _get_bsgs(G, base, gens, list_free_indices[i])
|
| 1109 |
+
res_base, res_gens = bsgs_direct_product(res_base, res_gens,
|
| 1110 |
+
base1, gens1, 1)
|
| 1111 |
+
if not list_free_indices[i]:
|
| 1112 |
+
no_free.append(list(range(size - 2, size - 2 + num_indices)))
|
| 1113 |
+
size += num_indices
|
| 1114 |
+
nr = size - 2
|
| 1115 |
+
res_gens = [h for h in res_gens if h._array_form != id_af]
|
| 1116 |
+
# if sym there are no commuting tensors stop here
|
| 1117 |
+
if sym is None or not no_free:
|
| 1118 |
+
if not res_gens:
|
| 1119 |
+
res_gens = [_af_new(id_af)]
|
| 1120 |
+
return size, res_base, res_gens
|
| 1121 |
+
|
| 1122 |
+
# if the component tensors have moinimal BSGS, so is their direct
|
| 1123 |
+
# product P; the slot symmetry group is S = P*C, where C is the group
|
| 1124 |
+
# to (anti)commute the component tensors with no free indices
|
| 1125 |
+
# a stabilizer has the property S_i = P_i*C_i;
|
| 1126 |
+
# the BSGS of P*C has SGS_P + SGS_C and the base is
|
| 1127 |
+
# the ordered union of the bases of P and C.
|
| 1128 |
+
# If P has minimal BSGS, so has S with this base.
|
| 1129 |
+
base_comm = []
|
| 1130 |
+
for i in range(len(no_free) - 1):
|
| 1131 |
+
ind1 = no_free[i]
|
| 1132 |
+
ind2 = no_free[i + 1]
|
| 1133 |
+
a = list(range(ind1[0]))
|
| 1134 |
+
a.extend(ind2)
|
| 1135 |
+
a.extend(ind1)
|
| 1136 |
+
base_comm.append(ind1[0])
|
| 1137 |
+
a.extend(list(range(ind2[-1] + 1, nr)))
|
| 1138 |
+
if sym == 0:
|
| 1139 |
+
a.extend([nr, nr + 1])
|
| 1140 |
+
else:
|
| 1141 |
+
a.extend([nr + 1, nr])
|
| 1142 |
+
res_gens.append(_af_new(a))
|
| 1143 |
+
res_base = list(res_base)
|
| 1144 |
+
# each base is ordered; order the union of the two bases
|
| 1145 |
+
for i in base_comm:
|
| 1146 |
+
if i not in res_base:
|
| 1147 |
+
res_base.append(i)
|
| 1148 |
+
res_base.sort()
|
| 1149 |
+
if not res_gens:
|
| 1150 |
+
res_gens = [_af_new(id_af)]
|
| 1151 |
+
|
| 1152 |
+
return size, res_base, res_gens
|
| 1153 |
+
|
| 1154 |
+
|
| 1155 |
+
def gens_products(*v):
|
| 1156 |
+
"""
|
| 1157 |
+
Returns size, res_base, res_gens BSGS for n tensors of different types.
|
| 1158 |
+
|
| 1159 |
+
Explanation
|
| 1160 |
+
===========
|
| 1161 |
+
|
| 1162 |
+
v is a sequence of (base_i, gens_i, free_i, sym_i)
|
| 1163 |
+
where
|
| 1164 |
+
base_i, gens_i BSGS of tensor of type `i`
|
| 1165 |
+
free_i list of the fixed slots for each of the tensors
|
| 1166 |
+
of type `i`; if there are `n_i` tensors of type `i`
|
| 1167 |
+
and none of them have fixed slots, `free = [[]]*n_i`
|
| 1168 |
+
sym 0 (1) if the tensors of type `i` (anti)commute among themselves
|
| 1169 |
+
|
| 1170 |
+
Examples
|
| 1171 |
+
========
|
| 1172 |
+
|
| 1173 |
+
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, gens_products
|
| 1174 |
+
>>> base, gens = get_symmetric_group_sgs(2)
|
| 1175 |
+
>>> gens_products((base, gens, [[], []], 0))
|
| 1176 |
+
(6, [0, 2], [(5)(0 1), (5)(2 3), (5)(0 2)(1 3)])
|
| 1177 |
+
>>> gens_products((base, gens, [[1], []], 0))
|
| 1178 |
+
(6, [2], [(5)(2 3)])
|
| 1179 |
+
"""
|
| 1180 |
+
res_size, res_base, res_gens = tensor_gens(*v[0])
|
| 1181 |
+
for i in range(1, len(v)):
|
| 1182 |
+
size, base, gens = tensor_gens(*v[i])
|
| 1183 |
+
res_base, res_gens = bsgs_direct_product(res_base, res_gens, base,
|
| 1184 |
+
gens, 1)
|
| 1185 |
+
res_size = res_gens[0].size
|
| 1186 |
+
id_af = list(range(res_size))
|
| 1187 |
+
res_gens = [h for h in res_gens if h != id_af]
|
| 1188 |
+
if not res_gens:
|
| 1189 |
+
res_gens = [id_af]
|
| 1190 |
+
return res_size, res_base, res_gens
|
lib/python3.12/site-packages/sympy/combinatorics/testutil.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics import Permutation
|
| 2 |
+
from sympy.combinatorics.util import _distribute_gens_by_base
|
| 3 |
+
|
| 4 |
+
rmul = Permutation.rmul
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _cmp_perm_lists(first, second):
|
| 8 |
+
"""
|
| 9 |
+
Compare two lists of permutations as sets.
|
| 10 |
+
|
| 11 |
+
Explanation
|
| 12 |
+
===========
|
| 13 |
+
|
| 14 |
+
This is used for testing purposes. Since the array form of a
|
| 15 |
+
permutation is currently a list, Permutation is not hashable
|
| 16 |
+
and cannot be put into a set.
|
| 17 |
+
|
| 18 |
+
Examples
|
| 19 |
+
========
|
| 20 |
+
|
| 21 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 22 |
+
>>> from sympy.combinatorics.testutil import _cmp_perm_lists
|
| 23 |
+
>>> a = Permutation([0, 2, 3, 4, 1])
|
| 24 |
+
>>> b = Permutation([1, 2, 0, 4, 3])
|
| 25 |
+
>>> c = Permutation([3, 4, 0, 1, 2])
|
| 26 |
+
>>> ls1 = [a, b, c]
|
| 27 |
+
>>> ls2 = [b, c, a]
|
| 28 |
+
>>> _cmp_perm_lists(ls1, ls2)
|
| 29 |
+
True
|
| 30 |
+
|
| 31 |
+
"""
|
| 32 |
+
return {tuple(a) for a in first} == \
|
| 33 |
+
{tuple(a) for a in second}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _naive_list_centralizer(self, other, af=False):
|
| 37 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 38 |
+
"""
|
| 39 |
+
Return a list of elements for the centralizer of a subgroup/set/element.
|
| 40 |
+
|
| 41 |
+
Explanation
|
| 42 |
+
===========
|
| 43 |
+
|
| 44 |
+
This is a brute force implementation that goes over all elements of the
|
| 45 |
+
group and checks for membership in the centralizer. It is used to
|
| 46 |
+
test ``.centralizer()`` from ``sympy.combinatorics.perm_groups``.
|
| 47 |
+
|
| 48 |
+
Examples
|
| 49 |
+
========
|
| 50 |
+
|
| 51 |
+
>>> from sympy.combinatorics.testutil import _naive_list_centralizer
|
| 52 |
+
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
| 53 |
+
>>> D = DihedralGroup(4)
|
| 54 |
+
>>> _naive_list_centralizer(D, D)
|
| 55 |
+
[Permutation([0, 1, 2, 3]), Permutation([2, 3, 0, 1])]
|
| 56 |
+
|
| 57 |
+
See Also
|
| 58 |
+
========
|
| 59 |
+
|
| 60 |
+
sympy.combinatorics.perm_groups.centralizer
|
| 61 |
+
|
| 62 |
+
"""
|
| 63 |
+
from sympy.combinatorics.permutations import _af_commutes_with
|
| 64 |
+
if hasattr(other, 'generators'):
|
| 65 |
+
elements = list(self.generate_dimino(af=True))
|
| 66 |
+
gens = [x._array_form for x in other.generators]
|
| 67 |
+
commutes_with_gens = lambda x: all(_af_commutes_with(x, gen) for gen in gens)
|
| 68 |
+
centralizer_list = []
|
| 69 |
+
if not af:
|
| 70 |
+
for element in elements:
|
| 71 |
+
if commutes_with_gens(element):
|
| 72 |
+
centralizer_list.append(Permutation._af_new(element))
|
| 73 |
+
else:
|
| 74 |
+
for element in elements:
|
| 75 |
+
if commutes_with_gens(element):
|
| 76 |
+
centralizer_list.append(element)
|
| 77 |
+
return centralizer_list
|
| 78 |
+
elif hasattr(other, 'getitem'):
|
| 79 |
+
return _naive_list_centralizer(self, PermutationGroup(other), af)
|
| 80 |
+
elif hasattr(other, 'array_form'):
|
| 81 |
+
return _naive_list_centralizer(self, PermutationGroup([other]), af)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _verify_bsgs(group, base, gens):
|
| 85 |
+
"""
|
| 86 |
+
Verify the correctness of a base and strong generating set.
|
| 87 |
+
|
| 88 |
+
Explanation
|
| 89 |
+
===========
|
| 90 |
+
|
| 91 |
+
This is a naive implementation using the definition of a base and a strong
|
| 92 |
+
generating set relative to it. There are other procedures for
|
| 93 |
+
verifying a base and strong generating set, but this one will
|
| 94 |
+
serve for more robust testing.
|
| 95 |
+
|
| 96 |
+
Examples
|
| 97 |
+
========
|
| 98 |
+
|
| 99 |
+
>>> from sympy.combinatorics.named_groups import AlternatingGroup
|
| 100 |
+
>>> from sympy.combinatorics.testutil import _verify_bsgs
|
| 101 |
+
>>> A = AlternatingGroup(4)
|
| 102 |
+
>>> A.schreier_sims()
|
| 103 |
+
>>> _verify_bsgs(A, A.base, A.strong_gens)
|
| 104 |
+
True
|
| 105 |
+
|
| 106 |
+
See Also
|
| 107 |
+
========
|
| 108 |
+
|
| 109 |
+
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
|
| 110 |
+
|
| 111 |
+
"""
|
| 112 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 113 |
+
strong_gens_distr = _distribute_gens_by_base(base, gens)
|
| 114 |
+
current_stabilizer = group
|
| 115 |
+
for i in range(len(base)):
|
| 116 |
+
candidate = PermutationGroup(strong_gens_distr[i])
|
| 117 |
+
if current_stabilizer.order() != candidate.order():
|
| 118 |
+
return False
|
| 119 |
+
current_stabilizer = current_stabilizer.stabilizer(base[i])
|
| 120 |
+
if current_stabilizer.order() != 1:
|
| 121 |
+
return False
|
| 122 |
+
return True
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def _verify_centralizer(group, arg, centr=None):
|
| 126 |
+
"""
|
| 127 |
+
Verify the centralizer of a group/set/element inside another group.
|
| 128 |
+
|
| 129 |
+
This is used for testing ``.centralizer()`` from
|
| 130 |
+
``sympy.combinatorics.perm_groups``
|
| 131 |
+
|
| 132 |
+
Examples
|
| 133 |
+
========
|
| 134 |
+
|
| 135 |
+
>>> from sympy.combinatorics.named_groups import (SymmetricGroup,
|
| 136 |
+
... AlternatingGroup)
|
| 137 |
+
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
| 138 |
+
>>> from sympy.combinatorics.permutations import Permutation
|
| 139 |
+
>>> from sympy.combinatorics.testutil import _verify_centralizer
|
| 140 |
+
>>> S = SymmetricGroup(5)
|
| 141 |
+
>>> A = AlternatingGroup(5)
|
| 142 |
+
>>> centr = PermutationGroup([Permutation([0, 1, 2, 3, 4])])
|
| 143 |
+
>>> _verify_centralizer(S, A, centr)
|
| 144 |
+
True
|
| 145 |
+
|
| 146 |
+
See Also
|
| 147 |
+
========
|
| 148 |
+
|
| 149 |
+
_naive_list_centralizer,
|
| 150 |
+
sympy.combinatorics.perm_groups.PermutationGroup.centralizer,
|
| 151 |
+
_cmp_perm_lists
|
| 152 |
+
|
| 153 |
+
"""
|
| 154 |
+
if centr is None:
|
| 155 |
+
centr = group.centralizer(arg)
|
| 156 |
+
centr_list = list(centr.generate_dimino(af=True))
|
| 157 |
+
centr_list_naive = _naive_list_centralizer(group, arg, af=True)
|
| 158 |
+
return _cmp_perm_lists(centr_list, centr_list_naive)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _verify_normal_closure(group, arg, closure=None):
|
| 162 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 163 |
+
"""
|
| 164 |
+
Verify the normal closure of a subgroup/subset/element in a group.
|
| 165 |
+
|
| 166 |
+
This is used to test
|
| 167 |
+
sympy.combinatorics.perm_groups.PermutationGroup.normal_closure
|
| 168 |
+
|
| 169 |
+
Examples
|
| 170 |
+
========
|
| 171 |
+
|
| 172 |
+
>>> from sympy.combinatorics.named_groups import (SymmetricGroup,
|
| 173 |
+
... AlternatingGroup)
|
| 174 |
+
>>> from sympy.combinatorics.testutil import _verify_normal_closure
|
| 175 |
+
>>> S = SymmetricGroup(3)
|
| 176 |
+
>>> A = AlternatingGroup(3)
|
| 177 |
+
>>> _verify_normal_closure(S, A, closure=A)
|
| 178 |
+
True
|
| 179 |
+
|
| 180 |
+
See Also
|
| 181 |
+
========
|
| 182 |
+
|
| 183 |
+
sympy.combinatorics.perm_groups.PermutationGroup.normal_closure
|
| 184 |
+
|
| 185 |
+
"""
|
| 186 |
+
if closure is None:
|
| 187 |
+
closure = group.normal_closure(arg)
|
| 188 |
+
conjugates = set()
|
| 189 |
+
if hasattr(arg, 'generators'):
|
| 190 |
+
subgr_gens = arg.generators
|
| 191 |
+
elif hasattr(arg, '__getitem__'):
|
| 192 |
+
subgr_gens = arg
|
| 193 |
+
elif hasattr(arg, 'array_form'):
|
| 194 |
+
subgr_gens = [arg]
|
| 195 |
+
for el in group.generate_dimino():
|
| 196 |
+
conjugates.update(gen ^ el for gen in subgr_gens)
|
| 197 |
+
naive_closure = PermutationGroup(list(conjugates))
|
| 198 |
+
return closure.is_subgroup(naive_closure)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def canonicalize_naive(g, dummies, sym, *v):
|
| 202 |
+
"""
|
| 203 |
+
Canonicalize tensor formed by tensors of the different types.
|
| 204 |
+
|
| 205 |
+
Explanation
|
| 206 |
+
===========
|
| 207 |
+
|
| 208 |
+
sym_i symmetry under exchange of two component tensors of type `i`
|
| 209 |
+
None no symmetry
|
| 210 |
+
0 commuting
|
| 211 |
+
1 anticommuting
|
| 212 |
+
|
| 213 |
+
Parameters
|
| 214 |
+
==========
|
| 215 |
+
|
| 216 |
+
g : Permutation representing the tensor.
|
| 217 |
+
dummies : List of dummy indices.
|
| 218 |
+
msym : Symmetry of the metric.
|
| 219 |
+
v : A list of (base_i, gens_i, n_i, sym_i) for tensors of type `i`.
|
| 220 |
+
base_i, gens_i BSGS for tensors of this type
|
| 221 |
+
n_i number of tensors of type `i`
|
| 222 |
+
|
| 223 |
+
Returns
|
| 224 |
+
=======
|
| 225 |
+
|
| 226 |
+
Returns 0 if the tensor is zero, else returns the array form of
|
| 227 |
+
the permutation representing the canonical form of the tensor.
|
| 228 |
+
|
| 229 |
+
Examples
|
| 230 |
+
========
|
| 231 |
+
|
| 232 |
+
>>> from sympy.combinatorics.testutil import canonicalize_naive
|
| 233 |
+
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs
|
| 234 |
+
>>> from sympy.combinatorics import Permutation
|
| 235 |
+
>>> g = Permutation([1, 3, 2, 0, 4, 5])
|
| 236 |
+
>>> base2, gens2 = get_symmetric_group_sgs(2)
|
| 237 |
+
>>> canonicalize_naive(g, [2, 3], 0, (base2, gens2, 2, 0))
|
| 238 |
+
[0, 2, 1, 3, 4, 5]
|
| 239 |
+
"""
|
| 240 |
+
from sympy.combinatorics.perm_groups import PermutationGroup
|
| 241 |
+
from sympy.combinatorics.tensor_can import gens_products, dummy_sgs
|
| 242 |
+
from sympy.combinatorics.permutations import _af_rmul
|
| 243 |
+
v1 = []
|
| 244 |
+
for i in range(len(v)):
|
| 245 |
+
base_i, gens_i, n_i, sym_i = v[i]
|
| 246 |
+
v1.append((base_i, gens_i, [[]]*n_i, sym_i))
|
| 247 |
+
size, sbase, sgens = gens_products(*v1)
|
| 248 |
+
dgens = dummy_sgs(dummies, sym, size-2)
|
| 249 |
+
if isinstance(sym, int):
|
| 250 |
+
num_types = 1
|
| 251 |
+
dummies = [dummies]
|
| 252 |
+
sym = [sym]
|
| 253 |
+
else:
|
| 254 |
+
num_types = len(sym)
|
| 255 |
+
dgens = []
|
| 256 |
+
for i in range(num_types):
|
| 257 |
+
dgens.extend(dummy_sgs(dummies[i], sym[i], size - 2))
|
| 258 |
+
S = PermutationGroup(sgens)
|
| 259 |
+
D = PermutationGroup([Permutation(x) for x in dgens])
|
| 260 |
+
dlist = list(D.generate(af=True))
|
| 261 |
+
g = g.array_form
|
| 262 |
+
st = set()
|
| 263 |
+
for s in S.generate(af=True):
|
| 264 |
+
h = _af_rmul(g, s)
|
| 265 |
+
for d in dlist:
|
| 266 |
+
q = tuple(_af_rmul(d, h))
|
| 267 |
+
st.add(q)
|
| 268 |
+
a = list(st)
|
| 269 |
+
a.sort()
|
| 270 |
+
prev = (0,)*size
|
| 271 |
+
for h in a:
|
| 272 |
+
if h[:-2] == prev[:-2]:
|
| 273 |
+
if h[-1] != prev[-1]:
|
| 274 |
+
return 0
|
| 275 |
+
prev = h
|
| 276 |
+
return list(a[0])
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def graph_certificate(gr):
|
| 280 |
+
"""
|
| 281 |
+
Return a certificate for the graph
|
| 282 |
+
|
| 283 |
+
Parameters
|
| 284 |
+
==========
|
| 285 |
+
|
| 286 |
+
gr : adjacency list
|
| 287 |
+
|
| 288 |
+
Explanation
|
| 289 |
+
===========
|
| 290 |
+
|
| 291 |
+
The graph is assumed to be unoriented and without
|
| 292 |
+
external lines.
|
| 293 |
+
|
| 294 |
+
Associate to each vertex of the graph a symmetric tensor with
|
| 295 |
+
number of indices equal to the degree of the vertex; indices
|
| 296 |
+
are contracted when they correspond to the same line of the graph.
|
| 297 |
+
The canonical form of the tensor gives a certificate for the graph.
|
| 298 |
+
|
| 299 |
+
This is not an efficient algorithm to get the certificate of a graph.
|
| 300 |
+
|
| 301 |
+
Examples
|
| 302 |
+
========
|
| 303 |
+
|
| 304 |
+
>>> from sympy.combinatorics.testutil import graph_certificate
|
| 305 |
+
>>> gr1 = {0:[1, 2, 3, 5], 1:[0, 2, 4], 2:[0, 1, 3, 4], 3:[0, 2, 4], 4:[1, 2, 3, 5], 5:[0, 4]}
|
| 306 |
+
>>> gr2 = {0:[1, 5], 1:[0, 2, 3, 4], 2:[1, 3, 5], 3:[1, 2, 4, 5], 4:[1, 3, 5], 5:[0, 2, 3, 4]}
|
| 307 |
+
>>> c1 = graph_certificate(gr1)
|
| 308 |
+
>>> c2 = graph_certificate(gr2)
|
| 309 |
+
>>> c1
|
| 310 |
+
[0, 2, 4, 6, 1, 8, 10, 12, 3, 14, 16, 18, 5, 9, 15, 7, 11, 17, 13, 19, 20, 21]
|
| 311 |
+
>>> c1 == c2
|
| 312 |
+
True
|
| 313 |
+
"""
|
| 314 |
+
from sympy.combinatorics.permutations import _af_invert
|
| 315 |
+
from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize
|
| 316 |
+
items = list(gr.items())
|
| 317 |
+
items.sort(key=lambda x: len(x[1]), reverse=True)
|
| 318 |
+
pvert = [x[0] for x in items]
|
| 319 |
+
pvert = _af_invert(pvert)
|
| 320 |
+
|
| 321 |
+
# the indices of the tensor are twice the number of lines of the graph
|
| 322 |
+
num_indices = 0
|
| 323 |
+
for v, neigh in items:
|
| 324 |
+
num_indices += len(neigh)
|
| 325 |
+
# associate to each vertex its indices; for each line
|
| 326 |
+
# between two vertices assign the
|
| 327 |
+
# even index to the vertex which comes first in items,
|
| 328 |
+
# the odd index to the other vertex
|
| 329 |
+
vertices = [[] for i in items]
|
| 330 |
+
i = 0
|
| 331 |
+
for v, neigh in items:
|
| 332 |
+
for v2 in neigh:
|
| 333 |
+
if pvert[v] < pvert[v2]:
|
| 334 |
+
vertices[pvert[v]].append(i)
|
| 335 |
+
vertices[pvert[v2]].append(i+1)
|
| 336 |
+
i += 2
|
| 337 |
+
g = []
|
| 338 |
+
for v in vertices:
|
| 339 |
+
g.extend(v)
|
| 340 |
+
assert len(g) == num_indices
|
| 341 |
+
g += [num_indices, num_indices + 1]
|
| 342 |
+
size = num_indices + 2
|
| 343 |
+
assert sorted(g) == list(range(size))
|
| 344 |
+
g = Permutation(g)
|
| 345 |
+
vlen = [0]*(len(vertices[0])+1)
|
| 346 |
+
for neigh in vertices:
|
| 347 |
+
vlen[len(neigh)] += 1
|
| 348 |
+
v = []
|
| 349 |
+
for i in range(len(vlen)):
|
| 350 |
+
n = vlen[i]
|
| 351 |
+
if n:
|
| 352 |
+
base, gens = get_symmetric_group_sgs(i)
|
| 353 |
+
v.append((base, gens, n, 0))
|
| 354 |
+
v.reverse()
|
| 355 |
+
dummies = list(range(num_indices))
|
| 356 |
+
can = canonicalize(g, dummies, 0, *v)
|
| 357 |
+
return can
|
lib/python3.12/site-packages/sympy/combinatorics/util.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul
|
| 2 |
+
from sympy.ntheory import isprime
|
| 3 |
+
|
| 4 |
+
rmul = Permutation.rmul
|
| 5 |
+
_af_new = Permutation._af_new
|
| 6 |
+
|
| 7 |
+
############################################
|
| 8 |
+
#
|
| 9 |
+
# Utilities for computational group theory
|
| 10 |
+
#
|
| 11 |
+
############################################
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _base_ordering(base, degree):
|
| 15 |
+
r"""
|
| 16 |
+
Order `\{0, 1, \dots, n-1\}` so that base points come first and in order.
|
| 17 |
+
|
| 18 |
+
Parameters
|
| 19 |
+
==========
|
| 20 |
+
|
| 21 |
+
base : the base
|
| 22 |
+
degree : the degree of the associated permutation group
|
| 23 |
+
|
| 24 |
+
Returns
|
| 25 |
+
=======
|
| 26 |
+
|
| 27 |
+
A list ``base_ordering`` such that ``base_ordering[point]`` is the
|
| 28 |
+
number of ``point`` in the ordering.
|
| 29 |
+
|
| 30 |
+
Examples
|
| 31 |
+
========
|
| 32 |
+
|
| 33 |
+
>>> from sympy.combinatorics import SymmetricGroup
|
| 34 |
+
>>> from sympy.combinatorics.util import _base_ordering
|
| 35 |
+
>>> S = SymmetricGroup(4)
|
| 36 |
+
>>> S.schreier_sims()
|
| 37 |
+
>>> _base_ordering(S.base, S.degree)
|
| 38 |
+
[0, 1, 2, 3]
|
| 39 |
+
|
| 40 |
+
Notes
|
| 41 |
+
=====
|
| 42 |
+
|
| 43 |
+
This is used in backtrack searches, when we define a relation `\ll` on
|
| 44 |
+
the underlying set for a permutation group of degree `n`,
|
| 45 |
+
`\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we
|
| 46 |
+
have `b_i \ll b_j` whenever `i<j` and `b_i \ll a` for all
|
| 47 |
+
`i\in\{1,2, \dots, k\}` and `a` is not in the base. The idea is developed
|
| 48 |
+
and applied to backtracking algorithms in [1], pp.108-132. The points
|
| 49 |
+
that are not in the base are taken in increasing order.
|
| 50 |
+
|
| 51 |
+
References
|
| 52 |
+
==========
|
| 53 |
+
|
| 54 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 55 |
+
"Handbook of computational group theory"
|
| 56 |
+
|
| 57 |
+
"""
|
| 58 |
+
base_len = len(base)
|
| 59 |
+
ordering = [0]*degree
|
| 60 |
+
for i in range(base_len):
|
| 61 |
+
ordering[base[i]] = i
|
| 62 |
+
current = base_len
|
| 63 |
+
for i in range(degree):
|
| 64 |
+
if i not in base:
|
| 65 |
+
ordering[i] = current
|
| 66 |
+
current += 1
|
| 67 |
+
return ordering
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _check_cycles_alt_sym(perm):
|
| 71 |
+
"""
|
| 72 |
+
Checks for cycles of prime length p with n/2 < p < n-2.
|
| 73 |
+
|
| 74 |
+
Explanation
|
| 75 |
+
===========
|
| 76 |
+
|
| 77 |
+
Here `n` is the degree of the permutation. This is a helper function for
|
| 78 |
+
the function is_alt_sym from sympy.combinatorics.perm_groups.
|
| 79 |
+
|
| 80 |
+
Examples
|
| 81 |
+
========
|
| 82 |
+
|
| 83 |
+
>>> from sympy.combinatorics.util import _check_cycles_alt_sym
|
| 84 |
+
>>> from sympy.combinatorics import Permutation
|
| 85 |
+
>>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]])
|
| 86 |
+
>>> _check_cycles_alt_sym(a)
|
| 87 |
+
False
|
| 88 |
+
>>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]])
|
| 89 |
+
>>> _check_cycles_alt_sym(b)
|
| 90 |
+
True
|
| 91 |
+
|
| 92 |
+
See Also
|
| 93 |
+
========
|
| 94 |
+
|
| 95 |
+
sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym
|
| 96 |
+
|
| 97 |
+
"""
|
| 98 |
+
n = perm.size
|
| 99 |
+
af = perm.array_form
|
| 100 |
+
current_len = 0
|
| 101 |
+
total_len = 0
|
| 102 |
+
used = set()
|
| 103 |
+
for i in range(n//2):
|
| 104 |
+
if i not in used and i < n//2 - total_len:
|
| 105 |
+
current_len = 1
|
| 106 |
+
used.add(i)
|
| 107 |
+
j = i
|
| 108 |
+
while af[j] != i:
|
| 109 |
+
current_len += 1
|
| 110 |
+
j = af[j]
|
| 111 |
+
used.add(j)
|
| 112 |
+
total_len += current_len
|
| 113 |
+
if current_len > n//2 and current_len < n - 2 and isprime(current_len):
|
| 114 |
+
return True
|
| 115 |
+
return False
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _distribute_gens_by_base(base, gens):
|
| 119 |
+
r"""
|
| 120 |
+
Distribute the group elements ``gens`` by membership in basic stabilizers.
|
| 121 |
+
|
| 122 |
+
Explanation
|
| 123 |
+
===========
|
| 124 |
+
|
| 125 |
+
Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers
|
| 126 |
+
are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for
|
| 127 |
+
`i \in\{1, 2, \dots, k\}`.
|
| 128 |
+
|
| 129 |
+
Parameters
|
| 130 |
+
==========
|
| 131 |
+
|
| 132 |
+
base : a sequence of points in `\{0, 1, \dots, n-1\}`
|
| 133 |
+
gens : a list of elements of a permutation group of degree `n`.
|
| 134 |
+
|
| 135 |
+
Returns
|
| 136 |
+
=======
|
| 137 |
+
list
|
| 138 |
+
List of length `k`, where `k` is the length of *base*. The `i`-th entry
|
| 139 |
+
contains those elements in *gens* which fix the first `i` elements of
|
| 140 |
+
*base* (so that the `0`-th entry is equal to *gens* itself). If no
|
| 141 |
+
element fixes the first `i` elements of *base*, the `i`-th element is
|
| 142 |
+
set to a list containing the identity element.
|
| 143 |
+
|
| 144 |
+
Examples
|
| 145 |
+
========
|
| 146 |
+
|
| 147 |
+
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
| 148 |
+
>>> from sympy.combinatorics.util import _distribute_gens_by_base
|
| 149 |
+
>>> D = DihedralGroup(3)
|
| 150 |
+
>>> D.schreier_sims()
|
| 151 |
+
>>> D.strong_gens
|
| 152 |
+
[(0 1 2), (0 2), (1 2)]
|
| 153 |
+
>>> D.base
|
| 154 |
+
[0, 1]
|
| 155 |
+
>>> _distribute_gens_by_base(D.base, D.strong_gens)
|
| 156 |
+
[[(0 1 2), (0 2), (1 2)],
|
| 157 |
+
[(1 2)]]
|
| 158 |
+
|
| 159 |
+
See Also
|
| 160 |
+
========
|
| 161 |
+
|
| 162 |
+
_strong_gens_from_distr, _orbits_transversals_from_bsgs,
|
| 163 |
+
_handle_precomputed_bsgs
|
| 164 |
+
|
| 165 |
+
"""
|
| 166 |
+
base_len = len(base)
|
| 167 |
+
degree = gens[0].size
|
| 168 |
+
stabs = [[] for _ in range(base_len)]
|
| 169 |
+
max_stab_index = 0
|
| 170 |
+
for gen in gens:
|
| 171 |
+
j = 0
|
| 172 |
+
while j < base_len - 1 and gen._array_form[base[j]] == base[j]:
|
| 173 |
+
j += 1
|
| 174 |
+
if j > max_stab_index:
|
| 175 |
+
max_stab_index = j
|
| 176 |
+
for k in range(j + 1):
|
| 177 |
+
stabs[k].append(gen)
|
| 178 |
+
for i in range(max_stab_index + 1, base_len):
|
| 179 |
+
stabs[i].append(_af_new(list(range(degree))))
|
| 180 |
+
return stabs
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _handle_precomputed_bsgs(base, strong_gens, transversals=None,
|
| 184 |
+
basic_orbits=None, strong_gens_distr=None):
|
| 185 |
+
"""
|
| 186 |
+
Calculate BSGS-related structures from those present.
|
| 187 |
+
|
| 188 |
+
Explanation
|
| 189 |
+
===========
|
| 190 |
+
|
| 191 |
+
The base and strong generating set must be provided; if any of the
|
| 192 |
+
transversals, basic orbits or distributed strong generators are not
|
| 193 |
+
provided, they will be calculated from the base and strong generating set.
|
| 194 |
+
|
| 195 |
+
Parameters
|
| 196 |
+
==========
|
| 197 |
+
|
| 198 |
+
base : the base
|
| 199 |
+
strong_gens : the strong generators
|
| 200 |
+
transversals : basic transversals
|
| 201 |
+
basic_orbits : basic orbits
|
| 202 |
+
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
| 203 |
+
|
| 204 |
+
Returns
|
| 205 |
+
=======
|
| 206 |
+
|
| 207 |
+
(transversals, basic_orbits, strong_gens_distr)
|
| 208 |
+
where *transversals* are the basic transversals, *basic_orbits* are the
|
| 209 |
+
basic orbits, and *strong_gens_distr* are the strong generators distributed
|
| 210 |
+
by membership in basic stabilizers.
|
| 211 |
+
|
| 212 |
+
Examples
|
| 213 |
+
========
|
| 214 |
+
|
| 215 |
+
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
| 216 |
+
>>> from sympy.combinatorics.util import _handle_precomputed_bsgs
|
| 217 |
+
>>> D = DihedralGroup(3)
|
| 218 |
+
>>> D.schreier_sims()
|
| 219 |
+
>>> _handle_precomputed_bsgs(D.base, D.strong_gens,
|
| 220 |
+
... basic_orbits=D.basic_orbits)
|
| 221 |
+
([{0: (2), 1: (0 1 2), 2: (0 2)}, {1: (2), 2: (1 2)}], [[0, 1, 2], [1, 2]], [[(0 1 2), (0 2), (1 2)], [(1 2)]])
|
| 222 |
+
|
| 223 |
+
See Also
|
| 224 |
+
========
|
| 225 |
+
|
| 226 |
+
_orbits_transversals_from_bsgs, _distribute_gens_by_base
|
| 227 |
+
|
| 228 |
+
"""
|
| 229 |
+
if strong_gens_distr is None:
|
| 230 |
+
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
| 231 |
+
if transversals is None:
|
| 232 |
+
if basic_orbits is None:
|
| 233 |
+
basic_orbits, transversals = \
|
| 234 |
+
_orbits_transversals_from_bsgs(base, strong_gens_distr)
|
| 235 |
+
else:
|
| 236 |
+
transversals = \
|
| 237 |
+
_orbits_transversals_from_bsgs(base, strong_gens_distr,
|
| 238 |
+
transversals_only=True)
|
| 239 |
+
else:
|
| 240 |
+
if basic_orbits is None:
|
| 241 |
+
base_len = len(base)
|
| 242 |
+
basic_orbits = [None]*base_len
|
| 243 |
+
for i in range(base_len):
|
| 244 |
+
basic_orbits[i] = list(transversals[i].keys())
|
| 245 |
+
return transversals, basic_orbits, strong_gens_distr
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _orbits_transversals_from_bsgs(base, strong_gens_distr,
|
| 249 |
+
transversals_only=False, slp=False):
|
| 250 |
+
"""
|
| 251 |
+
Compute basic orbits and transversals from a base and strong generating set.
|
| 252 |
+
|
| 253 |
+
Explanation
|
| 254 |
+
===========
|
| 255 |
+
|
| 256 |
+
The generators are provided as distributed across the basic stabilizers.
|
| 257 |
+
If the optional argument ``transversals_only`` is set to True, only the
|
| 258 |
+
transversals are returned.
|
| 259 |
+
|
| 260 |
+
Parameters
|
| 261 |
+
==========
|
| 262 |
+
|
| 263 |
+
base : The base.
|
| 264 |
+
strong_gens_distr : Strong generators distributed by membership in basic stabilizers.
|
| 265 |
+
transversals_only : bool, default: False
|
| 266 |
+
A flag switching between returning only the
|
| 267 |
+
transversals and both orbits and transversals.
|
| 268 |
+
slp : bool, default: False
|
| 269 |
+
If ``True``, return a list of dictionaries containing the
|
| 270 |
+
generator presentations of the elements of the transversals,
|
| 271 |
+
i.e. the list of indices of generators from ``strong_gens_distr[i]``
|
| 272 |
+
such that their product is the relevant transversal element.
|
| 273 |
+
|
| 274 |
+
Examples
|
| 275 |
+
========
|
| 276 |
+
|
| 277 |
+
>>> from sympy.combinatorics import SymmetricGroup
|
| 278 |
+
>>> from sympy.combinatorics.util import _distribute_gens_by_base
|
| 279 |
+
>>> S = SymmetricGroup(3)
|
| 280 |
+
>>> S.schreier_sims()
|
| 281 |
+
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
|
| 282 |
+
>>> (S.base, strong_gens_distr)
|
| 283 |
+
([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]])
|
| 284 |
+
|
| 285 |
+
See Also
|
| 286 |
+
========
|
| 287 |
+
|
| 288 |
+
_distribute_gens_by_base, _handle_precomputed_bsgs
|
| 289 |
+
|
| 290 |
+
"""
|
| 291 |
+
from sympy.combinatorics.perm_groups import _orbit_transversal
|
| 292 |
+
base_len = len(base)
|
| 293 |
+
degree = strong_gens_distr[0][0].size
|
| 294 |
+
transversals = [None]*base_len
|
| 295 |
+
slps = [None]*base_len
|
| 296 |
+
if transversals_only is False:
|
| 297 |
+
basic_orbits = [None]*base_len
|
| 298 |
+
for i in range(base_len):
|
| 299 |
+
transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
|
| 300 |
+
base[i], pairs=True, slp=True)
|
| 301 |
+
transversals[i] = dict(transversals[i])
|
| 302 |
+
if transversals_only is False:
|
| 303 |
+
basic_orbits[i] = list(transversals[i].keys())
|
| 304 |
+
if transversals_only:
|
| 305 |
+
return transversals
|
| 306 |
+
else:
|
| 307 |
+
if not slp:
|
| 308 |
+
return basic_orbits, transversals
|
| 309 |
+
return basic_orbits, transversals, slps
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None):
|
| 313 |
+
"""
|
| 314 |
+
Remove redundant generators from a strong generating set.
|
| 315 |
+
|
| 316 |
+
Parameters
|
| 317 |
+
==========
|
| 318 |
+
|
| 319 |
+
base : a base
|
| 320 |
+
strong_gens : a strong generating set relative to *base*
|
| 321 |
+
basic_orbits : basic orbits
|
| 322 |
+
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
| 323 |
+
|
| 324 |
+
Returns
|
| 325 |
+
=======
|
| 326 |
+
|
| 327 |
+
A strong generating set with respect to ``base`` which is a subset of
|
| 328 |
+
``strong_gens``.
|
| 329 |
+
|
| 330 |
+
Examples
|
| 331 |
+
========
|
| 332 |
+
|
| 333 |
+
>>> from sympy.combinatorics import SymmetricGroup
|
| 334 |
+
>>> from sympy.combinatorics.util import _remove_gens
|
| 335 |
+
>>> from sympy.combinatorics.testutil import _verify_bsgs
|
| 336 |
+
>>> S = SymmetricGroup(15)
|
| 337 |
+
>>> base, strong_gens = S.schreier_sims_incremental()
|
| 338 |
+
>>> new_gens = _remove_gens(base, strong_gens)
|
| 339 |
+
>>> len(new_gens)
|
| 340 |
+
14
|
| 341 |
+
>>> _verify_bsgs(S, base, new_gens)
|
| 342 |
+
True
|
| 343 |
+
|
| 344 |
+
Notes
|
| 345 |
+
=====
|
| 346 |
+
|
| 347 |
+
This procedure is outlined in [1],p.95.
|
| 348 |
+
|
| 349 |
+
References
|
| 350 |
+
==========
|
| 351 |
+
|
| 352 |
+
.. [1] Holt, D., Eick, B., O'Brien, E.
|
| 353 |
+
"Handbook of computational group theory"
|
| 354 |
+
|
| 355 |
+
"""
|
| 356 |
+
from sympy.combinatorics.perm_groups import _orbit
|
| 357 |
+
base_len = len(base)
|
| 358 |
+
degree = strong_gens[0].size
|
| 359 |
+
if strong_gens_distr is None:
|
| 360 |
+
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
| 361 |
+
if basic_orbits is None:
|
| 362 |
+
basic_orbits = []
|
| 363 |
+
for i in range(base_len):
|
| 364 |
+
basic_orbit = _orbit(degree, strong_gens_distr[i], base[i])
|
| 365 |
+
basic_orbits.append(basic_orbit)
|
| 366 |
+
strong_gens_distr.append([])
|
| 367 |
+
res = strong_gens[:]
|
| 368 |
+
for i in range(base_len - 1, -1, -1):
|
| 369 |
+
gens_copy = strong_gens_distr[i][:]
|
| 370 |
+
for gen in strong_gens_distr[i]:
|
| 371 |
+
if gen not in strong_gens_distr[i + 1]:
|
| 372 |
+
temp_gens = gens_copy[:]
|
| 373 |
+
temp_gens.remove(gen)
|
| 374 |
+
if temp_gens == []:
|
| 375 |
+
continue
|
| 376 |
+
temp_orbit = _orbit(degree, temp_gens, base[i])
|
| 377 |
+
if temp_orbit == basic_orbits[i]:
|
| 378 |
+
gens_copy.remove(gen)
|
| 379 |
+
res.remove(gen)
|
| 380 |
+
return res
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def _strip(g, base, orbits, transversals):
|
| 384 |
+
"""
|
| 385 |
+
Attempt to decompose a permutation using a (possibly partial) BSGS
|
| 386 |
+
structure.
|
| 387 |
+
|
| 388 |
+
Explanation
|
| 389 |
+
===========
|
| 390 |
+
|
| 391 |
+
This is done by treating the sequence ``base`` as an actual base, and
|
| 392 |
+
the orbits ``orbits`` and transversals ``transversals`` as basic orbits and
|
| 393 |
+
transversals relative to it.
|
| 394 |
+
|
| 395 |
+
This process is called "sifting". A sift is unsuccessful when a certain
|
| 396 |
+
orbit element is not found or when after the sift the decomposition
|
| 397 |
+
does not end with the identity element.
|
| 398 |
+
|
| 399 |
+
The argument ``transversals`` is a list of dictionaries that provides
|
| 400 |
+
transversal elements for the orbits ``orbits``.
|
| 401 |
+
|
| 402 |
+
Parameters
|
| 403 |
+
==========
|
| 404 |
+
|
| 405 |
+
g : permutation to be decomposed
|
| 406 |
+
base : sequence of points
|
| 407 |
+
orbits : list
|
| 408 |
+
A list in which the ``i``-th entry is an orbit of ``base[i]``
|
| 409 |
+
under some subgroup of the pointwise stabilizer of `
|
| 410 |
+
`base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit
|
| 411 |
+
in this function since the only information we need is encoded in the orbits
|
| 412 |
+
and transversals
|
| 413 |
+
transversals : list
|
| 414 |
+
A list of orbit transversals associated with the orbits *orbits*.
|
| 415 |
+
|
| 416 |
+
Examples
|
| 417 |
+
========
|
| 418 |
+
|
| 419 |
+
>>> from sympy.combinatorics import Permutation, SymmetricGroup
|
| 420 |
+
>>> from sympy.combinatorics.util import _strip
|
| 421 |
+
>>> S = SymmetricGroup(5)
|
| 422 |
+
>>> S.schreier_sims()
|
| 423 |
+
>>> g = Permutation([0, 2, 3, 1, 4])
|
| 424 |
+
>>> _strip(g, S.base, S.basic_orbits, S.basic_transversals)
|
| 425 |
+
((4), 5)
|
| 426 |
+
|
| 427 |
+
Notes
|
| 428 |
+
=====
|
| 429 |
+
|
| 430 |
+
The algorithm is described in [1],pp.89-90. The reason for returning
|
| 431 |
+
both the current state of the element being decomposed and the level
|
| 432 |
+
at which the sifting ends is that they provide important information for
|
| 433 |
+
the randomized version of the Schreier-Sims algorithm.
|
| 434 |
+
|
| 435 |
+
References
|
| 436 |
+
==========
|
| 437 |
+
|
| 438 |
+
.. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory"
|
| 439 |
+
|
| 440 |
+
See Also
|
| 441 |
+
========
|
| 442 |
+
|
| 443 |
+
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
|
| 444 |
+
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random
|
| 445 |
+
|
| 446 |
+
"""
|
| 447 |
+
h = g._array_form
|
| 448 |
+
base_len = len(base)
|
| 449 |
+
for i in range(base_len):
|
| 450 |
+
beta = h[base[i]]
|
| 451 |
+
if beta == base[i]:
|
| 452 |
+
continue
|
| 453 |
+
if beta not in orbits[i]:
|
| 454 |
+
return _af_new(h), i + 1
|
| 455 |
+
u = transversals[i][beta]._array_form
|
| 456 |
+
h = _af_rmul(_af_invert(u), h)
|
| 457 |
+
return _af_new(h), base_len + 1
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}):
|
| 461 |
+
"""
|
| 462 |
+
optimized _strip, with h, transversals and result in array form
|
| 463 |
+
if the stripped elements is the identity, it returns False, base_len + 1
|
| 464 |
+
|
| 465 |
+
j h[base[i]] == base[i] for i <= j
|
| 466 |
+
|
| 467 |
+
"""
|
| 468 |
+
base_len = len(base)
|
| 469 |
+
for i in range(j+1, base_len):
|
| 470 |
+
beta = h[base[i]]
|
| 471 |
+
if beta == base[i]:
|
| 472 |
+
continue
|
| 473 |
+
if beta not in orbits[i]:
|
| 474 |
+
if not slp:
|
| 475 |
+
return h, i + 1
|
| 476 |
+
return h, i + 1, slp
|
| 477 |
+
u = transversals[i][beta]
|
| 478 |
+
if h == u:
|
| 479 |
+
if not slp:
|
| 480 |
+
return False, base_len + 1
|
| 481 |
+
return False, base_len + 1, slp
|
| 482 |
+
h = _af_rmul(_af_invert(u), h)
|
| 483 |
+
if slp:
|
| 484 |
+
u_slp = slps[i][beta][:]
|
| 485 |
+
u_slp.reverse()
|
| 486 |
+
u_slp = [(i, (g,)) for g in u_slp]
|
| 487 |
+
slp = u_slp + slp
|
| 488 |
+
if not slp:
|
| 489 |
+
return h, base_len + 1
|
| 490 |
+
return h, base_len + 1, slp
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
def _strong_gens_from_distr(strong_gens_distr):
|
| 494 |
+
"""
|
| 495 |
+
Retrieve strong generating set from generators of basic stabilizers.
|
| 496 |
+
|
| 497 |
+
This is just the union of the generators of the first and second basic
|
| 498 |
+
stabilizers.
|
| 499 |
+
|
| 500 |
+
Parameters
|
| 501 |
+
==========
|
| 502 |
+
|
| 503 |
+
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
| 504 |
+
|
| 505 |
+
Examples
|
| 506 |
+
========
|
| 507 |
+
|
| 508 |
+
>>> from sympy.combinatorics import SymmetricGroup
|
| 509 |
+
>>> from sympy.combinatorics.util import (_strong_gens_from_distr,
|
| 510 |
+
... _distribute_gens_by_base)
|
| 511 |
+
>>> S = SymmetricGroup(3)
|
| 512 |
+
>>> S.schreier_sims()
|
| 513 |
+
>>> S.strong_gens
|
| 514 |
+
[(0 1 2), (2)(0 1), (1 2)]
|
| 515 |
+
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
|
| 516 |
+
>>> _strong_gens_from_distr(strong_gens_distr)
|
| 517 |
+
[(0 1 2), (2)(0 1), (1 2)]
|
| 518 |
+
|
| 519 |
+
See Also
|
| 520 |
+
========
|
| 521 |
+
|
| 522 |
+
_distribute_gens_by_base
|
| 523 |
+
|
| 524 |
+
"""
|
| 525 |
+
if len(strong_gens_distr) == 1:
|
| 526 |
+
return strong_gens_distr[0][:]
|
| 527 |
+
else:
|
| 528 |
+
result = strong_gens_distr[0]
|
| 529 |
+
for gen in strong_gens_distr[1]:
|
| 530 |
+
if gen not in result:
|
| 531 |
+
result.append(gen)
|
| 532 |
+
return result
|
lib/python3.12/site-packages/sympy/functions/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (6.15 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Stub __init__.py for sympy.functions.combinatorial
|
lib/python3.12/site-packages/sympy/functions/combinatorial/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (208 Bytes). View file
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/__pycache__/factorials.cpython-312.pyc
ADDED
|
Binary file (47.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/factorials.py
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
from functools import reduce
|
| 3 |
+
|
| 4 |
+
from sympy.core import S, sympify, Dummy, Mod
|
| 5 |
+
from sympy.core.cache import cacheit
|
| 6 |
+
from sympy.core.function import Function, ArgumentIndexError, PoleError
|
| 7 |
+
from sympy.core.logic import fuzzy_and
|
| 8 |
+
from sympy.core.numbers import Integer, pi, I
|
| 9 |
+
from sympy.core.relational import Eq
|
| 10 |
+
from sympy.external.gmpy import gmpy as _gmpy
|
| 11 |
+
from sympy.ntheory import sieve
|
| 12 |
+
from sympy.ntheory.residue_ntheory import binomial_mod
|
| 13 |
+
from sympy.polys.polytools import Poly
|
| 14 |
+
|
| 15 |
+
from math import factorial as _factorial, prod, sqrt as _sqrt
|
| 16 |
+
|
| 17 |
+
class CombinatorialFunction(Function):
|
| 18 |
+
"""Base class for combinatorial functions. """
|
| 19 |
+
|
| 20 |
+
def _eval_simplify(self, **kwargs):
|
| 21 |
+
from sympy.simplify.combsimp import combsimp
|
| 22 |
+
# combinatorial function with non-integer arguments is
|
| 23 |
+
# automatically passed to gammasimp
|
| 24 |
+
expr = combsimp(self)
|
| 25 |
+
measure = kwargs['measure']
|
| 26 |
+
if measure(expr) <= kwargs['ratio']*measure(self):
|
| 27 |
+
return expr
|
| 28 |
+
return self
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
###############################################################################
|
| 32 |
+
######################## FACTORIAL and MULTI-FACTORIAL ########################
|
| 33 |
+
###############################################################################
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class factorial(CombinatorialFunction):
|
| 37 |
+
r"""Implementation of factorial function over nonnegative integers.
|
| 38 |
+
By convention (consistent with the gamma function and the binomial
|
| 39 |
+
coefficients), factorial of a negative integer is complex infinity.
|
| 40 |
+
|
| 41 |
+
The factorial is very important in combinatorics where it gives
|
| 42 |
+
the number of ways in which `n` objects can be permuted. It also
|
| 43 |
+
arises in calculus, probability, number theory, etc.
|
| 44 |
+
|
| 45 |
+
There is strict relation of factorial with gamma function. In
|
| 46 |
+
fact `n! = gamma(n+1)` for nonnegative integers. Rewrite of this
|
| 47 |
+
kind is very useful in case of combinatorial simplification.
|
| 48 |
+
|
| 49 |
+
Computation of the factorial is done using two algorithms. For
|
| 50 |
+
small arguments a precomputed look up table is used. However for bigger
|
| 51 |
+
input algorithm Prime-Swing is used. It is the fastest algorithm
|
| 52 |
+
known and computes `n!` via prime factorization of special class
|
| 53 |
+
of numbers, called here the 'Swing Numbers'.
|
| 54 |
+
|
| 55 |
+
Examples
|
| 56 |
+
========
|
| 57 |
+
|
| 58 |
+
>>> from sympy import Symbol, factorial, S
|
| 59 |
+
>>> n = Symbol('n', integer=True)
|
| 60 |
+
|
| 61 |
+
>>> factorial(0)
|
| 62 |
+
1
|
| 63 |
+
|
| 64 |
+
>>> factorial(7)
|
| 65 |
+
5040
|
| 66 |
+
|
| 67 |
+
>>> factorial(-2)
|
| 68 |
+
zoo
|
| 69 |
+
|
| 70 |
+
>>> factorial(n)
|
| 71 |
+
factorial(n)
|
| 72 |
+
|
| 73 |
+
>>> factorial(2*n)
|
| 74 |
+
factorial(2*n)
|
| 75 |
+
|
| 76 |
+
>>> factorial(S(1)/2)
|
| 77 |
+
factorial(1/2)
|
| 78 |
+
|
| 79 |
+
See Also
|
| 80 |
+
========
|
| 81 |
+
|
| 82 |
+
factorial2, RisingFactorial, FallingFactorial
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
def fdiff(self, argindex=1):
|
| 86 |
+
from sympy.functions.special.gamma_functions import (gamma, polygamma)
|
| 87 |
+
if argindex == 1:
|
| 88 |
+
return gamma(self.args[0] + 1)*polygamma(0, self.args[0] + 1)
|
| 89 |
+
else:
|
| 90 |
+
raise ArgumentIndexError(self, argindex)
|
| 91 |
+
|
| 92 |
+
_small_swing = [
|
| 93 |
+
1, 1, 1, 3, 3, 15, 5, 35, 35, 315, 63, 693, 231, 3003, 429, 6435, 6435, 109395,
|
| 94 |
+
12155, 230945, 46189, 969969, 88179, 2028117, 676039, 16900975, 1300075,
|
| 95 |
+
35102025, 5014575, 145422675, 9694845, 300540195, 300540195
|
| 96 |
+
]
|
| 97 |
+
|
| 98 |
+
_small_factorials: list[int] = []
|
| 99 |
+
|
| 100 |
+
@classmethod
|
| 101 |
+
def _swing(cls, n):
|
| 102 |
+
if n < 33:
|
| 103 |
+
return cls._small_swing[n]
|
| 104 |
+
else:
|
| 105 |
+
N, primes = int(_sqrt(n)), []
|
| 106 |
+
|
| 107 |
+
for prime in sieve.primerange(3, N + 1):
|
| 108 |
+
p, q = 1, n
|
| 109 |
+
|
| 110 |
+
while True:
|
| 111 |
+
q //= prime
|
| 112 |
+
|
| 113 |
+
if q > 0:
|
| 114 |
+
if q & 1 == 1:
|
| 115 |
+
p *= prime
|
| 116 |
+
else:
|
| 117 |
+
break
|
| 118 |
+
|
| 119 |
+
if p > 1:
|
| 120 |
+
primes.append(p)
|
| 121 |
+
|
| 122 |
+
for prime in sieve.primerange(N + 1, n//3 + 1):
|
| 123 |
+
if (n // prime) & 1 == 1:
|
| 124 |
+
primes.append(prime)
|
| 125 |
+
|
| 126 |
+
L_product = prod(sieve.primerange(n//2 + 1, n + 1))
|
| 127 |
+
R_product = prod(primes)
|
| 128 |
+
|
| 129 |
+
return L_product*R_product
|
| 130 |
+
|
| 131 |
+
@classmethod
|
| 132 |
+
def _recursive(cls, n):
|
| 133 |
+
if n < 2:
|
| 134 |
+
return 1
|
| 135 |
+
else:
|
| 136 |
+
return (cls._recursive(n//2)**2)*cls._swing(n)
|
| 137 |
+
|
| 138 |
+
@classmethod
|
| 139 |
+
def eval(cls, n):
|
| 140 |
+
n = sympify(n)
|
| 141 |
+
|
| 142 |
+
if n.is_Number:
|
| 143 |
+
if n.is_zero:
|
| 144 |
+
return S.One
|
| 145 |
+
elif n is S.Infinity:
|
| 146 |
+
return S.Infinity
|
| 147 |
+
elif n.is_Integer:
|
| 148 |
+
if n.is_negative:
|
| 149 |
+
return S.ComplexInfinity
|
| 150 |
+
else:
|
| 151 |
+
n = n.p
|
| 152 |
+
|
| 153 |
+
if n < 20:
|
| 154 |
+
if not cls._small_factorials:
|
| 155 |
+
result = 1
|
| 156 |
+
for i in range(1, 20):
|
| 157 |
+
result *= i
|
| 158 |
+
cls._small_factorials.append(result)
|
| 159 |
+
result = cls._small_factorials[n-1]
|
| 160 |
+
|
| 161 |
+
# GMPY factorial is faster, use it when available
|
| 162 |
+
#
|
| 163 |
+
# XXX: There is a sympy.external.gmpy.factorial function
|
| 164 |
+
# which provides gmpy.fac if available or the flint version
|
| 165 |
+
# if flint is used. It could be used here to avoid the
|
| 166 |
+
# conditional logic but it needs to be checked whether the
|
| 167 |
+
# pure Python fallback used there is as fast as the
|
| 168 |
+
# fallback used here (perhaps the fallback here should be
|
| 169 |
+
# moved to sympy.external.ntheory).
|
| 170 |
+
elif _gmpy is not None:
|
| 171 |
+
result = _gmpy.fac(n)
|
| 172 |
+
|
| 173 |
+
else:
|
| 174 |
+
bits = bin(n).count('1')
|
| 175 |
+
result = cls._recursive(n)*2**(n - bits)
|
| 176 |
+
|
| 177 |
+
return Integer(result)
|
| 178 |
+
|
| 179 |
+
def _facmod(self, n, q):
|
| 180 |
+
res, N = 1, int(_sqrt(n))
|
| 181 |
+
|
| 182 |
+
# Exponent of prime p in n! is e_p(n) = [n/p] + [n/p**2] + ...
|
| 183 |
+
# for p > sqrt(n), e_p(n) < sqrt(n), the primes with [n/p] = m,
|
| 184 |
+
# occur consecutively and are grouped together in pw[m] for
|
| 185 |
+
# simultaneous exponentiation at a later stage
|
| 186 |
+
pw = [1]*N
|
| 187 |
+
|
| 188 |
+
m = 2 # to initialize the if condition below
|
| 189 |
+
for prime in sieve.primerange(2, n + 1):
|
| 190 |
+
if m > 1:
|
| 191 |
+
m, y = 0, n // prime
|
| 192 |
+
while y:
|
| 193 |
+
m += y
|
| 194 |
+
y //= prime
|
| 195 |
+
if m < N:
|
| 196 |
+
pw[m] = pw[m]*prime % q
|
| 197 |
+
else:
|
| 198 |
+
res = res*pow(prime, m, q) % q
|
| 199 |
+
|
| 200 |
+
for ex, bs in enumerate(pw):
|
| 201 |
+
if ex == 0 or bs == 1:
|
| 202 |
+
continue
|
| 203 |
+
if bs == 0:
|
| 204 |
+
return 0
|
| 205 |
+
res = res*pow(bs, ex, q) % q
|
| 206 |
+
|
| 207 |
+
return res
|
| 208 |
+
|
| 209 |
+
def _eval_Mod(self, q):
|
| 210 |
+
n = self.args[0]
|
| 211 |
+
if n.is_integer and n.is_nonnegative and q.is_integer:
|
| 212 |
+
aq = abs(q)
|
| 213 |
+
d = aq - n
|
| 214 |
+
if d.is_nonpositive:
|
| 215 |
+
return S.Zero
|
| 216 |
+
else:
|
| 217 |
+
isprime = aq.is_prime
|
| 218 |
+
if d == 1:
|
| 219 |
+
# Apply Wilson's theorem (if a natural number n > 1
|
| 220 |
+
# is a prime number, then (n-1)! = -1 mod n) and
|
| 221 |
+
# its inverse (if n > 4 is a composite number, then
|
| 222 |
+
# (n-1)! = 0 mod n)
|
| 223 |
+
if isprime:
|
| 224 |
+
return -1 % q
|
| 225 |
+
elif isprime is False and (aq - 6).is_nonnegative:
|
| 226 |
+
return S.Zero
|
| 227 |
+
elif n.is_Integer and q.is_Integer:
|
| 228 |
+
n, d, aq = map(int, (n, d, aq))
|
| 229 |
+
if isprime and (d - 1 < n):
|
| 230 |
+
fc = self._facmod(d - 1, aq)
|
| 231 |
+
fc = pow(fc, aq - 2, aq)
|
| 232 |
+
if d%2:
|
| 233 |
+
fc = -fc
|
| 234 |
+
else:
|
| 235 |
+
fc = self._facmod(n, aq)
|
| 236 |
+
|
| 237 |
+
return fc % q
|
| 238 |
+
|
| 239 |
+
def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
|
| 240 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 241 |
+
return gamma(n + 1)
|
| 242 |
+
|
| 243 |
+
def _eval_rewrite_as_Product(self, n, **kwargs):
|
| 244 |
+
from sympy.concrete.products import Product
|
| 245 |
+
if n.is_nonnegative and n.is_integer:
|
| 246 |
+
i = Dummy('i', integer=True)
|
| 247 |
+
return Product(i, (i, 1, n))
|
| 248 |
+
|
| 249 |
+
def _eval_is_integer(self):
|
| 250 |
+
if self.args[0].is_integer and self.args[0].is_nonnegative:
|
| 251 |
+
return True
|
| 252 |
+
|
| 253 |
+
def _eval_is_positive(self):
|
| 254 |
+
if self.args[0].is_integer and self.args[0].is_nonnegative:
|
| 255 |
+
return True
|
| 256 |
+
|
| 257 |
+
def _eval_is_even(self):
|
| 258 |
+
x = self.args[0]
|
| 259 |
+
if x.is_integer and x.is_nonnegative:
|
| 260 |
+
return (x - 2).is_nonnegative
|
| 261 |
+
|
| 262 |
+
def _eval_is_composite(self):
|
| 263 |
+
x = self.args[0]
|
| 264 |
+
if x.is_integer and x.is_nonnegative:
|
| 265 |
+
return (x - 3).is_nonnegative
|
| 266 |
+
|
| 267 |
+
def _eval_is_real(self):
|
| 268 |
+
x = self.args[0]
|
| 269 |
+
if x.is_nonnegative or x.is_noninteger:
|
| 270 |
+
return True
|
| 271 |
+
|
| 272 |
+
def _eval_as_leading_term(self, x, logx=None, cdir=0):
|
| 273 |
+
arg = self.args[0].as_leading_term(x)
|
| 274 |
+
arg0 = arg.subs(x, 0)
|
| 275 |
+
if arg0.is_zero:
|
| 276 |
+
return S.One
|
| 277 |
+
elif not arg0.is_infinite:
|
| 278 |
+
return self.func(arg)
|
| 279 |
+
raise PoleError("Cannot expand %s around 0" % (self))
|
| 280 |
+
|
| 281 |
+
class MultiFactorial(CombinatorialFunction):
|
| 282 |
+
pass
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
class subfactorial(CombinatorialFunction):
|
| 286 |
+
r"""The subfactorial counts the derangements of $n$ items and is
|
| 287 |
+
defined for non-negative integers as:
|
| 288 |
+
|
| 289 |
+
.. math:: !n = \begin{cases} 1 & n = 0 \\ 0 & n = 1 \\
|
| 290 |
+
(n-1)(!(n-1) + !(n-2)) & n > 1 \end{cases}
|
| 291 |
+
|
| 292 |
+
It can also be written as ``int(round(n!/exp(1)))`` but the
|
| 293 |
+
recursive definition with caching is implemented for this function.
|
| 294 |
+
|
| 295 |
+
An interesting analytic expression is the following [2]_
|
| 296 |
+
|
| 297 |
+
.. math:: !x = \Gamma(x + 1, -1)/e
|
| 298 |
+
|
| 299 |
+
which is valid for non-negative integers `x`. The above formula
|
| 300 |
+
is not very useful in case of non-integers. `\Gamma(x + 1, -1)` is
|
| 301 |
+
single-valued only for integral arguments `x`, elsewhere on the positive
|
| 302 |
+
real axis it has an infinite number of branches none of which are real.
|
| 303 |
+
|
| 304 |
+
References
|
| 305 |
+
==========
|
| 306 |
+
|
| 307 |
+
.. [1] https://en.wikipedia.org/wiki/Subfactorial
|
| 308 |
+
.. [2] https://mathworld.wolfram.com/Subfactorial.html
|
| 309 |
+
|
| 310 |
+
Examples
|
| 311 |
+
========
|
| 312 |
+
|
| 313 |
+
>>> from sympy import subfactorial
|
| 314 |
+
>>> from sympy.abc import n
|
| 315 |
+
>>> subfactorial(n + 1)
|
| 316 |
+
subfactorial(n + 1)
|
| 317 |
+
>>> subfactorial(5)
|
| 318 |
+
44
|
| 319 |
+
|
| 320 |
+
See Also
|
| 321 |
+
========
|
| 322 |
+
|
| 323 |
+
factorial, uppergamma,
|
| 324 |
+
sympy.utilities.iterables.generate_derangements
|
| 325 |
+
"""
|
| 326 |
+
|
| 327 |
+
@classmethod
|
| 328 |
+
@cacheit
|
| 329 |
+
def _eval(self, n):
|
| 330 |
+
if not n:
|
| 331 |
+
return S.One
|
| 332 |
+
elif n == 1:
|
| 333 |
+
return S.Zero
|
| 334 |
+
else:
|
| 335 |
+
z1, z2 = 1, 0
|
| 336 |
+
for i in range(2, n + 1):
|
| 337 |
+
z1, z2 = z2, (i - 1)*(z2 + z1)
|
| 338 |
+
return z2
|
| 339 |
+
|
| 340 |
+
@classmethod
|
| 341 |
+
def eval(cls, arg):
|
| 342 |
+
if arg.is_Number:
|
| 343 |
+
if arg.is_Integer and arg.is_nonnegative:
|
| 344 |
+
return cls._eval(arg)
|
| 345 |
+
elif arg is S.NaN:
|
| 346 |
+
return S.NaN
|
| 347 |
+
elif arg is S.Infinity:
|
| 348 |
+
return S.Infinity
|
| 349 |
+
|
| 350 |
+
def _eval_is_even(self):
|
| 351 |
+
if self.args[0].is_odd and self.args[0].is_nonnegative:
|
| 352 |
+
return True
|
| 353 |
+
|
| 354 |
+
def _eval_is_integer(self):
|
| 355 |
+
if self.args[0].is_integer and self.args[0].is_nonnegative:
|
| 356 |
+
return True
|
| 357 |
+
|
| 358 |
+
def _eval_rewrite_as_factorial(self, arg, **kwargs):
|
| 359 |
+
from sympy.concrete.summations import summation
|
| 360 |
+
i = Dummy('i')
|
| 361 |
+
f = S.NegativeOne**i / factorial(i)
|
| 362 |
+
return factorial(arg) * summation(f, (i, 0, arg))
|
| 363 |
+
|
| 364 |
+
def _eval_rewrite_as_gamma(self, arg, piecewise=True, **kwargs):
|
| 365 |
+
from sympy.functions.elementary.exponential import exp
|
| 366 |
+
from sympy.functions.special.gamma_functions import (gamma, lowergamma)
|
| 367 |
+
return (S.NegativeOne**(arg + 1)*exp(-I*pi*arg)*lowergamma(arg + 1, -1)
|
| 368 |
+
+ gamma(arg + 1))*exp(-1)
|
| 369 |
+
|
| 370 |
+
def _eval_rewrite_as_uppergamma(self, arg, **kwargs):
|
| 371 |
+
from sympy.functions.special.gamma_functions import uppergamma
|
| 372 |
+
return uppergamma(arg + 1, -1)/S.Exp1
|
| 373 |
+
|
| 374 |
+
def _eval_is_nonnegative(self):
|
| 375 |
+
if self.args[0].is_integer and self.args[0].is_nonnegative:
|
| 376 |
+
return True
|
| 377 |
+
|
| 378 |
+
def _eval_is_odd(self):
|
| 379 |
+
if self.args[0].is_even and self.args[0].is_nonnegative:
|
| 380 |
+
return True
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
class factorial2(CombinatorialFunction):
|
| 384 |
+
r"""The double factorial `n!!`, not to be confused with `(n!)!`
|
| 385 |
+
|
| 386 |
+
The double factorial is defined for nonnegative integers and for odd
|
| 387 |
+
negative integers as:
|
| 388 |
+
|
| 389 |
+
.. math:: n!! = \begin{cases} 1 & n = 0 \\
|
| 390 |
+
n(n-2)(n-4) \cdots 1 & n\ \text{positive odd} \\
|
| 391 |
+
n(n-2)(n-4) \cdots 2 & n\ \text{positive even} \\
|
| 392 |
+
(n+2)!!/(n+2) & n\ \text{negative odd} \end{cases}
|
| 393 |
+
|
| 394 |
+
References
|
| 395 |
+
==========
|
| 396 |
+
|
| 397 |
+
.. [1] https://en.wikipedia.org/wiki/Double_factorial
|
| 398 |
+
|
| 399 |
+
Examples
|
| 400 |
+
========
|
| 401 |
+
|
| 402 |
+
>>> from sympy import factorial2, var
|
| 403 |
+
>>> n = var('n')
|
| 404 |
+
>>> n
|
| 405 |
+
n
|
| 406 |
+
>>> factorial2(n + 1)
|
| 407 |
+
factorial2(n + 1)
|
| 408 |
+
>>> factorial2(5)
|
| 409 |
+
15
|
| 410 |
+
>>> factorial2(-1)
|
| 411 |
+
1
|
| 412 |
+
>>> factorial2(-5)
|
| 413 |
+
1/3
|
| 414 |
+
|
| 415 |
+
See Also
|
| 416 |
+
========
|
| 417 |
+
|
| 418 |
+
factorial, RisingFactorial, FallingFactorial
|
| 419 |
+
"""
|
| 420 |
+
|
| 421 |
+
@classmethod
|
| 422 |
+
def eval(cls, arg):
|
| 423 |
+
# TODO: extend this to complex numbers?
|
| 424 |
+
|
| 425 |
+
if arg.is_Number:
|
| 426 |
+
if not arg.is_Integer:
|
| 427 |
+
raise ValueError("argument must be nonnegative integer "
|
| 428 |
+
"or negative odd integer")
|
| 429 |
+
|
| 430 |
+
# This implementation is faster than the recursive one
|
| 431 |
+
# It also avoids "maximum recursion depth exceeded" runtime error
|
| 432 |
+
if arg.is_nonnegative:
|
| 433 |
+
if arg.is_even:
|
| 434 |
+
k = arg / 2
|
| 435 |
+
return 2**k * factorial(k)
|
| 436 |
+
return factorial(arg) / factorial2(arg - 1)
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
if arg.is_odd:
|
| 440 |
+
return arg*(S.NegativeOne)**((1 - arg)/2) / factorial2(-arg)
|
| 441 |
+
raise ValueError("argument must be nonnegative integer "
|
| 442 |
+
"or negative odd integer")
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def _eval_is_even(self):
|
| 446 |
+
# Double factorial is even for every positive even input
|
| 447 |
+
n = self.args[0]
|
| 448 |
+
if n.is_integer:
|
| 449 |
+
if n.is_odd:
|
| 450 |
+
return False
|
| 451 |
+
if n.is_even:
|
| 452 |
+
if n.is_positive:
|
| 453 |
+
return True
|
| 454 |
+
if n.is_zero:
|
| 455 |
+
return False
|
| 456 |
+
|
| 457 |
+
def _eval_is_integer(self):
|
| 458 |
+
# Double factorial is an integer for every nonnegative input, and for
|
| 459 |
+
# -1 and -3
|
| 460 |
+
n = self.args[0]
|
| 461 |
+
if n.is_integer:
|
| 462 |
+
if (n + 1).is_nonnegative:
|
| 463 |
+
return True
|
| 464 |
+
if n.is_odd:
|
| 465 |
+
return (n + 3).is_nonnegative
|
| 466 |
+
|
| 467 |
+
def _eval_is_odd(self):
|
| 468 |
+
# Double factorial is odd for every odd input not smaller than -3, and
|
| 469 |
+
# for 0
|
| 470 |
+
n = self.args[0]
|
| 471 |
+
if n.is_odd:
|
| 472 |
+
return (n + 3).is_nonnegative
|
| 473 |
+
if n.is_even:
|
| 474 |
+
if n.is_positive:
|
| 475 |
+
return False
|
| 476 |
+
if n.is_zero:
|
| 477 |
+
return True
|
| 478 |
+
|
| 479 |
+
def _eval_is_positive(self):
|
| 480 |
+
# Double factorial is positive for every nonnegative input, and for
|
| 481 |
+
# every odd negative input which is of the form -1-4k for an
|
| 482 |
+
# nonnegative integer k
|
| 483 |
+
n = self.args[0]
|
| 484 |
+
if n.is_integer:
|
| 485 |
+
if (n + 1).is_nonnegative:
|
| 486 |
+
return True
|
| 487 |
+
if n.is_odd:
|
| 488 |
+
return ((n + 1) / 2).is_even
|
| 489 |
+
|
| 490 |
+
def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
|
| 491 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 492 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 493 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 494 |
+
return 2**(n/2)*gamma(n/2 + 1) * Piecewise((1, Eq(Mod(n, 2), 0)),
|
| 495 |
+
(sqrt(2/pi), Eq(Mod(n, 2), 1)))
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
###############################################################################
|
| 499 |
+
######################## RISING and FALLING FACTORIALS ########################
|
| 500 |
+
###############################################################################
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
class RisingFactorial(CombinatorialFunction):
|
| 504 |
+
r"""
|
| 505 |
+
Rising factorial (also called Pochhammer symbol [1]_) is a double valued
|
| 506 |
+
function arising in concrete mathematics, hypergeometric functions
|
| 507 |
+
and series expansions. It is defined by:
|
| 508 |
+
|
| 509 |
+
.. math:: \texttt{rf(y, k)} = (x)^k = x \cdot (x+1) \cdots (x+k-1)
|
| 510 |
+
|
| 511 |
+
where `x` can be arbitrary expression and `k` is an integer. For
|
| 512 |
+
more information check "Concrete mathematics" by Graham, pp. 66
|
| 513 |
+
or visit https://mathworld.wolfram.com/RisingFactorial.html page.
|
| 514 |
+
|
| 515 |
+
When `x` is a `~.Poly` instance of degree $\ge 1$ with a single variable,
|
| 516 |
+
`(x)^k = x(y) \cdot x(y+1) \cdots x(y+k-1)`, where `y` is the
|
| 517 |
+
variable of `x`. This is as described in [2]_.
|
| 518 |
+
|
| 519 |
+
Examples
|
| 520 |
+
========
|
| 521 |
+
|
| 522 |
+
>>> from sympy import rf, Poly
|
| 523 |
+
>>> from sympy.abc import x
|
| 524 |
+
>>> rf(x, 0)
|
| 525 |
+
1
|
| 526 |
+
>>> rf(1, 5)
|
| 527 |
+
120
|
| 528 |
+
>>> rf(x, 5) == x*(1 + x)*(2 + x)*(3 + x)*(4 + x)
|
| 529 |
+
True
|
| 530 |
+
>>> rf(Poly(x**3, x), 2)
|
| 531 |
+
Poly(x**6 + 3*x**5 + 3*x**4 + x**3, x, domain='ZZ')
|
| 532 |
+
|
| 533 |
+
Rewriting is complicated unless the relationship between
|
| 534 |
+
the arguments is known, but rising factorial can
|
| 535 |
+
be rewritten in terms of gamma, factorial, binomial,
|
| 536 |
+
and falling factorial.
|
| 537 |
+
|
| 538 |
+
>>> from sympy import Symbol, factorial, ff, binomial, gamma
|
| 539 |
+
>>> n = Symbol('n', integer=True, positive=True)
|
| 540 |
+
>>> R = rf(n, n + 2)
|
| 541 |
+
>>> for i in (rf, ff, factorial, binomial, gamma):
|
| 542 |
+
... R.rewrite(i)
|
| 543 |
+
...
|
| 544 |
+
RisingFactorial(n, n + 2)
|
| 545 |
+
FallingFactorial(2*n + 1, n + 2)
|
| 546 |
+
factorial(2*n + 1)/factorial(n - 1)
|
| 547 |
+
binomial(2*n + 1, n + 2)*factorial(n + 2)
|
| 548 |
+
gamma(2*n + 2)/gamma(n)
|
| 549 |
+
|
| 550 |
+
See Also
|
| 551 |
+
========
|
| 552 |
+
|
| 553 |
+
factorial, factorial2, FallingFactorial
|
| 554 |
+
|
| 555 |
+
References
|
| 556 |
+
==========
|
| 557 |
+
|
| 558 |
+
.. [1] https://en.wikipedia.org/wiki/Pochhammer_symbol
|
| 559 |
+
.. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
|
| 560 |
+
Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
|
| 561 |
+
1995.
|
| 562 |
+
|
| 563 |
+
"""
|
| 564 |
+
|
| 565 |
+
@classmethod
|
| 566 |
+
def eval(cls, x, k):
|
| 567 |
+
x = sympify(x)
|
| 568 |
+
k = sympify(k)
|
| 569 |
+
|
| 570 |
+
if x is S.NaN or k is S.NaN:
|
| 571 |
+
return S.NaN
|
| 572 |
+
elif x is S.One:
|
| 573 |
+
return factorial(k)
|
| 574 |
+
elif k.is_Integer:
|
| 575 |
+
if k.is_zero:
|
| 576 |
+
return S.One
|
| 577 |
+
else:
|
| 578 |
+
if k.is_positive:
|
| 579 |
+
if x is S.Infinity:
|
| 580 |
+
return S.Infinity
|
| 581 |
+
elif x is S.NegativeInfinity:
|
| 582 |
+
if k.is_odd:
|
| 583 |
+
return S.NegativeInfinity
|
| 584 |
+
else:
|
| 585 |
+
return S.Infinity
|
| 586 |
+
else:
|
| 587 |
+
if isinstance(x, Poly):
|
| 588 |
+
gens = x.gens
|
| 589 |
+
if len(gens)!= 1:
|
| 590 |
+
raise ValueError("rf only defined for "
|
| 591 |
+
"polynomials on one generator")
|
| 592 |
+
else:
|
| 593 |
+
return reduce(lambda r, i:
|
| 594 |
+
r*(x.shift(i)),
|
| 595 |
+
range(int(k)), 1)
|
| 596 |
+
else:
|
| 597 |
+
return reduce(lambda r, i: r*(x + i),
|
| 598 |
+
range(int(k)), 1)
|
| 599 |
+
|
| 600 |
+
else:
|
| 601 |
+
if x is S.Infinity:
|
| 602 |
+
return S.Infinity
|
| 603 |
+
elif x is S.NegativeInfinity:
|
| 604 |
+
return S.Infinity
|
| 605 |
+
else:
|
| 606 |
+
if isinstance(x, Poly):
|
| 607 |
+
gens = x.gens
|
| 608 |
+
if len(gens)!= 1:
|
| 609 |
+
raise ValueError("rf only defined for "
|
| 610 |
+
"polynomials on one generator")
|
| 611 |
+
else:
|
| 612 |
+
return 1/reduce(lambda r, i:
|
| 613 |
+
r*(x.shift(-i)),
|
| 614 |
+
range(1, abs(int(k)) + 1), 1)
|
| 615 |
+
else:
|
| 616 |
+
return 1/reduce(lambda r, i:
|
| 617 |
+
r*(x - i),
|
| 618 |
+
range(1, abs(int(k)) + 1), 1)
|
| 619 |
+
|
| 620 |
+
if k.is_integer == False:
|
| 621 |
+
if x.is_integer and x.is_negative:
|
| 622 |
+
return S.Zero
|
| 623 |
+
|
| 624 |
+
def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
|
| 625 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 626 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 627 |
+
if not piecewise:
|
| 628 |
+
if (x <= 0) == True:
|
| 629 |
+
return S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1)
|
| 630 |
+
return gamma(x + k) / gamma(x)
|
| 631 |
+
return Piecewise(
|
| 632 |
+
(gamma(x + k) / gamma(x), x > 0),
|
| 633 |
+
(S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1), True))
|
| 634 |
+
|
| 635 |
+
def _eval_rewrite_as_FallingFactorial(self, x, k, **kwargs):
|
| 636 |
+
return FallingFactorial(x + k - 1, k)
|
| 637 |
+
|
| 638 |
+
def _eval_rewrite_as_factorial(self, x, k, **kwargs):
|
| 639 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 640 |
+
if x.is_integer and k.is_integer:
|
| 641 |
+
return Piecewise(
|
| 642 |
+
(factorial(k + x - 1)/factorial(x - 1), x > 0),
|
| 643 |
+
(S.NegativeOne**k*factorial(-x)/factorial(-k - x), True))
|
| 644 |
+
|
| 645 |
+
def _eval_rewrite_as_binomial(self, x, k, **kwargs):
|
| 646 |
+
if k.is_integer:
|
| 647 |
+
return factorial(k) * binomial(x + k - 1, k)
|
| 648 |
+
|
| 649 |
+
def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
|
| 650 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 651 |
+
if limitvar:
|
| 652 |
+
k_lim = k.subs(limitvar, S.Infinity)
|
| 653 |
+
if k_lim is S.Infinity:
|
| 654 |
+
return (gamma(x + k).rewrite('tractable', deep=True) / gamma(x))
|
| 655 |
+
elif k_lim is S.NegativeInfinity:
|
| 656 |
+
return (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1).rewrite('tractable', deep=True))
|
| 657 |
+
return self.rewrite(gamma).rewrite('tractable', deep=True)
|
| 658 |
+
|
| 659 |
+
def _eval_is_integer(self):
|
| 660 |
+
return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
|
| 661 |
+
self.args[1].is_nonnegative))
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
class FallingFactorial(CombinatorialFunction):
|
| 665 |
+
r"""
|
| 666 |
+
Falling factorial (related to rising factorial) is a double valued
|
| 667 |
+
function arising in concrete mathematics, hypergeometric functions
|
| 668 |
+
and series expansions. It is defined by
|
| 669 |
+
|
| 670 |
+
.. math:: \texttt{ff(x, k)} = (x)_k = x \cdot (x-1) \cdots (x-k+1)
|
| 671 |
+
|
| 672 |
+
where `x` can be arbitrary expression and `k` is an integer. For
|
| 673 |
+
more information check "Concrete mathematics" by Graham, pp. 66
|
| 674 |
+
or [1]_.
|
| 675 |
+
|
| 676 |
+
When `x` is a `~.Poly` instance of degree $\ge 1$ with single variable,
|
| 677 |
+
`(x)_k = x(y) \cdot x(y-1) \cdots x(y-k+1)`, where `y` is the
|
| 678 |
+
variable of `x`. This is as described in
|
| 679 |
+
|
| 680 |
+
>>> from sympy import ff, Poly, Symbol
|
| 681 |
+
>>> from sympy.abc import x
|
| 682 |
+
>>> n = Symbol('n', integer=True)
|
| 683 |
+
|
| 684 |
+
>>> ff(x, 0)
|
| 685 |
+
1
|
| 686 |
+
>>> ff(5, 5)
|
| 687 |
+
120
|
| 688 |
+
>>> ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4)
|
| 689 |
+
True
|
| 690 |
+
>>> ff(Poly(x**2, x), 2)
|
| 691 |
+
Poly(x**4 - 2*x**3 + x**2, x, domain='ZZ')
|
| 692 |
+
>>> ff(n, n)
|
| 693 |
+
factorial(n)
|
| 694 |
+
|
| 695 |
+
Rewriting is complicated unless the relationship between
|
| 696 |
+
the arguments is known, but falling factorial can
|
| 697 |
+
be rewritten in terms of gamma, factorial and binomial
|
| 698 |
+
and rising factorial.
|
| 699 |
+
|
| 700 |
+
>>> from sympy import factorial, rf, gamma, binomial, Symbol
|
| 701 |
+
>>> n = Symbol('n', integer=True, positive=True)
|
| 702 |
+
>>> F = ff(n, n - 2)
|
| 703 |
+
>>> for i in (rf, ff, factorial, binomial, gamma):
|
| 704 |
+
... F.rewrite(i)
|
| 705 |
+
...
|
| 706 |
+
RisingFactorial(3, n - 2)
|
| 707 |
+
FallingFactorial(n, n - 2)
|
| 708 |
+
factorial(n)/2
|
| 709 |
+
binomial(n, n - 2)*factorial(n - 2)
|
| 710 |
+
gamma(n + 1)/2
|
| 711 |
+
|
| 712 |
+
See Also
|
| 713 |
+
========
|
| 714 |
+
|
| 715 |
+
factorial, factorial2, RisingFactorial
|
| 716 |
+
|
| 717 |
+
References
|
| 718 |
+
==========
|
| 719 |
+
|
| 720 |
+
.. [1] https://mathworld.wolfram.com/FallingFactorial.html
|
| 721 |
+
.. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
|
| 722 |
+
Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
|
| 723 |
+
1995.
|
| 724 |
+
|
| 725 |
+
"""
|
| 726 |
+
|
| 727 |
+
@classmethod
|
| 728 |
+
def eval(cls, x, k):
|
| 729 |
+
x = sympify(x)
|
| 730 |
+
k = sympify(k)
|
| 731 |
+
|
| 732 |
+
if x is S.NaN or k is S.NaN:
|
| 733 |
+
return S.NaN
|
| 734 |
+
elif k.is_integer and x == k:
|
| 735 |
+
return factorial(x)
|
| 736 |
+
elif k.is_Integer:
|
| 737 |
+
if k.is_zero:
|
| 738 |
+
return S.One
|
| 739 |
+
else:
|
| 740 |
+
if k.is_positive:
|
| 741 |
+
if x is S.Infinity:
|
| 742 |
+
return S.Infinity
|
| 743 |
+
elif x is S.NegativeInfinity:
|
| 744 |
+
if k.is_odd:
|
| 745 |
+
return S.NegativeInfinity
|
| 746 |
+
else:
|
| 747 |
+
return S.Infinity
|
| 748 |
+
else:
|
| 749 |
+
if isinstance(x, Poly):
|
| 750 |
+
gens = x.gens
|
| 751 |
+
if len(gens)!= 1:
|
| 752 |
+
raise ValueError("ff only defined for "
|
| 753 |
+
"polynomials on one generator")
|
| 754 |
+
else:
|
| 755 |
+
return reduce(lambda r, i:
|
| 756 |
+
r*(x.shift(-i)),
|
| 757 |
+
range(int(k)), 1)
|
| 758 |
+
else:
|
| 759 |
+
return reduce(lambda r, i: r*(x - i),
|
| 760 |
+
range(int(k)), 1)
|
| 761 |
+
else:
|
| 762 |
+
if x is S.Infinity:
|
| 763 |
+
return S.Infinity
|
| 764 |
+
elif x is S.NegativeInfinity:
|
| 765 |
+
return S.Infinity
|
| 766 |
+
else:
|
| 767 |
+
if isinstance(x, Poly):
|
| 768 |
+
gens = x.gens
|
| 769 |
+
if len(gens)!= 1:
|
| 770 |
+
raise ValueError("rf only defined for "
|
| 771 |
+
"polynomials on one generator")
|
| 772 |
+
else:
|
| 773 |
+
return 1/reduce(lambda r, i:
|
| 774 |
+
r*(x.shift(i)),
|
| 775 |
+
range(1, abs(int(k)) + 1), 1)
|
| 776 |
+
else:
|
| 777 |
+
return 1/reduce(lambda r, i: r*(x + i),
|
| 778 |
+
range(1, abs(int(k)) + 1), 1)
|
| 779 |
+
|
| 780 |
+
def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
|
| 781 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 782 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 783 |
+
if not piecewise:
|
| 784 |
+
if (x < 0) == True:
|
| 785 |
+
return S.NegativeOne**k*gamma(k - x) / gamma(-x)
|
| 786 |
+
return gamma(x + 1) / gamma(x - k + 1)
|
| 787 |
+
return Piecewise(
|
| 788 |
+
(gamma(x + 1) / gamma(x - k + 1), x >= 0),
|
| 789 |
+
(S.NegativeOne**k*gamma(k - x) / gamma(-x), True))
|
| 790 |
+
|
| 791 |
+
def _eval_rewrite_as_RisingFactorial(self, x, k, **kwargs):
|
| 792 |
+
return rf(x - k + 1, k)
|
| 793 |
+
|
| 794 |
+
def _eval_rewrite_as_binomial(self, x, k, **kwargs):
|
| 795 |
+
if k.is_integer:
|
| 796 |
+
return factorial(k) * binomial(x, k)
|
| 797 |
+
|
| 798 |
+
def _eval_rewrite_as_factorial(self, x, k, **kwargs):
|
| 799 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 800 |
+
if x.is_integer and k.is_integer:
|
| 801 |
+
return Piecewise(
|
| 802 |
+
(factorial(x)/factorial(-k + x), x >= 0),
|
| 803 |
+
(S.NegativeOne**k*factorial(k - x - 1)/factorial(-x - 1), True))
|
| 804 |
+
|
| 805 |
+
def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
|
| 806 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 807 |
+
if limitvar:
|
| 808 |
+
k_lim = k.subs(limitvar, S.Infinity)
|
| 809 |
+
if k_lim is S.Infinity:
|
| 810 |
+
return (S.NegativeOne**k*gamma(k - x).rewrite('tractable', deep=True) / gamma(-x))
|
| 811 |
+
elif k_lim is S.NegativeInfinity:
|
| 812 |
+
return (gamma(x + 1) / gamma(x - k + 1).rewrite('tractable', deep=True))
|
| 813 |
+
return self.rewrite(gamma).rewrite('tractable', deep=True)
|
| 814 |
+
|
| 815 |
+
def _eval_is_integer(self):
|
| 816 |
+
return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
|
| 817 |
+
self.args[1].is_nonnegative))
|
| 818 |
+
|
| 819 |
+
|
| 820 |
+
rf = RisingFactorial
|
| 821 |
+
ff = FallingFactorial
|
| 822 |
+
|
| 823 |
+
###############################################################################
|
| 824 |
+
########################### BINOMIAL COEFFICIENTS #############################
|
| 825 |
+
###############################################################################
|
| 826 |
+
|
| 827 |
+
|
| 828 |
+
class binomial(CombinatorialFunction):
|
| 829 |
+
r"""Implementation of the binomial coefficient. It can be defined
|
| 830 |
+
in two ways depending on its desired interpretation:
|
| 831 |
+
|
| 832 |
+
.. math:: \binom{n}{k} = \frac{n!}{k!(n-k)!}\ \text{or}\
|
| 833 |
+
\binom{n}{k} = \frac{(n)_k}{k!}
|
| 834 |
+
|
| 835 |
+
First, in a strict combinatorial sense it defines the
|
| 836 |
+
number of ways we can choose `k` elements from a set of
|
| 837 |
+
`n` elements. In this case both arguments are nonnegative
|
| 838 |
+
integers and binomial is computed using an efficient
|
| 839 |
+
algorithm based on prime factorization.
|
| 840 |
+
|
| 841 |
+
The other definition is generalization for arbitrary `n`,
|
| 842 |
+
however `k` must also be nonnegative. This case is very
|
| 843 |
+
useful when evaluating summations.
|
| 844 |
+
|
| 845 |
+
For the sake of convenience, for negative integer `k` this function
|
| 846 |
+
will return zero no matter the other argument.
|
| 847 |
+
|
| 848 |
+
To expand the binomial when `n` is a symbol, use either
|
| 849 |
+
``expand_func()`` or ``expand(func=True)``. The former will keep
|
| 850 |
+
the polynomial in factored form while the latter will expand the
|
| 851 |
+
polynomial itself. See examples for details.
|
| 852 |
+
|
| 853 |
+
Examples
|
| 854 |
+
========
|
| 855 |
+
|
| 856 |
+
>>> from sympy import Symbol, Rational, binomial, expand_func
|
| 857 |
+
>>> n = Symbol('n', integer=True, positive=True)
|
| 858 |
+
|
| 859 |
+
>>> binomial(15, 8)
|
| 860 |
+
6435
|
| 861 |
+
|
| 862 |
+
>>> binomial(n, -1)
|
| 863 |
+
0
|
| 864 |
+
|
| 865 |
+
Rows of Pascal's triangle can be generated with the binomial function:
|
| 866 |
+
|
| 867 |
+
>>> for N in range(8):
|
| 868 |
+
... print([binomial(N, i) for i in range(N + 1)])
|
| 869 |
+
...
|
| 870 |
+
[1]
|
| 871 |
+
[1, 1]
|
| 872 |
+
[1, 2, 1]
|
| 873 |
+
[1, 3, 3, 1]
|
| 874 |
+
[1, 4, 6, 4, 1]
|
| 875 |
+
[1, 5, 10, 10, 5, 1]
|
| 876 |
+
[1, 6, 15, 20, 15, 6, 1]
|
| 877 |
+
[1, 7, 21, 35, 35, 21, 7, 1]
|
| 878 |
+
|
| 879 |
+
As can a given diagonal, e.g. the 4th diagonal:
|
| 880 |
+
|
| 881 |
+
>>> N = -4
|
| 882 |
+
>>> [binomial(N, i) for i in range(1 - N)]
|
| 883 |
+
[1, -4, 10, -20, 35]
|
| 884 |
+
|
| 885 |
+
>>> binomial(Rational(5, 4), 3)
|
| 886 |
+
-5/128
|
| 887 |
+
>>> binomial(Rational(-5, 4), 3)
|
| 888 |
+
-195/128
|
| 889 |
+
|
| 890 |
+
>>> binomial(n, 3)
|
| 891 |
+
binomial(n, 3)
|
| 892 |
+
|
| 893 |
+
>>> binomial(n, 3).expand(func=True)
|
| 894 |
+
n**3/6 - n**2/2 + n/3
|
| 895 |
+
|
| 896 |
+
>>> expand_func(binomial(n, 3))
|
| 897 |
+
n*(n - 2)*(n - 1)/6
|
| 898 |
+
|
| 899 |
+
In many cases, we can also compute binomial coefficients modulo a
|
| 900 |
+
prime p quickly using Lucas' Theorem [2]_, though we need to include
|
| 901 |
+
`evaluate=False` to postpone evaluation:
|
| 902 |
+
|
| 903 |
+
>>> from sympy import Mod
|
| 904 |
+
>>> Mod(binomial(156675, 4433, evaluate=False), 10**5 + 3)
|
| 905 |
+
28625
|
| 906 |
+
|
| 907 |
+
Using a generalisation of Lucas's Theorem given by Granville [3]_,
|
| 908 |
+
we can extend this to arbitrary n:
|
| 909 |
+
|
| 910 |
+
>>> Mod(binomial(10**18, 10**12, evaluate=False), (10**5 + 3)**2)
|
| 911 |
+
3744312326
|
| 912 |
+
|
| 913 |
+
References
|
| 914 |
+
==========
|
| 915 |
+
|
| 916 |
+
.. [1] https://www.johndcook.com/blog/binomial_coefficients/
|
| 917 |
+
.. [2] https://en.wikipedia.org/wiki/Lucas%27s_theorem
|
| 918 |
+
.. [3] Binomial coefficients modulo prime powers, Andrew Granville,
|
| 919 |
+
Available: https://web.archive.org/web/20170202003812/http://www.dms.umontreal.ca/~andrew/PDF/BinCoeff.pdf
|
| 920 |
+
"""
|
| 921 |
+
|
| 922 |
+
def fdiff(self, argindex=1):
|
| 923 |
+
from sympy.functions.special.gamma_functions import polygamma
|
| 924 |
+
if argindex == 1:
|
| 925 |
+
# https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/01/
|
| 926 |
+
n, k = self.args
|
| 927 |
+
return binomial(n, k)*(polygamma(0, n + 1) - \
|
| 928 |
+
polygamma(0, n - k + 1))
|
| 929 |
+
elif argindex == 2:
|
| 930 |
+
# https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/02/
|
| 931 |
+
n, k = self.args
|
| 932 |
+
return binomial(n, k)*(polygamma(0, n - k + 1) - \
|
| 933 |
+
polygamma(0, k + 1))
|
| 934 |
+
else:
|
| 935 |
+
raise ArgumentIndexError(self, argindex)
|
| 936 |
+
|
| 937 |
+
@classmethod
|
| 938 |
+
def _eval(self, n, k):
|
| 939 |
+
# n.is_Number and k.is_Integer and k != 1 and n != k
|
| 940 |
+
|
| 941 |
+
if k.is_Integer:
|
| 942 |
+
if n.is_Integer and n >= 0:
|
| 943 |
+
n, k = int(n), int(k)
|
| 944 |
+
|
| 945 |
+
if k > n:
|
| 946 |
+
return S.Zero
|
| 947 |
+
elif k > n // 2:
|
| 948 |
+
k = n - k
|
| 949 |
+
|
| 950 |
+
# XXX: This conditional logic should be moved to
|
| 951 |
+
# sympy.external.gmpy and the pure Python version of bincoef
|
| 952 |
+
# should be moved to sympy.external.ntheory.
|
| 953 |
+
if _gmpy is not None:
|
| 954 |
+
return Integer(_gmpy.bincoef(n, k))
|
| 955 |
+
|
| 956 |
+
d, result = n - k, 1
|
| 957 |
+
for i in range(1, k + 1):
|
| 958 |
+
d += 1
|
| 959 |
+
result = result * d // i
|
| 960 |
+
return Integer(result)
|
| 961 |
+
else:
|
| 962 |
+
d, result = n - k, 1
|
| 963 |
+
for i in range(1, k + 1):
|
| 964 |
+
d += 1
|
| 965 |
+
result *= d
|
| 966 |
+
return result / _factorial(k)
|
| 967 |
+
|
| 968 |
+
@classmethod
|
| 969 |
+
def eval(cls, n, k):
|
| 970 |
+
n, k = map(sympify, (n, k))
|
| 971 |
+
d = n - k
|
| 972 |
+
n_nonneg, n_isint = n.is_nonnegative, n.is_integer
|
| 973 |
+
if k.is_zero or ((n_nonneg or n_isint is False)
|
| 974 |
+
and d.is_zero):
|
| 975 |
+
return S.One
|
| 976 |
+
if (k - 1).is_zero or ((n_nonneg or n_isint is False)
|
| 977 |
+
and (d - 1).is_zero):
|
| 978 |
+
return n
|
| 979 |
+
if k.is_integer:
|
| 980 |
+
if k.is_negative or (n_nonneg and n_isint and d.is_negative):
|
| 981 |
+
return S.Zero
|
| 982 |
+
elif n.is_number:
|
| 983 |
+
res = cls._eval(n, k)
|
| 984 |
+
return res.expand(basic=True) if res else res
|
| 985 |
+
elif n_nonneg is False and n_isint:
|
| 986 |
+
# a special case when binomial evaluates to complex infinity
|
| 987 |
+
return S.ComplexInfinity
|
| 988 |
+
elif k.is_number:
|
| 989 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 990 |
+
return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1))
|
| 991 |
+
|
| 992 |
+
def _eval_Mod(self, q):
|
| 993 |
+
n, k = self.args
|
| 994 |
+
|
| 995 |
+
if any(x.is_integer is False for x in (n, k, q)):
|
| 996 |
+
raise ValueError("Integers expected for binomial Mod")
|
| 997 |
+
|
| 998 |
+
if all(x.is_Integer for x in (n, k, q)):
|
| 999 |
+
n, k = map(int, (n, k))
|
| 1000 |
+
aq, res = abs(q), 1
|
| 1001 |
+
|
| 1002 |
+
# handle negative integers k or n
|
| 1003 |
+
if k < 0:
|
| 1004 |
+
return S.Zero
|
| 1005 |
+
if n < 0:
|
| 1006 |
+
n = -n + k - 1
|
| 1007 |
+
res = -1 if k%2 else 1
|
| 1008 |
+
|
| 1009 |
+
# non negative integers k and n
|
| 1010 |
+
if k > n:
|
| 1011 |
+
return S.Zero
|
| 1012 |
+
|
| 1013 |
+
isprime = aq.is_prime
|
| 1014 |
+
aq = int(aq)
|
| 1015 |
+
if isprime:
|
| 1016 |
+
if aq < n:
|
| 1017 |
+
# use Lucas Theorem
|
| 1018 |
+
N, K = n, k
|
| 1019 |
+
while N or K:
|
| 1020 |
+
res = res*binomial(N % aq, K % aq) % aq
|
| 1021 |
+
N, K = N // aq, K // aq
|
| 1022 |
+
|
| 1023 |
+
else:
|
| 1024 |
+
# use Factorial Modulo
|
| 1025 |
+
d = n - k
|
| 1026 |
+
if k > d:
|
| 1027 |
+
k, d = d, k
|
| 1028 |
+
kf = 1
|
| 1029 |
+
for i in range(2, k + 1):
|
| 1030 |
+
kf = kf*i % aq
|
| 1031 |
+
df = kf
|
| 1032 |
+
for i in range(k + 1, d + 1):
|
| 1033 |
+
df = df*i % aq
|
| 1034 |
+
res *= df
|
| 1035 |
+
for i in range(d + 1, n + 1):
|
| 1036 |
+
res = res*i % aq
|
| 1037 |
+
|
| 1038 |
+
res *= pow(kf*df % aq, aq - 2, aq)
|
| 1039 |
+
res %= aq
|
| 1040 |
+
|
| 1041 |
+
elif _sqrt(q) < k and q != 1:
|
| 1042 |
+
res = binomial_mod(n, k, q)
|
| 1043 |
+
|
| 1044 |
+
else:
|
| 1045 |
+
# Binomial Factorization is performed by calculating the
|
| 1046 |
+
# exponents of primes <= n in `n! /(k! (n - k)!)`,
|
| 1047 |
+
# for non-negative integers n and k. As the exponent of
|
| 1048 |
+
# prime in n! is e_p(n) = [n/p] + [n/p**2] + ...
|
| 1049 |
+
# the exponent of prime in binomial(n, k) would be
|
| 1050 |
+
# e_p(n) - e_p(k) - e_p(n - k)
|
| 1051 |
+
M = int(_sqrt(n))
|
| 1052 |
+
for prime in sieve.primerange(2, n + 1):
|
| 1053 |
+
if prime > n - k:
|
| 1054 |
+
res = res*prime % aq
|
| 1055 |
+
elif prime > n // 2:
|
| 1056 |
+
continue
|
| 1057 |
+
elif prime > M:
|
| 1058 |
+
if n % prime < k % prime:
|
| 1059 |
+
res = res*prime % aq
|
| 1060 |
+
else:
|
| 1061 |
+
N, K = n, k
|
| 1062 |
+
exp = a = 0
|
| 1063 |
+
|
| 1064 |
+
while N > 0:
|
| 1065 |
+
a = int((N % prime) < (K % prime + a))
|
| 1066 |
+
N, K = N // prime, K // prime
|
| 1067 |
+
exp += a
|
| 1068 |
+
|
| 1069 |
+
if exp > 0:
|
| 1070 |
+
res *= pow(prime, exp, aq)
|
| 1071 |
+
res %= aq
|
| 1072 |
+
|
| 1073 |
+
return S(res % q)
|
| 1074 |
+
|
| 1075 |
+
def _eval_expand_func(self, **hints):
|
| 1076 |
+
"""
|
| 1077 |
+
Function to expand binomial(n, k) when m is positive integer
|
| 1078 |
+
Also,
|
| 1079 |
+
n is self.args[0] and k is self.args[1] while using binomial(n, k)
|
| 1080 |
+
"""
|
| 1081 |
+
n = self.args[0]
|
| 1082 |
+
if n.is_Number:
|
| 1083 |
+
return binomial(*self.args)
|
| 1084 |
+
|
| 1085 |
+
k = self.args[1]
|
| 1086 |
+
if (n-k).is_Integer:
|
| 1087 |
+
k = n - k
|
| 1088 |
+
|
| 1089 |
+
if k.is_Integer:
|
| 1090 |
+
if k.is_zero:
|
| 1091 |
+
return S.One
|
| 1092 |
+
elif k.is_negative:
|
| 1093 |
+
return S.Zero
|
| 1094 |
+
else:
|
| 1095 |
+
n, result = self.args[0], 1
|
| 1096 |
+
for i in range(1, k + 1):
|
| 1097 |
+
result *= n - k + i
|
| 1098 |
+
return result / _factorial(k)
|
| 1099 |
+
else:
|
| 1100 |
+
return binomial(*self.args)
|
| 1101 |
+
|
| 1102 |
+
def _eval_rewrite_as_factorial(self, n, k, **kwargs):
|
| 1103 |
+
return factorial(n)/(factorial(k)*factorial(n - k))
|
| 1104 |
+
|
| 1105 |
+
def _eval_rewrite_as_gamma(self, n, k, piecewise=True, **kwargs):
|
| 1106 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 1107 |
+
return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1))
|
| 1108 |
+
|
| 1109 |
+
def _eval_rewrite_as_tractable(self, n, k, limitvar=None, **kwargs):
|
| 1110 |
+
return self._eval_rewrite_as_gamma(n, k).rewrite('tractable')
|
| 1111 |
+
|
| 1112 |
+
def _eval_rewrite_as_FallingFactorial(self, n, k, **kwargs):
|
| 1113 |
+
if k.is_integer:
|
| 1114 |
+
return ff(n, k) / factorial(k)
|
| 1115 |
+
|
| 1116 |
+
def _eval_is_integer(self):
|
| 1117 |
+
n, k = self.args
|
| 1118 |
+
if n.is_integer and k.is_integer:
|
| 1119 |
+
return True
|
| 1120 |
+
elif k.is_integer is False:
|
| 1121 |
+
return False
|
| 1122 |
+
|
| 1123 |
+
def _eval_is_nonnegative(self):
|
| 1124 |
+
n, k = self.args
|
| 1125 |
+
if n.is_integer and k.is_integer:
|
| 1126 |
+
if n.is_nonnegative or k.is_negative or k.is_even:
|
| 1127 |
+
return True
|
| 1128 |
+
elif k.is_even is False:
|
| 1129 |
+
return False
|
| 1130 |
+
|
| 1131 |
+
def _eval_as_leading_term(self, x, logx=None, cdir=0):
|
| 1132 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 1133 |
+
return self.rewrite(gamma)._eval_as_leading_term(x, logx=logx, cdir=cdir)
|
lib/python3.12/site-packages/sympy/functions/combinatorial/numbers.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (214 Bytes). View file
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_factorials.cpython-312.pyc
ADDED
|
Binary file (50.9 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/functions/combinatorial/tests/test_comb_factorials.py
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.concrete.products import Product
|
| 2 |
+
from sympy.core.function import expand_func
|
| 3 |
+
from sympy.core.mod import Mod
|
| 4 |
+
from sympy.core.mul import Mul
|
| 5 |
+
from sympy.core import EulerGamma
|
| 6 |
+
from sympy.core.numbers import (Float, I, Rational, nan, oo, pi, zoo)
|
| 7 |
+
from sympy.core.relational import Eq
|
| 8 |
+
from sympy.core.singleton import S
|
| 9 |
+
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
| 10 |
+
from sympy.functions.combinatorial.factorials import (ff, rf, binomial, factorial, factorial2)
|
| 11 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 12 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 13 |
+
from sympy.functions.special.gamma_functions import (gamma, polygamma)
|
| 14 |
+
from sympy.polys.polytools import Poly
|
| 15 |
+
from sympy.series.order import O
|
| 16 |
+
from sympy.simplify.simplify import simplify
|
| 17 |
+
from sympy.core.expr import unchanged
|
| 18 |
+
from sympy.core.function import ArgumentIndexError
|
| 19 |
+
from sympy.functions.combinatorial.factorials import subfactorial
|
| 20 |
+
from sympy.functions.special.gamma_functions import uppergamma
|
| 21 |
+
from sympy.testing.pytest import XFAIL, raises, slow
|
| 22 |
+
|
| 23 |
+
#Solves and Fixes Issue #10388 - This is the updated test for the same solved issue
|
| 24 |
+
|
| 25 |
+
def test_rf_eval_apply():
|
| 26 |
+
x, y = symbols('x,y')
|
| 27 |
+
n, k = symbols('n k', integer=True)
|
| 28 |
+
m = Symbol('m', integer=True, nonnegative=True)
|
| 29 |
+
|
| 30 |
+
assert rf(nan, y) is nan
|
| 31 |
+
assert rf(x, nan) is nan
|
| 32 |
+
|
| 33 |
+
assert unchanged(rf, x, y)
|
| 34 |
+
|
| 35 |
+
assert rf(oo, 0) == 1
|
| 36 |
+
assert rf(-oo, 0) == 1
|
| 37 |
+
|
| 38 |
+
assert rf(oo, 6) is oo
|
| 39 |
+
assert rf(-oo, 7) is -oo
|
| 40 |
+
assert rf(-oo, 6) is oo
|
| 41 |
+
|
| 42 |
+
assert rf(oo, -6) is oo
|
| 43 |
+
assert rf(-oo, -7) is oo
|
| 44 |
+
|
| 45 |
+
assert rf(-1, pi) == 0
|
| 46 |
+
assert rf(-5, 1 + I) == 0
|
| 47 |
+
|
| 48 |
+
assert unchanged(rf, -3, k)
|
| 49 |
+
assert unchanged(rf, x, Symbol('k', integer=False))
|
| 50 |
+
assert rf(-3, Symbol('k', integer=False)) == 0
|
| 51 |
+
assert rf(Symbol('x', negative=True, integer=True), Symbol('k', integer=False)) == 0
|
| 52 |
+
|
| 53 |
+
assert rf(x, 0) == 1
|
| 54 |
+
assert rf(x, 1) == x
|
| 55 |
+
assert rf(x, 2) == x*(x + 1)
|
| 56 |
+
assert rf(x, 3) == x*(x + 1)*(x + 2)
|
| 57 |
+
assert rf(x, 5) == x*(x + 1)*(x + 2)*(x + 3)*(x + 4)
|
| 58 |
+
|
| 59 |
+
assert rf(x, -1) == 1/(x - 1)
|
| 60 |
+
assert rf(x, -2) == 1/((x - 1)*(x - 2))
|
| 61 |
+
assert rf(x, -3) == 1/((x - 1)*(x - 2)*(x - 3))
|
| 62 |
+
|
| 63 |
+
assert rf(1, 100) == factorial(100)
|
| 64 |
+
|
| 65 |
+
assert rf(x**2 + 3*x, 2) == (x**2 + 3*x)*(x**2 + 3*x + 1)
|
| 66 |
+
assert isinstance(rf(x**2 + 3*x, 2), Mul)
|
| 67 |
+
assert rf(x**3 + x, -2) == 1/((x**3 + x - 1)*(x**3 + x - 2))
|
| 68 |
+
|
| 69 |
+
assert rf(Poly(x**2 + 3*x, x), 2) == Poly(x**4 + 8*x**3 + 19*x**2 + 12*x, x)
|
| 70 |
+
assert isinstance(rf(Poly(x**2 + 3*x, x), 2), Poly)
|
| 71 |
+
raises(ValueError, lambda: rf(Poly(x**2 + 3*x, x, y), 2))
|
| 72 |
+
assert rf(Poly(x**3 + x, x), -2) == 1/(x**6 - 9*x**5 + 35*x**4 - 75*x**3 + 94*x**2 - 66*x + 20)
|
| 73 |
+
raises(ValueError, lambda: rf(Poly(x**3 + x, x, y), -2))
|
| 74 |
+
|
| 75 |
+
assert rf(x, m).is_integer is None
|
| 76 |
+
assert rf(n, k).is_integer is None
|
| 77 |
+
assert rf(n, m).is_integer is True
|
| 78 |
+
assert rf(n, k + pi).is_integer is False
|
| 79 |
+
assert rf(n, m + pi).is_integer is False
|
| 80 |
+
assert rf(pi, m).is_integer is False
|
| 81 |
+
|
| 82 |
+
def check(x, k, o, n):
|
| 83 |
+
a, b = Dummy(), Dummy()
|
| 84 |
+
r = lambda x, k: o(a, b).rewrite(n).subs({a:x,b:k})
|
| 85 |
+
for i in range(-5,5):
|
| 86 |
+
for j in range(-5,5):
|
| 87 |
+
assert o(i, j) == r(i, j), (o, n, i, j)
|
| 88 |
+
check(x, k, rf, ff)
|
| 89 |
+
check(x, k, rf, binomial)
|
| 90 |
+
check(n, k, rf, factorial)
|
| 91 |
+
check(x, y, rf, factorial)
|
| 92 |
+
check(x, y, rf, binomial)
|
| 93 |
+
|
| 94 |
+
assert rf(x, k).rewrite(ff) == ff(x + k - 1, k)
|
| 95 |
+
assert rf(x, k).rewrite(gamma) == Piecewise(
|
| 96 |
+
(gamma(k + x)/gamma(x), x > 0),
|
| 97 |
+
((-1)**k*gamma(1 - x)/gamma(-k - x + 1), True))
|
| 98 |
+
assert rf(5, k).rewrite(gamma) == gamma(k + 5)/24
|
| 99 |
+
assert rf(x, k).rewrite(binomial) == factorial(k)*binomial(x + k - 1, k)
|
| 100 |
+
assert rf(n, k).rewrite(factorial) == Piecewise(
|
| 101 |
+
(factorial(k + n - 1)/factorial(n - 1), n > 0),
|
| 102 |
+
((-1)**k*factorial(-n)/factorial(-k - n), True))
|
| 103 |
+
assert rf(5, k).rewrite(factorial) == factorial(k + 4)/24
|
| 104 |
+
assert rf(x, y).rewrite(factorial) == rf(x, y)
|
| 105 |
+
assert rf(x, y).rewrite(binomial) == rf(x, y)
|
| 106 |
+
|
| 107 |
+
import random
|
| 108 |
+
from mpmath import rf as mpmath_rf
|
| 109 |
+
for i in range(100):
|
| 110 |
+
x = -500 + 500 * random.random()
|
| 111 |
+
k = -500 + 500 * random.random()
|
| 112 |
+
assert (abs(mpmath_rf(x, k) - rf(x, k)) < 10**(-15))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def test_ff_eval_apply():
|
| 116 |
+
x, y = symbols('x,y')
|
| 117 |
+
n, k = symbols('n k', integer=True)
|
| 118 |
+
m = Symbol('m', integer=True, nonnegative=True)
|
| 119 |
+
|
| 120 |
+
assert ff(nan, y) is nan
|
| 121 |
+
assert ff(x, nan) is nan
|
| 122 |
+
|
| 123 |
+
assert unchanged(ff, x, y)
|
| 124 |
+
|
| 125 |
+
assert ff(oo, 0) == 1
|
| 126 |
+
assert ff(-oo, 0) == 1
|
| 127 |
+
|
| 128 |
+
assert ff(oo, 6) is oo
|
| 129 |
+
assert ff(-oo, 7) is -oo
|
| 130 |
+
assert ff(-oo, 6) is oo
|
| 131 |
+
|
| 132 |
+
assert ff(oo, -6) is oo
|
| 133 |
+
assert ff(-oo, -7) is oo
|
| 134 |
+
|
| 135 |
+
assert ff(x, 0) == 1
|
| 136 |
+
assert ff(x, 1) == x
|
| 137 |
+
assert ff(x, 2) == x*(x - 1)
|
| 138 |
+
assert ff(x, 3) == x*(x - 1)*(x - 2)
|
| 139 |
+
assert ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4)
|
| 140 |
+
|
| 141 |
+
assert ff(x, -1) == 1/(x + 1)
|
| 142 |
+
assert ff(x, -2) == 1/((x + 1)*(x + 2))
|
| 143 |
+
assert ff(x, -3) == 1/((x + 1)*(x + 2)*(x + 3))
|
| 144 |
+
|
| 145 |
+
assert ff(100, 100) == factorial(100)
|
| 146 |
+
|
| 147 |
+
assert ff(2*x**2 - 5*x, 2) == (2*x**2 - 5*x)*(2*x**2 - 5*x - 1)
|
| 148 |
+
assert isinstance(ff(2*x**2 - 5*x, 2), Mul)
|
| 149 |
+
assert ff(x**2 + 3*x, -2) == 1/((x**2 + 3*x + 1)*(x**2 + 3*x + 2))
|
| 150 |
+
|
| 151 |
+
assert ff(Poly(2*x**2 - 5*x, x), 2) == Poly(4*x**4 - 28*x**3 + 59*x**2 - 35*x, x)
|
| 152 |
+
assert isinstance(ff(Poly(2*x**2 - 5*x, x), 2), Poly)
|
| 153 |
+
raises(ValueError, lambda: ff(Poly(2*x**2 - 5*x, x, y), 2))
|
| 154 |
+
assert ff(Poly(x**2 + 3*x, x), -2) == 1/(x**4 + 12*x**3 + 49*x**2 + 78*x + 40)
|
| 155 |
+
raises(ValueError, lambda: ff(Poly(x**2 + 3*x, x, y), -2))
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
assert ff(x, m).is_integer is None
|
| 159 |
+
assert ff(n, k).is_integer is None
|
| 160 |
+
assert ff(n, m).is_integer is True
|
| 161 |
+
assert ff(n, k + pi).is_integer is False
|
| 162 |
+
assert ff(n, m + pi).is_integer is False
|
| 163 |
+
assert ff(pi, m).is_integer is False
|
| 164 |
+
|
| 165 |
+
assert isinstance(ff(x, x), ff)
|
| 166 |
+
assert ff(n, n) == factorial(n)
|
| 167 |
+
|
| 168 |
+
def check(x, k, o, n):
|
| 169 |
+
a, b = Dummy(), Dummy()
|
| 170 |
+
r = lambda x, k: o(a, b).rewrite(n).subs({a:x,b:k})
|
| 171 |
+
for i in range(-5,5):
|
| 172 |
+
for j in range(-5,5):
|
| 173 |
+
assert o(i, j) == r(i, j), (o, n)
|
| 174 |
+
check(x, k, ff, rf)
|
| 175 |
+
check(x, k, ff, gamma)
|
| 176 |
+
check(n, k, ff, factorial)
|
| 177 |
+
check(x, k, ff, binomial)
|
| 178 |
+
check(x, y, ff, factorial)
|
| 179 |
+
check(x, y, ff, binomial)
|
| 180 |
+
|
| 181 |
+
assert ff(x, k).rewrite(rf) == rf(x - k + 1, k)
|
| 182 |
+
assert ff(x, k).rewrite(gamma) == Piecewise(
|
| 183 |
+
(gamma(x + 1)/gamma(-k + x + 1), x >= 0),
|
| 184 |
+
((-1)**k*gamma(k - x)/gamma(-x), True))
|
| 185 |
+
assert ff(5, k).rewrite(gamma) == 120/gamma(6 - k)
|
| 186 |
+
assert ff(n, k).rewrite(factorial) == Piecewise(
|
| 187 |
+
(factorial(n)/factorial(-k + n), n >= 0),
|
| 188 |
+
((-1)**k*factorial(k - n - 1)/factorial(-n - 1), True))
|
| 189 |
+
assert ff(5, k).rewrite(factorial) == 120/factorial(5 - k)
|
| 190 |
+
assert ff(x, k).rewrite(binomial) == factorial(k) * binomial(x, k)
|
| 191 |
+
assert ff(x, y).rewrite(factorial) == ff(x, y)
|
| 192 |
+
assert ff(x, y).rewrite(binomial) == ff(x, y)
|
| 193 |
+
|
| 194 |
+
import random
|
| 195 |
+
from mpmath import ff as mpmath_ff
|
| 196 |
+
for i in range(100):
|
| 197 |
+
x = -500 + 500 * random.random()
|
| 198 |
+
k = -500 + 500 * random.random()
|
| 199 |
+
a = mpmath_ff(x, k)
|
| 200 |
+
b = ff(x, k)
|
| 201 |
+
assert (abs(a - b) < abs(a) * 10**(-15))
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def test_rf_ff_eval_hiprec():
|
| 205 |
+
maple = Float('6.9109401292234329956525265438452')
|
| 206 |
+
us = ff(18, Rational(2, 3)).evalf(32)
|
| 207 |
+
assert abs(us - maple)/us < 1e-31
|
| 208 |
+
|
| 209 |
+
maple = Float('6.8261540131125511557924466355367')
|
| 210 |
+
us = rf(18, Rational(2, 3)).evalf(32)
|
| 211 |
+
assert abs(us - maple)/us < 1e-31
|
| 212 |
+
|
| 213 |
+
maple = Float('34.007346127440197150854651814225')
|
| 214 |
+
us = rf(Float('4.4', 32), Float('2.2', 32));
|
| 215 |
+
assert abs(us - maple)/us < 1e-31
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def test_rf_lambdify_mpmath():
|
| 219 |
+
from sympy.utilities.lambdify import lambdify
|
| 220 |
+
x, y = symbols('x,y')
|
| 221 |
+
f = lambdify((x,y), rf(x, y), 'mpmath')
|
| 222 |
+
maple = Float('34.007346127440197')
|
| 223 |
+
us = f(4.4, 2.2)
|
| 224 |
+
assert abs(us - maple)/us < 1e-15
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def test_factorial():
|
| 228 |
+
x = Symbol('x')
|
| 229 |
+
n = Symbol('n', integer=True)
|
| 230 |
+
k = Symbol('k', integer=True, nonnegative=True)
|
| 231 |
+
r = Symbol('r', integer=False)
|
| 232 |
+
s = Symbol('s', integer=False, negative=True)
|
| 233 |
+
t = Symbol('t', nonnegative=True)
|
| 234 |
+
u = Symbol('u', noninteger=True)
|
| 235 |
+
|
| 236 |
+
assert factorial(-2) is zoo
|
| 237 |
+
assert factorial(0) == 1
|
| 238 |
+
assert factorial(7) == 5040
|
| 239 |
+
assert factorial(19) == 121645100408832000
|
| 240 |
+
assert factorial(31) == 8222838654177922817725562880000000
|
| 241 |
+
assert factorial(n).func == factorial
|
| 242 |
+
assert factorial(2*n).func == factorial
|
| 243 |
+
|
| 244 |
+
assert factorial(x).is_integer is None
|
| 245 |
+
assert factorial(n).is_integer is None
|
| 246 |
+
assert factorial(k).is_integer
|
| 247 |
+
assert factorial(r).is_integer is None
|
| 248 |
+
|
| 249 |
+
assert factorial(n).is_positive is None
|
| 250 |
+
assert factorial(k).is_positive
|
| 251 |
+
|
| 252 |
+
assert factorial(x).is_real is None
|
| 253 |
+
assert factorial(n).is_real is None
|
| 254 |
+
assert factorial(k).is_real is True
|
| 255 |
+
assert factorial(r).is_real is None
|
| 256 |
+
assert factorial(s).is_real is True
|
| 257 |
+
assert factorial(t).is_real is True
|
| 258 |
+
assert factorial(u).is_real is True
|
| 259 |
+
|
| 260 |
+
assert factorial(x).is_composite is None
|
| 261 |
+
assert factorial(n).is_composite is None
|
| 262 |
+
assert factorial(k).is_composite is None
|
| 263 |
+
assert factorial(k + 3).is_composite is True
|
| 264 |
+
assert factorial(r).is_composite is None
|
| 265 |
+
assert factorial(s).is_composite is None
|
| 266 |
+
assert factorial(t).is_composite is None
|
| 267 |
+
assert factorial(u).is_composite is None
|
| 268 |
+
|
| 269 |
+
assert factorial(oo) is oo
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def test_factorial_Mod():
|
| 273 |
+
pr = Symbol('pr', prime=True)
|
| 274 |
+
p, q = 10**9 + 9, 10**9 + 33 # prime modulo
|
| 275 |
+
r, s = 10**7 + 5, 33333333 # composite modulo
|
| 276 |
+
assert Mod(factorial(pr - 1), pr) == pr - 1
|
| 277 |
+
assert Mod(factorial(pr - 1), -pr) == -1
|
| 278 |
+
assert Mod(factorial(r - 1, evaluate=False), r) == 0
|
| 279 |
+
assert Mod(factorial(s - 1, evaluate=False), s) == 0
|
| 280 |
+
assert Mod(factorial(p - 1, evaluate=False), p) == p - 1
|
| 281 |
+
assert Mod(factorial(q - 1, evaluate=False), q) == q - 1
|
| 282 |
+
assert Mod(factorial(p - 50, evaluate=False), p) == 854928834
|
| 283 |
+
assert Mod(factorial(q - 1800, evaluate=False), q) == 905504050
|
| 284 |
+
assert Mod(factorial(153, evaluate=False), r) == Mod(factorial(153), r)
|
| 285 |
+
assert Mod(factorial(255, evaluate=False), s) == Mod(factorial(255), s)
|
| 286 |
+
assert Mod(factorial(4, evaluate=False), 3) == S.Zero
|
| 287 |
+
assert Mod(factorial(5, evaluate=False), 6) == S.Zero
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def test_factorial_diff():
|
| 291 |
+
n = Symbol('n', integer=True)
|
| 292 |
+
|
| 293 |
+
assert factorial(n).diff(n) == \
|
| 294 |
+
gamma(1 + n)*polygamma(0, 1 + n)
|
| 295 |
+
assert factorial(n**2).diff(n) == \
|
| 296 |
+
2*n*gamma(1 + n**2)*polygamma(0, 1 + n**2)
|
| 297 |
+
raises(ArgumentIndexError, lambda: factorial(n**2).fdiff(2))
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def test_factorial_series():
|
| 301 |
+
n = Symbol('n', integer=True)
|
| 302 |
+
|
| 303 |
+
assert factorial(n).series(n, 0, 3) == \
|
| 304 |
+
1 - n*EulerGamma + n**2*(EulerGamma**2/2 + pi**2/12) + O(n**3)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def test_factorial_rewrite():
|
| 308 |
+
n = Symbol('n', integer=True)
|
| 309 |
+
k = Symbol('k', integer=True, nonnegative=True)
|
| 310 |
+
|
| 311 |
+
assert factorial(n).rewrite(gamma) == gamma(n + 1)
|
| 312 |
+
_i = Dummy('i')
|
| 313 |
+
assert factorial(k).rewrite(Product).dummy_eq(Product(_i, (_i, 1, k)))
|
| 314 |
+
assert factorial(n).rewrite(Product) == factorial(n)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def test_factorial2():
|
| 318 |
+
n = Symbol('n', integer=True)
|
| 319 |
+
|
| 320 |
+
assert factorial2(-1) == 1
|
| 321 |
+
assert factorial2(0) == 1
|
| 322 |
+
assert factorial2(7) == 105
|
| 323 |
+
assert factorial2(8) == 384
|
| 324 |
+
|
| 325 |
+
# The following is exhaustive
|
| 326 |
+
tt = Symbol('tt', integer=True, nonnegative=True)
|
| 327 |
+
tte = Symbol('tte', even=True, nonnegative=True)
|
| 328 |
+
tpe = Symbol('tpe', even=True, positive=True)
|
| 329 |
+
tto = Symbol('tto', odd=True, nonnegative=True)
|
| 330 |
+
tf = Symbol('tf', integer=True, nonnegative=False)
|
| 331 |
+
tfe = Symbol('tfe', even=True, nonnegative=False)
|
| 332 |
+
tfo = Symbol('tfo', odd=True, nonnegative=False)
|
| 333 |
+
ft = Symbol('ft', integer=False, nonnegative=True)
|
| 334 |
+
ff = Symbol('ff', integer=False, nonnegative=False)
|
| 335 |
+
fn = Symbol('fn', integer=False)
|
| 336 |
+
nt = Symbol('nt', nonnegative=True)
|
| 337 |
+
nf = Symbol('nf', nonnegative=False)
|
| 338 |
+
nn = Symbol('nn')
|
| 339 |
+
z = Symbol('z', zero=True)
|
| 340 |
+
#Solves and Fixes Issue #10388 - This is the updated test for the same solved issue
|
| 341 |
+
raises(ValueError, lambda: factorial2(oo))
|
| 342 |
+
raises(ValueError, lambda: factorial2(Rational(5, 2)))
|
| 343 |
+
raises(ValueError, lambda: factorial2(-4))
|
| 344 |
+
assert factorial2(n).is_integer is None
|
| 345 |
+
assert factorial2(tt - 1).is_integer
|
| 346 |
+
assert factorial2(tte - 1).is_integer
|
| 347 |
+
assert factorial2(tpe - 3).is_integer
|
| 348 |
+
assert factorial2(tto - 4).is_integer
|
| 349 |
+
assert factorial2(tto - 2).is_integer
|
| 350 |
+
assert factorial2(tf).is_integer is None
|
| 351 |
+
assert factorial2(tfe).is_integer is None
|
| 352 |
+
assert factorial2(tfo).is_integer is None
|
| 353 |
+
assert factorial2(ft).is_integer is None
|
| 354 |
+
assert factorial2(ff).is_integer is None
|
| 355 |
+
assert factorial2(fn).is_integer is None
|
| 356 |
+
assert factorial2(nt).is_integer is None
|
| 357 |
+
assert factorial2(nf).is_integer is None
|
| 358 |
+
assert factorial2(nn).is_integer is None
|
| 359 |
+
|
| 360 |
+
assert factorial2(n).is_positive is None
|
| 361 |
+
assert factorial2(tt - 1).is_positive is True
|
| 362 |
+
assert factorial2(tte - 1).is_positive is True
|
| 363 |
+
assert factorial2(tpe - 3).is_positive is True
|
| 364 |
+
assert factorial2(tpe - 1).is_positive is True
|
| 365 |
+
assert factorial2(tto - 2).is_positive is True
|
| 366 |
+
assert factorial2(tto - 1).is_positive is True
|
| 367 |
+
assert factorial2(tf).is_positive is None
|
| 368 |
+
assert factorial2(tfe).is_positive is None
|
| 369 |
+
assert factorial2(tfo).is_positive is None
|
| 370 |
+
assert factorial2(ft).is_positive is None
|
| 371 |
+
assert factorial2(ff).is_positive is None
|
| 372 |
+
assert factorial2(fn).is_positive is None
|
| 373 |
+
assert factorial2(nt).is_positive is None
|
| 374 |
+
assert factorial2(nf).is_positive is None
|
| 375 |
+
assert factorial2(nn).is_positive is None
|
| 376 |
+
|
| 377 |
+
assert factorial2(tt).is_even is None
|
| 378 |
+
assert factorial2(tt).is_odd is None
|
| 379 |
+
assert factorial2(tte).is_even is None
|
| 380 |
+
assert factorial2(tte).is_odd is None
|
| 381 |
+
assert factorial2(tte + 2).is_even is True
|
| 382 |
+
assert factorial2(tpe).is_even is True
|
| 383 |
+
assert factorial2(tpe).is_odd is False
|
| 384 |
+
assert factorial2(tto).is_odd is True
|
| 385 |
+
assert factorial2(tf).is_even is None
|
| 386 |
+
assert factorial2(tf).is_odd is None
|
| 387 |
+
assert factorial2(tfe).is_even is None
|
| 388 |
+
assert factorial2(tfe).is_odd is None
|
| 389 |
+
assert factorial2(tfo).is_even is False
|
| 390 |
+
assert factorial2(tfo).is_odd is None
|
| 391 |
+
assert factorial2(z).is_even is False
|
| 392 |
+
assert factorial2(z).is_odd is True
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def test_factorial2_rewrite():
|
| 396 |
+
n = Symbol('n', integer=True)
|
| 397 |
+
assert factorial2(n).rewrite(gamma) == \
|
| 398 |
+
2**(n/2)*Piecewise((1, Eq(Mod(n, 2), 0)), (sqrt(2)/sqrt(pi), Eq(Mod(n, 2), 1)))*gamma(n/2 + 1)
|
| 399 |
+
assert factorial2(2*n).rewrite(gamma) == 2**n*gamma(n + 1)
|
| 400 |
+
assert factorial2(2*n + 1).rewrite(gamma) == \
|
| 401 |
+
sqrt(2)*2**(n + S.Half)*gamma(n + Rational(3, 2))/sqrt(pi)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def test_binomial():
|
| 405 |
+
x = Symbol('x')
|
| 406 |
+
n = Symbol('n', integer=True)
|
| 407 |
+
nz = Symbol('nz', integer=True, nonzero=True)
|
| 408 |
+
k = Symbol('k', integer=True)
|
| 409 |
+
kp = Symbol('kp', integer=True, positive=True)
|
| 410 |
+
kn = Symbol('kn', integer=True, negative=True)
|
| 411 |
+
u = Symbol('u', negative=True)
|
| 412 |
+
v = Symbol('v', nonnegative=True)
|
| 413 |
+
p = Symbol('p', positive=True)
|
| 414 |
+
z = Symbol('z', zero=True)
|
| 415 |
+
nt = Symbol('nt', integer=False)
|
| 416 |
+
kt = Symbol('kt', integer=False)
|
| 417 |
+
a = Symbol('a', integer=True, nonnegative=True)
|
| 418 |
+
b = Symbol('b', integer=True, nonnegative=True)
|
| 419 |
+
|
| 420 |
+
assert binomial(0, 0) == 1
|
| 421 |
+
assert binomial(1, 1) == 1
|
| 422 |
+
assert binomial(10, 10) == 1
|
| 423 |
+
assert binomial(n, z) == 1
|
| 424 |
+
assert binomial(1, 2) == 0
|
| 425 |
+
assert binomial(-1, 2) == 1
|
| 426 |
+
assert binomial(1, -1) == 0
|
| 427 |
+
assert binomial(-1, 1) == -1
|
| 428 |
+
assert binomial(-1, -1) == 0
|
| 429 |
+
assert binomial(S.Half, S.Half) == 1
|
| 430 |
+
assert binomial(-10, 1) == -10
|
| 431 |
+
assert binomial(-10, 7) == -11440
|
| 432 |
+
assert binomial(n, -1) == 0 # holds for all integers (negative, zero, positive)
|
| 433 |
+
assert binomial(kp, -1) == 0
|
| 434 |
+
assert binomial(nz, 0) == 1
|
| 435 |
+
assert expand_func(binomial(n, 1)) == n
|
| 436 |
+
assert expand_func(binomial(n, 2)) == n*(n - 1)/2
|
| 437 |
+
assert expand_func(binomial(n, n - 2)) == n*(n - 1)/2
|
| 438 |
+
assert expand_func(binomial(n, n - 1)) == n
|
| 439 |
+
assert binomial(n, 3).func == binomial
|
| 440 |
+
assert binomial(n, 3).expand(func=True) == n**3/6 - n**2/2 + n/3
|
| 441 |
+
assert expand_func(binomial(n, 3)) == n*(n - 2)*(n - 1)/6
|
| 442 |
+
assert binomial(n, n).func == binomial # e.g. (-1, -1) == 0, (2, 2) == 1
|
| 443 |
+
assert binomial(n, n + 1).func == binomial # e.g. (-1, 0) == 1
|
| 444 |
+
assert binomial(kp, kp + 1) == 0
|
| 445 |
+
assert binomial(kn, kn) == 0 # issue #14529
|
| 446 |
+
assert binomial(n, u).func == binomial
|
| 447 |
+
assert binomial(kp, u).func == binomial
|
| 448 |
+
assert binomial(n, p).func == binomial
|
| 449 |
+
assert binomial(n, k).func == binomial
|
| 450 |
+
assert binomial(n, n + p).func == binomial
|
| 451 |
+
assert binomial(kp, kp + p).func == binomial
|
| 452 |
+
|
| 453 |
+
assert expand_func(binomial(n, n - 3)) == n*(n - 2)*(n - 1)/6
|
| 454 |
+
|
| 455 |
+
assert binomial(n, k).is_integer
|
| 456 |
+
assert binomial(nt, k).is_integer is None
|
| 457 |
+
assert binomial(x, nt).is_integer is False
|
| 458 |
+
|
| 459 |
+
assert binomial(gamma(25), 6) == 79232165267303928292058750056084441948572511312165380965440075720159859792344339983120618959044048198214221915637090855535036339620413440000
|
| 460 |
+
assert binomial(1324, 47) == 906266255662694632984994480774946083064699457235920708992926525848438478406790323869952
|
| 461 |
+
assert binomial(1735, 43) == 190910140420204130794758005450919715396159959034348676124678207874195064798202216379800
|
| 462 |
+
assert binomial(2512, 53) == 213894469313832631145798303740098720367984955243020898718979538096223399813295457822575338958939834177325304000
|
| 463 |
+
assert binomial(3383, 52) == 27922807788818096863529701501764372757272890613101645521813434902890007725667814813832027795881839396839287659777235
|
| 464 |
+
assert binomial(4321, 51) == 124595639629264868916081001263541480185227731958274383287107643816863897851139048158022599533438936036467601690983780576
|
| 465 |
+
|
| 466 |
+
assert binomial(a, b).is_nonnegative is True
|
| 467 |
+
assert binomial(-1, 2, evaluate=False).is_nonnegative is True
|
| 468 |
+
assert binomial(10, 5, evaluate=False).is_nonnegative is True
|
| 469 |
+
assert binomial(10, -3, evaluate=False).is_nonnegative is True
|
| 470 |
+
assert binomial(-10, -3, evaluate=False).is_nonnegative is True
|
| 471 |
+
assert binomial(-10, 2, evaluate=False).is_nonnegative is True
|
| 472 |
+
assert binomial(-10, 1, evaluate=False).is_nonnegative is False
|
| 473 |
+
assert binomial(-10, 7, evaluate=False).is_nonnegative is False
|
| 474 |
+
|
| 475 |
+
# issue #14625
|
| 476 |
+
for _ in (pi, -pi, nt, v, a):
|
| 477 |
+
assert binomial(_, _) == 1
|
| 478 |
+
assert binomial(_, _ - 1) == _
|
| 479 |
+
assert isinstance(binomial(u, u), binomial)
|
| 480 |
+
assert isinstance(binomial(u, u - 1), binomial)
|
| 481 |
+
assert isinstance(binomial(x, x), binomial)
|
| 482 |
+
assert isinstance(binomial(x, x - 1), binomial)
|
| 483 |
+
|
| 484 |
+
#issue #18802
|
| 485 |
+
assert expand_func(binomial(x + 1, x)) == x + 1
|
| 486 |
+
assert expand_func(binomial(x, x - 1)) == x
|
| 487 |
+
assert expand_func(binomial(x + 1, x - 1)) == x*(x + 1)/2
|
| 488 |
+
assert expand_func(binomial(x**2 + 1, x**2)) == x**2 + 1
|
| 489 |
+
|
| 490 |
+
# issue #13980 and #13981
|
| 491 |
+
assert binomial(-7, -5) == 0
|
| 492 |
+
assert binomial(-23, -12) == 0
|
| 493 |
+
assert binomial(Rational(13, 2), -10) == 0
|
| 494 |
+
assert binomial(-49, -51) == 0
|
| 495 |
+
|
| 496 |
+
assert binomial(19, Rational(-7, 2)) == S(-68719476736)/(911337863661225*pi)
|
| 497 |
+
assert binomial(0, Rational(3, 2)) == S(-2)/(3*pi)
|
| 498 |
+
assert binomial(-3, Rational(-7, 2)) is zoo
|
| 499 |
+
assert binomial(kn, kt) is zoo
|
| 500 |
+
|
| 501 |
+
assert binomial(nt, kt).func == binomial
|
| 502 |
+
assert binomial(nt, Rational(15, 6)) == 8*gamma(nt + 1)/(15*sqrt(pi)*gamma(nt - Rational(3, 2)))
|
| 503 |
+
assert binomial(Rational(20, 3), Rational(-10, 8)) == gamma(Rational(23, 3))/(gamma(Rational(-1, 4))*gamma(Rational(107, 12)))
|
| 504 |
+
assert binomial(Rational(19, 2), Rational(-7, 2)) == Rational(-1615, 8388608)
|
| 505 |
+
assert binomial(Rational(-13, 5), Rational(-7, 8)) == gamma(Rational(-8, 5))/(gamma(Rational(-29, 40))*gamma(Rational(1, 8)))
|
| 506 |
+
assert binomial(Rational(-19, 8), Rational(-13, 5)) == gamma(Rational(-11, 8))/(gamma(Rational(-8, 5))*gamma(Rational(49, 40)))
|
| 507 |
+
|
| 508 |
+
# binomial for complexes
|
| 509 |
+
assert binomial(I, Rational(-89, 8)) == gamma(1 + I)/(gamma(Rational(-81, 8))*gamma(Rational(97, 8) + I))
|
| 510 |
+
assert binomial(I, 2*I) == gamma(1 + I)/(gamma(1 - I)*gamma(1 + 2*I))
|
| 511 |
+
assert binomial(-7, I) is zoo
|
| 512 |
+
assert binomial(Rational(-7, 6), I) == gamma(Rational(-1, 6))/(gamma(Rational(-1, 6) - I)*gamma(1 + I))
|
| 513 |
+
assert binomial((1+2*I), (1+3*I)) == gamma(2 + 2*I)/(gamma(1 - I)*gamma(2 + 3*I))
|
| 514 |
+
assert binomial(I, 5) == Rational(1, 3) - I/S(12)
|
| 515 |
+
assert binomial((2*I + 3), 7) == -13*I/S(63)
|
| 516 |
+
assert isinstance(binomial(I, n), binomial)
|
| 517 |
+
assert expand_func(binomial(3, 2, evaluate=False)) == 3
|
| 518 |
+
assert expand_func(binomial(n, 0, evaluate=False)) == 1
|
| 519 |
+
assert expand_func(binomial(n, -2, evaluate=False)) == 0
|
| 520 |
+
assert expand_func(binomial(n, k)) == binomial(n, k)
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def test_binomial_Mod():
|
| 524 |
+
p, q = 10**5 + 3, 10**9 + 33 # prime modulo
|
| 525 |
+
r = 10**7 + 5 # composite modulo
|
| 526 |
+
|
| 527 |
+
# A few tests to get coverage
|
| 528 |
+
# Lucas Theorem
|
| 529 |
+
assert Mod(binomial(156675, 4433, evaluate=False), p) == Mod(binomial(156675, 4433), p)
|
| 530 |
+
|
| 531 |
+
# factorial Mod
|
| 532 |
+
assert Mod(binomial(1234, 432, evaluate=False), q) == Mod(binomial(1234, 432), q)
|
| 533 |
+
|
| 534 |
+
# binomial factorize
|
| 535 |
+
assert Mod(binomial(253, 113, evaluate=False), r) == Mod(binomial(253, 113), r)
|
| 536 |
+
|
| 537 |
+
# using Granville's generalisation of Lucas' Theorem
|
| 538 |
+
assert Mod(binomial(10**18, 10**12, evaluate=False), p*p) == 3744312326
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
@slow
|
| 542 |
+
def test_binomial_Mod_slow():
|
| 543 |
+
p, q = 10**5 + 3, 10**9 + 33 # prime modulo
|
| 544 |
+
r, s = 10**7 + 5, 33333333 # composite modulo
|
| 545 |
+
|
| 546 |
+
n, k, m = symbols('n k m')
|
| 547 |
+
assert (binomial(n, k) % q).subs({n: s, k: p}) == Mod(binomial(s, p), q)
|
| 548 |
+
assert (binomial(n, k) % m).subs({n: 8, k: 5, m: 13}) == 4
|
| 549 |
+
assert (binomial(9, k) % 7).subs(k, 2) == 1
|
| 550 |
+
|
| 551 |
+
# Lucas Theorem
|
| 552 |
+
assert Mod(binomial(123456, 43253, evaluate=False), p) == Mod(binomial(123456, 43253), p)
|
| 553 |
+
assert Mod(binomial(-178911, 237, evaluate=False), p) == Mod(-binomial(178911 + 237 - 1, 237), p)
|
| 554 |
+
assert Mod(binomial(-178911, 238, evaluate=False), p) == Mod(binomial(178911 + 238 - 1, 238), p)
|
| 555 |
+
|
| 556 |
+
# factorial Mod
|
| 557 |
+
assert Mod(binomial(9734, 451, evaluate=False), q) == Mod(binomial(9734, 451), q)
|
| 558 |
+
assert Mod(binomial(-10733, 4459, evaluate=False), q) == Mod(binomial(-10733, 4459), q)
|
| 559 |
+
assert Mod(binomial(-15733, 4458, evaluate=False), q) == Mod(binomial(-15733, 4458), q)
|
| 560 |
+
assert Mod(binomial(23, -38, evaluate=False), q) is S.Zero
|
| 561 |
+
assert Mod(binomial(23, 38, evaluate=False), q) is S.Zero
|
| 562 |
+
|
| 563 |
+
# binomial factorize
|
| 564 |
+
assert Mod(binomial(753, 119, evaluate=False), r) == Mod(binomial(753, 119), r)
|
| 565 |
+
assert Mod(binomial(3781, 948, evaluate=False), s) == Mod(binomial(3781, 948), s)
|
| 566 |
+
assert Mod(binomial(25773, 1793, evaluate=False), s) == Mod(binomial(25773, 1793), s)
|
| 567 |
+
assert Mod(binomial(-753, 118, evaluate=False), r) == Mod(binomial(-753, 118), r)
|
| 568 |
+
assert Mod(binomial(-25773, 1793, evaluate=False), s) == Mod(binomial(-25773, 1793), s)
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def test_binomial_diff():
|
| 572 |
+
n = Symbol('n', integer=True)
|
| 573 |
+
k = Symbol('k', integer=True)
|
| 574 |
+
|
| 575 |
+
assert binomial(n, k).diff(n) == \
|
| 576 |
+
(-polygamma(0, 1 + n - k) + polygamma(0, 1 + n))*binomial(n, k)
|
| 577 |
+
assert binomial(n**2, k**3).diff(n) == \
|
| 578 |
+
2*n*(-polygamma(
|
| 579 |
+
0, 1 + n**2 - k**3) + polygamma(0, 1 + n**2))*binomial(n**2, k**3)
|
| 580 |
+
|
| 581 |
+
assert binomial(n, k).diff(k) == \
|
| 582 |
+
(-polygamma(0, 1 + k) + polygamma(0, 1 + n - k))*binomial(n, k)
|
| 583 |
+
assert binomial(n**2, k**3).diff(k) == \
|
| 584 |
+
3*k**2*(-polygamma(
|
| 585 |
+
0, 1 + k**3) + polygamma(0, 1 + n**2 - k**3))*binomial(n**2, k**3)
|
| 586 |
+
raises(ArgumentIndexError, lambda: binomial(n, k).fdiff(3))
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
def test_binomial_rewrite():
|
| 590 |
+
n = Symbol('n', integer=True)
|
| 591 |
+
k = Symbol('k', integer=True)
|
| 592 |
+
x = Symbol('x')
|
| 593 |
+
|
| 594 |
+
assert binomial(n, k).rewrite(
|
| 595 |
+
factorial) == factorial(n)/(factorial(k)*factorial(n - k))
|
| 596 |
+
assert binomial(
|
| 597 |
+
n, k).rewrite(gamma) == gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1))
|
| 598 |
+
assert binomial(n, k).rewrite(ff) == ff(n, k) / factorial(k)
|
| 599 |
+
assert binomial(n, x).rewrite(ff) == binomial(n, x)
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
@XFAIL
|
| 603 |
+
def test_factorial_simplify_fail():
|
| 604 |
+
# simplify(factorial(x + 1).diff(x) - ((x + 1)*factorial(x)).diff(x))) == 0
|
| 605 |
+
from sympy.abc import x
|
| 606 |
+
assert simplify(x*polygamma(0, x + 1) - x*polygamma(0, x + 2) +
|
| 607 |
+
polygamma(0, x + 1) - polygamma(0, x + 2) + 1) == 0
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
def test_subfactorial():
|
| 611 |
+
assert all(subfactorial(i) == ans for i, ans in enumerate(
|
| 612 |
+
[1, 0, 1, 2, 9, 44, 265, 1854, 14833, 133496]))
|
| 613 |
+
assert subfactorial(oo) is oo
|
| 614 |
+
assert subfactorial(nan) is nan
|
| 615 |
+
assert subfactorial(23) == 9510425471055777937262
|
| 616 |
+
assert unchanged(subfactorial, 2.2)
|
| 617 |
+
|
| 618 |
+
x = Symbol('x')
|
| 619 |
+
assert subfactorial(x).rewrite(uppergamma) == uppergamma(x + 1, -1)/S.Exp1
|
| 620 |
+
|
| 621 |
+
tt = Symbol('tt', integer=True, nonnegative=True)
|
| 622 |
+
tf = Symbol('tf', integer=True, nonnegative=False)
|
| 623 |
+
tn = Symbol('tf', integer=True)
|
| 624 |
+
ft = Symbol('ft', integer=False, nonnegative=True)
|
| 625 |
+
ff = Symbol('ff', integer=False, nonnegative=False)
|
| 626 |
+
fn = Symbol('ff', integer=False)
|
| 627 |
+
nt = Symbol('nt', nonnegative=True)
|
| 628 |
+
nf = Symbol('nf', nonnegative=False)
|
| 629 |
+
nn = Symbol('nf')
|
| 630 |
+
te = Symbol('te', even=True, nonnegative=True)
|
| 631 |
+
to = Symbol('to', odd=True, nonnegative=True)
|
| 632 |
+
assert subfactorial(tt).is_integer
|
| 633 |
+
assert subfactorial(tf).is_integer is None
|
| 634 |
+
assert subfactorial(tn).is_integer is None
|
| 635 |
+
assert subfactorial(ft).is_integer is None
|
| 636 |
+
assert subfactorial(ff).is_integer is None
|
| 637 |
+
assert subfactorial(fn).is_integer is None
|
| 638 |
+
assert subfactorial(nt).is_integer is None
|
| 639 |
+
assert subfactorial(nf).is_integer is None
|
| 640 |
+
assert subfactorial(nn).is_integer is None
|
| 641 |
+
assert subfactorial(tt).is_nonnegative
|
| 642 |
+
assert subfactorial(tf).is_nonnegative is None
|
| 643 |
+
assert subfactorial(tn).is_nonnegative is None
|
| 644 |
+
assert subfactorial(ft).is_nonnegative is None
|
| 645 |
+
assert subfactorial(ff).is_nonnegative is None
|
| 646 |
+
assert subfactorial(fn).is_nonnegative is None
|
| 647 |
+
assert subfactorial(nt).is_nonnegative is None
|
| 648 |
+
assert subfactorial(nf).is_nonnegative is None
|
| 649 |
+
assert subfactorial(nn).is_nonnegative is None
|
| 650 |
+
assert subfactorial(tt).is_even is None
|
| 651 |
+
assert subfactorial(tt).is_odd is None
|
| 652 |
+
assert subfactorial(te).is_odd is True
|
| 653 |
+
assert subfactorial(to).is_even is True
|
lib/python3.12/site-packages/sympy/functions/combinatorial/tests/test_comb_numbers.py
ADDED
|
@@ -0,0 +1,1241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import string
|
| 2 |
+
|
| 3 |
+
from sympy.concrete.products import Product
|
| 4 |
+
from sympy.concrete.summations import Sum
|
| 5 |
+
from sympy.core.function import (diff, expand_func)
|
| 6 |
+
from sympy.core import (EulerGamma, TribonacciConstant)
|
| 7 |
+
from sympy.core.numbers import (Float, I, Rational, oo, pi)
|
| 8 |
+
from sympy.core.singleton import S
|
| 9 |
+
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
| 10 |
+
from sympy.functions.combinatorial.numbers import carmichael
|
| 11 |
+
from sympy.functions.elementary.complexes import (im, re)
|
| 12 |
+
from sympy.functions.elementary.integers import floor
|
| 13 |
+
from sympy.polys.polytools import cancel
|
| 14 |
+
from sympy.series.limits import limit, Limit
|
| 15 |
+
from sympy.series.order import O
|
| 16 |
+
from sympy.functions import (
|
| 17 |
+
bernoulli, harmonic, bell, fibonacci, tribonacci, lucas, euler, catalan,
|
| 18 |
+
genocchi, andre, partition, divisor_sigma, udivisor_sigma, legendre_symbol,
|
| 19 |
+
jacobi_symbol, kronecker_symbol, mobius,
|
| 20 |
+
primenu, primeomega, totient, reduced_totient, primepi,
|
| 21 |
+
motzkin, binomial, gamma, sqrt, cbrt, hyper, log, digamma,
|
| 22 |
+
trigamma, polygamma, factorial, sin, cos, cot, polylog, zeta, dirichlet_eta)
|
| 23 |
+
from sympy.functions.combinatorial.numbers import _nT
|
| 24 |
+
from sympy.ntheory.factor_ import factorint
|
| 25 |
+
|
| 26 |
+
from sympy.core.expr import unchanged
|
| 27 |
+
from sympy.core.numbers import GoldenRatio, Integer
|
| 28 |
+
|
| 29 |
+
from sympy.testing.pytest import raises, nocache_fail, warns_deprecated_sympy
|
| 30 |
+
from sympy.abc import x
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def test_carmichael():
|
| 34 |
+
with warns_deprecated_sympy():
|
| 35 |
+
assert carmichael.is_prime(2821) == False
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_bernoulli():
|
| 39 |
+
assert bernoulli(0) == 1
|
| 40 |
+
assert bernoulli(1) == Rational(1, 2)
|
| 41 |
+
assert bernoulli(2) == Rational(1, 6)
|
| 42 |
+
assert bernoulli(3) == 0
|
| 43 |
+
assert bernoulli(4) == Rational(-1, 30)
|
| 44 |
+
assert bernoulli(5) == 0
|
| 45 |
+
assert bernoulli(6) == Rational(1, 42)
|
| 46 |
+
assert bernoulli(7) == 0
|
| 47 |
+
assert bernoulli(8) == Rational(-1, 30)
|
| 48 |
+
assert bernoulli(10) == Rational(5, 66)
|
| 49 |
+
assert bernoulli(1000001) == 0
|
| 50 |
+
|
| 51 |
+
assert bernoulli(0, x) == 1
|
| 52 |
+
assert bernoulli(1, x) == x - S.Half
|
| 53 |
+
assert bernoulli(2, x) == x**2 - x + Rational(1, 6)
|
| 54 |
+
assert bernoulli(3, x) == x**3 - (3*x**2)/2 + x/2
|
| 55 |
+
|
| 56 |
+
# Should be fast; computed with mpmath
|
| 57 |
+
b = bernoulli(1000)
|
| 58 |
+
assert b.p % 10**10 == 7950421099
|
| 59 |
+
assert b.q == 342999030
|
| 60 |
+
|
| 61 |
+
b = bernoulli(10**6, evaluate=False).evalf()
|
| 62 |
+
assert str(b) == '-2.23799235765713e+4767529'
|
| 63 |
+
|
| 64 |
+
# Issue #8527
|
| 65 |
+
l = Symbol('l', integer=True)
|
| 66 |
+
m = Symbol('m', integer=True, nonnegative=True)
|
| 67 |
+
n = Symbol('n', integer=True, positive=True)
|
| 68 |
+
assert isinstance(bernoulli(2 * l + 1), bernoulli)
|
| 69 |
+
assert isinstance(bernoulli(2 * m + 1), bernoulli)
|
| 70 |
+
assert bernoulli(2 * n + 1) == 0
|
| 71 |
+
|
| 72 |
+
assert bernoulli(x, 1) == bernoulli(x)
|
| 73 |
+
|
| 74 |
+
assert str(bernoulli(0.0, 2.3).evalf(n=10)) == '1.000000000'
|
| 75 |
+
assert str(bernoulli(1.0).evalf(n=10)) == '0.5000000000'
|
| 76 |
+
assert str(bernoulli(1.2).evalf(n=10)) == '0.4195995367'
|
| 77 |
+
assert str(bernoulli(1.2, 0.8).evalf(n=10)) == '0.2144830348'
|
| 78 |
+
assert str(bernoulli(1.2, -0.8).evalf(n=10)) == '-1.158865646 - 0.6745558744*I'
|
| 79 |
+
assert str(bernoulli(3.0, 1j).evalf(n=10)) == '1.5 - 0.5*I'
|
| 80 |
+
assert str(bernoulli(I).evalf(n=10)) == '0.9268485643 - 0.5821580598*I'
|
| 81 |
+
assert str(bernoulli(I, I).evalf(n=10)) == '0.1267792071 + 0.01947413152*I'
|
| 82 |
+
assert bernoulli(x).evalf() == bernoulli(x)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def test_bernoulli_rewrite():
|
| 86 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 87 |
+
n = Symbol('n', integer=True, nonnegative=True)
|
| 88 |
+
|
| 89 |
+
assert bernoulli(-1).rewrite(zeta) == pi**2/6
|
| 90 |
+
assert bernoulli(-2).rewrite(zeta) == 2*zeta(3)
|
| 91 |
+
assert not bernoulli(n, -3).rewrite(zeta).has(harmonic)
|
| 92 |
+
assert bernoulli(-4, x).rewrite(zeta) == 4*zeta(5, x)
|
| 93 |
+
assert isinstance(bernoulli(n, x).rewrite(zeta), Piecewise)
|
| 94 |
+
assert bernoulli(n+1, x).rewrite(zeta) == -(n+1) * zeta(-n, x)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def test_fibonacci():
|
| 98 |
+
assert [fibonacci(n) for n in range(-3, 5)] == [2, -1, 1, 0, 1, 1, 2, 3]
|
| 99 |
+
assert fibonacci(100) == 354224848179261915075
|
| 100 |
+
assert [lucas(n) for n in range(-3, 5)] == [-4, 3, -1, 2, 1, 3, 4, 7]
|
| 101 |
+
assert lucas(100) == 792070839848372253127
|
| 102 |
+
|
| 103 |
+
assert fibonacci(1, x) == 1
|
| 104 |
+
assert fibonacci(2, x) == x
|
| 105 |
+
assert fibonacci(3, x) == x**2 + 1
|
| 106 |
+
assert fibonacci(4, x) == x**3 + 2*x
|
| 107 |
+
|
| 108 |
+
# issue #8800
|
| 109 |
+
n = Dummy('n')
|
| 110 |
+
assert fibonacci(n).limit(n, S.Infinity) is S.Infinity
|
| 111 |
+
assert lucas(n).limit(n, S.Infinity) is S.Infinity
|
| 112 |
+
|
| 113 |
+
assert fibonacci(n).rewrite(sqrt) == \
|
| 114 |
+
2**(-n)*sqrt(5)*((1 + sqrt(5))**n - (-sqrt(5) + 1)**n) / 5
|
| 115 |
+
assert fibonacci(n).rewrite(sqrt).subs(n, 10).expand() == fibonacci(10)
|
| 116 |
+
assert fibonacci(n).rewrite(GoldenRatio).subs(n,10).evalf() == \
|
| 117 |
+
Float(fibonacci(10))
|
| 118 |
+
assert lucas(n).rewrite(sqrt) == \
|
| 119 |
+
(fibonacci(n-1).rewrite(sqrt) + fibonacci(n+1).rewrite(sqrt)).simplify()
|
| 120 |
+
assert lucas(n).rewrite(sqrt).subs(n, 10).expand() == lucas(10)
|
| 121 |
+
raises(ValueError, lambda: fibonacci(-3, x))
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def test_tribonacci():
|
| 125 |
+
assert [tribonacci(n) for n in range(8)] == [0, 1, 1, 2, 4, 7, 13, 24]
|
| 126 |
+
assert tribonacci(100) == 98079530178586034536500564
|
| 127 |
+
|
| 128 |
+
assert tribonacci(0, x) == 0
|
| 129 |
+
assert tribonacci(1, x) == 1
|
| 130 |
+
assert tribonacci(2, x) == x**2
|
| 131 |
+
assert tribonacci(3, x) == x**4 + x
|
| 132 |
+
assert tribonacci(4, x) == x**6 + 2*x**3 + 1
|
| 133 |
+
assert tribonacci(5, x) == x**8 + 3*x**5 + 3*x**2
|
| 134 |
+
|
| 135 |
+
n = Dummy('n')
|
| 136 |
+
assert tribonacci(n).limit(n, S.Infinity) is S.Infinity
|
| 137 |
+
|
| 138 |
+
w = (-1 + S.ImaginaryUnit * sqrt(3)) / 2
|
| 139 |
+
a = (1 + cbrt(19 + 3*sqrt(33)) + cbrt(19 - 3*sqrt(33))) / 3
|
| 140 |
+
b = (1 + w*cbrt(19 + 3*sqrt(33)) + w**2*cbrt(19 - 3*sqrt(33))) / 3
|
| 141 |
+
c = (1 + w**2*cbrt(19 + 3*sqrt(33)) + w*cbrt(19 - 3*sqrt(33))) / 3
|
| 142 |
+
assert tribonacci(n).rewrite(sqrt) == \
|
| 143 |
+
(a**(n + 1)/((a - b)*(a - c))
|
| 144 |
+
+ b**(n + 1)/((b - a)*(b - c))
|
| 145 |
+
+ c**(n + 1)/((c - a)*(c - b)))
|
| 146 |
+
assert tribonacci(n).rewrite(sqrt).subs(n, 4).simplify() == tribonacci(4)
|
| 147 |
+
assert tribonacci(n).rewrite(GoldenRatio).subs(n,10).evalf() == \
|
| 148 |
+
Float(tribonacci(10))
|
| 149 |
+
assert tribonacci(n).rewrite(TribonacciConstant) == floor(
|
| 150 |
+
3*TribonacciConstant**n*(102*sqrt(33) + 586)**Rational(1, 3)/
|
| 151 |
+
(-2*(102*sqrt(33) + 586)**Rational(1, 3) + 4 + (102*sqrt(33)
|
| 152 |
+
+ 586)**Rational(2, 3)) + S.Half)
|
| 153 |
+
raises(ValueError, lambda: tribonacci(-1, x))
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
@nocache_fail
|
| 157 |
+
def test_bell():
|
| 158 |
+
assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877]
|
| 159 |
+
|
| 160 |
+
assert bell(0, x) == 1
|
| 161 |
+
assert bell(1, x) == x
|
| 162 |
+
assert bell(2, x) == x**2 + x
|
| 163 |
+
assert bell(5, x) == x**5 + 10*x**4 + 25*x**3 + 15*x**2 + x
|
| 164 |
+
assert bell(oo) is S.Infinity
|
| 165 |
+
raises(ValueError, lambda: bell(oo, x))
|
| 166 |
+
|
| 167 |
+
raises(ValueError, lambda: bell(-1))
|
| 168 |
+
raises(ValueError, lambda: bell(S.Half))
|
| 169 |
+
|
| 170 |
+
X = symbols('x:6')
|
| 171 |
+
# X = (x0, x1, .. x5)
|
| 172 |
+
# at the same time: X[1] = x1, X[2] = x2 for standard readablity.
|
| 173 |
+
# but we must supply zero-based indexed object X[1:] = (x1, .. x5)
|
| 174 |
+
|
| 175 |
+
assert bell(6, 2, X[1:]) == 6*X[5]*X[1] + 15*X[4]*X[2] + 10*X[3]**2
|
| 176 |
+
assert bell(
|
| 177 |
+
6, 3, X[1:]) == 15*X[4]*X[1]**2 + 60*X[3]*X[2]*X[1] + 15*X[2]**3
|
| 178 |
+
|
| 179 |
+
X = (1, 10, 100, 1000, 10000)
|
| 180 |
+
assert bell(6, 2, X) == (6 + 15 + 10)*10000
|
| 181 |
+
|
| 182 |
+
X = (1, 2, 3, 3, 5)
|
| 183 |
+
assert bell(6, 2, X) == 6*5 + 15*3*2 + 10*3**2
|
| 184 |
+
|
| 185 |
+
X = (1, 2, 3, 5)
|
| 186 |
+
assert bell(6, 3, X) == 15*5 + 60*3*2 + 15*2**3
|
| 187 |
+
|
| 188 |
+
# Dobinski's formula
|
| 189 |
+
n = Symbol('n', integer=True, nonnegative=True)
|
| 190 |
+
# For large numbers, this is too slow
|
| 191 |
+
# For nonintegers, there are significant precision errors
|
| 192 |
+
for i in [0, 2, 3, 7, 13, 42, 55]:
|
| 193 |
+
# Running without the cache this is either very slow or goes into an
|
| 194 |
+
# infinite loop.
|
| 195 |
+
assert bell(i).evalf() == bell(n).rewrite(Sum).evalf(subs={n: i})
|
| 196 |
+
|
| 197 |
+
m = Symbol("m")
|
| 198 |
+
assert bell(m).rewrite(Sum) == bell(m)
|
| 199 |
+
assert bell(n, m).rewrite(Sum) == bell(n, m)
|
| 200 |
+
# issue 9184
|
| 201 |
+
n = Dummy('n')
|
| 202 |
+
assert bell(n).limit(n, S.Infinity) is S.Infinity
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def test_harmonic():
|
| 206 |
+
n = Symbol("n")
|
| 207 |
+
m = Symbol("m")
|
| 208 |
+
|
| 209 |
+
assert harmonic(n, 0) == n
|
| 210 |
+
assert harmonic(n).evalf() == harmonic(n)
|
| 211 |
+
assert harmonic(n, 1) == harmonic(n)
|
| 212 |
+
assert harmonic(1, n) == 1
|
| 213 |
+
|
| 214 |
+
assert harmonic(0, 1) == 0
|
| 215 |
+
assert harmonic(1, 1) == 1
|
| 216 |
+
assert harmonic(2, 1) == Rational(3, 2)
|
| 217 |
+
assert harmonic(3, 1) == Rational(11, 6)
|
| 218 |
+
assert harmonic(4, 1) == Rational(25, 12)
|
| 219 |
+
assert harmonic(0, 2) == 0
|
| 220 |
+
assert harmonic(1, 2) == 1
|
| 221 |
+
assert harmonic(2, 2) == Rational(5, 4)
|
| 222 |
+
assert harmonic(3, 2) == Rational(49, 36)
|
| 223 |
+
assert harmonic(4, 2) == Rational(205, 144)
|
| 224 |
+
assert harmonic(0, 3) == 0
|
| 225 |
+
assert harmonic(1, 3) == 1
|
| 226 |
+
assert harmonic(2, 3) == Rational(9, 8)
|
| 227 |
+
assert harmonic(3, 3) == Rational(251, 216)
|
| 228 |
+
assert harmonic(4, 3) == Rational(2035, 1728)
|
| 229 |
+
|
| 230 |
+
assert harmonic(oo, -1) is S.NaN
|
| 231 |
+
assert harmonic(oo, 0) is oo
|
| 232 |
+
assert harmonic(oo, S.Half) is oo
|
| 233 |
+
assert harmonic(oo, 1) is oo
|
| 234 |
+
assert harmonic(oo, 2) == (pi**2)/6
|
| 235 |
+
assert harmonic(oo, 3) == zeta(3)
|
| 236 |
+
assert harmonic(oo, Dummy(negative=True)) is S.NaN
|
| 237 |
+
ip = Dummy(integer=True, positive=True)
|
| 238 |
+
if (1/ip <= 1) is True: #---------------------------------+
|
| 239 |
+
assert None, 'delete this if-block and the next line' #|
|
| 240 |
+
ip = Dummy(even=True, positive=True) #--------------------+
|
| 241 |
+
assert harmonic(oo, 1/ip) is oo
|
| 242 |
+
assert harmonic(oo, 1 + ip) is zeta(1 + ip)
|
| 243 |
+
|
| 244 |
+
assert harmonic(0, m) == 0
|
| 245 |
+
assert harmonic(-1, -1) == 0
|
| 246 |
+
assert harmonic(-1, 0) == -1
|
| 247 |
+
assert harmonic(-1, 1) is S.ComplexInfinity
|
| 248 |
+
assert harmonic(-1, 2) is S.NaN
|
| 249 |
+
assert harmonic(-3, -2) == -5
|
| 250 |
+
assert harmonic(-3, -3) == 9
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def test_harmonic_rational():
|
| 254 |
+
ne = S(6)
|
| 255 |
+
no = S(5)
|
| 256 |
+
pe = S(8)
|
| 257 |
+
po = S(9)
|
| 258 |
+
qe = S(10)
|
| 259 |
+
qo = S(13)
|
| 260 |
+
|
| 261 |
+
Heee = harmonic(ne + pe/qe)
|
| 262 |
+
Aeee = (-log(10) + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8)))
|
| 263 |
+
+ 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8)))
|
| 264 |
+
+ pi*sqrt(2*sqrt(5)/5 + 1)/2 + Rational(13944145, 4720968))
|
| 265 |
+
|
| 266 |
+
Heeo = harmonic(ne + pe/qo)
|
| 267 |
+
Aeeo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(4, 13)) + 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(32, 13))
|
| 268 |
+
+ 2*log(sin(pi*Rational(5, 13)))*cos(pi*Rational(80, 13)) - 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(5, 13))
|
| 269 |
+
- 2*log(sin(pi*Rational(4, 13)))*cos(pi/13) + pi*cot(pi*Rational(5, 13))/2 - 2*log(sin(pi/13))*cos(pi*Rational(3, 13))
|
| 270 |
+
+ Rational(2422020029, 702257080))
|
| 271 |
+
|
| 272 |
+
Heoe = harmonic(ne + po/qe)
|
| 273 |
+
Aeoe = (-log(20) + 2*(Rational(1, 4) + sqrt(5)/4)*log(Rational(-1, 4) + sqrt(5)/4)
|
| 274 |
+
+ 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8)))
|
| 275 |
+
+ 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8)))
|
| 276 |
+
+ 2*(-sqrt(5)/4 + Rational(1, 4))*log(Rational(1, 4) + sqrt(5)/4)
|
| 277 |
+
+ Rational(11818877030, 4286604231) + pi*sqrt(2*sqrt(5) + 5)/2)
|
| 278 |
+
|
| 279 |
+
Heoo = harmonic(ne + po/qo)
|
| 280 |
+
Aeoo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(54, 13)) + 2*log(sin(pi*Rational(4, 13)))*cos(pi*Rational(6, 13))
|
| 281 |
+
+ 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(108, 13)) - 2*log(sin(pi*Rational(5, 13)))*cos(pi/13)
|
| 282 |
+
- 2*log(sin(pi/13))*cos(pi*Rational(5, 13)) + pi*cot(pi*Rational(4, 13))/2
|
| 283 |
+
- 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(3, 13)) + Rational(11669332571, 3628714320))
|
| 284 |
+
|
| 285 |
+
Hoee = harmonic(no + pe/qe)
|
| 286 |
+
Aoee = (-log(10) + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8)))
|
| 287 |
+
+ 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8)))
|
| 288 |
+
+ pi*sqrt(2*sqrt(5)/5 + 1)/2 + Rational(779405, 277704))
|
| 289 |
+
|
| 290 |
+
Hoeo = harmonic(no + pe/qo)
|
| 291 |
+
Aoeo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(4, 13)) + 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(32, 13))
|
| 292 |
+
+ 2*log(sin(pi*Rational(5, 13)))*cos(pi*Rational(80, 13)) - 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(5, 13))
|
| 293 |
+
- 2*log(sin(pi*Rational(4, 13)))*cos(pi/13) + pi*cot(pi*Rational(5, 13))/2
|
| 294 |
+
- 2*log(sin(pi/13))*cos(pi*Rational(3, 13)) + Rational(53857323, 16331560))
|
| 295 |
+
|
| 296 |
+
Hooe = harmonic(no + po/qe)
|
| 297 |
+
Aooe = (-log(20) + 2*(Rational(1, 4) + sqrt(5)/4)*log(Rational(-1, 4) + sqrt(5)/4)
|
| 298 |
+
+ 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8)))
|
| 299 |
+
+ 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8)))
|
| 300 |
+
+ 2*(-sqrt(5)/4 + Rational(1, 4))*log(Rational(1, 4) + sqrt(5)/4)
|
| 301 |
+
+ Rational(486853480, 186374097) + pi*sqrt(2*sqrt(5) + 5)/2)
|
| 302 |
+
|
| 303 |
+
Hooo = harmonic(no + po/qo)
|
| 304 |
+
Aooo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(54, 13)) + 2*log(sin(pi*Rational(4, 13)))*cos(pi*Rational(6, 13))
|
| 305 |
+
+ 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(108, 13)) - 2*log(sin(pi*Rational(5, 13)))*cos(pi/13)
|
| 306 |
+
- 2*log(sin(pi/13))*cos(pi*Rational(5, 13)) + pi*cot(pi*Rational(4, 13))/2
|
| 307 |
+
- 2*log(sin(pi*Rational(2, 13)))*cos(3*pi/13) + Rational(383693479, 125128080))
|
| 308 |
+
|
| 309 |
+
H = [Heee, Heeo, Heoe, Heoo, Hoee, Hoeo, Hooe, Hooo]
|
| 310 |
+
A = [Aeee, Aeeo, Aeoe, Aeoo, Aoee, Aoeo, Aooe, Aooo]
|
| 311 |
+
for h, a in zip(H, A):
|
| 312 |
+
e = expand_func(h).doit()
|
| 313 |
+
assert cancel(e/a) == 1
|
| 314 |
+
assert abs(h.n() - a.n()) < 1e-12
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def test_harmonic_evalf():
|
| 318 |
+
assert str(harmonic(1.5).evalf(n=10)) == '1.280372306'
|
| 319 |
+
assert str(harmonic(1.5, 2).evalf(n=10)) == '1.154576311' # issue 7443
|
| 320 |
+
assert str(harmonic(4.0, -3).evalf(n=10)) == '100.0000000'
|
| 321 |
+
assert str(harmonic(7.0, 1.0).evalf(n=10)) == '2.592857143'
|
| 322 |
+
assert str(harmonic(1, pi).evalf(n=10)) == '1.000000000'
|
| 323 |
+
assert str(harmonic(2, pi).evalf(n=10)) == '1.113314732'
|
| 324 |
+
assert str(harmonic(1000.0, pi).evalf(n=10)) == '1.176241563'
|
| 325 |
+
assert str(harmonic(I).evalf(n=10)) == '0.6718659855 + 1.076674047*I'
|
| 326 |
+
assert str(harmonic(I, I).evalf(n=10)) == '-0.3970915266 + 1.9629689*I'
|
| 327 |
+
|
| 328 |
+
assert harmonic(-1.0, 1).evalf() is S.NaN
|
| 329 |
+
assert harmonic(-2.0, 2.0).evalf() is S.NaN
|
| 330 |
+
|
| 331 |
+
def test_harmonic_rewrite():
|
| 332 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 333 |
+
n = Symbol("n")
|
| 334 |
+
m = Symbol("m", integer=True, positive=True)
|
| 335 |
+
x1 = Symbol("x1", positive=True)
|
| 336 |
+
x2 = Symbol("x2", negative=True)
|
| 337 |
+
|
| 338 |
+
assert harmonic(n).rewrite(digamma) == polygamma(0, n + 1) + EulerGamma
|
| 339 |
+
assert harmonic(n).rewrite(trigamma) == polygamma(0, n + 1) + EulerGamma
|
| 340 |
+
assert harmonic(n).rewrite(polygamma) == polygamma(0, n + 1) + EulerGamma
|
| 341 |
+
|
| 342 |
+
assert harmonic(n,3).rewrite(polygamma) == polygamma(2, n + 1)/2 - polygamma(2, 1)/2
|
| 343 |
+
assert isinstance(harmonic(n,m).rewrite(polygamma), Piecewise)
|
| 344 |
+
|
| 345 |
+
assert expand_func(harmonic(n+4)) == harmonic(n) + 1/(n + 4) + 1/(n + 3) + 1/(n + 2) + 1/(n + 1)
|
| 346 |
+
assert expand_func(harmonic(n-4)) == harmonic(n) - 1/(n - 1) - 1/(n - 2) - 1/(n - 3) - 1/n
|
| 347 |
+
|
| 348 |
+
assert harmonic(n, m).rewrite("tractable") == harmonic(n, m).rewrite(polygamma)
|
| 349 |
+
assert harmonic(n, x1).rewrite("tractable") == harmonic(n, x1)
|
| 350 |
+
assert harmonic(n, x1 + 1).rewrite("tractable") == zeta(x1 + 1) - zeta(x1 + 1, n + 1)
|
| 351 |
+
assert harmonic(n, x2).rewrite("tractable") == zeta(x2) - zeta(x2, n + 1)
|
| 352 |
+
|
| 353 |
+
_k = Dummy("k")
|
| 354 |
+
assert harmonic(n).rewrite(Sum).dummy_eq(Sum(1/_k, (_k, 1, n)))
|
| 355 |
+
assert harmonic(n, m).rewrite(Sum).dummy_eq(Sum(_k**(-m), (_k, 1, n)))
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def test_harmonic_calculus():
|
| 359 |
+
y = Symbol("y", positive=True)
|
| 360 |
+
z = Symbol("z", negative=True)
|
| 361 |
+
assert harmonic(x, 1).limit(x, 0) == 0
|
| 362 |
+
assert harmonic(x, y).limit(x, 0) == 0
|
| 363 |
+
assert harmonic(x, 1).series(x, y, 2) == \
|
| 364 |
+
harmonic(y) + (x - y)*zeta(2, y + 1) + O((x - y)**2, (x, y))
|
| 365 |
+
assert limit(harmonic(x, y), x, oo) == harmonic(oo, y)
|
| 366 |
+
assert limit(harmonic(x, y + 1), x, oo) == zeta(y + 1)
|
| 367 |
+
assert limit(harmonic(x, y - 1), x, oo) == harmonic(oo, y - 1)
|
| 368 |
+
assert limit(harmonic(x, z), x, oo) == Limit(harmonic(x, z), x, oo, dir='-')
|
| 369 |
+
assert limit(harmonic(x, z + 1), x, oo) == oo
|
| 370 |
+
assert limit(harmonic(x, z + 2), x, oo) == harmonic(oo, z + 2)
|
| 371 |
+
assert limit(harmonic(x, z - 1), x, oo) == Limit(harmonic(x, z - 1), x, oo, dir='-')
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def test_euler():
|
| 375 |
+
assert euler(0) == 1
|
| 376 |
+
assert euler(1) == 0
|
| 377 |
+
assert euler(2) == -1
|
| 378 |
+
assert euler(3) == 0
|
| 379 |
+
assert euler(4) == 5
|
| 380 |
+
assert euler(6) == -61
|
| 381 |
+
assert euler(8) == 1385
|
| 382 |
+
|
| 383 |
+
assert euler(20, evaluate=False) != 370371188237525
|
| 384 |
+
|
| 385 |
+
n = Symbol('n', integer=True)
|
| 386 |
+
assert euler(n) != -1
|
| 387 |
+
assert euler(n).subs(n, 2) == -1
|
| 388 |
+
|
| 389 |
+
assert euler(-1) == S.Pi / 2
|
| 390 |
+
assert euler(-1, 1) == 2*log(2)
|
| 391 |
+
assert euler(-2).evalf() == (2*S.Catalan).evalf()
|
| 392 |
+
assert euler(-3).evalf() == (S.Pi**3 / 16).evalf()
|
| 393 |
+
assert str(euler(2.3).evalf(n=10)) == '-1.052850274'
|
| 394 |
+
assert str(euler(1.2, 3.4).evalf(n=10)) == '3.575613489'
|
| 395 |
+
assert str(euler(I).evalf(n=10)) == '1.248446443 - 0.7675445124*I'
|
| 396 |
+
assert str(euler(I, I).evalf(n=10)) == '0.04812930469 + 0.01052411008*I'
|
| 397 |
+
|
| 398 |
+
assert euler(20).evalf() == 370371188237525.0
|
| 399 |
+
assert euler(20, evaluate=False).evalf() == 370371188237525.0
|
| 400 |
+
|
| 401 |
+
assert euler(n).rewrite(Sum) == euler(n)
|
| 402 |
+
n = Symbol('n', integer=True, nonnegative=True)
|
| 403 |
+
assert euler(2*n + 1).rewrite(Sum) == 0
|
| 404 |
+
_j = Dummy('j')
|
| 405 |
+
_k = Dummy('k')
|
| 406 |
+
assert euler(2*n).rewrite(Sum).dummy_eq(
|
| 407 |
+
I*Sum((-1)**_j*2**(-_k)*I**(-_k)*(-2*_j + _k)**(2*n + 1)*
|
| 408 |
+
binomial(_k, _j)/_k, (_j, 0, _k), (_k, 1, 2*n + 1)))
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def test_euler_odd():
|
| 412 |
+
n = Symbol('n', odd=True, positive=True)
|
| 413 |
+
assert euler(n) == 0
|
| 414 |
+
n = Symbol('n', odd=True)
|
| 415 |
+
assert euler(n) != 0
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def test_euler_polynomials():
|
| 419 |
+
assert euler(0, x) == 1
|
| 420 |
+
assert euler(1, x) == x - S.Half
|
| 421 |
+
assert euler(2, x) == x**2 - x
|
| 422 |
+
assert euler(3, x) == x**3 - (3*x**2)/2 + Rational(1, 4)
|
| 423 |
+
m = Symbol('m')
|
| 424 |
+
assert isinstance(euler(m, x), euler)
|
| 425 |
+
from sympy.core.numbers import Float
|
| 426 |
+
A = Float('-0.46237208575048694923364757452876131e8') # from Maple
|
| 427 |
+
B = euler(19, S.Pi).evalf(32)
|
| 428 |
+
assert abs((A - B)/A) < 1e-31
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def test_euler_polynomial_rewrite():
|
| 432 |
+
m = Symbol('m')
|
| 433 |
+
A = euler(m, x).rewrite('Sum');
|
| 434 |
+
assert A.subs({m:3, x:5}).doit() == euler(3, 5)
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def test_catalan():
|
| 438 |
+
n = Symbol('n', integer=True)
|
| 439 |
+
m = Symbol('m', integer=True, positive=True)
|
| 440 |
+
k = Symbol('k', integer=True, nonnegative=True)
|
| 441 |
+
p = Symbol('p', nonnegative=True)
|
| 442 |
+
|
| 443 |
+
catalans = [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786]
|
| 444 |
+
for i, c in enumerate(catalans):
|
| 445 |
+
assert catalan(i) == c
|
| 446 |
+
assert catalan(n).rewrite(factorial).subs(n, i) == c
|
| 447 |
+
assert catalan(n).rewrite(Product).subs(n, i).doit() == c
|
| 448 |
+
|
| 449 |
+
assert unchanged(catalan, x)
|
| 450 |
+
assert catalan(2*x).rewrite(binomial) == binomial(4*x, 2*x)/(2*x + 1)
|
| 451 |
+
assert catalan(S.Half).rewrite(gamma) == 8/(3*pi)
|
| 452 |
+
assert catalan(S.Half).rewrite(factorial).rewrite(gamma) ==\
|
| 453 |
+
8 / (3 * pi)
|
| 454 |
+
assert catalan(3*x).rewrite(gamma) == 4**(
|
| 455 |
+
3*x)*gamma(3*x + S.Half)/(sqrt(pi)*gamma(3*x + 2))
|
| 456 |
+
assert catalan(x).rewrite(hyper) == hyper((-x + 1, -x), (2,), 1)
|
| 457 |
+
|
| 458 |
+
assert catalan(n).rewrite(factorial) == factorial(2*n) / (factorial(n + 1)
|
| 459 |
+
* factorial(n))
|
| 460 |
+
assert isinstance(catalan(n).rewrite(Product), catalan)
|
| 461 |
+
assert isinstance(catalan(m).rewrite(Product), Product)
|
| 462 |
+
|
| 463 |
+
assert diff(catalan(x), x) == (polygamma(
|
| 464 |
+
0, x + S.Half) - polygamma(0, x + 2) + log(4))*catalan(x)
|
| 465 |
+
|
| 466 |
+
assert catalan(x).evalf() == catalan(x)
|
| 467 |
+
c = catalan(S.Half).evalf()
|
| 468 |
+
assert str(c) == '0.848826363156775'
|
| 469 |
+
c = catalan(I).evalf(3)
|
| 470 |
+
assert str((re(c), im(c))) == '(0.398, -0.0209)'
|
| 471 |
+
|
| 472 |
+
# Assumptions
|
| 473 |
+
assert catalan(p).is_positive is True
|
| 474 |
+
assert catalan(k).is_integer is True
|
| 475 |
+
assert catalan(m+3).is_composite is True
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def test_genocchi():
|
| 479 |
+
genocchis = [0, -1, -1, 0, 1, 0, -3, 0, 17]
|
| 480 |
+
for n, g in enumerate(genocchis):
|
| 481 |
+
assert genocchi(n) == g
|
| 482 |
+
|
| 483 |
+
m = Symbol('m', integer=True)
|
| 484 |
+
n = Symbol('n', integer=True, positive=True)
|
| 485 |
+
assert unchanged(genocchi, m)
|
| 486 |
+
assert genocchi(2*n + 1) == 0
|
| 487 |
+
gn = 2 * (1 - 2**n) * bernoulli(n)
|
| 488 |
+
assert genocchi(n).rewrite(bernoulli).factor() == gn.factor()
|
| 489 |
+
gnx = 2 * (bernoulli(n, x) - 2**n * bernoulli(n, (x+1) / 2))
|
| 490 |
+
assert genocchi(n, x).rewrite(bernoulli).factor() == gnx.factor()
|
| 491 |
+
assert genocchi(2 * n).is_odd
|
| 492 |
+
assert genocchi(2 * n).is_even is False
|
| 493 |
+
assert genocchi(2 * n + 1).is_even
|
| 494 |
+
assert genocchi(n).is_integer
|
| 495 |
+
assert genocchi(4 * n).is_positive
|
| 496 |
+
# these are the only 2 prime Genocchi numbers
|
| 497 |
+
assert genocchi(6, evaluate=False).is_prime == S(-3).is_prime
|
| 498 |
+
assert genocchi(8, evaluate=False).is_prime
|
| 499 |
+
assert genocchi(4 * n + 2).is_negative
|
| 500 |
+
assert genocchi(4 * n + 1).is_negative is False
|
| 501 |
+
assert genocchi(4 * n - 2).is_negative
|
| 502 |
+
|
| 503 |
+
g0 = genocchi(0, evaluate=False)
|
| 504 |
+
assert g0.is_positive is False
|
| 505 |
+
assert g0.is_negative is False
|
| 506 |
+
assert g0.is_even is True
|
| 507 |
+
assert g0.is_odd is False
|
| 508 |
+
|
| 509 |
+
assert genocchi(0, x) == 0
|
| 510 |
+
assert genocchi(1, x) == -1
|
| 511 |
+
assert genocchi(2, x) == 1 - 2*x
|
| 512 |
+
assert genocchi(3, x) == 3*x - 3*x**2
|
| 513 |
+
assert genocchi(4, x) == -1 + 6*x**2 - 4*x**3
|
| 514 |
+
y = Symbol("y")
|
| 515 |
+
assert genocchi(5, (x+y)**100) == -5*(x+y)**400 + 10*(x+y)**300 - 5*(x+y)**100
|
| 516 |
+
|
| 517 |
+
assert str(genocchi(5.0, 4.0).evalf(n=10)) == '-660.0000000'
|
| 518 |
+
assert str(genocchi(Rational(5, 4)).evalf(n=10)) == '-1.104286457'
|
| 519 |
+
assert str(genocchi(-2).evalf(n=10)) == '3.606170709'
|
| 520 |
+
assert str(genocchi(1.3, 3.7).evalf(n=10)) == '-1.847375373'
|
| 521 |
+
assert str(genocchi(I, 1.0).evalf(n=10)) == '-0.3161917278 - 1.45311955*I'
|
| 522 |
+
|
| 523 |
+
n = Symbol('n')
|
| 524 |
+
assert genocchi(n, x).rewrite(dirichlet_eta) == -2*n * dirichlet_eta(1-n, x)
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def test_andre():
|
| 528 |
+
nums = [1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521]
|
| 529 |
+
for n, a in enumerate(nums):
|
| 530 |
+
assert andre(n) == a
|
| 531 |
+
assert andre(S.Infinity) == S.Infinity
|
| 532 |
+
assert andre(-1) == -log(2)
|
| 533 |
+
assert andre(-2) == -2*S.Catalan
|
| 534 |
+
assert andre(-3) == 3*zeta(3)/16
|
| 535 |
+
assert andre(-5) == -15*zeta(5)/256
|
| 536 |
+
# In fact andre(-2*n) is related to the Dirichlet *beta* function
|
| 537 |
+
# at 2*n, but SymPy doesn't implement that (or general L-functions)
|
| 538 |
+
assert unchanged(andre, -4)
|
| 539 |
+
|
| 540 |
+
n = Symbol('n', integer=True, nonnegative=True)
|
| 541 |
+
assert unchanged(andre, n)
|
| 542 |
+
assert andre(n).is_integer is True
|
| 543 |
+
assert andre(n).is_positive is True
|
| 544 |
+
|
| 545 |
+
assert str(andre(10, evaluate=False).evalf(n=10)) == '50521.00000'
|
| 546 |
+
assert str(andre(-1, evaluate=False).evalf(n=10)) == '-0.6931471806'
|
| 547 |
+
assert str(andre(-2, evaluate=False).evalf(n=10)) == '-1.831931188'
|
| 548 |
+
assert str(andre(-4, evaluate=False).evalf(n=10)) == '1.977889103'
|
| 549 |
+
assert str(andre(I, evaluate=False).evalf(n=10)) == '2.378417833 + 0.6343322845*I'
|
| 550 |
+
|
| 551 |
+
assert andre(x).rewrite(polylog) == \
|
| 552 |
+
(-I)**(x+1) * polylog(-x, I) + I**(x+1) * polylog(-x, -I)
|
| 553 |
+
assert andre(x).rewrite(zeta) == \
|
| 554 |
+
2 * gamma(x+1) / (2*pi)**(x+1) * \
|
| 555 |
+
(zeta(x+1, Rational(1,4)) - cos(pi*x) * zeta(x+1, Rational(3,4)))
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
@nocache_fail
|
| 559 |
+
def test_partition():
|
| 560 |
+
partition_nums = [1, 1, 2, 3, 5, 7, 11, 15, 22]
|
| 561 |
+
for n, p in enumerate(partition_nums):
|
| 562 |
+
assert partition(n) == p
|
| 563 |
+
|
| 564 |
+
x = Symbol('x')
|
| 565 |
+
y = Symbol('y', real=True)
|
| 566 |
+
m = Symbol('m', integer=True)
|
| 567 |
+
n = Symbol('n', integer=True, negative=True)
|
| 568 |
+
p = Symbol('p', integer=True, nonnegative=True)
|
| 569 |
+
assert partition(m).is_integer
|
| 570 |
+
assert not partition(m).is_negative
|
| 571 |
+
assert partition(m).is_nonnegative
|
| 572 |
+
assert partition(n).is_zero
|
| 573 |
+
assert partition(p).is_positive
|
| 574 |
+
assert partition(x).subs(x, 7) == 15
|
| 575 |
+
assert partition(y).subs(y, 8) == 22
|
| 576 |
+
raises(TypeError, lambda: partition(Rational(5, 4)))
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
def test_divisor_sigma():
|
| 580 |
+
# error
|
| 581 |
+
m = Symbol('m', integer=False)
|
| 582 |
+
raises(TypeError, lambda: divisor_sigma(m))
|
| 583 |
+
raises(TypeError, lambda: divisor_sigma(4.5))
|
| 584 |
+
raises(TypeError, lambda: divisor_sigma(1, m))
|
| 585 |
+
raises(TypeError, lambda: divisor_sigma(1, 4.5))
|
| 586 |
+
m = Symbol('m', positive=False)
|
| 587 |
+
raises(ValueError, lambda: divisor_sigma(m))
|
| 588 |
+
raises(ValueError, lambda: divisor_sigma(0))
|
| 589 |
+
m = Symbol('m', negative=True)
|
| 590 |
+
raises(ValueError, lambda: divisor_sigma(1, m))
|
| 591 |
+
raises(ValueError, lambda: divisor_sigma(1, -1))
|
| 592 |
+
|
| 593 |
+
# special case
|
| 594 |
+
p = Symbol('p', prime=True)
|
| 595 |
+
k = Symbol('k', integer=True)
|
| 596 |
+
assert divisor_sigma(p, 1) == p + 1
|
| 597 |
+
assert divisor_sigma(p, k) == p**k + 1
|
| 598 |
+
|
| 599 |
+
# property
|
| 600 |
+
n = Symbol('n', integer=True, positive=True)
|
| 601 |
+
assert divisor_sigma(n).is_integer is True
|
| 602 |
+
assert divisor_sigma(n).is_positive is True
|
| 603 |
+
|
| 604 |
+
# symbolic
|
| 605 |
+
k = Symbol('k', integer=True, zero=False)
|
| 606 |
+
assert divisor_sigma(4, k) == 2**(2*k) + 2**k + 1
|
| 607 |
+
assert divisor_sigma(6, k) == (2**k + 1) * (3**k + 1)
|
| 608 |
+
|
| 609 |
+
# Integer
|
| 610 |
+
assert divisor_sigma(23450) == 50592
|
| 611 |
+
assert divisor_sigma(23450, 0) == 24
|
| 612 |
+
assert divisor_sigma(23450, 1) == 50592
|
| 613 |
+
assert divisor_sigma(23450, 2) == 730747500
|
| 614 |
+
assert divisor_sigma(23450, 3) == 14666785333344
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
def test_udivisor_sigma():
|
| 618 |
+
# error
|
| 619 |
+
m = Symbol('m', integer=False)
|
| 620 |
+
raises(TypeError, lambda: udivisor_sigma(m))
|
| 621 |
+
raises(TypeError, lambda: udivisor_sigma(4.5))
|
| 622 |
+
raises(TypeError, lambda: udivisor_sigma(1, m))
|
| 623 |
+
raises(TypeError, lambda: udivisor_sigma(1, 4.5))
|
| 624 |
+
m = Symbol('m', positive=False)
|
| 625 |
+
raises(ValueError, lambda: udivisor_sigma(m))
|
| 626 |
+
raises(ValueError, lambda: udivisor_sigma(0))
|
| 627 |
+
m = Symbol('m', negative=True)
|
| 628 |
+
raises(ValueError, lambda: udivisor_sigma(1, m))
|
| 629 |
+
raises(ValueError, lambda: udivisor_sigma(1, -1))
|
| 630 |
+
|
| 631 |
+
# special case
|
| 632 |
+
p = Symbol('p', prime=True)
|
| 633 |
+
k = Symbol('k', integer=True)
|
| 634 |
+
assert udivisor_sigma(p, 1) == p + 1
|
| 635 |
+
assert udivisor_sigma(p, k) == p**k + 1
|
| 636 |
+
|
| 637 |
+
# property
|
| 638 |
+
n = Symbol('n', integer=True, positive=True)
|
| 639 |
+
assert udivisor_sigma(n).is_integer is True
|
| 640 |
+
assert udivisor_sigma(n).is_positive is True
|
| 641 |
+
|
| 642 |
+
# Integer
|
| 643 |
+
A034444 = [1, 2, 2, 2, 2, 4, 2, 2, 2, 4, 2, 4, 2, 4, 4, 2, 2, 4, 2, 4,
|
| 644 |
+
4, 4, 2, 4, 2, 4, 2, 4, 2, 8, 2, 2, 4, 4, 4, 4, 2, 4, 4, 4,
|
| 645 |
+
2, 8, 2, 4, 4, 4, 2, 4, 2, 4, 4, 4, 2, 4, 4, 4, 4, 4, 2, 8]
|
| 646 |
+
for n, val in enumerate(A034444, 1):
|
| 647 |
+
assert udivisor_sigma(n, 0) == val
|
| 648 |
+
A034448 = [1, 3, 4, 5, 6, 12, 8, 9, 10, 18, 12, 20, 14, 24, 24, 17, 18,
|
| 649 |
+
30, 20, 30, 32, 36, 24, 36, 26, 42, 28, 40, 30, 72, 32, 33,
|
| 650 |
+
48, 54, 48, 50, 38, 60, 56, 54, 42, 96, 44, 60, 60, 72, 48]
|
| 651 |
+
for n, val in enumerate(A034448, 1):
|
| 652 |
+
assert udivisor_sigma(n, 1) == val
|
| 653 |
+
A034676 = [1, 5, 10, 17, 26, 50, 50, 65, 82, 130, 122, 170, 170, 250,
|
| 654 |
+
260, 257, 290, 410, 362, 442, 500, 610, 530, 650, 626, 850,
|
| 655 |
+
730, 850, 842, 1300, 962, 1025, 1220, 1450, 1300, 1394, 1370]
|
| 656 |
+
for n, val in enumerate(A034676, 1):
|
| 657 |
+
assert udivisor_sigma(n, 2) == val
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
def test_legendre_symbol():
|
| 661 |
+
# error
|
| 662 |
+
m = Symbol('m', integer=False)
|
| 663 |
+
raises(TypeError, lambda: legendre_symbol(m, 3))
|
| 664 |
+
raises(TypeError, lambda: legendre_symbol(4.5, 3))
|
| 665 |
+
raises(TypeError, lambda: legendre_symbol(1, m))
|
| 666 |
+
raises(TypeError, lambda: legendre_symbol(1, 4.5))
|
| 667 |
+
m = Symbol('m', prime=False)
|
| 668 |
+
raises(ValueError, lambda: legendre_symbol(1, m))
|
| 669 |
+
raises(ValueError, lambda: legendre_symbol(1, 6))
|
| 670 |
+
m = Symbol('m', odd=False)
|
| 671 |
+
raises(ValueError, lambda: legendre_symbol(1, m))
|
| 672 |
+
raises(ValueError, lambda: legendre_symbol(1, 2))
|
| 673 |
+
|
| 674 |
+
# special case
|
| 675 |
+
p = Symbol('p', prime=True)
|
| 676 |
+
k = Symbol('k', integer=True)
|
| 677 |
+
assert legendre_symbol(p*k, p) == 0
|
| 678 |
+
assert legendre_symbol(1, p) == 1
|
| 679 |
+
|
| 680 |
+
# property
|
| 681 |
+
n = Symbol('n')
|
| 682 |
+
m = Symbol('m')
|
| 683 |
+
assert legendre_symbol(m, n).is_integer is True
|
| 684 |
+
assert legendre_symbol(m, n).is_prime is False
|
| 685 |
+
|
| 686 |
+
# Integer
|
| 687 |
+
assert legendre_symbol(5, 11) == 1
|
| 688 |
+
assert legendre_symbol(25, 41) == 1
|
| 689 |
+
assert legendre_symbol(67, 101) == -1
|
| 690 |
+
assert legendre_symbol(0, 13) == 0
|
| 691 |
+
assert legendre_symbol(9, 3) == 0
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
def test_jacobi_symbol():
|
| 695 |
+
# error
|
| 696 |
+
m = Symbol('m', integer=False)
|
| 697 |
+
raises(TypeError, lambda: jacobi_symbol(m, 3))
|
| 698 |
+
raises(TypeError, lambda: jacobi_symbol(4.5, 3))
|
| 699 |
+
raises(TypeError, lambda: jacobi_symbol(1, m))
|
| 700 |
+
raises(TypeError, lambda: jacobi_symbol(1, 4.5))
|
| 701 |
+
m = Symbol('m', positive=False)
|
| 702 |
+
raises(ValueError, lambda: jacobi_symbol(1, m))
|
| 703 |
+
raises(ValueError, lambda: jacobi_symbol(1, -6))
|
| 704 |
+
m = Symbol('m', odd=False)
|
| 705 |
+
raises(ValueError, lambda: jacobi_symbol(1, m))
|
| 706 |
+
raises(ValueError, lambda: jacobi_symbol(1, 2))
|
| 707 |
+
|
| 708 |
+
# special case
|
| 709 |
+
p = Symbol('p', integer=True)
|
| 710 |
+
k = Symbol('k', integer=True)
|
| 711 |
+
assert jacobi_symbol(p*k, p) == 0
|
| 712 |
+
assert jacobi_symbol(1, p) == 1
|
| 713 |
+
assert jacobi_symbol(1, 1) == 1
|
| 714 |
+
assert jacobi_symbol(0, 1) == 1
|
| 715 |
+
|
| 716 |
+
# property
|
| 717 |
+
n = Symbol('n')
|
| 718 |
+
m = Symbol('m')
|
| 719 |
+
assert jacobi_symbol(m, n).is_integer is True
|
| 720 |
+
assert jacobi_symbol(m, n).is_prime is False
|
| 721 |
+
|
| 722 |
+
# Integer
|
| 723 |
+
assert jacobi_symbol(25, 41) == 1
|
| 724 |
+
assert jacobi_symbol(-23, 83) == -1
|
| 725 |
+
assert jacobi_symbol(3, 9) == 0
|
| 726 |
+
assert jacobi_symbol(42, 97) == -1
|
| 727 |
+
assert jacobi_symbol(3, 5) == -1
|
| 728 |
+
assert jacobi_symbol(7, 9) == 1
|
| 729 |
+
assert jacobi_symbol(0, 3) == 0
|
| 730 |
+
assert jacobi_symbol(0, 1) == 1
|
| 731 |
+
assert jacobi_symbol(2, 1) == 1
|
| 732 |
+
assert jacobi_symbol(1, 3) == 1
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
def test_kronecker_symbol():
|
| 736 |
+
# error
|
| 737 |
+
m = Symbol('m', integer=False)
|
| 738 |
+
raises(TypeError, lambda: kronecker_symbol(m, 3))
|
| 739 |
+
raises(TypeError, lambda: kronecker_symbol(4.5, 3))
|
| 740 |
+
raises(TypeError, lambda: kronecker_symbol(1, m))
|
| 741 |
+
raises(TypeError, lambda: kronecker_symbol(1, 4.5))
|
| 742 |
+
|
| 743 |
+
# special case
|
| 744 |
+
p = Symbol('p', integer=True)
|
| 745 |
+
assert kronecker_symbol(1, p) == 1
|
| 746 |
+
assert kronecker_symbol(1, 1) == 1
|
| 747 |
+
assert kronecker_symbol(0, 1) == 1
|
| 748 |
+
|
| 749 |
+
# property
|
| 750 |
+
n = Symbol('n')
|
| 751 |
+
m = Symbol('m')
|
| 752 |
+
assert kronecker_symbol(m, n).is_integer is True
|
| 753 |
+
assert kronecker_symbol(m, n).is_prime is False
|
| 754 |
+
|
| 755 |
+
# Integer
|
| 756 |
+
for n in range(3, 10, 2):
|
| 757 |
+
for a in range(-n, n):
|
| 758 |
+
val = kronecker_symbol(a, n)
|
| 759 |
+
assert val == jacobi_symbol(a, n)
|
| 760 |
+
minus = kronecker_symbol(a, -n)
|
| 761 |
+
if a < 0:
|
| 762 |
+
assert -minus == val
|
| 763 |
+
else:
|
| 764 |
+
assert minus == val
|
| 765 |
+
even = kronecker_symbol(a, 2 * n)
|
| 766 |
+
if a % 2 == 0:
|
| 767 |
+
assert even == 0
|
| 768 |
+
elif a % 8 in [1, 7]:
|
| 769 |
+
assert even == val
|
| 770 |
+
else:
|
| 771 |
+
assert -even == val
|
| 772 |
+
assert kronecker_symbol(1, 0) == kronecker_symbol(-1, 0) == 1
|
| 773 |
+
assert kronecker_symbol(0, 0) == 0
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def test_mobius():
|
| 777 |
+
# error
|
| 778 |
+
m = Symbol('m', integer=False)
|
| 779 |
+
raises(TypeError, lambda: mobius(m))
|
| 780 |
+
raises(TypeError, lambda: mobius(4.5))
|
| 781 |
+
m = Symbol('m', positive=False)
|
| 782 |
+
raises(ValueError, lambda: mobius(m))
|
| 783 |
+
raises(ValueError, lambda: mobius(-3))
|
| 784 |
+
|
| 785 |
+
# special case
|
| 786 |
+
p = Symbol('p', prime=True)
|
| 787 |
+
assert mobius(p) == -1
|
| 788 |
+
|
| 789 |
+
# property
|
| 790 |
+
n = Symbol('n', integer=True, positive=True)
|
| 791 |
+
assert mobius(n).is_integer is True
|
| 792 |
+
assert mobius(n).is_prime is False
|
| 793 |
+
|
| 794 |
+
# symbolic
|
| 795 |
+
n = Symbol('n', integer=True, positive=True)
|
| 796 |
+
k = Symbol('k', integer=True, positive=True)
|
| 797 |
+
assert mobius(n**2) == 0
|
| 798 |
+
assert mobius(4*n) == 0
|
| 799 |
+
assert isinstance(mobius(n**k), mobius)
|
| 800 |
+
assert mobius(n**(k+1)) == 0
|
| 801 |
+
assert isinstance(mobius(3**k), mobius)
|
| 802 |
+
assert mobius(3**(k+1)) == 0
|
| 803 |
+
m = Symbol('m')
|
| 804 |
+
assert isinstance(mobius(4*m), mobius)
|
| 805 |
+
|
| 806 |
+
# Integer
|
| 807 |
+
assert mobius(13*7) == 1
|
| 808 |
+
assert mobius(1) == 1
|
| 809 |
+
assert mobius(13*7*5) == -1
|
| 810 |
+
assert mobius(13**2) == 0
|
| 811 |
+
A008683 = [1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0, -1, 1, 1, 0, -1, 0,
|
| 812 |
+
-1, 0, 1, 1, -1, 0, 0, 1, 0, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1,
|
| 813 |
+
1, 1, 0, -1, -1, -1, 0, 0, 1, -1, 0, 0, 0, 1, 0, -1, 0, 1, 0]
|
| 814 |
+
for n, val in enumerate(A008683, 1):
|
| 815 |
+
assert mobius(n) == val
|
| 816 |
+
|
| 817 |
+
|
| 818 |
+
def test_primenu():
|
| 819 |
+
# error
|
| 820 |
+
m = Symbol('m', integer=False)
|
| 821 |
+
raises(TypeError, lambda: primenu(m))
|
| 822 |
+
raises(TypeError, lambda: primenu(4.5))
|
| 823 |
+
m = Symbol('m', positive=False)
|
| 824 |
+
raises(ValueError, lambda: primenu(m))
|
| 825 |
+
raises(ValueError, lambda: primenu(0))
|
| 826 |
+
|
| 827 |
+
# special case
|
| 828 |
+
p = Symbol('p', prime=True)
|
| 829 |
+
assert primenu(p) == 1
|
| 830 |
+
|
| 831 |
+
# property
|
| 832 |
+
n = Symbol('n', integer=True, positive=True)
|
| 833 |
+
assert primenu(n).is_integer is True
|
| 834 |
+
assert primenu(n).is_nonnegative is True
|
| 835 |
+
|
| 836 |
+
# Integer
|
| 837 |
+
assert primenu(7*13) == 2
|
| 838 |
+
assert primenu(2*17*19) == 3
|
| 839 |
+
assert primenu(2**3 * 17 * 19**2) == 3
|
| 840 |
+
A001221 = [0, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2,
|
| 841 |
+
1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 1, 2, 2, 2, 2]
|
| 842 |
+
for n, val in enumerate(A001221, 1):
|
| 843 |
+
assert primenu(n) == val
|
| 844 |
+
|
| 845 |
+
|
| 846 |
+
def test_primeomega():
|
| 847 |
+
# error
|
| 848 |
+
m = Symbol('m', integer=False)
|
| 849 |
+
raises(TypeError, lambda: primeomega(m))
|
| 850 |
+
raises(TypeError, lambda: primeomega(4.5))
|
| 851 |
+
m = Symbol('m', positive=False)
|
| 852 |
+
raises(ValueError, lambda: primeomega(m))
|
| 853 |
+
raises(ValueError, lambda: primeomega(0))
|
| 854 |
+
|
| 855 |
+
# special case
|
| 856 |
+
p = Symbol('p', prime=True)
|
| 857 |
+
assert primeomega(p) == 1
|
| 858 |
+
|
| 859 |
+
# property
|
| 860 |
+
n = Symbol('n', integer=True, positive=True)
|
| 861 |
+
assert primeomega(n).is_integer is True
|
| 862 |
+
assert primeomega(n).is_nonnegative is True
|
| 863 |
+
|
| 864 |
+
# Integer
|
| 865 |
+
assert primeomega(7*13) == 2
|
| 866 |
+
assert primeomega(2*17*19) == 3
|
| 867 |
+
assert primeomega(2**3 * 17 * 19**2) == 6
|
| 868 |
+
A001222 = [0, 1, 1, 2, 1, 2, 1, 3, 2, 2, 1, 3, 1, 2, 2, 4, 1, 3,
|
| 869 |
+
1, 3, 2, 2, 1, 4, 2, 2, 3, 3, 1, 3, 1, 5, 2, 2, 2, 4]
|
| 870 |
+
for n, val in enumerate(A001222, 1):
|
| 871 |
+
assert primeomega(n) == val
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
def test_totient():
|
| 875 |
+
# error
|
| 876 |
+
m = Symbol('m', integer=False)
|
| 877 |
+
raises(TypeError, lambda: totient(m))
|
| 878 |
+
raises(TypeError, lambda: totient(4.5))
|
| 879 |
+
m = Symbol('m', positive=False)
|
| 880 |
+
raises(ValueError, lambda: totient(m))
|
| 881 |
+
raises(ValueError, lambda: totient(0))
|
| 882 |
+
|
| 883 |
+
# special case
|
| 884 |
+
p = Symbol('p', prime=True)
|
| 885 |
+
assert totient(p) == p - 1
|
| 886 |
+
|
| 887 |
+
# property
|
| 888 |
+
n = Symbol('n', integer=True, positive=True)
|
| 889 |
+
assert totient(n).is_integer is True
|
| 890 |
+
assert totient(n).is_positive is True
|
| 891 |
+
|
| 892 |
+
# Integer
|
| 893 |
+
assert totient(7*13) == totient(factorint(7*13)) == (7-1)*(13-1)
|
| 894 |
+
assert totient(2*17*19) == totient(factorint(2*17*19)) == (17-1)*(19-1)
|
| 895 |
+
assert totient(2**3 * 17 * 19**2) == totient({2: 3, 17: 1, 19: 2}) == 2**2 * (17-1) * 19*(19-1)
|
| 896 |
+
A000010 = [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16,
|
| 897 |
+
6, 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16,
|
| 898 |
+
20, 16, 24, 12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22]
|
| 899 |
+
for n, val in enumerate(A000010, 1):
|
| 900 |
+
assert totient(n) == val
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
def test_reduced_totient():
|
| 904 |
+
# error
|
| 905 |
+
m = Symbol('m', integer=False)
|
| 906 |
+
raises(TypeError, lambda: reduced_totient(m))
|
| 907 |
+
raises(TypeError, lambda: reduced_totient(4.5))
|
| 908 |
+
m = Symbol('m', positive=False)
|
| 909 |
+
raises(ValueError, lambda: reduced_totient(m))
|
| 910 |
+
raises(ValueError, lambda: reduced_totient(0))
|
| 911 |
+
|
| 912 |
+
# special case
|
| 913 |
+
p = Symbol('p', prime=True)
|
| 914 |
+
assert reduced_totient(p) == p - 1
|
| 915 |
+
|
| 916 |
+
# property
|
| 917 |
+
n = Symbol('n', integer=True, positive=True)
|
| 918 |
+
assert reduced_totient(n).is_integer is True
|
| 919 |
+
assert reduced_totient(n).is_positive is True
|
| 920 |
+
|
| 921 |
+
# Integer
|
| 922 |
+
assert reduced_totient(7*13) == reduced_totient(factorint(7*13)) == 12
|
| 923 |
+
assert reduced_totient(2*17*19) == reduced_totient(factorint(2*17*19)) == 144
|
| 924 |
+
assert reduced_totient(2**2 * 11) == reduced_totient({2: 2, 11: 1}) == 10
|
| 925 |
+
assert reduced_totient(2**3 * 17 * 19**2) == reduced_totient({2: 3, 17: 1, 19: 2}) == 2736
|
| 926 |
+
A002322 = [1, 1, 2, 2, 4, 2, 6, 2, 6, 4, 10, 2, 12, 6, 4, 4, 16, 6,
|
| 927 |
+
18, 4, 6, 10, 22, 2, 20, 12, 18, 6, 28, 4, 30, 8, 10, 16,
|
| 928 |
+
12, 6, 36, 18, 12, 4, 40, 6, 42, 10, 12, 22, 46, 4, 42]
|
| 929 |
+
for n, val in enumerate(A002322, 1):
|
| 930 |
+
assert reduced_totient(n) == val
|
| 931 |
+
|
| 932 |
+
|
| 933 |
+
def test_primepi():
|
| 934 |
+
# error
|
| 935 |
+
z = Symbol('z', real=False)
|
| 936 |
+
raises(TypeError, lambda: primepi(z))
|
| 937 |
+
raises(TypeError, lambda: primepi(I))
|
| 938 |
+
|
| 939 |
+
# property
|
| 940 |
+
n = Symbol('n', integer=True, positive=True)
|
| 941 |
+
assert primepi(n).is_integer is True
|
| 942 |
+
assert primepi(n).is_nonnegative is True
|
| 943 |
+
|
| 944 |
+
# infinity
|
| 945 |
+
assert primepi(oo) == oo
|
| 946 |
+
assert primepi(-oo) == 0
|
| 947 |
+
|
| 948 |
+
# symbol
|
| 949 |
+
x = Symbol('x')
|
| 950 |
+
assert isinstance(primepi(x), primepi)
|
| 951 |
+
|
| 952 |
+
# Integer
|
| 953 |
+
assert primepi(0) == 0
|
| 954 |
+
A000720 = [0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8,
|
| 955 |
+
8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11,
|
| 956 |
+
12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15]
|
| 957 |
+
for n, val in enumerate(A000720, 1):
|
| 958 |
+
assert primepi(n) == primepi(n + 0.5) == val
|
| 959 |
+
|
| 960 |
+
|
| 961 |
+
def test__nT():
|
| 962 |
+
assert [_nT(i, j) for i in range(5) for j in range(i + 2)] == [
|
| 963 |
+
1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 1, 0]
|
| 964 |
+
check = [_nT(10, i) for i in range(11)]
|
| 965 |
+
assert check == [0, 1, 5, 8, 9, 7, 5, 3, 2, 1, 1]
|
| 966 |
+
assert all(type(i) is int for i in check)
|
| 967 |
+
assert _nT(10, 5) == 7
|
| 968 |
+
assert _nT(100, 98) == 2
|
| 969 |
+
assert _nT(100, 100) == 1
|
| 970 |
+
assert _nT(10, 3) == 8
|
| 971 |
+
|
| 972 |
+
|
| 973 |
+
def test_nC_nP_nT():
|
| 974 |
+
from sympy.utilities.iterables import (
|
| 975 |
+
multiset_permutations, multiset_combinations, multiset_partitions,
|
| 976 |
+
partitions, subsets, permutations)
|
| 977 |
+
from sympy.functions.combinatorial.numbers import (
|
| 978 |
+
nP, nC, nT, stirling, _stirling1, _stirling2, _multiset_histogram, _AOP_product)
|
| 979 |
+
|
| 980 |
+
from sympy.combinatorics.permutations import Permutation
|
| 981 |
+
from sympy.core.random import choice
|
| 982 |
+
|
| 983 |
+
c = string.ascii_lowercase
|
| 984 |
+
for i in range(100):
|
| 985 |
+
s = ''.join(choice(c) for i in range(7))
|
| 986 |
+
u = len(s) == len(set(s))
|
| 987 |
+
try:
|
| 988 |
+
tot = 0
|
| 989 |
+
for i in range(8):
|
| 990 |
+
check = nP(s, i)
|
| 991 |
+
tot += check
|
| 992 |
+
assert len(list(multiset_permutations(s, i))) == check
|
| 993 |
+
if u:
|
| 994 |
+
assert nP(len(s), i) == check
|
| 995 |
+
assert nP(s) == tot
|
| 996 |
+
except AssertionError:
|
| 997 |
+
print(s, i, 'failed perm test')
|
| 998 |
+
raise ValueError()
|
| 999 |
+
|
| 1000 |
+
for i in range(100):
|
| 1001 |
+
s = ''.join(choice(c) for i in range(7))
|
| 1002 |
+
u = len(s) == len(set(s))
|
| 1003 |
+
try:
|
| 1004 |
+
tot = 0
|
| 1005 |
+
for i in range(8):
|
| 1006 |
+
check = nC(s, i)
|
| 1007 |
+
tot += check
|
| 1008 |
+
assert len(list(multiset_combinations(s, i))) == check
|
| 1009 |
+
if u:
|
| 1010 |
+
assert nC(len(s), i) == check
|
| 1011 |
+
assert nC(s) == tot
|
| 1012 |
+
if u:
|
| 1013 |
+
assert nC(len(s)) == tot
|
| 1014 |
+
except AssertionError:
|
| 1015 |
+
print(s, i, 'failed combo test')
|
| 1016 |
+
raise ValueError()
|
| 1017 |
+
|
| 1018 |
+
for i in range(1, 10):
|
| 1019 |
+
tot = 0
|
| 1020 |
+
for j in range(1, i + 2):
|
| 1021 |
+
check = nT(i, j)
|
| 1022 |
+
assert check.is_Integer
|
| 1023 |
+
tot += check
|
| 1024 |
+
assert sum(1 for p in partitions(i, j, size=True) if p[0] == j) == check
|
| 1025 |
+
assert nT(i) == tot
|
| 1026 |
+
|
| 1027 |
+
for i in range(1, 10):
|
| 1028 |
+
tot = 0
|
| 1029 |
+
for j in range(1, i + 2):
|
| 1030 |
+
check = nT(range(i), j)
|
| 1031 |
+
tot += check
|
| 1032 |
+
assert len(list(multiset_partitions(list(range(i)), j))) == check
|
| 1033 |
+
assert nT(range(i)) == tot
|
| 1034 |
+
|
| 1035 |
+
for i in range(100):
|
| 1036 |
+
s = ''.join(choice(c) for i in range(7))
|
| 1037 |
+
u = len(s) == len(set(s))
|
| 1038 |
+
try:
|
| 1039 |
+
tot = 0
|
| 1040 |
+
for i in range(1, 8):
|
| 1041 |
+
check = nT(s, i)
|
| 1042 |
+
tot += check
|
| 1043 |
+
assert len(list(multiset_partitions(s, i))) == check
|
| 1044 |
+
if u:
|
| 1045 |
+
assert nT(range(len(s)), i) == check
|
| 1046 |
+
if u:
|
| 1047 |
+
assert nT(range(len(s))) == tot
|
| 1048 |
+
assert nT(s) == tot
|
| 1049 |
+
except AssertionError:
|
| 1050 |
+
print(s, i, 'failed partition test')
|
| 1051 |
+
raise ValueError()
|
| 1052 |
+
|
| 1053 |
+
# tests for Stirling numbers of the first kind that are not tested in the
|
| 1054 |
+
# above
|
| 1055 |
+
assert [stirling(9, i, kind=1) for i in range(11)] == [
|
| 1056 |
+
0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0]
|
| 1057 |
+
perms = list(permutations(range(4)))
|
| 1058 |
+
assert [sum(1 for p in perms if Permutation(p).cycles == i)
|
| 1059 |
+
for i in range(5)] == [0, 6, 11, 6, 1] == [
|
| 1060 |
+
stirling(4, i, kind=1) for i in range(5)]
|
| 1061 |
+
# http://oeis.org/A008275
|
| 1062 |
+
assert [stirling(n, k, signed=1)
|
| 1063 |
+
for n in range(10) for k in range(1, n + 1)] == [
|
| 1064 |
+
1, -1,
|
| 1065 |
+
1, 2, -3,
|
| 1066 |
+
1, -6, 11, -6,
|
| 1067 |
+
1, 24, -50, 35, -10,
|
| 1068 |
+
1, -120, 274, -225, 85, -15,
|
| 1069 |
+
1, 720, -1764, 1624, -735, 175, -21,
|
| 1070 |
+
1, -5040, 13068, -13132, 6769, -1960, 322, -28,
|
| 1071 |
+
1, 40320, -109584, 118124, -67284, 22449, -4536, 546, -36, 1]
|
| 1072 |
+
# https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
|
| 1073 |
+
assert [stirling(n, k, kind=1)
|
| 1074 |
+
for n in range(10) for k in range(n+1)] == [
|
| 1075 |
+
1,
|
| 1076 |
+
0, 1,
|
| 1077 |
+
0, 1, 1,
|
| 1078 |
+
0, 2, 3, 1,
|
| 1079 |
+
0, 6, 11, 6, 1,
|
| 1080 |
+
0, 24, 50, 35, 10, 1,
|
| 1081 |
+
0, 120, 274, 225, 85, 15, 1,
|
| 1082 |
+
0, 720, 1764, 1624, 735, 175, 21, 1,
|
| 1083 |
+
0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1,
|
| 1084 |
+
0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1]
|
| 1085 |
+
# https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
|
| 1086 |
+
assert [stirling(n, k, kind=2)
|
| 1087 |
+
for n in range(10) for k in range(n+1)] == [
|
| 1088 |
+
1,
|
| 1089 |
+
0, 1,
|
| 1090 |
+
0, 1, 1,
|
| 1091 |
+
0, 1, 3, 1,
|
| 1092 |
+
0, 1, 7, 6, 1,
|
| 1093 |
+
0, 1, 15, 25, 10, 1,
|
| 1094 |
+
0, 1, 31, 90, 65, 15, 1,
|
| 1095 |
+
0, 1, 63, 301, 350, 140, 21, 1,
|
| 1096 |
+
0, 1, 127, 966, 1701, 1050, 266, 28, 1,
|
| 1097 |
+
0, 1, 255, 3025, 7770, 6951, 2646, 462, 36, 1]
|
| 1098 |
+
assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0
|
| 1099 |
+
raises(ValueError, lambda: stirling(-2, 2))
|
| 1100 |
+
|
| 1101 |
+
# Assertion that the return type is SymPy Integer.
|
| 1102 |
+
assert isinstance(_stirling1(6, 3), Integer)
|
| 1103 |
+
assert isinstance(_stirling2(6, 3), Integer)
|
| 1104 |
+
|
| 1105 |
+
def delta(p):
|
| 1106 |
+
if len(p) == 1:
|
| 1107 |
+
return oo
|
| 1108 |
+
return min(abs(i[0] - i[1]) for i in subsets(p, 2))
|
| 1109 |
+
parts = multiset_partitions(range(5), 3)
|
| 1110 |
+
d = 2
|
| 1111 |
+
assert (sum(1 for p in parts if all(delta(i) >= d for i in p)) ==
|
| 1112 |
+
stirling(5, 3, d=d) == 7)
|
| 1113 |
+
|
| 1114 |
+
# other coverage tests
|
| 1115 |
+
assert nC('abb', 2) == nC('aab', 2) == 2
|
| 1116 |
+
assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27
|
| 1117 |
+
assert nP(3, 4) == 0
|
| 1118 |
+
assert nP('aabc', 5) == 0
|
| 1119 |
+
assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \
|
| 1120 |
+
len(list(multiset_combinations('aabbccdd', 2))) == 10
|
| 1121 |
+
assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24
|
| 1122 |
+
assert nC(list('abcdd'), 4) == 4
|
| 1123 |
+
assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5
|
| 1124 |
+
assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7
|
| 1125 |
+
assert nC('aabb'*3, 3) == 4 # aaa, bbb, abb, baa
|
| 1126 |
+
assert dict(_AOP_product((4,1,1,1))) == {
|
| 1127 |
+
0: 1, 1: 4, 2: 7, 3: 8, 4: 8, 5: 7, 6: 4, 7: 1}
|
| 1128 |
+
# the following was the first t that showed a problem in a previous form of
|
| 1129 |
+
# the function, so it's not as random as it may appear
|
| 1130 |
+
t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4)
|
| 1131 |
+
assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000
|
| 1132 |
+
raises(ValueError, lambda: _multiset_histogram({1:'a'}))
|
| 1133 |
+
|
| 1134 |
+
|
| 1135 |
+
def test_PR_14617():
|
| 1136 |
+
from sympy.functions.combinatorial.numbers import nT
|
| 1137 |
+
for n in (0, []):
|
| 1138 |
+
for k in (-1, 0, 1):
|
| 1139 |
+
if k == 0:
|
| 1140 |
+
assert nT(n, k) == 1
|
| 1141 |
+
else:
|
| 1142 |
+
assert nT(n, k) == 0
|
| 1143 |
+
|
| 1144 |
+
|
| 1145 |
+
def test_issue_8496():
|
| 1146 |
+
n = Symbol("n")
|
| 1147 |
+
k = Symbol("k")
|
| 1148 |
+
|
| 1149 |
+
raises(TypeError, lambda: catalan(n, k))
|
| 1150 |
+
|
| 1151 |
+
|
| 1152 |
+
def test_issue_8601():
|
| 1153 |
+
n = Symbol('n', integer=True, negative=True)
|
| 1154 |
+
|
| 1155 |
+
assert catalan(n - 1) is S.Zero
|
| 1156 |
+
assert catalan(Rational(-1, 2)) is S.ComplexInfinity
|
| 1157 |
+
assert catalan(-S.One) == Rational(-1, 2)
|
| 1158 |
+
c1 = catalan(-5.6).evalf()
|
| 1159 |
+
assert str(c1) == '6.93334070531408e-5'
|
| 1160 |
+
c2 = catalan(-35.4).evalf()
|
| 1161 |
+
assert str(c2) == '-4.14189164517449e-24'
|
| 1162 |
+
|
| 1163 |
+
|
| 1164 |
+
def test_motzkin():
|
| 1165 |
+
assert motzkin.is_motzkin(4) == True
|
| 1166 |
+
assert motzkin.is_motzkin(9) == True
|
| 1167 |
+
assert motzkin.is_motzkin(10) == False
|
| 1168 |
+
assert motzkin.find_motzkin_numbers_in_range(10,200) == [21, 51, 127]
|
| 1169 |
+
assert motzkin.find_motzkin_numbers_in_range(10,400) == [21, 51, 127, 323]
|
| 1170 |
+
assert motzkin.find_motzkin_numbers_in_range(10,1600) == [21, 51, 127, 323, 835]
|
| 1171 |
+
assert motzkin.find_first_n_motzkins(5) == [1, 1, 2, 4, 9]
|
| 1172 |
+
assert motzkin.find_first_n_motzkins(7) == [1, 1, 2, 4, 9, 21, 51]
|
| 1173 |
+
assert motzkin.find_first_n_motzkins(10) == [1, 1, 2, 4, 9, 21, 51, 127, 323, 835]
|
| 1174 |
+
raises(ValueError, lambda: motzkin.eval(77.58))
|
| 1175 |
+
raises(ValueError, lambda: motzkin.eval(-8))
|
| 1176 |
+
raises(ValueError, lambda: motzkin.find_motzkin_numbers_in_range(-2,7))
|
| 1177 |
+
raises(ValueError, lambda: motzkin.find_motzkin_numbers_in_range(13,7))
|
| 1178 |
+
raises(ValueError, lambda: motzkin.find_first_n_motzkins(112.8))
|
| 1179 |
+
|
| 1180 |
+
|
| 1181 |
+
def test_nD_derangements():
|
| 1182 |
+
from sympy.utilities.iterables import (partitions, multiset,
|
| 1183 |
+
multiset_derangements, multiset_permutations)
|
| 1184 |
+
from sympy.functions.combinatorial.numbers import nD
|
| 1185 |
+
|
| 1186 |
+
got = []
|
| 1187 |
+
for i in partitions(8, k=4):
|
| 1188 |
+
s = []
|
| 1189 |
+
it = 0
|
| 1190 |
+
for k, v in i.items():
|
| 1191 |
+
for i in range(v):
|
| 1192 |
+
s.extend([it]*k)
|
| 1193 |
+
it += 1
|
| 1194 |
+
ms = multiset(s)
|
| 1195 |
+
c1 = sum(1 for i in multiset_permutations(s) if
|
| 1196 |
+
all(i != j for i, j in zip(i, s)))
|
| 1197 |
+
assert c1 == nD(ms) == nD(ms, 0) == nD(ms, 1)
|
| 1198 |
+
v = [tuple(i) for i in multiset_derangements(s)]
|
| 1199 |
+
c2 = len(v)
|
| 1200 |
+
assert c2 == len(set(v))
|
| 1201 |
+
assert c1 == c2
|
| 1202 |
+
got.append(c1)
|
| 1203 |
+
assert got == [1, 4, 6, 12, 24, 24, 61, 126, 315, 780, 297, 772,
|
| 1204 |
+
2033, 5430, 14833]
|
| 1205 |
+
|
| 1206 |
+
assert nD('1112233456', brute=True) == nD('1112233456') == 16356
|
| 1207 |
+
assert nD('') == nD([]) == nD({}) == 0
|
| 1208 |
+
assert nD({1: 0}) == 0
|
| 1209 |
+
raises(ValueError, lambda: nD({1: -1}))
|
| 1210 |
+
assert nD('112') == 0
|
| 1211 |
+
assert nD(i='112') == 0
|
| 1212 |
+
assert [nD(n=i) for i in range(6)] == [0, 0, 1, 2, 9, 44]
|
| 1213 |
+
assert nD((i for i in range(4))) == nD('0123') == 9
|
| 1214 |
+
assert nD(m=(i for i in range(4))) == 3
|
| 1215 |
+
assert nD(m={0: 1, 1: 1, 2: 1, 3: 1}) == 3
|
| 1216 |
+
assert nD(m=[0, 1, 2, 3]) == 3
|
| 1217 |
+
raises(TypeError, lambda: nD(m=0))
|
| 1218 |
+
raises(TypeError, lambda: nD(-1))
|
| 1219 |
+
assert nD({-1: 1, -2: 1}) == 1
|
| 1220 |
+
assert nD(m={0: 3}) == 0
|
| 1221 |
+
raises(ValueError, lambda: nD(i='123', n=3))
|
| 1222 |
+
raises(ValueError, lambda: nD(i='123', m=(1,2)))
|
| 1223 |
+
raises(ValueError, lambda: nD(n=0, m=(1,2)))
|
| 1224 |
+
raises(ValueError, lambda: nD({1: -1}))
|
| 1225 |
+
raises(ValueError, lambda: nD(m={-1: 1, 2: 1}))
|
| 1226 |
+
raises(ValueError, lambda: nD(m={1: -1, 2: 1}))
|
| 1227 |
+
raises(ValueError, lambda: nD(m=[-1, 2]))
|
| 1228 |
+
raises(TypeError, lambda: nD({1: x}))
|
| 1229 |
+
raises(TypeError, lambda: nD(m={1: x}))
|
| 1230 |
+
raises(TypeError, lambda: nD(m={x: 1}))
|
| 1231 |
+
|
| 1232 |
+
|
| 1233 |
+
def test_deprecated_ntheory_symbolic_functions():
|
| 1234 |
+
from sympy.testing.pytest import warns_deprecated_sympy
|
| 1235 |
+
|
| 1236 |
+
with warns_deprecated_sympy():
|
| 1237 |
+
assert not carmichael.is_carmichael(3)
|
| 1238 |
+
with warns_deprecated_sympy():
|
| 1239 |
+
assert carmichael.find_carmichael_numbers_in_range(10, 20) == []
|
| 1240 |
+
with warns_deprecated_sympy():
|
| 1241 |
+
assert carmichael.find_first_n_carmichaels(1)
|
lib/python3.12/site-packages/sympy/functions/special/__pycache__/elliptic_integrals.cpython-312.pyc
ADDED
|
Binary file (23.5 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""A module that handles series: find a limit, order the series etc.
|
| 2 |
+
"""
|
| 3 |
+
from .order import Order
|
| 4 |
+
from .limits import limit, Limit
|
| 5 |
+
from .gruntz import gruntz
|
| 6 |
+
from .series import series
|
| 7 |
+
from .approximants import approximants
|
| 8 |
+
from .residues import residue
|
| 9 |
+
from .sequences import SeqPer, SeqFormula, sequence, SeqAdd, SeqMul
|
| 10 |
+
from .fourier import fourier_series
|
| 11 |
+
from .formal import fps
|
| 12 |
+
from .limitseq import difference_delta, limit_seq
|
| 13 |
+
|
| 14 |
+
from sympy.core.singleton import S
|
| 15 |
+
EmptySequence = S.EmptySequence
|
| 16 |
+
|
| 17 |
+
O = Order
|
| 18 |
+
|
| 19 |
+
__all__ = ['Order', 'O', 'limit', 'Limit', 'gruntz', 'series', 'approximants',
|
| 20 |
+
'residue', 'EmptySequence', 'SeqPer', 'SeqFormula', 'sequence',
|
| 21 |
+
'SeqAdd', 'SeqMul', 'fourier_series', 'fps', 'difference_delta',
|
| 22 |
+
'limit_seq'
|
| 23 |
+
]
|
lib/python3.12/site-packages/sympy/series/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/acceleration.cpython-312.pyc
ADDED
|
Binary file (4.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/approximants.cpython-312.pyc
ADDED
|
Binary file (5.04 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/aseries.cpython-312.pyc
ADDED
|
Binary file (568 Bytes). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/formal.cpython-312.pyc
ADDED
|
Binary file (75 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/fourier.cpython-312.pyc
ADDED
|
Binary file (33 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/gruntz.cpython-312.pyc
ADDED
|
Binary file (31.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/kauers.cpython-312.pyc
ADDED
|
Binary file (2.19 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/limits.cpython-312.pyc
ADDED
|
Binary file (16.2 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/limitseq.cpython-312.pyc
ADDED
|
Binary file (10.1 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/order.cpython-312.pyc
ADDED
|
Binary file (25.8 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/residues.cpython-312.pyc
ADDED
|
Binary file (2.39 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/sequences.cpython-312.pyc
ADDED
|
Binary file (47.6 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/series.cpython-312.pyc
ADDED
|
Binary file (2.19 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/__pycache__/series_class.cpython-312.pyc
ADDED
|
Binary file (4.87 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/series/acceleration.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Convergence acceleration / extrapolation methods for series and
|
| 3 |
+
sequences.
|
| 4 |
+
|
| 5 |
+
References:
|
| 6 |
+
Carl M. Bender & Steven A. Orszag, "Advanced Mathematical Methods for
|
| 7 |
+
Scientists and Engineers: Asymptotic Methods and Perturbation Theory",
|
| 8 |
+
Springer 1999. (Shanks transformation: pp. 368-375, Richardson
|
| 9 |
+
extrapolation: pp. 375-377.)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from sympy.core.numbers import Integer
|
| 13 |
+
from sympy.core.singleton import S
|
| 14 |
+
from sympy.functions.combinatorial.factorials import factorial
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def richardson(A, k, n, N):
|
| 18 |
+
"""
|
| 19 |
+
Calculate an approximation for lim k->oo A(k) using Richardson
|
| 20 |
+
extrapolation with the terms A(n), A(n+1), ..., A(n+N+1).
|
| 21 |
+
Choosing N ~= 2*n often gives good results.
|
| 22 |
+
|
| 23 |
+
Examples
|
| 24 |
+
========
|
| 25 |
+
|
| 26 |
+
A simple example is to calculate exp(1) using the limit definition.
|
| 27 |
+
This limit converges slowly; n = 100 only produces two accurate
|
| 28 |
+
digits:
|
| 29 |
+
|
| 30 |
+
>>> from sympy.abc import n
|
| 31 |
+
>>> e = (1 + 1/n)**n
|
| 32 |
+
>>> print(round(e.subs(n, 100).evalf(), 10))
|
| 33 |
+
2.7048138294
|
| 34 |
+
|
| 35 |
+
Richardson extrapolation with 11 appropriately chosen terms gives
|
| 36 |
+
a value that is accurate to the indicated precision:
|
| 37 |
+
|
| 38 |
+
>>> from sympy import E
|
| 39 |
+
>>> from sympy.series.acceleration import richardson
|
| 40 |
+
>>> print(round(richardson(e, n, 10, 20).evalf(), 10))
|
| 41 |
+
2.7182818285
|
| 42 |
+
>>> print(round(E.evalf(), 10))
|
| 43 |
+
2.7182818285
|
| 44 |
+
|
| 45 |
+
Another useful application is to speed up convergence of series.
|
| 46 |
+
Computing 100 terms of the zeta(2) series 1/k**2 yields only
|
| 47 |
+
two accurate digits:
|
| 48 |
+
|
| 49 |
+
>>> from sympy.abc import k, n
|
| 50 |
+
>>> from sympy import Sum
|
| 51 |
+
>>> A = Sum(k**-2, (k, 1, n))
|
| 52 |
+
>>> print(round(A.subs(n, 100).evalf(), 10))
|
| 53 |
+
1.6349839002
|
| 54 |
+
|
| 55 |
+
Richardson extrapolation performs much better:
|
| 56 |
+
|
| 57 |
+
>>> from sympy import pi
|
| 58 |
+
>>> print(round(richardson(A, n, 10, 20).evalf(), 10))
|
| 59 |
+
1.6449340668
|
| 60 |
+
>>> print(round(((pi**2)/6).evalf(), 10)) # Exact value
|
| 61 |
+
1.6449340668
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
s = S.Zero
|
| 65 |
+
for j in range(0, N + 1):
|
| 66 |
+
s += (A.subs(k, Integer(n + j)).doit() * (n + j)**N *
|
| 67 |
+
S.NegativeOne**(j + N) / (factorial(j) * factorial(N - j)))
|
| 68 |
+
return s
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def shanks(A, k, n, m=1):
|
| 72 |
+
"""
|
| 73 |
+
Calculate an approximation for lim k->oo A(k) using the n-term Shanks
|
| 74 |
+
transformation S(A)(n). With m > 1, calculate the m-fold recursive
|
| 75 |
+
Shanks transformation S(S(...S(A)...))(n).
|
| 76 |
+
|
| 77 |
+
The Shanks transformation is useful for summing Taylor series that
|
| 78 |
+
converge slowly near a pole or singularity, e.g. for log(2):
|
| 79 |
+
|
| 80 |
+
>>> from sympy.abc import k, n
|
| 81 |
+
>>> from sympy import Sum, Integer
|
| 82 |
+
>>> from sympy.series.acceleration import shanks
|
| 83 |
+
>>> A = Sum(Integer(-1)**(k+1) / k, (k, 1, n))
|
| 84 |
+
>>> print(round(A.subs(n, 100).doit().evalf(), 10))
|
| 85 |
+
0.6881721793
|
| 86 |
+
>>> print(round(shanks(A, n, 25).evalf(), 10))
|
| 87 |
+
0.6931396564
|
| 88 |
+
>>> print(round(shanks(A, n, 25, 5).evalf(), 10))
|
| 89 |
+
0.6931471806
|
| 90 |
+
|
| 91 |
+
The correct value is 0.6931471805599453094172321215.
|
| 92 |
+
"""
|
| 93 |
+
table = [A.subs(k, Integer(j)).doit() for j in range(n + m + 2)]
|
| 94 |
+
table2 = table[:]
|
| 95 |
+
|
| 96 |
+
for i in range(1, m + 1):
|
| 97 |
+
for j in range(i, n + m + 1):
|
| 98 |
+
x, y, z = table[j - 1], table[j], table[j + 1]
|
| 99 |
+
table2[j] = (z*x - y**2) / (z + x - 2*y)
|
| 100 |
+
table = table2[:]
|
| 101 |
+
return table[n]
|