AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
This commit is contained in:
+43
@@ -0,0 +1,43 @@
|
||||
from sympy.combinatorics.permutations import Permutation, Cycle
|
||||
from sympy.combinatorics.prufer import Prufer
|
||||
from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral
|
||||
from sympy.combinatorics.subsets import Subset
|
||||
from sympy.combinatorics.partitions import (Partition, IntegerPartition,
|
||||
RGS_rank, RGS_unrank, RGS_enum)
|
||||
from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube,
|
||||
octahedron, dodecahedron, icosahedron)
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup
|
||||
from sympy.combinatorics.group_constructs import DirectProduct
|
||||
from sympy.combinatorics.graycode import GrayCode
|
||||
from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup,
|
||||
CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup)
|
||||
from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector
|
||||
from sympy.combinatorics.free_groups import free_group
|
||||
|
||||
__all__ = [
|
||||
'Permutation', 'Cycle',
|
||||
|
||||
'Prufer',
|
||||
|
||||
'cyclic', 'alternating', 'symmetric', 'dihedral',
|
||||
|
||||
'Subset',
|
||||
|
||||
'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum',
|
||||
|
||||
'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron',
|
||||
'icosahedron',
|
||||
|
||||
'PermutationGroup', 'Coset', 'SymmetricPermutationGroup',
|
||||
|
||||
'DirectProduct',
|
||||
|
||||
'GrayCode',
|
||||
|
||||
'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup',
|
||||
'AbelianGroup', 'RubikGroup',
|
||||
|
||||
'PolycyclicGroup', 'Collector',
|
||||
|
||||
'free_group',
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1259
File diff suppressed because it is too large
Load Diff
+1352
File diff suppressed because it is too large
Load Diff
+1360
File diff suppressed because it is too large
Load Diff
+611
@@ -0,0 +1,611 @@
|
||||
r"""
|
||||
Construct transitive subgroups of symmetric groups, useful in Galois theory.
|
||||
|
||||
Besides constructing instances of the :py:class:`~.PermutationGroup` class to
|
||||
represent the transitive subgroups of $S_n$ for small $n$, this module provides
|
||||
*names* for these groups.
|
||||
|
||||
In some applications, it may be preferable to know the name of a group,
|
||||
rather than receive an instance of the :py:class:`~.PermutationGroup`
|
||||
class, and then have to do extra work to determine which group it is, by
|
||||
checking various properties.
|
||||
|
||||
Names are instances of ``Enum`` classes defined in this module. With a name in
|
||||
hand, the name's ``get_perm_group`` method can then be used to retrieve a
|
||||
:py:class:`~.PermutationGroup`.
|
||||
|
||||
The names used for groups in this module are taken from [1].
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
|
||||
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
import itertools
|
||||
|
||||
from sympy.combinatorics.named_groups import (
|
||||
SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup,
|
||||
set_symmetric_group_properties, set_alternating_group_properties,
|
||||
)
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
|
||||
class S1TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S1.
|
||||
"""
|
||||
S1 = "S1"
|
||||
|
||||
def get_perm_group(self):
|
||||
return SymmetricGroup(1)
|
||||
|
||||
|
||||
class S2TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S2.
|
||||
"""
|
||||
S2 = "S2"
|
||||
|
||||
def get_perm_group(self):
|
||||
return SymmetricGroup(2)
|
||||
|
||||
|
||||
class S3TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S3.
|
||||
"""
|
||||
A3 = "A3"
|
||||
S3 = "S3"
|
||||
|
||||
def get_perm_group(self):
|
||||
if self == S3TransitiveSubgroups.A3:
|
||||
return AlternatingGroup(3)
|
||||
elif self == S3TransitiveSubgroups.S3:
|
||||
return SymmetricGroup(3)
|
||||
|
||||
|
||||
class S4TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S4.
|
||||
"""
|
||||
C4 = "C4"
|
||||
V = "V"
|
||||
D4 = "D4"
|
||||
A4 = "A4"
|
||||
S4 = "S4"
|
||||
|
||||
def get_perm_group(self):
|
||||
if self == S4TransitiveSubgroups.C4:
|
||||
return CyclicGroup(4)
|
||||
elif self == S4TransitiveSubgroups.V:
|
||||
return four_group()
|
||||
elif self == S4TransitiveSubgroups.D4:
|
||||
return DihedralGroup(4)
|
||||
elif self == S4TransitiveSubgroups.A4:
|
||||
return AlternatingGroup(4)
|
||||
elif self == S4TransitiveSubgroups.S4:
|
||||
return SymmetricGroup(4)
|
||||
|
||||
|
||||
class S5TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S5.
|
||||
"""
|
||||
C5 = "C5"
|
||||
D5 = "D5"
|
||||
M20 = "M20"
|
||||
A5 = "A5"
|
||||
S5 = "S5"
|
||||
|
||||
def get_perm_group(self):
|
||||
if self == S5TransitiveSubgroups.C5:
|
||||
return CyclicGroup(5)
|
||||
elif self == S5TransitiveSubgroups.D5:
|
||||
return DihedralGroup(5)
|
||||
elif self == S5TransitiveSubgroups.M20:
|
||||
return M20()
|
||||
elif self == S5TransitiveSubgroups.A5:
|
||||
return AlternatingGroup(5)
|
||||
elif self == S5TransitiveSubgroups.S5:
|
||||
return SymmetricGroup(5)
|
||||
|
||||
|
||||
class S6TransitiveSubgroups(Enum):
|
||||
"""
|
||||
Names for the transitive subgroups of S6.
|
||||
"""
|
||||
C6 = "C6"
|
||||
S3 = "S3"
|
||||
D6 = "D6"
|
||||
A4 = "A4"
|
||||
G18 = "G18"
|
||||
A4xC2 = "A4 x C2"
|
||||
S4m = "S4-"
|
||||
S4p = "S4+"
|
||||
G36m = "G36-"
|
||||
G36p = "G36+"
|
||||
S4xC2 = "S4 x C2"
|
||||
PSL2F5 = "PSL2(F5)"
|
||||
G72 = "G72"
|
||||
PGL2F5 = "PGL2(F5)"
|
||||
A6 = "A6"
|
||||
S6 = "S6"
|
||||
|
||||
def get_perm_group(self):
|
||||
if self == S6TransitiveSubgroups.C6:
|
||||
return CyclicGroup(6)
|
||||
elif self == S6TransitiveSubgroups.S3:
|
||||
return S3_in_S6()
|
||||
elif self == S6TransitiveSubgroups.D6:
|
||||
return DihedralGroup(6)
|
||||
elif self == S6TransitiveSubgroups.A4:
|
||||
return A4_in_S6()
|
||||
elif self == S6TransitiveSubgroups.G18:
|
||||
return G18()
|
||||
elif self == S6TransitiveSubgroups.A4xC2:
|
||||
return A4xC2()
|
||||
elif self == S6TransitiveSubgroups.S4m:
|
||||
return S4m()
|
||||
elif self == S6TransitiveSubgroups.S4p:
|
||||
return S4p()
|
||||
elif self == S6TransitiveSubgroups.G36m:
|
||||
return G36m()
|
||||
elif self == S6TransitiveSubgroups.G36p:
|
||||
return G36p()
|
||||
elif self == S6TransitiveSubgroups.S4xC2:
|
||||
return S4xC2()
|
||||
elif self == S6TransitiveSubgroups.PSL2F5:
|
||||
return PSL2F5()
|
||||
elif self == S6TransitiveSubgroups.G72:
|
||||
return G72()
|
||||
elif self == S6TransitiveSubgroups.PGL2F5:
|
||||
return PGL2F5()
|
||||
elif self == S6TransitiveSubgroups.A6:
|
||||
return AlternatingGroup(6)
|
||||
elif self == S6TransitiveSubgroups.S6:
|
||||
return SymmetricGroup(6)
|
||||
|
||||
|
||||
def four_group():
|
||||
"""
|
||||
Return a representation of the Klein four-group as a transitive subgroup
|
||||
of S4.
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(0, 1)(2, 3),
|
||||
Permutation(0, 2)(1, 3)
|
||||
)
|
||||
|
||||
|
||||
def M20():
|
||||
"""
|
||||
Return a representation of the metacyclic group M20, a transitive subgroup
|
||||
of S5 that is one of the possible Galois groups for polys of degree 5.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
See [1], Page 323.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3))
|
||||
G._degree = 5
|
||||
G._order = 20
|
||||
G._is_transitive = True
|
||||
G._is_sym = False
|
||||
G._is_alt = False
|
||||
G._is_cyclic = False
|
||||
G._is_dihedral = False
|
||||
return G
|
||||
|
||||
|
||||
def S3_in_S6():
|
||||
"""
|
||||
Return a representation of S3 as a transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The representation is found by viewing the group as the symmetries of a
|
||||
triangular prism.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5))
|
||||
set_symmetric_group_properties(G, 3, 6)
|
||||
return G
|
||||
|
||||
|
||||
def A4_in_S6():
|
||||
"""
|
||||
Return a representation of A4 as a transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4))
|
||||
set_alternating_group_properties(G, 4, 6)
|
||||
return G
|
||||
|
||||
|
||||
def S4m():
|
||||
"""
|
||||
Return a representation of the S4- transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3))
|
||||
set_symmetric_group_properties(G, 4, 6)
|
||||
return G
|
||||
|
||||
|
||||
def S4p():
|
||||
"""
|
||||
Return a representation of the S4+ transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5))
|
||||
set_symmetric_group_properties(G, 4, 6)
|
||||
return G
|
||||
|
||||
|
||||
def A4xC2():
|
||||
"""
|
||||
Return a representation of the (A4 x C2) transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4),
|
||||
Permutation(5)(2, 4))
|
||||
|
||||
|
||||
def S4xC2():
|
||||
"""
|
||||
Return a representation of the (S4 x C2) transitive subgroup of S6.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3),
|
||||
Permutation(1, 4)(3, 5))
|
||||
|
||||
|
||||
def G18():
|
||||
"""
|
||||
Return a representation of the group G18, a transitive subgroup of S6
|
||||
isomorphic to the semidirect product of C3^2 with C2.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
||||
Permutation(0, 4)(1, 5)(2, 3))
|
||||
|
||||
|
||||
def G36m():
|
||||
"""
|
||||
Return a representation of the group G36-, a transitive subgroup of S6
|
||||
isomorphic to the semidirect product of C3^2 with C2^2.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
||||
Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3))
|
||||
|
||||
|
||||
def G36p():
|
||||
"""
|
||||
Return a representation of the group G36+, a transitive subgroup of S6
|
||||
isomorphic to the semidirect product of C3^2 with C4.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
|
||||
Permutation(0, 5, 2, 3)(1, 4))
|
||||
|
||||
|
||||
def G72():
|
||||
"""
|
||||
Return a representation of the group G72, a transitive subgroup of S6
|
||||
isomorphic to the semidirect product of C3^2 with D4.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
See [1], Page 325.
|
||||
|
||||
"""
|
||||
return PermutationGroup(
|
||||
Permutation(5)(0, 1, 2),
|
||||
Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5))
|
||||
|
||||
|
||||
def PSL2F5():
|
||||
r"""
|
||||
Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive
|
||||
subgroup of S6, isomorphic to $A_5$.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(
|
||||
Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5))
|
||||
set_alternating_group_properties(G, 5, 6)
|
||||
return G
|
||||
|
||||
|
||||
def PGL2F5():
|
||||
r"""
|
||||
Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive
|
||||
subgroup of S6, isomorphic to $S_5$.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
See [1], Page 325.
|
||||
|
||||
"""
|
||||
G = PermutationGroup(
|
||||
Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4))
|
||||
set_symmetric_group_properties(G, 5, 6)
|
||||
return G
|
||||
|
||||
|
||||
def find_transitive_subgroups_of_S6(*targets, print_report=False):
|
||||
r"""
|
||||
Search for certain transitive subgroups of $S_6$.
|
||||
|
||||
The symmetric group $S_6$ has 16 different transitive subgroups, up to
|
||||
conjugacy. Some are more easily constructed than others. For example, the
|
||||
dihedral group $D_6$ is immediately found, but it is not at all obvious how
|
||||
to realize $S_4$ or $S_5$ *transitively* within $S_6$.
|
||||
|
||||
In some cases there are well-known constructions that can be used. For
|
||||
example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a
|
||||
natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6.
|
||||
|
||||
In absence of such special constructions however, we can simply search for
|
||||
generators. For example, transitive instances of $A_4$ and $S_4$ can be
|
||||
found within $S_6$ in this way.
|
||||
|
||||
Once we are engaged in such searches, it may then be easier (if less
|
||||
elegant) to find even those groups like $S_5$ that do have special
|
||||
constructions, by mere search.
|
||||
|
||||
This function locates generators for transitive instances in $S_6$ of the
|
||||
following subgroups:
|
||||
|
||||
* $A_4$
|
||||
* $S_4^-$ ($S_4$ not contained within $A_6$)
|
||||
* $S_4^+$ ($S_4$ contained within $A_6$)
|
||||
* $A_4 \times C_2$
|
||||
* $S_4 \times C_2$
|
||||
* $G_{18} = C_3^2 \rtimes C_2$
|
||||
* $G_{36}^- = C_3^2 \rtimes C_2^2$
|
||||
* $G_{36}^+ = C_3^2 \rtimes C_4$
|
||||
* $G_{72} = C_3^2 \rtimes D_4$
|
||||
* $A_5$
|
||||
* $S_5$
|
||||
|
||||
Note: Each of these groups also has a dedicated function in this module
|
||||
that returns the group immediately, using generators that were found by
|
||||
this search procedure.
|
||||
|
||||
The search procedure serves as a record of how these generators were
|
||||
found. Also, due to randomness in the generation of the elements of
|
||||
permutation groups, it can be called again, in order to (probably) get
|
||||
different generators for the same groups.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
targets : list of :py:class:`~.S6TransitiveSubgroups` values
|
||||
The groups you want to find.
|
||||
|
||||
print_report : bool (default False)
|
||||
If True, print to stdout the generators found for each group.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
dict
|
||||
mapping each name in *targets* to the :py:class:`~.PermutationGroup`
|
||||
that was found
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms
|
||||
.. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL%282,5%29
|
||||
|
||||
"""
|
||||
def elts_by_order(G):
|
||||
"""Sort the elements of a group by their order. """
|
||||
elts = defaultdict(list)
|
||||
for g in G.elements:
|
||||
elts[g.order()].append(g)
|
||||
return elts
|
||||
|
||||
def order_profile(G, name=None):
|
||||
"""Determine how many elements a group has, of each order. """
|
||||
elts = elts_by_order(G)
|
||||
profile = {o:len(e) for o, e in elts.items()}
|
||||
if name:
|
||||
print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys())))
|
||||
return profile
|
||||
|
||||
S6 = SymmetricGroup(6)
|
||||
A6 = AlternatingGroup(6)
|
||||
S6_by_order = elts_by_order(S6)
|
||||
|
||||
def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None):
|
||||
"""
|
||||
Find a transitive subgroup of S6.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
existing_gens : list of Permutation
|
||||
Optionally empty list of generators that must be in the group.
|
||||
|
||||
needed_gen_orders : list of positive int
|
||||
Nonempty list of the orders of the additional generators that are
|
||||
to be found.
|
||||
|
||||
order: int
|
||||
The order of the group being sought.
|
||||
|
||||
alt: bool, None
|
||||
If True, require the group to be contained in A6.
|
||||
If False, require the group not to be contained in A6.
|
||||
|
||||
profile : dict
|
||||
If given, the group's order profile must equal this.
|
||||
|
||||
anti_profile : dict
|
||||
If given, the group's order profile must *not* equal this.
|
||||
|
||||
"""
|
||||
for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]):
|
||||
if len(set(gens)) < len(gens):
|
||||
continue
|
||||
G = PermutationGroup(existing_gens + list(gens))
|
||||
if G.order() == order and G.is_transitive():
|
||||
if alt is not None and G.is_subgroup(A6) != alt:
|
||||
continue
|
||||
if profile and order_profile(G) != profile:
|
||||
continue
|
||||
if anti_profile and order_profile(G) == anti_profile:
|
||||
continue
|
||||
return G
|
||||
|
||||
def match_known_group(G, alt=None):
|
||||
needed = [g.order() for g in G.generators]
|
||||
return search([], needed, G.order(), alt=alt, profile=order_profile(G))
|
||||
|
||||
found = {}
|
||||
|
||||
def finish_up(name, G):
|
||||
found[name] = G
|
||||
if print_report:
|
||||
print("=" * 40)
|
||||
print(f"{name}:")
|
||||
print(G.generators)
|
||||
|
||||
if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets:
|
||||
A4_in_S6 = match_known_group(AlternatingGroup(4))
|
||||
finish_up(S6TransitiveSubgroups.A4, A4_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets:
|
||||
S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False)
|
||||
finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.S4p in targets:
|
||||
S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True)
|
||||
finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.A4xC2 in targets:
|
||||
A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4)))
|
||||
finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.S4xC2 in targets:
|
||||
S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48)
|
||||
finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6)
|
||||
|
||||
# For the normal factor N = C3^2 in any of the G_n subgroups, we take one
|
||||
# obvious instance of C3^2 in S6:
|
||||
N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)]
|
||||
|
||||
if S6TransitiveSubgroups.G18 in targets:
|
||||
G18_in_S6 = search(N_gens, [2], 18)
|
||||
finish_up(S6TransitiveSubgroups.G18, G18_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.G36m in targets:
|
||||
G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False)
|
||||
finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.G36p in targets:
|
||||
G36p_in_S6 = search(N_gens, [4], 36, alt=True)
|
||||
finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.G72 in targets:
|
||||
G72_in_S6 = search(N_gens, [4, 2], 72)
|
||||
finish_up(S6TransitiveSubgroups.G72, G72_in_S6)
|
||||
|
||||
# The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp.
|
||||
|
||||
if S6TransitiveSubgroups.PSL2F5 in targets:
|
||||
PSL2F5_in_S6 = match_known_group(AlternatingGroup(5))
|
||||
finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6)
|
||||
|
||||
if S6TransitiveSubgroups.PGL2F5 in targets:
|
||||
PGL2F5_in_S6 = match_known_group(SymmetricGroup(5))
|
||||
finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6)
|
||||
|
||||
# There is little need to "search" for any of the groups C6, S3, D6, A6,
|
||||
# or S6, since they all have obvious realizations within S6. However, we
|
||||
# support them here just in case a random representation is desired.
|
||||
|
||||
if S6TransitiveSubgroups.C6 in targets:
|
||||
C6 = match_known_group(CyclicGroup(6))
|
||||
finish_up(S6TransitiveSubgroups.C6, C6)
|
||||
|
||||
if S6TransitiveSubgroups.S3 in targets:
|
||||
S3 = match_known_group(SymmetricGroup(3))
|
||||
finish_up(S6TransitiveSubgroups.S3, S3)
|
||||
|
||||
if S6TransitiveSubgroups.D6 in targets:
|
||||
D6 = match_known_group(DihedralGroup(6))
|
||||
finish_up(S6TransitiveSubgroups.D6, D6)
|
||||
|
||||
if S6TransitiveSubgroups.A6 in targets:
|
||||
A6 = match_known_group(A6)
|
||||
finish_up(S6TransitiveSubgroups.A6, A6)
|
||||
|
||||
if S6TransitiveSubgroups.S6 in targets:
|
||||
S6 = match_known_group(S6)
|
||||
finish_up(S6TransitiveSubgroups.S6, S6)
|
||||
|
||||
return found
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.utilities.iterables import variations, rotate_left
|
||||
|
||||
|
||||
def symmetric(n):
|
||||
"""
|
||||
Generates the symmetric group of order n, Sn.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.generators import symmetric
|
||||
>>> list(symmetric(3))
|
||||
[(2), (1 2), (2)(0 1), (0 1 2), (0 2 1), (0 2)]
|
||||
"""
|
||||
yield from (Permutation(perm) for perm in variations(range(n), n))
|
||||
|
||||
|
||||
def cyclic(n):
|
||||
"""
|
||||
Generates the cyclic group of order n, Cn.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.generators import cyclic
|
||||
>>> list(cyclic(5))
|
||||
[(4), (0 1 2 3 4), (0 2 4 1 3),
|
||||
(0 3 1 4 2), (0 4 3 2 1)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
dihedral
|
||||
"""
|
||||
gen = list(range(n))
|
||||
for i in range(n):
|
||||
yield Permutation(gen)
|
||||
gen = rotate_left(gen, 1)
|
||||
|
||||
|
||||
def alternating(n):
|
||||
"""
|
||||
Generates the alternating group of order n, An.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.generators import alternating
|
||||
>>> list(alternating(3))
|
||||
[(2), (0 1 2), (0 2 1)]
|
||||
"""
|
||||
for perm in variations(range(n), n):
|
||||
p = Permutation(perm)
|
||||
if p.is_even:
|
||||
yield p
|
||||
|
||||
|
||||
def dihedral(n):
|
||||
"""
|
||||
Generates the dihedral group of order 2n, Dn.
|
||||
|
||||
The result is given as a subgroup of Sn, except for the special cases n=1
|
||||
(the group S2) and n=2 (the Klein 4-group) where that's not possible
|
||||
and embeddings in S2 and S4 respectively are given.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.generators import dihedral
|
||||
>>> list(dihedral(3))
|
||||
[(2), (0 2), (0 1 2), (1 2), (0 2 1), (2)(0 1)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
cyclic
|
||||
"""
|
||||
if n == 1:
|
||||
yield Permutation([0, 1])
|
||||
yield Permutation([1, 0])
|
||||
elif n == 2:
|
||||
yield Permutation([0, 1, 2, 3])
|
||||
yield Permutation([1, 0, 3, 2])
|
||||
yield Permutation([2, 3, 0, 1])
|
||||
yield Permutation([3, 2, 1, 0])
|
||||
else:
|
||||
gen = list(range(n))
|
||||
for i in range(n):
|
||||
yield Permutation(gen)
|
||||
yield Permutation(gen[::-1])
|
||||
gen = rotate_left(gen, 1)
|
||||
|
||||
|
||||
def rubik_cube_generators():
|
||||
"""Return the permutations of the 3x3 Rubik's cube, see
|
||||
https://www.gap-system.org/Doc/Examples/rubik.html
|
||||
"""
|
||||
a = [
|
||||
[(1, 3, 8, 6), (2, 5, 7, 4), (9, 33, 25, 17), (10, 34, 26, 18),
|
||||
(11, 35, 27, 19)],
|
||||
[(9, 11, 16, 14), (10, 13, 15, 12), (1, 17, 41, 40), (4, 20, 44, 37),
|
||||
(6, 22, 46, 35)],
|
||||
[(17, 19, 24, 22), (18, 21, 23, 20), (6, 25, 43, 16), (7, 28, 42, 13),
|
||||
(8, 30, 41, 11)],
|
||||
[(25, 27, 32, 30), (26, 29, 31, 28), (3, 38, 43, 19), (5, 36, 45, 21),
|
||||
(8, 33, 48, 24)],
|
||||
[(33, 35, 40, 38), (34, 37, 39, 36), (3, 9, 46, 32), (2, 12, 47, 29),
|
||||
(1, 14, 48, 27)],
|
||||
[(41, 43, 48, 46), (42, 45, 47, 44), (14, 22, 30, 38),
|
||||
(15, 23, 31, 39), (16, 24, 32, 40)]
|
||||
]
|
||||
return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a]
|
||||
|
||||
|
||||
def rubik(n):
|
||||
"""Return permutations for an nxn Rubik's cube.
|
||||
|
||||
Permutations returned are for rotation of each of the slice
|
||||
from the face up to the last face for each of the 3 sides (in this order):
|
||||
front, right and bottom. Hence, the first n - 1 permutations are for the
|
||||
slices from the front.
|
||||
"""
|
||||
|
||||
if n < 2:
|
||||
raise ValueError('dimension of cube must be > 1')
|
||||
|
||||
# 1-based reference to rows and columns in Matrix
|
||||
def getr(f, i):
|
||||
return faces[f].col(n - i)
|
||||
|
||||
def getl(f, i):
|
||||
return faces[f].col(i - 1)
|
||||
|
||||
def getu(f, i):
|
||||
return faces[f].row(i - 1)
|
||||
|
||||
def getd(f, i):
|
||||
return faces[f].row(n - i)
|
||||
|
||||
def setr(f, i, s):
|
||||
faces[f][:, n - i] = Matrix(n, 1, s)
|
||||
|
||||
def setl(f, i, s):
|
||||
faces[f][:, i - 1] = Matrix(n, 1, s)
|
||||
|
||||
def setu(f, i, s):
|
||||
faces[f][i - 1, :] = Matrix(1, n, s)
|
||||
|
||||
def setd(f, i, s):
|
||||
faces[f][n - i, :] = Matrix(1, n, s)
|
||||
|
||||
# motion of a single face
|
||||
def cw(F, r=1):
|
||||
for _ in range(r):
|
||||
face = faces[F]
|
||||
rv = []
|
||||
for c in range(n):
|
||||
for r in range(n - 1, -1, -1):
|
||||
rv.append(face[r, c])
|
||||
faces[F] = Matrix(n, n, rv)
|
||||
|
||||
def ccw(F):
|
||||
cw(F, 3)
|
||||
|
||||
# motion of plane i from the F side;
|
||||
# fcw(0) moves the F face, fcw(1) moves the plane
|
||||
# just behind the front face, etc...
|
||||
def fcw(i, r=1):
|
||||
for _ in range(r):
|
||||
if i == 0:
|
||||
cw(F)
|
||||
i += 1
|
||||
temp = getr(L, i)
|
||||
setr(L, i, list(getu(D, i)))
|
||||
setu(D, i, list(reversed(getl(R, i))))
|
||||
setl(R, i, list(getd(U, i)))
|
||||
setd(U, i, list(reversed(temp)))
|
||||
i -= 1
|
||||
|
||||
def fccw(i):
|
||||
fcw(i, 3)
|
||||
|
||||
# motion of the entire cube from the F side
|
||||
def FCW(r=1):
|
||||
for _ in range(r):
|
||||
cw(F)
|
||||
ccw(B)
|
||||
cw(U)
|
||||
t = faces[U]
|
||||
cw(L)
|
||||
faces[U] = faces[L]
|
||||
cw(D)
|
||||
faces[L] = faces[D]
|
||||
cw(R)
|
||||
faces[D] = faces[R]
|
||||
faces[R] = t
|
||||
|
||||
def FCCW():
|
||||
FCW(3)
|
||||
|
||||
# motion of the entire cube from the U side
|
||||
def UCW(r=1):
|
||||
for _ in range(r):
|
||||
cw(U)
|
||||
ccw(D)
|
||||
t = faces[F]
|
||||
faces[F] = faces[R]
|
||||
faces[R] = faces[B]
|
||||
faces[B] = faces[L]
|
||||
faces[L] = t
|
||||
|
||||
def UCCW():
|
||||
UCW(3)
|
||||
|
||||
# defining the permutations for the cube
|
||||
|
||||
U, F, R, B, L, D = names = symbols('U, F, R, B, L, D')
|
||||
|
||||
# the faces are represented by nxn matrices
|
||||
faces = {}
|
||||
count = 0
|
||||
for fi in range(6):
|
||||
f = []
|
||||
for a in range(n**2):
|
||||
f.append(count)
|
||||
count += 1
|
||||
faces[names[fi]] = Matrix(n, n, f)
|
||||
|
||||
# this will either return the value of the current permutation
|
||||
# (show != 1) or else append the permutation to the group, g
|
||||
def perm(show=0):
|
||||
# add perm to the list of perms
|
||||
p = []
|
||||
for f in names:
|
||||
p.extend(faces[f])
|
||||
if show:
|
||||
return p
|
||||
g.append(Permutation(p))
|
||||
|
||||
g = [] # container for the group's permutations
|
||||
I = list(range(6*n**2)) # the identity permutation used for checking
|
||||
|
||||
# define permutations corresponding to cw rotations of the planes
|
||||
# up TO the last plane from that direction; by not including the
|
||||
# last plane, the orientation of the cube is maintained.
|
||||
|
||||
# F slices
|
||||
for i in range(n - 1):
|
||||
fcw(i)
|
||||
perm()
|
||||
fccw(i) # restore
|
||||
assert perm(1) == I
|
||||
|
||||
# R slices
|
||||
# bring R to front
|
||||
UCW()
|
||||
for i in range(n - 1):
|
||||
fcw(i)
|
||||
# put it back in place
|
||||
UCCW()
|
||||
# record
|
||||
perm()
|
||||
# restore
|
||||
# bring face to front
|
||||
UCW()
|
||||
fccw(i)
|
||||
# restore
|
||||
UCCW()
|
||||
assert perm(1) == I
|
||||
|
||||
# D slices
|
||||
# bring up bottom
|
||||
FCW()
|
||||
UCCW()
|
||||
FCCW()
|
||||
for i in range(n - 1):
|
||||
# turn strip
|
||||
fcw(i)
|
||||
# put bottom back on the bottom
|
||||
FCW()
|
||||
UCW()
|
||||
FCCW()
|
||||
# record
|
||||
perm()
|
||||
# restore
|
||||
# bring up bottom
|
||||
FCW()
|
||||
UCCW()
|
||||
FCCW()
|
||||
# turn strip
|
||||
fccw(i)
|
||||
# put bottom back on the bottom
|
||||
FCW()
|
||||
UCW()
|
||||
FCCW()
|
||||
assert perm(1) == I
|
||||
|
||||
return g
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
from sympy.core import Basic, Integer
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class GrayCode(Basic):
|
||||
"""
|
||||
A Gray code is essentially a Hamiltonian walk on
|
||||
a n-dimensional cube with edge length of one.
|
||||
The vertices of the cube are represented by vectors
|
||||
whose values are binary. The Hamilton walk visits
|
||||
each vertex exactly once. The Gray code for a 3d
|
||||
cube is ['000','100','110','010','011','111','101',
|
||||
'001'].
|
||||
|
||||
A Gray code solves the problem of sequentially
|
||||
generating all possible subsets of n objects in such
|
||||
a way that each subset is obtained from the previous
|
||||
one by either deleting or adding a single object.
|
||||
In the above example, 1 indicates that the object is
|
||||
present, and 0 indicates that its absent.
|
||||
|
||||
Gray codes have applications in statistics as well when
|
||||
we want to compute various statistics related to subsets
|
||||
in an efficient manner.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> list(a.generate_gray())
|
||||
['000', '001', '011', '010', '110', '111', '101', '100']
|
||||
>>> a = GrayCode(4)
|
||||
>>> list(a.generate_gray())
|
||||
['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \
|
||||
'1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000']
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Nijenhuis,A. and Wilf,H.S.(1978).
|
||||
Combinatorial Algorithms. Academic Press.
|
||||
.. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4
|
||||
Addison Wesley
|
||||
|
||||
|
||||
"""
|
||||
|
||||
_skip = False
|
||||
_current = 0
|
||||
_rank = None
|
||||
|
||||
def __new__(cls, n, *args, **kw_args):
|
||||
"""
|
||||
Default constructor.
|
||||
|
||||
It takes a single argument ``n`` which gives the dimension of the Gray
|
||||
code. The starting Gray code string (``start``) or the starting ``rank``
|
||||
may also be given; the default is to start at rank = 0 ('0...0').
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> a
|
||||
GrayCode(3)
|
||||
>>> a.n
|
||||
3
|
||||
|
||||
>>> a = GrayCode(3, start='100')
|
||||
>>> a.current
|
||||
'100'
|
||||
|
||||
>>> a = GrayCode(4, rank=4)
|
||||
>>> a.current
|
||||
'0110'
|
||||
>>> a.rank
|
||||
4
|
||||
|
||||
"""
|
||||
if n < 1 or int(n) != n:
|
||||
raise ValueError(
|
||||
'Gray code dimension must be a positive integer, not %i' % n)
|
||||
n = Integer(n)
|
||||
args = (n,) + args
|
||||
obj = Basic.__new__(cls, *args)
|
||||
if 'start' in kw_args:
|
||||
obj._current = kw_args["start"]
|
||||
if len(obj._current) > n:
|
||||
raise ValueError('Gray code start has length %i but '
|
||||
'should not be greater than %i' % (len(obj._current), n))
|
||||
elif 'rank' in kw_args:
|
||||
if int(kw_args["rank"]) != kw_args["rank"]:
|
||||
raise ValueError('Gray code rank must be a positive integer, '
|
||||
'not %i' % kw_args["rank"])
|
||||
obj._rank = int(kw_args["rank"]) % obj.selections
|
||||
obj._current = obj.unrank(n, obj._rank)
|
||||
return obj
|
||||
|
||||
def next(self, delta=1):
|
||||
"""
|
||||
Returns the Gray code a distance ``delta`` (default = 1) from the
|
||||
current value in canonical order.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3, start='110')
|
||||
>>> a.next().current
|
||||
'111'
|
||||
>>> a.next(-1).current
|
||||
'010'
|
||||
"""
|
||||
return GrayCode(self.n, rank=(self.rank + delta) % self.selections)
|
||||
|
||||
@property
|
||||
def selections(self):
|
||||
"""
|
||||
Returns the number of bit vectors in the Gray code.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> a.selections
|
||||
8
|
||||
"""
|
||||
return 2**self.n
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
"""
|
||||
Returns the dimension of the Gray code.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(5)
|
||||
>>> a.n
|
||||
5
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
def generate_gray(self, **hints):
|
||||
"""
|
||||
Generates the sequence of bit vectors of a Gray Code.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> list(a.generate_gray())
|
||||
['000', '001', '011', '010', '110', '111', '101', '100']
|
||||
>>> list(a.generate_gray(start='011'))
|
||||
['011', '010', '110', '111', '101', '100']
|
||||
>>> list(a.generate_gray(rank=4))
|
||||
['110', '111', '101', '100']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
skip
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Knuth, D. (2011). The Art of Computer Programming,
|
||||
Vol 4, Addison Wesley
|
||||
|
||||
"""
|
||||
bits = self.n
|
||||
start = None
|
||||
if "start" in hints:
|
||||
start = hints["start"]
|
||||
elif "rank" in hints:
|
||||
start = GrayCode.unrank(self.n, hints["rank"])
|
||||
if start is not None:
|
||||
self._current = start
|
||||
current = self.current
|
||||
graycode_bin = gray_to_bin(current)
|
||||
if len(graycode_bin) > self.n:
|
||||
raise ValueError('Gray code start has length %i but should '
|
||||
'not be greater than %i' % (len(graycode_bin), bits))
|
||||
self._current = int(current, 2)
|
||||
graycode_int = int(''.join(graycode_bin), 2)
|
||||
for i in range(graycode_int, 1 << bits):
|
||||
if self._skip:
|
||||
self._skip = False
|
||||
else:
|
||||
yield self.current
|
||||
bbtc = (i ^ (i + 1))
|
||||
gbtc = (bbtc ^ (bbtc >> 1))
|
||||
self._current = (self._current ^ gbtc)
|
||||
self._current = 0
|
||||
|
||||
def skip(self):
|
||||
"""
|
||||
Skips the bit generation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> for i in a.generate_gray():
|
||||
... if i == '010':
|
||||
... a.skip()
|
||||
... print(i)
|
||||
...
|
||||
000
|
||||
001
|
||||
011
|
||||
010
|
||||
111
|
||||
101
|
||||
100
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
generate_gray
|
||||
"""
|
||||
self._skip = True
|
||||
|
||||
@property
|
||||
def rank(self):
|
||||
"""
|
||||
Ranks the Gray code.
|
||||
|
||||
A ranking algorithm determines the position (or rank)
|
||||
of a combinatorial object among all the objects w.r.t.
|
||||
a given order. For example, the 4 bit binary reflected
|
||||
Gray code (BRGC) '0101' has a rank of 6 as it appears in
|
||||
the 6th position in the canonical ordering of the family
|
||||
of 4 bit Gray codes.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> a = GrayCode(3)
|
||||
>>> list(a.generate_gray())
|
||||
['000', '001', '011', '010', '110', '111', '101', '100']
|
||||
>>> GrayCode(3, start='100').rank
|
||||
7
|
||||
>>> GrayCode(3, rank=7).current
|
||||
'100'
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
unrank
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html
|
||||
|
||||
"""
|
||||
if self._rank is None:
|
||||
self._rank = int(gray_to_bin(self.current), 2)
|
||||
return self._rank
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
"""
|
||||
Returns the currently referenced Gray code as a bit string.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> GrayCode(3, start='100').current
|
||||
'100'
|
||||
"""
|
||||
rv = self._current or '0'
|
||||
if not isinstance(rv, str):
|
||||
rv = bin(rv)[2:]
|
||||
return rv.rjust(self.n, '0')
|
||||
|
||||
@classmethod
|
||||
def unrank(self, n, rank):
|
||||
"""
|
||||
Unranks an n-bit sized Gray code of rank k. This method exists
|
||||
so that a derivative GrayCode class can define its own code of
|
||||
a given rank.
|
||||
|
||||
The string here is generated in reverse order to allow for tail-call
|
||||
optimization.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import GrayCode
|
||||
>>> GrayCode(5, rank=3).current
|
||||
'00010'
|
||||
>>> GrayCode.unrank(5, 3)
|
||||
'00010'
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
rank
|
||||
"""
|
||||
def _unrank(k, n):
|
||||
if n == 1:
|
||||
return str(k % 2)
|
||||
m = 2**(n - 1)
|
||||
if k < m:
|
||||
return '0' + _unrank(k, n - 1)
|
||||
return '1' + _unrank(m - (k % m) - 1, n - 1)
|
||||
return _unrank(rank, n)
|
||||
|
||||
|
||||
def random_bitstring(n):
|
||||
"""
|
||||
Generates a random bitlist of length n.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.graycode import random_bitstring
|
||||
>>> random_bitstring(3) # doctest: +SKIP
|
||||
100
|
||||
"""
|
||||
return ''.join([random.choice('01') for i in range(n)])
|
||||
|
||||
|
||||
def gray_to_bin(bin_list):
|
||||
"""
|
||||
Convert from Gray coding to binary coding.
|
||||
|
||||
We assume big endian encoding.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.graycode import gray_to_bin
|
||||
>>> gray_to_bin('100')
|
||||
'111'
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
bin_to_gray
|
||||
"""
|
||||
b = [bin_list[0]]
|
||||
for i in range(1, len(bin_list)):
|
||||
b += str(int(b[i - 1] != bin_list[i]))
|
||||
return ''.join(b)
|
||||
|
||||
|
||||
def bin_to_gray(bin_list):
|
||||
"""
|
||||
Convert from binary coding to gray coding.
|
||||
|
||||
We assume big endian encoding.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.graycode import bin_to_gray
|
||||
>>> bin_to_gray('111')
|
||||
'100'
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
gray_to_bin
|
||||
"""
|
||||
b = [bin_list[0]]
|
||||
for i in range(1, len(bin_list)):
|
||||
b += str(int(bin_list[i]) ^ int(bin_list[i - 1]))
|
||||
return ''.join(b)
|
||||
|
||||
|
||||
def get_subset_from_bitstring(super_set, bitstring):
|
||||
"""
|
||||
Gets the subset defined by the bitstring.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.graycode import get_subset_from_bitstring
|
||||
>>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011')
|
||||
['c', 'd']
|
||||
>>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100')
|
||||
['c', 'a']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
graycode_subsets
|
||||
"""
|
||||
if len(super_set) != len(bitstring):
|
||||
raise ValueError("The sizes of the lists are not equal")
|
||||
return [super_set[i] for i, j in enumerate(bitstring)
|
||||
if bitstring[i] == '1']
|
||||
|
||||
|
||||
def graycode_subsets(gray_code_set):
|
||||
"""
|
||||
Generates the subsets as enumerated by a Gray code.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.graycode import graycode_subsets
|
||||
>>> list(graycode_subsets(['a', 'b', 'c']))
|
||||
[[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \
|
||||
['a', 'c'], ['a']]
|
||||
>>> list(graycode_subsets(['a', 'b', 'c', 'c']))
|
||||
[[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \
|
||||
['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \
|
||||
['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
get_subset_from_bitstring
|
||||
"""
|
||||
for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()):
|
||||
yield get_subset_from_bitstring(gray_code_set, bitstring)
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.utilities.iterables import uniq
|
||||
|
||||
_af_new = Permutation._af_new
|
||||
|
||||
|
||||
def DirectProduct(*groups):
|
||||
"""
|
||||
Returns the direct product of several groups as a permutation group.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This is implemented much like the __mul__ procedure for taking the direct
|
||||
product of two permutation groups, but the idea of shifting the
|
||||
generators is realized in the case of an arbitrary number of groups.
|
||||
A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster
|
||||
than a call to G1*G2*...*Gn (and thus the need for this algorithm).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_constructs import DirectProduct
|
||||
>>> from sympy.combinatorics.named_groups import CyclicGroup
|
||||
>>> C = CyclicGroup(4)
|
||||
>>> G = DirectProduct(C, C, C)
|
||||
>>> G.order()
|
||||
64
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.__mul__
|
||||
|
||||
"""
|
||||
degrees = []
|
||||
gens_count = []
|
||||
total_degree = 0
|
||||
total_gens = 0
|
||||
for group in groups:
|
||||
current_deg = group.degree
|
||||
current_num_gens = len(group.generators)
|
||||
degrees.append(current_deg)
|
||||
total_degree += current_deg
|
||||
gens_count.append(current_num_gens)
|
||||
total_gens += current_num_gens
|
||||
array_gens = []
|
||||
for i in range(total_gens):
|
||||
array_gens.append(list(range(total_degree)))
|
||||
current_gen = 0
|
||||
current_deg = 0
|
||||
for i in range(len(gens_count)):
|
||||
for j in range(current_gen, current_gen + gens_count[i]):
|
||||
gen = ((groups[i].generators)[j - current_gen]).array_form
|
||||
array_gens[j][current_deg:current_deg + degrees[i]] = \
|
||||
[x + current_deg for x in gen]
|
||||
current_gen += gens_count[i]
|
||||
current_deg += degrees[i]
|
||||
perm_gens = list(uniq([_af_new(list(a)) for a in array_gens]))
|
||||
return PermutationGroup(perm_gens, dups=False)
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
from itertools import chain, combinations
|
||||
|
||||
from sympy.external.gmpy import gcd
|
||||
from sympy.ntheory.factor_ import factorint
|
||||
from sympy.utilities.misc import as_int
|
||||
|
||||
|
||||
def _is_nilpotent_number(factors: dict) -> bool:
|
||||
""" Check whether `n` is a nilpotent number.
|
||||
Note that ``factors`` is a prime factorization of `n`.
|
||||
|
||||
This is a low-level helper for ``is_nilpotent_number``, for internal use.
|
||||
"""
|
||||
for p in factors.keys():
|
||||
for q, e in factors.items():
|
||||
# We want to calculate
|
||||
# any(pow(q, k, p) == 1 for k in range(1, e + 1))
|
||||
m = 1
|
||||
for _ in range(e):
|
||||
m = m*q % p
|
||||
if m == 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_nilpotent_number(n) -> bool:
|
||||
"""
|
||||
Check whether `n` is a nilpotent number. A number `n` is said to be
|
||||
nilpotent if and only if every finite group of order `n` is nilpotent.
|
||||
For more information see [1]_.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_numbers import is_nilpotent_number
|
||||
>>> from sympy import randprime
|
||||
>>> is_nilpotent_number(21)
|
||||
False
|
||||
>>> is_nilpotent_number(randprime(1, 30)**12)
|
||||
True
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
|
||||
The American Mathematical Monthly, 107(7), 631-634.
|
||||
.. [2] https://oeis.org/A056867
|
||||
|
||||
"""
|
||||
n = as_int(n)
|
||||
if n <= 0:
|
||||
raise ValueError("n must be a positive integer, not %i" % n)
|
||||
return _is_nilpotent_number(factorint(n))
|
||||
|
||||
|
||||
def is_abelian_number(n) -> bool:
|
||||
"""
|
||||
Check whether `n` is an abelian number. A number `n` is said to be abelian
|
||||
if and only if every finite group of order `n` is abelian. For more
|
||||
information see [1]_.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_numbers import is_abelian_number
|
||||
>>> from sympy import randprime
|
||||
>>> is_abelian_number(4)
|
||||
True
|
||||
>>> is_abelian_number(randprime(1, 2000)**2)
|
||||
True
|
||||
>>> is_abelian_number(60)
|
||||
False
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
|
||||
The American Mathematical Monthly, 107(7), 631-634.
|
||||
.. [2] https://oeis.org/A051532
|
||||
|
||||
"""
|
||||
n = as_int(n)
|
||||
if n <= 0:
|
||||
raise ValueError("n must be a positive integer, not %i" % n)
|
||||
factors = factorint(n)
|
||||
return all(e < 3 for e in factors.values()) and _is_nilpotent_number(factors)
|
||||
|
||||
|
||||
def is_cyclic_number(n) -> bool:
|
||||
"""
|
||||
Check whether `n` is a cyclic number. A number `n` is said to be cyclic
|
||||
if and only if every finite group of order `n` is cyclic. For more
|
||||
information see [1]_.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_numbers import is_cyclic_number
|
||||
>>> from sympy import randprime
|
||||
>>> is_cyclic_number(15)
|
||||
True
|
||||
>>> is_cyclic_number(randprime(1, 2000)**2)
|
||||
False
|
||||
>>> is_cyclic_number(4)
|
||||
False
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers,
|
||||
The American Mathematical Monthly, 107(7), 631-634.
|
||||
.. [2] https://oeis.org/A003277
|
||||
|
||||
"""
|
||||
n = as_int(n)
|
||||
if n <= 0:
|
||||
raise ValueError("n must be a positive integer, not %i" % n)
|
||||
factors = factorint(n)
|
||||
return all(e == 1 for e in factors.values()) and _is_nilpotent_number(factors)
|
||||
|
||||
|
||||
def _holder_formula(prime_factors):
|
||||
r""" Number of groups of order `n`.
|
||||
where `n` is squarefree and its prime factors are ``prime_factors``.
|
||||
i.e., ``n == math.prod(prime_factors)``
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
When `n` is squarefree, the number of groups of order `n` is expressed by
|
||||
|
||||
.. math ::
|
||||
\sum_{d \mid n} \prod_p \frac{p^{c(p, d)} - 1}{p - 1}
|
||||
|
||||
where `n=de`, `p` is the prime factor of `e`,
|
||||
and `c(p, d)` is the number of prime factors `q` of `d` such that `q \equiv 1 \pmod{p}` [2]_.
|
||||
|
||||
The formula is elegant, but can be improved when implemented as an algorithm.
|
||||
Since `n` is assumed to be squarefree, the divisor `d` of `n` can be identified with the power set of prime factors.
|
||||
We let `N` be the set of prime factors of `n`.
|
||||
`F = \{p \in N : \forall q \in N, q \not\equiv 1 \pmod{p} \}, M = N \setminus F`, we have the following.
|
||||
|
||||
.. math ::
|
||||
\sum_{d \in 2^{M}} \prod_{p \in M \setminus d} \frac{p^{c(p, F \cup d)} - 1}{p - 1}
|
||||
|
||||
Practically, many prime factors are expected to be members of `F`, thus reducing computation time.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
prime_factors : set
|
||||
The set of prime factors of ``n``. where `n` is squarefree.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
int : Number of groups of order ``n``
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_numbers import _holder_formula
|
||||
>>> _holder_formula({2}) # n = 2
|
||||
1
|
||||
>>> _holder_formula({2, 3}) # n = 2*3 = 6
|
||||
2
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
groups_count
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Otto Holder, Die Gruppen der Ordnungen p^3, pq^2, pqr, p^4,
|
||||
Math. Ann. 43 pp. 301-412 (1893).
|
||||
http://dx.doi.org/10.1007/BF01443651
|
||||
.. [2] John H. Conway, Heiko Dietrich and E.A. O'Brien,
|
||||
Counting groups: gnus, moas and other exotica
|
||||
The Mathematical Intelligencer 30, 6-15 (2008)
|
||||
https://doi.org/10.1007/BF02985731
|
||||
|
||||
"""
|
||||
F = {p for p in prime_factors if all(q % p != 1 for q in prime_factors)}
|
||||
M = prime_factors - F
|
||||
|
||||
s = 0
|
||||
powerset = chain.from_iterable(combinations(M, r) for r in range(len(M)+1))
|
||||
for ps in powerset:
|
||||
ps = set(ps)
|
||||
prod = 1
|
||||
for p in M - ps:
|
||||
c = len([q for q in F | ps if q % p == 1])
|
||||
prod *= (p**c - 1) // (p - 1)
|
||||
if not prod:
|
||||
break
|
||||
s += prod
|
||||
return s
|
||||
|
||||
|
||||
def groups_count(n):
|
||||
r""" Number of groups of order `n`.
|
||||
In [1]_, ``gnu(n)`` is given, so we follow this notation here as well.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Integer
|
||||
``n`` is a positive integer
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
int : ``gnu(n)``
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError
|
||||
Number of groups of order ``n`` is unknown or not implemented.
|
||||
For example, gnu(`2^{11}`) is not yet known.
|
||||
On the other hand, gnu(99) is known to be 2,
|
||||
but this has not yet been implemented in this function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.group_numbers import groups_count
|
||||
>>> groups_count(3) # There is only one cyclic group of order 3
|
||||
1
|
||||
>>> # There are two groups of order 10: the cyclic group and the dihedral group
|
||||
>>> groups_count(10)
|
||||
2
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
is_cyclic_number
|
||||
`n` is cyclic iff gnu(n) = 1
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] John H. Conway, Heiko Dietrich and E.A. O'Brien,
|
||||
Counting groups: gnus, moas and other exotica
|
||||
The Mathematical Intelligencer 30, 6-15 (2008)
|
||||
https://doi.org/10.1007/BF02985731
|
||||
.. [2] https://oeis.org/A000001
|
||||
|
||||
"""
|
||||
n = as_int(n)
|
||||
if n <= 0:
|
||||
raise ValueError("n must be a positive integer, not %i" % n)
|
||||
factors = factorint(n)
|
||||
if len(factors) == 1:
|
||||
(p, e) = list(factors.items())[0]
|
||||
if p == 2:
|
||||
A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289]
|
||||
if e < len(A000679):
|
||||
return A000679[e]
|
||||
if p == 3:
|
||||
A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645]
|
||||
if e < len(A090091):
|
||||
return A090091[e]
|
||||
if e <= 2: # gnu(p) = 1, gnu(p**2) = 2
|
||||
return e
|
||||
if e == 3: # gnu(p**3) = 5
|
||||
return 5
|
||||
if e == 4: # if p is an odd prime, gnu(p**4) = 15
|
||||
return 15
|
||||
if e == 5: # if p >= 5, gnu(p**5) is expressed by the following equation
|
||||
return 61 + 2*p + 2*gcd(p-1, 3) + gcd(p-1, 4)
|
||||
if e == 6: # if p >= 6, gnu(p**6) is expressed by the following equation
|
||||
return 3*p**2 + 39*p + 344 +\
|
||||
24*gcd(p-1, 3) + 11*gcd(p-1, 4) + 2*gcd(p-1, 5)
|
||||
if e == 7: # if p >= 7, gnu(p**7) is expressed by the following equation
|
||||
if p == 5:
|
||||
return 34297
|
||||
return 3*p**5 + 12*p**4 + 44*p**3 + 170*p**2 + 707*p + 2455 +\
|
||||
(4*p**2 + 44*p + 291)*gcd(p-1, 3) + (p**2 + 19*p + 135)*gcd(p-1, 4) + \
|
||||
(3*p + 31)*gcd(p-1, 5) + 4*gcd(p-1, 7) + 5*gcd(p-1, 8) + gcd(p-1, 9)
|
||||
if any(e > 1 for e in factors.values()): # n is not squarefree
|
||||
# some known values for small n that have more than 1 factor and are not square free (https://oeis.org/A000001)
|
||||
small = {12: 5, 18: 5, 20: 5, 24: 15, 28: 4, 36: 14, 40: 14, 44: 4, 45: 2, 48: 52,
|
||||
50: 5, 52: 5, 54: 15, 56: 13, 60: 13, 63: 4, 68: 5, 72: 50, 75: 3, 76: 4,
|
||||
80: 52, 84: 15, 88: 12, 90: 10, 92: 4}
|
||||
if n in small:
|
||||
return small[n]
|
||||
raise ValueError("Number of groups of order n is unknown or not implemented")
|
||||
if len(factors) == 2: # n is squarefree semiprime
|
||||
p, q = sorted(factors.keys())
|
||||
return 2 if q % p == 1 else 1
|
||||
return _holder_formula(set(factors.keys()))
|
||||
+549
@@ -0,0 +1,549 @@
|
||||
import itertools
|
||||
from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation
|
||||
from sympy.combinatorics.free_groups import FreeGroup
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.core.intfunc import igcd
|
||||
from sympy.functions.combinatorial.numbers import totient
|
||||
from sympy.core.singleton import S
|
||||
|
||||
class GroupHomomorphism:
|
||||
'''
|
||||
A class representing group homomorphisms. Instantiate using `homomorphism()`.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, domain, codomain, images):
|
||||
self.domain = domain
|
||||
self.codomain = codomain
|
||||
self.images = images
|
||||
self._inverses = None
|
||||
self._kernel = None
|
||||
self._image = None
|
||||
|
||||
def _invs(self):
|
||||
'''
|
||||
Return a dictionary with `{gen: inverse}` where `gen` is a rewriting
|
||||
generator of `codomain` (e.g. strong generator for permutation groups)
|
||||
and `inverse` is an element of its preimage
|
||||
|
||||
'''
|
||||
image = self.image()
|
||||
inverses = {}
|
||||
for k in list(self.images.keys()):
|
||||
v = self.images[k]
|
||||
if not (v in inverses
|
||||
or v.is_identity):
|
||||
inverses[v] = k
|
||||
if isinstance(self.codomain, PermutationGroup):
|
||||
gens = image.strong_gens
|
||||
else:
|
||||
gens = image.generators
|
||||
for g in gens:
|
||||
if g in inverses or g.is_identity:
|
||||
continue
|
||||
w = self.domain.identity
|
||||
if isinstance(self.codomain, PermutationGroup):
|
||||
parts = image._strong_gens_slp[g][::-1]
|
||||
else:
|
||||
parts = g
|
||||
for s in parts:
|
||||
if s in inverses:
|
||||
w = w*inverses[s]
|
||||
else:
|
||||
w = w*inverses[s**-1]**-1
|
||||
inverses[g] = w
|
||||
|
||||
return inverses
|
||||
|
||||
def invert(self, g):
|
||||
'''
|
||||
Return an element of the preimage of ``g`` or of each element
|
||||
of ``g`` if ``g`` is a list.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
If the codomain is an FpGroup, the inverse for equal
|
||||
elements might not always be the same unless the FpGroup's
|
||||
rewriting system is confluent. However, making a system
|
||||
confluent can be time-consuming. If it's important, try
|
||||
`self.codomain.make_confluent()` first.
|
||||
|
||||
'''
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.free_groups import FreeGroupElement
|
||||
if isinstance(g, (Permutation, FreeGroupElement)):
|
||||
if isinstance(self.codomain, FpGroup):
|
||||
g = self.codomain.reduce(g)
|
||||
if self._inverses is None:
|
||||
self._inverses = self._invs()
|
||||
image = self.image()
|
||||
w = self.domain.identity
|
||||
if isinstance(self.codomain, PermutationGroup):
|
||||
gens = image.generator_product(g)[::-1]
|
||||
else:
|
||||
gens = g
|
||||
# the following can't be "for s in gens:"
|
||||
# because that would be equivalent to
|
||||
# "for s in gens.array_form:" when g is
|
||||
# a FreeGroupElement. On the other hand,
|
||||
# when you call gens by index, the generator
|
||||
# (or inverse) at position i is returned.
|
||||
for i in range(len(gens)):
|
||||
s = gens[i]
|
||||
if s.is_identity:
|
||||
continue
|
||||
if s in self._inverses:
|
||||
w = w*self._inverses[s]
|
||||
else:
|
||||
w = w*self._inverses[s**-1]**-1
|
||||
return w
|
||||
elif isinstance(g, list):
|
||||
return [self.invert(e) for e in g]
|
||||
|
||||
def kernel(self):
|
||||
'''
|
||||
Compute the kernel of `self`.
|
||||
|
||||
'''
|
||||
if self._kernel is None:
|
||||
self._kernel = self._compute_kernel()
|
||||
return self._kernel
|
||||
|
||||
def _compute_kernel(self):
|
||||
G = self.domain
|
||||
G_order = G.order()
|
||||
if G_order is S.Infinity:
|
||||
raise NotImplementedError(
|
||||
"Kernel computation is not implemented for infinite groups")
|
||||
gens = []
|
||||
if isinstance(G, PermutationGroup):
|
||||
K = PermutationGroup(G.identity)
|
||||
else:
|
||||
K = FpSubgroup(G, gens, normal=True)
|
||||
i = self.image().order()
|
||||
while K.order()*i != G_order:
|
||||
r = G.random()
|
||||
k = r*self.invert(self(r))**-1
|
||||
if k not in K:
|
||||
gens.append(k)
|
||||
if isinstance(G, PermutationGroup):
|
||||
K = PermutationGroup(gens)
|
||||
else:
|
||||
K = FpSubgroup(G, gens, normal=True)
|
||||
return K
|
||||
|
||||
def image(self):
|
||||
'''
|
||||
Compute the image of `self`.
|
||||
|
||||
'''
|
||||
if self._image is None:
|
||||
values = list(set(self.images.values()))
|
||||
if isinstance(self.codomain, PermutationGroup):
|
||||
self._image = self.codomain.subgroup(values)
|
||||
else:
|
||||
self._image = FpSubgroup(self.codomain, values)
|
||||
return self._image
|
||||
|
||||
def _apply(self, elem):
|
||||
'''
|
||||
Apply `self` to `elem`.
|
||||
|
||||
'''
|
||||
if elem not in self.domain:
|
||||
if isinstance(elem, (list, tuple)):
|
||||
return [self._apply(e) for e in elem]
|
||||
raise ValueError("The supplied element does not belong to the domain")
|
||||
if elem.is_identity:
|
||||
return self.codomain.identity
|
||||
else:
|
||||
images = self.images
|
||||
value = self.codomain.identity
|
||||
if isinstance(self.domain, PermutationGroup):
|
||||
gens = self.domain.generator_product(elem, original=True)
|
||||
for g in gens:
|
||||
if g in self.images:
|
||||
value = images[g]*value
|
||||
else:
|
||||
value = images[g**-1]**-1*value
|
||||
else:
|
||||
i = 0
|
||||
for _, p in elem.array_form:
|
||||
if p < 0:
|
||||
g = elem[i]**-1
|
||||
else:
|
||||
g = elem[i]
|
||||
value = value*images[g]**p
|
||||
i += abs(p)
|
||||
return value
|
||||
|
||||
def __call__(self, elem):
|
||||
return self._apply(elem)
|
||||
|
||||
def is_injective(self):
|
||||
'''
|
||||
Check if the homomorphism is injective
|
||||
|
||||
'''
|
||||
return self.kernel().order() == 1
|
||||
|
||||
def is_surjective(self):
|
||||
'''
|
||||
Check if the homomorphism is surjective
|
||||
|
||||
'''
|
||||
im = self.image().order()
|
||||
oth = self.codomain.order()
|
||||
if im is S.Infinity and oth is S.Infinity:
|
||||
return None
|
||||
else:
|
||||
return im == oth
|
||||
|
||||
def is_isomorphism(self):
|
||||
'''
|
||||
Check if `self` is an isomorphism.
|
||||
|
||||
'''
|
||||
return self.is_injective() and self.is_surjective()
|
||||
|
||||
def is_trivial(self):
|
||||
'''
|
||||
Check is `self` is a trivial homomorphism, i.e. all elements
|
||||
are mapped to the identity.
|
||||
|
||||
'''
|
||||
return self.image().order() == 1
|
||||
|
||||
def compose(self, other):
|
||||
'''
|
||||
Return the composition of `self` and `other`, i.e.
|
||||
the homomorphism phi such that for all g in the domain
|
||||
of `other`, phi(g) = self(other(g))
|
||||
|
||||
'''
|
||||
if not other.image().is_subgroup(self.domain):
|
||||
raise ValueError("The image of `other` must be a subgroup of "
|
||||
"the domain of `self`")
|
||||
images = {g: self(other(g)) for g in other.images}
|
||||
return GroupHomomorphism(other.domain, self.codomain, images)
|
||||
|
||||
def restrict_to(self, H):
|
||||
'''
|
||||
Return the restriction of the homomorphism to the subgroup `H`
|
||||
of the domain.
|
||||
|
||||
'''
|
||||
if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain):
|
||||
raise ValueError("Given H is not a subgroup of the domain")
|
||||
domain = H
|
||||
images = {g: self(g) for g in H.generators}
|
||||
return GroupHomomorphism(domain, self.codomain, images)
|
||||
|
||||
def invert_subgroup(self, H):
|
||||
'''
|
||||
Return the subgroup of the domain that is the inverse image
|
||||
of the subgroup ``H`` of the homomorphism image
|
||||
|
||||
'''
|
||||
if not H.is_subgroup(self.image()):
|
||||
raise ValueError("Given H is not a subgroup of the image")
|
||||
gens = []
|
||||
P = PermutationGroup(self.image().identity)
|
||||
for h in H.generators:
|
||||
h_i = self.invert(h)
|
||||
if h_i not in P:
|
||||
gens.append(h_i)
|
||||
P = PermutationGroup(gens)
|
||||
for k in self.kernel().generators:
|
||||
if k*h_i not in P:
|
||||
gens.append(k*h_i)
|
||||
P = PermutationGroup(gens)
|
||||
return P
|
||||
|
||||
def homomorphism(domain, codomain, gens, images=(), check=True):
|
||||
'''
|
||||
Create (if possible) a group homomorphism from the group ``domain``
|
||||
to the group ``codomain`` defined by the images of the domain's
|
||||
generators ``gens``. ``gens`` and ``images`` can be either lists or tuples
|
||||
of equal sizes. If ``gens`` is a proper subset of the group's generators,
|
||||
the unspecified generators will be mapped to the identity. If the
|
||||
images are not specified, a trivial homomorphism will be created.
|
||||
|
||||
If the given images of the generators do not define a homomorphism,
|
||||
an exception is raised.
|
||||
|
||||
If ``check`` is ``False``, do not check whether the given images actually
|
||||
define a homomorphism.
|
||||
|
||||
'''
|
||||
if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
|
||||
raise TypeError("The domain must be a group")
|
||||
if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
|
||||
raise TypeError("The codomain must be a group")
|
||||
|
||||
generators = domain.generators
|
||||
if not all(g in generators for g in gens):
|
||||
raise ValueError("The supplied generators must be a subset of the domain's generators")
|
||||
if not all(g in codomain for g in images):
|
||||
raise ValueError("The images must be elements of the codomain")
|
||||
|
||||
if images and len(images) != len(gens):
|
||||
raise ValueError("The number of images must be equal to the number of generators")
|
||||
|
||||
gens = list(gens)
|
||||
images = list(images)
|
||||
|
||||
images.extend([codomain.identity]*(len(generators)-len(images)))
|
||||
gens.extend([g for g in generators if g not in gens])
|
||||
images = dict(zip(gens,images))
|
||||
|
||||
if check and not _check_homomorphism(domain, codomain, images):
|
||||
raise ValueError("The given images do not define a homomorphism")
|
||||
return GroupHomomorphism(domain, codomain, images)
|
||||
|
||||
def _check_homomorphism(domain, codomain, images):
|
||||
"""
|
||||
Check that a given mapping of generators to images defines a homomorphism.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
domain : PermutationGroup, FpGroup, FreeGroup
|
||||
codomain : PermutationGroup, FpGroup, FreeGroup
|
||||
images : dict
|
||||
The set of keys must be equal to domain.generators.
|
||||
The values must be elements of the codomain.
|
||||
|
||||
"""
|
||||
pres = domain if hasattr(domain, 'relators') else domain.presentation()
|
||||
rels = pres.relators
|
||||
gens = pres.generators
|
||||
symbols = [g.ext_rep[0] for g in gens]
|
||||
symbols_to_domain_generators = dict(zip(symbols, domain.generators))
|
||||
identity = codomain.identity
|
||||
|
||||
def _image(r):
|
||||
w = identity
|
||||
for symbol, power in r.array_form:
|
||||
g = symbols_to_domain_generators[symbol]
|
||||
w *= images[g]**power
|
||||
return w
|
||||
|
||||
for r in rels:
|
||||
if isinstance(codomain, FpGroup):
|
||||
s = codomain.equals(_image(r), identity)
|
||||
if s is None:
|
||||
# only try to make the rewriting system
|
||||
# confluent when it can't determine the
|
||||
# truth of equality otherwise
|
||||
success = codomain.make_confluent()
|
||||
s = codomain.equals(_image(r), identity)
|
||||
if s is None and not success:
|
||||
raise RuntimeError("Can't determine if the images "
|
||||
"define a homomorphism. Try increasing "
|
||||
"the maximum number of rewriting rules "
|
||||
"(group._rewriting_system.set_max(new_value); "
|
||||
"the current value is stored in group._rewriting"
|
||||
"_system.maxeqns)")
|
||||
else:
|
||||
s = _image(r).is_identity
|
||||
if not s:
|
||||
return False
|
||||
return True
|
||||
|
||||
def orbit_homomorphism(group, omega):
|
||||
'''
|
||||
Return the homomorphism induced by the action of the permutation
|
||||
group ``group`` on the set ``omega`` that is closed under the action.
|
||||
|
||||
'''
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
codomain = SymmetricGroup(len(omega))
|
||||
identity = codomain.identity
|
||||
omega = list(omega)
|
||||
images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators}
|
||||
group._schreier_sims(base=omega)
|
||||
H = GroupHomomorphism(group, codomain, images)
|
||||
if len(group.basic_stabilizers) > len(omega):
|
||||
H._kernel = group.basic_stabilizers[len(omega)]
|
||||
else:
|
||||
H._kernel = PermutationGroup([group.identity])
|
||||
return H
|
||||
|
||||
def block_homomorphism(group, blocks):
|
||||
'''
|
||||
Return the homomorphism induced by the action of the permutation
|
||||
group ``group`` on the block system ``blocks``. The latter should be
|
||||
of the same form as returned by the ``minimal_block`` method for
|
||||
permutation groups, namely a list of length ``group.degree`` where
|
||||
the i-th entry is a representative of the block i belongs to.
|
||||
|
||||
'''
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
|
||||
n = len(blocks)
|
||||
|
||||
# number the blocks; m is the total number,
|
||||
# b is such that b[i] is the number of the block i belongs to,
|
||||
# p is the list of length m such that p[i] is the representative
|
||||
# of the i-th block
|
||||
m = 0
|
||||
p = []
|
||||
b = [None]*n
|
||||
for i in range(n):
|
||||
if blocks[i] == i:
|
||||
p.append(i)
|
||||
b[i] = m
|
||||
m += 1
|
||||
for i in range(n):
|
||||
b[i] = b[blocks[i]]
|
||||
|
||||
codomain = SymmetricGroup(m)
|
||||
# the list corresponding to the identity permutation in codomain
|
||||
identity = range(m)
|
||||
images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators}
|
||||
H = GroupHomomorphism(group, codomain, images)
|
||||
return H
|
||||
|
||||
def group_isomorphism(G, H, isomorphism=True):
|
||||
'''
|
||||
Compute an isomorphism between 2 given groups.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
G : A finite ``FpGroup`` or a ``PermutationGroup``.
|
||||
First group.
|
||||
|
||||
H : A finite ``FpGroup`` or a ``PermutationGroup``
|
||||
Second group.
|
||||
|
||||
isomorphism : bool
|
||||
This is used to avoid the computation of homomorphism
|
||||
when the user only wants to check if there exists
|
||||
an isomorphism between the groups.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
If isomorphism = False -- Returns a boolean.
|
||||
If isomorphism = True -- Returns a boolean and an isomorphism between `G` and `H`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import free_group, Permutation
|
||||
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
>>> from sympy.combinatorics.fp_groups import FpGroup
|
||||
>>> from sympy.combinatorics.homomorphisms import group_isomorphism
|
||||
>>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup
|
||||
|
||||
>>> D = DihedralGroup(8)
|
||||
>>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
|
||||
>>> P = PermutationGroup(p)
|
||||
>>> group_isomorphism(D, P)
|
||||
(False, None)
|
||||
|
||||
>>> F, a, b = free_group("a, b")
|
||||
>>> G = FpGroup(F, [a**3, b**3, (a*b)**2])
|
||||
>>> H = AlternatingGroup(4)
|
||||
>>> (check, T) = group_isomorphism(G, H)
|
||||
>>> check
|
||||
True
|
||||
>>> T(b*a*b**-1*a**-1*b**-1)
|
||||
(0 2 3)
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups.
|
||||
First, the generators of ``G`` are mapped to the elements of ``H`` and
|
||||
we check if the mapping induces an isomorphism.
|
||||
|
||||
'''
|
||||
if not isinstance(G, (PermutationGroup, FpGroup)):
|
||||
raise TypeError("The group must be a PermutationGroup or an FpGroup")
|
||||
if not isinstance(H, (PermutationGroup, FpGroup)):
|
||||
raise TypeError("The group must be a PermutationGroup or an FpGroup")
|
||||
|
||||
if isinstance(G, FpGroup) and isinstance(H, FpGroup):
|
||||
G = simplify_presentation(G)
|
||||
H = simplify_presentation(H)
|
||||
# Two infinite FpGroups with the same generators are isomorphic
|
||||
# when the relators are same but are ordered differently.
|
||||
if G.generators == H.generators and (G.relators).sort() == (H.relators).sort():
|
||||
if not isomorphism:
|
||||
return True
|
||||
return (True, homomorphism(G, H, G.generators, H.generators))
|
||||
|
||||
# `_H` is the permutation group isomorphic to `H`.
|
||||
_H = H
|
||||
g_order = G.order()
|
||||
h_order = H.order()
|
||||
|
||||
if g_order is S.Infinity:
|
||||
raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
|
||||
|
||||
if isinstance(H, FpGroup):
|
||||
if h_order is S.Infinity:
|
||||
raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
|
||||
_H, h_isomorphism = H._to_perm_group()
|
||||
|
||||
if (g_order != h_order) or (G.is_abelian != H.is_abelian):
|
||||
if not isomorphism:
|
||||
return False
|
||||
return (False, None)
|
||||
|
||||
if not isomorphism:
|
||||
# Two groups of the same cyclic numbered order
|
||||
# are isomorphic to each other.
|
||||
n = g_order
|
||||
if (igcd(n, totient(n))) == 1:
|
||||
return True
|
||||
|
||||
# Match the generators of `G` with subsets of `_H`
|
||||
gens = list(G.generators)
|
||||
for subset in itertools.permutations(_H, len(gens)):
|
||||
images = list(subset)
|
||||
images.extend([_H.identity]*(len(G.generators)-len(images)))
|
||||
_images = dict(zip(gens,images))
|
||||
if _check_homomorphism(G, _H, _images):
|
||||
if isinstance(H, FpGroup):
|
||||
images = h_isomorphism.invert(images)
|
||||
T = homomorphism(G, H, G.generators, images, check=False)
|
||||
if T.is_isomorphism():
|
||||
# It is a valid isomorphism
|
||||
if not isomorphism:
|
||||
return True
|
||||
return (True, T)
|
||||
|
||||
if not isomorphism:
|
||||
return False
|
||||
return (False, None)
|
||||
|
||||
def is_isomorphic(G, H):
|
||||
'''
|
||||
Check if the groups are isomorphic to each other
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
G : A finite ``FpGroup`` or a ``PermutationGroup``
|
||||
First group.
|
||||
|
||||
H : A finite ``FpGroup`` or a ``PermutationGroup``
|
||||
Second group.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
boolean
|
||||
'''
|
||||
return group_isomorphism(G, H, isomorphism=False)
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
from sympy.combinatorics.group_constructs import DirectProduct
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
_af_new = Permutation._af_new
|
||||
|
||||
|
||||
def AbelianGroup(*cyclic_orders):
|
||||
"""
|
||||
Returns the direct product of cyclic groups with the given orders.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
According to the structure theorem for finite abelian groups ([1]),
|
||||
every finite abelian group can be written as the direct product of
|
||||
finitely many cyclic groups.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import AbelianGroup
|
||||
>>> AbelianGroup(3, 4)
|
||||
PermutationGroup([
|
||||
(6)(0 1 2),
|
||||
(3 4 5 6)])
|
||||
>>> _.is_group
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
DirectProduct
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://groupprops.subwiki.org/wiki/Structure_theorem_for_finitely_generated_abelian_groups
|
||||
|
||||
"""
|
||||
groups = []
|
||||
degree = 0
|
||||
order = 1
|
||||
for size in cyclic_orders:
|
||||
degree += size
|
||||
order *= size
|
||||
groups.append(CyclicGroup(size))
|
||||
G = DirectProduct(*groups)
|
||||
G._is_abelian = True
|
||||
G._degree = degree
|
||||
G._order = order
|
||||
|
||||
return G
|
||||
|
||||
|
||||
def AlternatingGroup(n):
|
||||
"""
|
||||
Generates the alternating group on ``n`` elements as a permutation group.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for
|
||||
``n`` odd
|
||||
and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.).
|
||||
After the group is generated, some of its basic properties are set.
|
||||
The cases ``n = 1, 2`` are handled separately.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import AlternatingGroup
|
||||
>>> G = AlternatingGroup(4)
|
||||
>>> G.is_group
|
||||
True
|
||||
>>> a = list(G.generate_dimino())
|
||||
>>> len(a)
|
||||
12
|
||||
>>> all(perm.is_even for perm in a)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
SymmetricGroup, CyclicGroup, DihedralGroup
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Armstrong, M. "Groups and Symmetry"
|
||||
|
||||
"""
|
||||
# small cases are special
|
||||
if n in (1, 2):
|
||||
return PermutationGroup([Permutation([0])])
|
||||
|
||||
a = list(range(n))
|
||||
a[0], a[1], a[2] = a[1], a[2], a[0]
|
||||
gen1 = a
|
||||
if n % 2:
|
||||
a = list(range(1, n))
|
||||
a.append(0)
|
||||
gen2 = a
|
||||
else:
|
||||
a = list(range(2, n))
|
||||
a.append(1)
|
||||
a.insert(0, 0)
|
||||
gen2 = a
|
||||
gens = [gen1, gen2]
|
||||
if gen1 == gen2:
|
||||
gens = gens[:1]
|
||||
G = PermutationGroup([_af_new(a) for a in gens], dups=False)
|
||||
|
||||
set_alternating_group_properties(G, n, n)
|
||||
G._is_alt = True
|
||||
return G
|
||||
|
||||
|
||||
def set_alternating_group_properties(G, n, degree):
|
||||
"""Set known properties of an alternating group. """
|
||||
if n < 4:
|
||||
G._is_abelian = True
|
||||
G._is_nilpotent = True
|
||||
else:
|
||||
G._is_abelian = False
|
||||
G._is_nilpotent = False
|
||||
if n < 5:
|
||||
G._is_solvable = True
|
||||
else:
|
||||
G._is_solvable = False
|
||||
G._degree = degree
|
||||
G._is_transitive = True
|
||||
G._is_dihedral = False
|
||||
|
||||
|
||||
def CyclicGroup(n):
|
||||
"""
|
||||
Generates the cyclic group of order ``n`` as a permutation group.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)``
|
||||
(in cycle notation). After the group is generated, some of its basic
|
||||
properties are set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import CyclicGroup
|
||||
>>> G = CyclicGroup(6)
|
||||
>>> G.is_group
|
||||
True
|
||||
>>> G.order()
|
||||
6
|
||||
>>> list(G.generate_schreier_sims(af=True))
|
||||
[[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1],
|
||||
[3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
SymmetricGroup, DihedralGroup, AlternatingGroup
|
||||
|
||||
"""
|
||||
a = list(range(1, n))
|
||||
a.append(0)
|
||||
gen = _af_new(a)
|
||||
G = PermutationGroup([gen])
|
||||
|
||||
G._is_abelian = True
|
||||
G._is_nilpotent = True
|
||||
G._is_solvable = True
|
||||
G._degree = n
|
||||
G._is_transitive = True
|
||||
G._order = n
|
||||
G._is_dihedral = (n == 2)
|
||||
return G
|
||||
|
||||
|
||||
def DihedralGroup(n):
|
||||
r"""
|
||||
Generates the dihedral group `D_n` as a permutation group.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The dihedral group `D_n` is the group of symmetries of the regular
|
||||
``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)``
|
||||
(a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...``
|
||||
(a reflection of the ``n``-gon) in cycle rotation. It is easy to see that
|
||||
these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate
|
||||
`D_n` (See [1]). After the group is generated, some of its basic properties
|
||||
are set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
||||
>>> G = DihedralGroup(5)
|
||||
>>> G.is_group
|
||||
True
|
||||
>>> a = list(G.generate_dimino())
|
||||
>>> [perm.cyclic_form for perm in a]
|
||||
[[], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]],
|
||||
[[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[0, 4], [1, 3]],
|
||||
[[1, 4], [2, 3]], [[0, 1], [2, 4]], [[0, 2], [3, 4]],
|
||||
[[0, 3], [1, 2]]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
SymmetricGroup, CyclicGroup, AlternatingGroup
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Dihedral_group
|
||||
|
||||
"""
|
||||
# small cases are special
|
||||
if n == 1:
|
||||
return PermutationGroup([Permutation([1, 0])])
|
||||
if n == 2:
|
||||
return PermutationGroup([Permutation([1, 0, 3, 2]),
|
||||
Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])])
|
||||
|
||||
a = list(range(1, n))
|
||||
a.append(0)
|
||||
gen1 = _af_new(a)
|
||||
a = list(range(n))
|
||||
a.reverse()
|
||||
gen2 = _af_new(a)
|
||||
G = PermutationGroup([gen1, gen2])
|
||||
# if n is a power of 2, group is nilpotent
|
||||
if n & (n-1) == 0:
|
||||
G._is_nilpotent = True
|
||||
else:
|
||||
G._is_nilpotent = False
|
||||
G._is_dihedral = True
|
||||
G._is_abelian = False
|
||||
G._is_solvable = True
|
||||
G._degree = n
|
||||
G._is_transitive = True
|
||||
G._order = 2*n
|
||||
return G
|
||||
|
||||
|
||||
def SymmetricGroup(n):
|
||||
"""
|
||||
Generates the symmetric group on ``n`` elements as a permutation group.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The generators taken are the ``n``-cycle
|
||||
``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation).
|
||||
(See [1]). After the group is generated, some of its basic properties
|
||||
are set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> G = SymmetricGroup(4)
|
||||
>>> G.is_group
|
||||
True
|
||||
>>> G.order()
|
||||
24
|
||||
>>> list(G.generate_schreier_sims(af=True))
|
||||
[[0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 1], [3, 1, 2, 0], [0, 2, 3, 1],
|
||||
[1, 3, 0, 2], [2, 0, 1, 3], [3, 2, 0, 1], [0, 3, 1, 2], [1, 0, 2, 3],
|
||||
[2, 1, 3, 0], [3, 0, 1, 2], [0, 1, 3, 2], [1, 2, 0, 3], [2, 3, 1, 0],
|
||||
[3, 1, 0, 2], [0, 2, 1, 3], [1, 3, 2, 0], [2, 0, 3, 1], [3, 2, 1, 0],
|
||||
[0, 3, 2, 1], [1, 0, 3, 2], [2, 1, 0, 3], [3, 0, 2, 1]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
CyclicGroup, DihedralGroup, AlternatingGroup
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations
|
||||
|
||||
"""
|
||||
if n == 1:
|
||||
G = PermutationGroup([Permutation([0])])
|
||||
elif n == 2:
|
||||
G = PermutationGroup([Permutation([1, 0])])
|
||||
else:
|
||||
a = list(range(1, n))
|
||||
a.append(0)
|
||||
gen1 = _af_new(a)
|
||||
a = list(range(n))
|
||||
a[0], a[1] = a[1], a[0]
|
||||
gen2 = _af_new(a)
|
||||
G = PermutationGroup([gen1, gen2])
|
||||
set_symmetric_group_properties(G, n, n)
|
||||
G._is_sym = True
|
||||
return G
|
||||
|
||||
|
||||
def set_symmetric_group_properties(G, n, degree):
|
||||
"""Set known properties of a symmetric group. """
|
||||
if n < 3:
|
||||
G._is_abelian = True
|
||||
G._is_nilpotent = True
|
||||
else:
|
||||
G._is_abelian = False
|
||||
G._is_nilpotent = False
|
||||
if n < 5:
|
||||
G._is_solvable = True
|
||||
else:
|
||||
G._is_solvable = False
|
||||
G._degree = degree
|
||||
G._is_transitive = True
|
||||
G._is_dihedral = (n in [2, 3]) # cf Landau's func and Stirling's approx
|
||||
|
||||
|
||||
def RubikGroup(n):
|
||||
"""Return a group of Rubik's cube generators
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import RubikGroup
|
||||
>>> RubikGroup(2).is_group
|
||||
True
|
||||
"""
|
||||
from sympy.combinatorics.generators import rubik
|
||||
if n <= 1:
|
||||
raise ValueError("Invalid cube. n has to be greater than 1")
|
||||
return PermutationGroup(rubik(n))
|
||||
+745
@@ -0,0 +1,745 @@
|
||||
from sympy.core import Basic, Dict, sympify, Tuple
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions.combinatorial.numbers import bell
|
||||
from sympy.matrices import zeros
|
||||
from sympy.sets.sets import FiniteSet, Union
|
||||
from sympy.utilities.iterables import flatten, group
|
||||
from sympy.utilities.misc import as_int
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class Partition(FiniteSet):
|
||||
"""
|
||||
This class represents an abstract partition.
|
||||
|
||||
A partition is a set of disjoint sets whose union equals a given set.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.utilities.iterables.partitions,
|
||||
sympy.utilities.iterables.multiset_partitions
|
||||
"""
|
||||
|
||||
_rank = None
|
||||
_partition = None
|
||||
|
||||
def __new__(cls, *partition):
|
||||
"""
|
||||
Generates a new partition object.
|
||||
|
||||
This method also verifies if the arguments passed are
|
||||
valid and raises a ValueError if they are not.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Creating Partition from Python lists:
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3])
|
||||
>>> a
|
||||
Partition({3}, {1, 2})
|
||||
>>> a.partition
|
||||
[[1, 2], [3]]
|
||||
>>> len(a)
|
||||
2
|
||||
>>> a.members
|
||||
(1, 2, 3)
|
||||
|
||||
Creating Partition from Python sets:
|
||||
|
||||
>>> Partition({1, 2, 3}, {4, 5})
|
||||
Partition({4, 5}, {1, 2, 3})
|
||||
|
||||
Creating Partition from SymPy finite sets:
|
||||
|
||||
>>> from sympy import FiniteSet
|
||||
>>> a = FiniteSet(1, 2, 3)
|
||||
>>> b = FiniteSet(4, 5)
|
||||
>>> Partition(a, b)
|
||||
Partition({4, 5}, {1, 2, 3})
|
||||
"""
|
||||
args = []
|
||||
dups = False
|
||||
for arg in partition:
|
||||
if isinstance(arg, list):
|
||||
as_set = set(arg)
|
||||
if len(as_set) < len(arg):
|
||||
dups = True
|
||||
break # error below
|
||||
arg = as_set
|
||||
args.append(_sympify(arg))
|
||||
|
||||
if not all(isinstance(part, FiniteSet) for part in args):
|
||||
raise ValueError(
|
||||
"Each argument to Partition should be " \
|
||||
"a list, set, or a FiniteSet")
|
||||
|
||||
# sort so we have a canonical reference for RGS
|
||||
U = Union(*args)
|
||||
if dups or len(U) < sum(len(arg) for arg in args):
|
||||
raise ValueError("Partition contained duplicate elements.")
|
||||
|
||||
obj = FiniteSet.__new__(cls, *args)
|
||||
obj.members = tuple(U)
|
||||
obj.size = len(U)
|
||||
return obj
|
||||
|
||||
def sort_key(self, order=None):
|
||||
"""Return a canonical key that can be used for sorting.
|
||||
|
||||
Ordering is based on the size and sorted elements of the partition
|
||||
and ties are broken with the rank.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import default_sort_key
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> from sympy.abc import x
|
||||
>>> a = Partition([1, 2])
|
||||
>>> b = Partition([3, 4])
|
||||
>>> c = Partition([1, x])
|
||||
>>> d = Partition(list(range(4)))
|
||||
>>> l = [d, b, a + 1, a, c]
|
||||
>>> l.sort(key=default_sort_key); l
|
||||
[Partition({1, 2}), Partition({1}, {2}), Partition({1, x}), Partition({3, 4}), Partition({0, 1, 2, 3})]
|
||||
"""
|
||||
if order is None:
|
||||
members = self.members
|
||||
else:
|
||||
members = tuple(sorted(self.members,
|
||||
key=lambda w: default_sort_key(w, order)))
|
||||
return tuple(map(default_sort_key, (self.size, members, self.rank)))
|
||||
|
||||
@property
|
||||
def partition(self):
|
||||
"""Return partition as a sorted list of lists.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> Partition([1], [2, 3]).partition
|
||||
[[1], [2, 3]]
|
||||
"""
|
||||
if self._partition is None:
|
||||
self._partition = sorted([sorted(p, key=default_sort_key)
|
||||
for p in self.args])
|
||||
return self._partition
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Return permutation whose rank is ``other`` greater than current rank,
|
||||
(mod the maximum rank for the set).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3])
|
||||
>>> a.rank
|
||||
1
|
||||
>>> (a + 1).rank
|
||||
2
|
||||
>>> (a + 100).rank
|
||||
1
|
||||
"""
|
||||
other = as_int(other)
|
||||
offset = self.rank + other
|
||||
result = RGS_unrank((offset) %
|
||||
RGS_enum(self.size),
|
||||
self.size)
|
||||
return Partition.from_rgs(result, self.members)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""
|
||||
Return permutation whose rank is ``other`` less than current rank,
|
||||
(mod the maximum rank for the set).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3])
|
||||
>>> a.rank
|
||||
1
|
||||
>>> (a - 1).rank
|
||||
0
|
||||
>>> (a - 100).rank
|
||||
1
|
||||
"""
|
||||
return self.__add__(-other)
|
||||
|
||||
def __le__(self, other):
|
||||
"""
|
||||
Checks if a partition is less than or equal to
|
||||
the other based on rank.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3, 4, 5])
|
||||
>>> b = Partition([1], [2, 3], [4], [5])
|
||||
>>> a.rank, b.rank
|
||||
(9, 34)
|
||||
>>> a <= a
|
||||
True
|
||||
>>> a <= b
|
||||
True
|
||||
"""
|
||||
return self.sort_key() <= sympify(other).sort_key()
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
Checks if a partition is less than the other.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3, 4, 5])
|
||||
>>> b = Partition([1], [2, 3], [4], [5])
|
||||
>>> a.rank, b.rank
|
||||
(9, 34)
|
||||
>>> a < b
|
||||
True
|
||||
"""
|
||||
return self.sort_key() < sympify(other).sort_key()
|
||||
|
||||
@property
|
||||
def rank(self):
|
||||
"""
|
||||
Gets the rank of a partition.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3], [4, 5])
|
||||
>>> a.rank
|
||||
13
|
||||
"""
|
||||
if self._rank is not None:
|
||||
return self._rank
|
||||
self._rank = RGS_rank(self.RGS)
|
||||
return self._rank
|
||||
|
||||
@property
|
||||
def RGS(self):
|
||||
"""
|
||||
Returns the "restricted growth string" of the partition.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The RGS is returned as a list of indices, L, where L[i] indicates
|
||||
the block in which element i appears. For example, in a partition
|
||||
of 3 elements (a, b, c) into 2 blocks ([c], [a, b]) the RGS is
|
||||
[1, 1, 0]: "a" is in block 1, "b" is in block 1 and "c" is in block 0.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> a = Partition([1, 2], [3], [4, 5])
|
||||
>>> a.members
|
||||
(1, 2, 3, 4, 5)
|
||||
>>> a.RGS
|
||||
(0, 0, 1, 2, 2)
|
||||
>>> a + 1
|
||||
Partition({3}, {4}, {5}, {1, 2})
|
||||
>>> _.RGS
|
||||
(0, 0, 1, 2, 3)
|
||||
"""
|
||||
rgs = {}
|
||||
partition = self.partition
|
||||
for i, part in enumerate(partition):
|
||||
for j in part:
|
||||
rgs[j] = i
|
||||
return tuple([rgs[i] for i in sorted(
|
||||
[i for p in partition for i in p], key=default_sort_key)])
|
||||
|
||||
@classmethod
|
||||
def from_rgs(self, rgs, elements):
|
||||
"""
|
||||
Creates a set partition from a restricted growth string.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The indices given in rgs are assumed to be the index
|
||||
of the element as given in elements *as provided* (the
|
||||
elements are not sorted by this routine). Block numbering
|
||||
starts from 0. If any block was not referenced in ``rgs``
|
||||
an error will be raised.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> Partition.from_rgs([0, 1, 2, 0, 1], list('abcde'))
|
||||
Partition({c}, {a, d}, {b, e})
|
||||
>>> Partition.from_rgs([0, 1, 2, 0, 1], list('cbead'))
|
||||
Partition({e}, {a, c}, {b, d})
|
||||
>>> a = Partition([1, 4], [2], [3, 5])
|
||||
>>> Partition.from_rgs(a.RGS, a.members)
|
||||
Partition({2}, {1, 4}, {3, 5})
|
||||
"""
|
||||
if len(rgs) != len(elements):
|
||||
raise ValueError('mismatch in rgs and element lengths')
|
||||
max_elem = max(rgs) + 1
|
||||
partition = [[] for i in range(max_elem)]
|
||||
j = 0
|
||||
for i in rgs:
|
||||
partition[i].append(elements[j])
|
||||
j += 1
|
||||
if not all(p for p in partition):
|
||||
raise ValueError('some blocks of the partition were empty.')
|
||||
return Partition(*partition)
|
||||
|
||||
|
||||
class IntegerPartition(Basic):
|
||||
"""
|
||||
This class represents an integer partition.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
In number theory and combinatorics, a partition of a positive integer,
|
||||
``n``, also called an integer partition, is a way of writing ``n`` as a
|
||||
list of positive integers that sum to n. Two partitions that differ only
|
||||
in the order of summands are considered to be the same partition; if order
|
||||
matters then the partitions are referred to as compositions. For example,
|
||||
4 has five partitions: [4], [3, 1], [2, 2], [2, 1, 1], and [1, 1, 1, 1];
|
||||
the compositions [1, 2, 1] and [1, 1, 2] are the same as partition
|
||||
[2, 1, 1].
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.utilities.iterables.partitions,
|
||||
sympy.utilities.iterables.multiset_partitions
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Partition_%28number_theory%29
|
||||
"""
|
||||
|
||||
_dict = None
|
||||
_keys = None
|
||||
|
||||
def __new__(cls, partition, integer=None):
|
||||
"""
|
||||
Generates a new IntegerPartition object from a list or dictionary.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The partition can be given as a list of positive integers or a
|
||||
dictionary of (integer, multiplicity) items. If the partition is
|
||||
preceded by an integer an error will be raised if the partition
|
||||
does not sum to that given integer.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> a = IntegerPartition([5, 4, 3, 1, 1])
|
||||
>>> a
|
||||
IntegerPartition(14, (5, 4, 3, 1, 1))
|
||||
>>> print(a)
|
||||
[5, 4, 3, 1, 1]
|
||||
>>> IntegerPartition({1:3, 2:1})
|
||||
IntegerPartition(5, (2, 1, 1, 1))
|
||||
|
||||
If the value that the partition should sum to is given first, a check
|
||||
will be made to see n error will be raised if there is a discrepancy:
|
||||
|
||||
>>> IntegerPartition(10, [5, 4, 3, 1])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: The partition is not valid
|
||||
|
||||
"""
|
||||
if integer is not None:
|
||||
integer, partition = partition, integer
|
||||
if isinstance(partition, (dict, Dict)):
|
||||
_ = []
|
||||
for k, v in sorted(partition.items(), reverse=True):
|
||||
if not v:
|
||||
continue
|
||||
k, v = as_int(k), as_int(v)
|
||||
_.extend([k]*v)
|
||||
partition = tuple(_)
|
||||
else:
|
||||
partition = tuple(sorted(map(as_int, partition), reverse=True))
|
||||
sum_ok = False
|
||||
if integer is None:
|
||||
integer = sum(partition)
|
||||
sum_ok = True
|
||||
else:
|
||||
integer = as_int(integer)
|
||||
|
||||
if not sum_ok and sum(partition) != integer:
|
||||
raise ValueError("Partition did not add to %s" % integer)
|
||||
if any(i < 1 for i in partition):
|
||||
raise ValueError("All integer summands must be greater than one")
|
||||
|
||||
obj = Basic.__new__(cls, Integer(integer), Tuple(*partition))
|
||||
obj.partition = list(partition)
|
||||
obj.integer = integer
|
||||
return obj
|
||||
|
||||
def prev_lex(self):
|
||||
"""Return the previous partition of the integer, n, in lexical order,
|
||||
wrapping around to [1, ..., 1] if the partition is [n].
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> p = IntegerPartition([4])
|
||||
>>> print(p.prev_lex())
|
||||
[3, 1]
|
||||
>>> p.partition > p.prev_lex().partition
|
||||
True
|
||||
"""
|
||||
d = defaultdict(int)
|
||||
d.update(self.as_dict())
|
||||
keys = self._keys
|
||||
if keys == [1]:
|
||||
return IntegerPartition({self.integer: 1})
|
||||
if keys[-1] != 1:
|
||||
d[keys[-1]] -= 1
|
||||
if keys[-1] == 2:
|
||||
d[1] = 2
|
||||
else:
|
||||
d[keys[-1] - 1] = d[1] = 1
|
||||
else:
|
||||
d[keys[-2]] -= 1
|
||||
left = d[1] + keys[-2]
|
||||
new = keys[-2]
|
||||
d[1] = 0
|
||||
while left:
|
||||
new -= 1
|
||||
if left - new >= 0:
|
||||
d[new] += left//new
|
||||
left -= d[new]*new
|
||||
return IntegerPartition(self.integer, d)
|
||||
|
||||
def next_lex(self):
|
||||
"""Return the next partition of the integer, n, in lexical order,
|
||||
wrapping around to [n] if the partition is [1, ..., 1].
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> p = IntegerPartition([3, 1])
|
||||
>>> print(p.next_lex())
|
||||
[4]
|
||||
>>> p.partition < p.next_lex().partition
|
||||
True
|
||||
"""
|
||||
d = defaultdict(int)
|
||||
d.update(self.as_dict())
|
||||
key = self._keys
|
||||
a = key[-1]
|
||||
if a == self.integer:
|
||||
d.clear()
|
||||
d[1] = self.integer
|
||||
elif a == 1:
|
||||
if d[a] > 1:
|
||||
d[a + 1] += 1
|
||||
d[a] -= 2
|
||||
else:
|
||||
b = key[-2]
|
||||
d[b + 1] += 1
|
||||
d[1] = (d[b] - 1)*b
|
||||
d[b] = 0
|
||||
else:
|
||||
if d[a] > 1:
|
||||
if len(key) == 1:
|
||||
d.clear()
|
||||
d[a + 1] = 1
|
||||
d[1] = self.integer - a - 1
|
||||
else:
|
||||
a1 = a + 1
|
||||
d[a1] += 1
|
||||
d[1] = d[a]*a - a1
|
||||
d[a] = 0
|
||||
else:
|
||||
b = key[-2]
|
||||
b1 = b + 1
|
||||
d[b1] += 1
|
||||
need = d[b]*b + d[a]*a - b1
|
||||
d[a] = d[b] = 0
|
||||
d[1] = need
|
||||
return IntegerPartition(self.integer, d)
|
||||
|
||||
def as_dict(self):
|
||||
"""Return the partition as a dictionary whose keys are the
|
||||
partition integers and the values are the multiplicity of that
|
||||
integer.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> IntegerPartition([1]*3 + [2] + [3]*4).as_dict()
|
||||
{1: 3, 2: 1, 3: 4}
|
||||
"""
|
||||
if self._dict is None:
|
||||
groups = group(self.partition, multiple=False)
|
||||
self._keys = [g[0] for g in groups]
|
||||
self._dict = dict(groups)
|
||||
return self._dict
|
||||
|
||||
@property
|
||||
def conjugate(self):
|
||||
"""
|
||||
Computes the conjugate partition of itself.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> a = IntegerPartition([6, 3, 3, 2, 1])
|
||||
>>> a.conjugate
|
||||
[5, 4, 3, 1, 1, 1]
|
||||
"""
|
||||
j = 1
|
||||
temp_arr = list(self.partition) + [0]
|
||||
k = temp_arr[0]
|
||||
b = [0]*k
|
||||
while k > 0:
|
||||
while k > temp_arr[j]:
|
||||
b[k - 1] = j
|
||||
k -= 1
|
||||
j += 1
|
||||
return b
|
||||
|
||||
def __lt__(self, other):
|
||||
"""Return True if self is less than other when the partition
|
||||
is listed from smallest to biggest.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> a = IntegerPartition([3, 1])
|
||||
>>> a < a
|
||||
False
|
||||
>>> b = a.next_lex()
|
||||
>>> a < b
|
||||
True
|
||||
>>> a == b
|
||||
False
|
||||
"""
|
||||
return list(reversed(self.partition)) < list(reversed(other.partition))
|
||||
|
||||
def __le__(self, other):
|
||||
"""Return True if self is less than other when the partition
|
||||
is listed from smallest to biggest.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> a = IntegerPartition([4])
|
||||
>>> a <= a
|
||||
True
|
||||
"""
|
||||
return list(reversed(self.partition)) <= list(reversed(other.partition))
|
||||
|
||||
def as_ferrers(self, char='#'):
|
||||
"""
|
||||
Prints the ferrer diagram of a partition.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import IntegerPartition
|
||||
>>> print(IntegerPartition([1, 1, 5]).as_ferrers())
|
||||
#####
|
||||
#
|
||||
#
|
||||
"""
|
||||
return "\n".join([char*i for i in self.partition])
|
||||
|
||||
def __str__(self):
|
||||
return str(list(self.partition))
|
||||
|
||||
|
||||
def random_integer_partition(n, seed=None):
|
||||
"""
|
||||
Generates a random integer partition summing to ``n`` as a list
|
||||
of reverse-sorted integers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import random_integer_partition
|
||||
|
||||
For the following, a seed is given so a known value can be shown; in
|
||||
practice, the seed would not be given.
|
||||
|
||||
>>> random_integer_partition(100, seed=[1, 1, 12, 1, 2, 1, 85, 1])
|
||||
[85, 12, 2, 1]
|
||||
>>> random_integer_partition(10, seed=[1, 2, 3, 1, 5, 1])
|
||||
[5, 3, 1, 1]
|
||||
>>> random_integer_partition(1)
|
||||
[1]
|
||||
"""
|
||||
from sympy.core.random import _randint
|
||||
|
||||
n = as_int(n)
|
||||
if n < 1:
|
||||
raise ValueError('n must be a positive integer')
|
||||
|
||||
randint = _randint(seed)
|
||||
|
||||
partition = []
|
||||
while (n > 0):
|
||||
k = randint(1, n)
|
||||
mult = randint(1, n//k)
|
||||
partition.append((k, mult))
|
||||
n -= k*mult
|
||||
partition.sort(reverse=True)
|
||||
partition = flatten([[k]*m for k, m in partition])
|
||||
return partition
|
||||
|
||||
|
||||
def RGS_generalized(m):
|
||||
"""
|
||||
Computes the m + 1 generalized unrestricted growth strings
|
||||
and returns them as rows in matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import RGS_generalized
|
||||
>>> RGS_generalized(6)
|
||||
Matrix([
|
||||
[ 1, 1, 1, 1, 1, 1, 1],
|
||||
[ 1, 2, 3, 4, 5, 6, 0],
|
||||
[ 2, 5, 10, 17, 26, 0, 0],
|
||||
[ 5, 15, 37, 77, 0, 0, 0],
|
||||
[ 15, 52, 151, 0, 0, 0, 0],
|
||||
[ 52, 203, 0, 0, 0, 0, 0],
|
||||
[203, 0, 0, 0, 0, 0, 0]])
|
||||
"""
|
||||
d = zeros(m + 1)
|
||||
for i in range(m + 1):
|
||||
d[0, i] = 1
|
||||
|
||||
for i in range(1, m + 1):
|
||||
for j in range(m):
|
||||
if j <= m - i:
|
||||
d[i, j] = j * d[i - 1, j] + d[i - 1, j + 1]
|
||||
else:
|
||||
d[i, j] = 0
|
||||
return d
|
||||
|
||||
|
||||
def RGS_enum(m):
|
||||
"""
|
||||
RGS_enum computes the total number of restricted growth strings
|
||||
possible for a superset of size m.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import RGS_enum
|
||||
>>> from sympy.combinatorics import Partition
|
||||
>>> RGS_enum(4)
|
||||
15
|
||||
>>> RGS_enum(5)
|
||||
52
|
||||
>>> RGS_enum(6)
|
||||
203
|
||||
|
||||
We can check that the enumeration is correct by actually generating
|
||||
the partitions. Here, the 15 partitions of 4 items are generated:
|
||||
|
||||
>>> a = Partition(list(range(4)))
|
||||
>>> s = set()
|
||||
>>> for i in range(20):
|
||||
... s.add(a)
|
||||
... a += 1
|
||||
...
|
||||
>>> assert len(s) == 15
|
||||
|
||||
"""
|
||||
if (m < 1):
|
||||
return 0
|
||||
elif (m == 1):
|
||||
return 1
|
||||
else:
|
||||
return bell(m)
|
||||
|
||||
|
||||
def RGS_unrank(rank, m):
|
||||
"""
|
||||
Gives the unranked restricted growth string for a given
|
||||
superset size.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import RGS_unrank
|
||||
>>> RGS_unrank(14, 4)
|
||||
[0, 1, 2, 3]
|
||||
>>> RGS_unrank(0, 4)
|
||||
[0, 0, 0, 0]
|
||||
"""
|
||||
if m < 1:
|
||||
raise ValueError("The superset size must be >= 1")
|
||||
if rank < 0 or RGS_enum(m) <= rank:
|
||||
raise ValueError("Invalid arguments")
|
||||
|
||||
L = [1] * (m + 1)
|
||||
j = 1
|
||||
D = RGS_generalized(m)
|
||||
for i in range(2, m + 1):
|
||||
v = D[m - i, j]
|
||||
cr = j*v
|
||||
if cr <= rank:
|
||||
L[i] = j + 1
|
||||
rank -= cr
|
||||
j += 1
|
||||
else:
|
||||
L[i] = int(rank / v + 1)
|
||||
rank %= v
|
||||
return [x - 1 for x in L[1:]]
|
||||
|
||||
|
||||
def RGS_rank(rgs):
|
||||
"""
|
||||
Computes the rank of a restricted growth string.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.partitions import RGS_rank, RGS_unrank
|
||||
>>> RGS_rank([0, 1, 2, 1, 3])
|
||||
42
|
||||
>>> RGS_rank(RGS_unrank(4, 7))
|
||||
4
|
||||
"""
|
||||
rgs_size = len(rgs)
|
||||
rank = 0
|
||||
D = RGS_generalized(rgs_size)
|
||||
for i in range(1, rgs_size):
|
||||
n = len(rgs[(i + 1):])
|
||||
m = max(rgs[0:i])
|
||||
rank += D[n, m + 1] * rgs[i]
|
||||
return rank
|
||||
+710
@@ -0,0 +1,710 @@
|
||||
from sympy.ntheory.primetest import isprime
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.printing.defaults import DefaultPrinting
|
||||
from sympy.combinatorics.free_groups import free_group
|
||||
|
||||
|
||||
class PolycyclicGroup(DefaultPrinting):
|
||||
|
||||
is_group = True
|
||||
is_solvable = True
|
||||
|
||||
def __init__(self, pc_sequence, pc_series, relative_order, collector=None):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
pc_sequence : list
|
||||
A sequence of elements whose classes generate the cyclic factor
|
||||
groups of pc_series.
|
||||
pc_series : list
|
||||
A subnormal sequence of subgroups where each factor group is cyclic.
|
||||
relative_order : list
|
||||
The orders of factor groups of pc_series.
|
||||
collector : Collector
|
||||
By default, it is None. Collector class provides the
|
||||
polycyclic presentation with various other functionalities.
|
||||
|
||||
"""
|
||||
self.pcgs = pc_sequence
|
||||
self.pc_series = pc_series
|
||||
self.relative_order = relative_order
|
||||
self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector
|
||||
|
||||
def is_prime_order(self):
|
||||
return all(isprime(order) for order in self.relative_order)
|
||||
|
||||
def length(self):
|
||||
return len(self.pcgs)
|
||||
|
||||
|
||||
class Collector(DefaultPrinting):
|
||||
|
||||
"""
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of Computational Group Theory"
|
||||
Section 8.1.3
|
||||
"""
|
||||
|
||||
def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None):
|
||||
"""
|
||||
|
||||
Most of the parameters for the Collector class are the same as for PolycyclicGroup.
|
||||
Others are described below.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
free_group_ : tuple
|
||||
free_group_ provides the mapping of polycyclic generating
|
||||
sequence with the free group elements.
|
||||
pc_presentation : dict
|
||||
Provides the presentation of polycyclic groups with the
|
||||
help of power and conjugate relators.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
PolycyclicGroup
|
||||
|
||||
"""
|
||||
self.pcgs = pcgs
|
||||
self.pc_series = pc_series
|
||||
self.relative_order = relative_order
|
||||
self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_
|
||||
self.index = {s: i for i, s in enumerate(self.free_group.symbols)}
|
||||
self.pc_presentation = self.pc_relators()
|
||||
|
||||
def minimal_uncollected_subword(self, word):
|
||||
r"""
|
||||
Returns the minimal uncollected subwords.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
A word ``v`` defined on generators in ``X`` is a minimal
|
||||
uncollected subword of the word ``w`` if ``v`` is a subword
|
||||
of ``w`` and it has one of the following form
|
||||
|
||||
* `v = {x_{i+1}}^{a_j}x_i`
|
||||
|
||||
* `v = {x_{i+1}}^{a_j}{x_i}^{-1}`
|
||||
|
||||
* `v = {x_i}^{a_j}`
|
||||
|
||||
for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power
|
||||
exponent of the corresponding generator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics import free_group
|
||||
>>> G = SymmetricGroup(4)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> F, x1, x2 = free_group("x1, x2")
|
||||
>>> word = x2**2*x1**7
|
||||
>>> collector.minimal_uncollected_subword(word)
|
||||
((x2, 2),)
|
||||
|
||||
"""
|
||||
# To handle the case word = <identity>
|
||||
if not word:
|
||||
return None
|
||||
|
||||
array = word.array_form
|
||||
re = self.relative_order
|
||||
index = self.index
|
||||
|
||||
for i in range(len(array)):
|
||||
s1, e1 = array[i]
|
||||
|
||||
if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1):
|
||||
return ((s1, e1), )
|
||||
|
||||
for i in range(len(array)-1):
|
||||
s1, e1 = array[i]
|
||||
s2, e2 = array[i+1]
|
||||
|
||||
if index[s1] > index[s2]:
|
||||
e = 1 if e2 > 0 else -1
|
||||
return ((s1, e1), (s2, e))
|
||||
|
||||
return None
|
||||
|
||||
def relations(self):
|
||||
"""
|
||||
Separates the given relators of pc presentation in power and
|
||||
conjugate relations.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(power_rel, conj_rel)
|
||||
Separates pc presentation into power and conjugate relations.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> G = SymmetricGroup(3)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> power_rel, conj_rel = collector.relations()
|
||||
>>> power_rel
|
||||
{x0**2: (), x1**3: ()}
|
||||
>>> conj_rel
|
||||
{x0**-1*x1*x0: x1**2}
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
pc_relators
|
||||
|
||||
"""
|
||||
power_relators = {}
|
||||
conjugate_relators = {}
|
||||
for key, value in self.pc_presentation.items():
|
||||
if len(key.array_form) == 1:
|
||||
power_relators[key] = value
|
||||
else:
|
||||
conjugate_relators[key] = value
|
||||
return power_relators, conjugate_relators
|
||||
|
||||
def subword_index(self, word, w):
|
||||
"""
|
||||
Returns the start and ending index of a given
|
||||
subword in a word.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
word : FreeGroupElement
|
||||
word defined on free group elements for a
|
||||
polycyclic group.
|
||||
w : FreeGroupElement
|
||||
subword of a given word, whose starting and
|
||||
ending index to be computed.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(i, j)
|
||||
A tuple containing starting and ending index of ``w``
|
||||
in the given word. If not exists, (-1,-1) is returned.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics import free_group
|
||||
>>> G = SymmetricGroup(4)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> F, x1, x2 = free_group("x1, x2")
|
||||
>>> word = x2**2*x1**7
|
||||
>>> w = x2**2*x1
|
||||
>>> collector.subword_index(word, w)
|
||||
(0, 3)
|
||||
>>> w = x1**7
|
||||
>>> collector.subword_index(word, w)
|
||||
(2, 9)
|
||||
>>> w = x1**8
|
||||
>>> collector.subword_index(word, w)
|
||||
(-1, -1)
|
||||
|
||||
"""
|
||||
low = -1
|
||||
high = -1
|
||||
for i in range(len(word)-len(w)+1):
|
||||
if word.subword(i, i+len(w)) == w:
|
||||
low = i
|
||||
high = i+len(w)
|
||||
break
|
||||
return low, high
|
||||
|
||||
def map_relation(self, w):
|
||||
"""
|
||||
Return a conjugate relation.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Given a word formed by two free group elements, the
|
||||
corresponding conjugate relation with those free
|
||||
group elements is formed and mapped with the collected
|
||||
word in the polycyclic presentation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics import free_group
|
||||
>>> G = SymmetricGroup(3)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> F, x0, x1 = free_group("x0, x1")
|
||||
>>> w = x1*x0
|
||||
>>> collector.map_relation(w)
|
||||
x1**2
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
pc_presentation
|
||||
|
||||
"""
|
||||
array = w.array_form
|
||||
s1 = array[0][0]
|
||||
s2 = array[1][0]
|
||||
key = ((s2, -1), (s1, 1), (s2, 1))
|
||||
key = self.free_group.dtype(key)
|
||||
return self.pc_presentation[key]
|
||||
|
||||
|
||||
def collected_word(self, word):
|
||||
r"""
|
||||
Return the collected form of a word.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
|
||||
{x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
|
||||
`\{1, \ldots, {s_j}-1\}`.
|
||||
|
||||
Otherwise w is uncollected.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
word : FreeGroupElement
|
||||
An uncollected word.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
word
|
||||
A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
|
||||
{x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
|
||||
\{1, \ldots, {s_j}-1\}`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
>>> from sympy.combinatorics import free_group
|
||||
>>> G = SymmetricGroup(4)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
|
||||
>>> word = x3*x2*x1*x0
|
||||
>>> collected_word = collector.collected_word(word)
|
||||
>>> free_to_perm = {}
|
||||
>>> free_group = collector.free_group
|
||||
>>> for sym, gen in zip(free_group.symbols, collector.pcgs):
|
||||
... free_to_perm[sym] = gen
|
||||
>>> G1 = PermutationGroup()
|
||||
>>> for w in word:
|
||||
... sym = w[0]
|
||||
... perm = free_to_perm[sym]
|
||||
... G1 = PermutationGroup([perm] + G1.generators)
|
||||
>>> G2 = PermutationGroup()
|
||||
>>> for w in collected_word:
|
||||
... sym = w[0]
|
||||
... perm = free_to_perm[sym]
|
||||
... G2 = PermutationGroup([perm] + G2.generators)
|
||||
|
||||
The two are not identical, but they are equivalent:
|
||||
|
||||
>>> G1.equals(G2), G1 == G2
|
||||
(True, False)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
minimal_uncollected_subword
|
||||
|
||||
"""
|
||||
free_group = self.free_group
|
||||
while True:
|
||||
w = self.minimal_uncollected_subword(word)
|
||||
if not w:
|
||||
break
|
||||
|
||||
low, high = self.subword_index(word, free_group.dtype(w))
|
||||
if low == -1:
|
||||
continue
|
||||
|
||||
s1, e1 = w[0]
|
||||
if len(w) == 1:
|
||||
re = self.relative_order[self.index[s1]]
|
||||
q = e1 // re
|
||||
r = e1-q*re
|
||||
|
||||
key = ((w[0][0], re), )
|
||||
key = free_group.dtype(key)
|
||||
if self.pc_presentation[key]:
|
||||
presentation = self.pc_presentation[key].array_form
|
||||
sym, exp = presentation[0]
|
||||
word_ = ((w[0][0], r), (sym, q*exp))
|
||||
word_ = free_group.dtype(word_)
|
||||
else:
|
||||
if r != 0:
|
||||
word_ = ((w[0][0], r), )
|
||||
word_ = free_group.dtype(word_)
|
||||
else:
|
||||
word_ = None
|
||||
word = word.eliminate_word(free_group.dtype(w), word_)
|
||||
|
||||
if len(w) == 2 and w[1][1] > 0:
|
||||
s2, e2 = w[1]
|
||||
s2 = ((s2, 1), )
|
||||
s2 = free_group.dtype(s2)
|
||||
word_ = self.map_relation(free_group.dtype(w))
|
||||
word_ = s2*word_**e1
|
||||
word_ = free_group.dtype(word_)
|
||||
word = word.substituted_word(low, high, word_)
|
||||
|
||||
elif len(w) == 2 and w[1][1] < 0:
|
||||
s2, e2 = w[1]
|
||||
s2 = ((s2, 1), )
|
||||
s2 = free_group.dtype(s2)
|
||||
word_ = self.map_relation(free_group.dtype(w))
|
||||
word_ = s2**-1*word_**e1
|
||||
word_ = free_group.dtype(word_)
|
||||
word = word.substituted_word(low, high, word_)
|
||||
|
||||
return word
|
||||
|
||||
|
||||
def pc_relators(self):
|
||||
r"""
|
||||
Return the polycyclic presentation.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
There are two types of relations used in polycyclic
|
||||
presentation.
|
||||
|
||||
* Power relations : Power relators are of the form `x_i^{re_i}`,
|
||||
where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
|
||||
generator and ``re`` is the corresponding relative order.
|
||||
|
||||
* Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
|
||||
where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
A dictionary with power and conjugate relations as key and
|
||||
their collected form as corresponding values.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Identity Permutation is mapped with empty ``()``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics.permutations import Permutation
|
||||
>>> S = SymmetricGroup(49).sylow_subgroup(7)
|
||||
>>> der = S.derived_series()
|
||||
>>> G = der[len(der)-2]
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> pcgs = PcGroup.pcgs
|
||||
>>> len(pcgs)
|
||||
6
|
||||
>>> free_group = collector.free_group
|
||||
>>> pc_resentation = collector.pc_presentation
|
||||
>>> free_to_perm = {}
|
||||
>>> for s, g in zip(free_group.symbols, pcgs):
|
||||
... free_to_perm[s] = g
|
||||
|
||||
>>> for k, v in pc_resentation.items():
|
||||
... k_array = k.array_form
|
||||
... if v != ():
|
||||
... v_array = v.array_form
|
||||
... lhs = Permutation()
|
||||
... for gen in k_array:
|
||||
... s = gen[0]
|
||||
... e = gen[1]
|
||||
... lhs = lhs*free_to_perm[s]**e
|
||||
... if v == ():
|
||||
... assert lhs.is_identity
|
||||
... continue
|
||||
... rhs = Permutation()
|
||||
... for gen in v_array:
|
||||
... s = gen[0]
|
||||
... e = gen[1]
|
||||
... rhs = rhs*free_to_perm[s]**e
|
||||
... assert lhs == rhs
|
||||
|
||||
"""
|
||||
free_group = self.free_group
|
||||
rel_order = self.relative_order
|
||||
pc_relators = {}
|
||||
perm_to_free = {}
|
||||
pcgs = self.pcgs
|
||||
|
||||
for gen, s in zip(pcgs, free_group.generators):
|
||||
perm_to_free[gen**-1] = s**-1
|
||||
perm_to_free[gen] = s
|
||||
|
||||
pcgs = pcgs[::-1]
|
||||
series = self.pc_series[::-1]
|
||||
rel_order = rel_order[::-1]
|
||||
collected_gens = []
|
||||
|
||||
for i, gen in enumerate(pcgs):
|
||||
re = rel_order[i]
|
||||
relation = perm_to_free[gen]**re
|
||||
G = series[i]
|
||||
|
||||
l = G.generator_product(gen**re, original = True)
|
||||
l.reverse()
|
||||
|
||||
word = free_group.identity
|
||||
for g in l:
|
||||
word = word*perm_to_free[g]
|
||||
|
||||
word = self.collected_word(word)
|
||||
pc_relators[relation] = word if word else ()
|
||||
self.pc_presentation = pc_relators
|
||||
|
||||
collected_gens.append(gen)
|
||||
if len(collected_gens) > 1:
|
||||
conj = collected_gens[len(collected_gens)-1]
|
||||
conjugator = perm_to_free[conj]
|
||||
|
||||
for j in range(len(collected_gens)-1):
|
||||
conjugated = perm_to_free[collected_gens[j]]
|
||||
|
||||
relation = conjugator**-1*conjugated*conjugator
|
||||
gens = conj**-1*collected_gens[j]*conj
|
||||
|
||||
l = G.generator_product(gens, original = True)
|
||||
l.reverse()
|
||||
word = free_group.identity
|
||||
for g in l:
|
||||
word = word*perm_to_free[g]
|
||||
|
||||
word = self.collected_word(word)
|
||||
pc_relators[relation] = word if word else ()
|
||||
self.pc_presentation = pc_relators
|
||||
|
||||
return pc_relators
|
||||
|
||||
def exponent_vector(self, element):
|
||||
r"""
|
||||
Return the exponent vector of length equal to the
|
||||
length of polycyclic generating sequence.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
For a given generator/element ``g`` of the polycyclic group,
|
||||
it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
|
||||
where `x_i` represents polycyclic generators and ``n`` is
|
||||
the number of generators in the free_group equal to the length
|
||||
of pcgs.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
element : Permutation
|
||||
Generator of a polycyclic group.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> from sympy.combinatorics.permutations import Permutation
|
||||
>>> G = SymmetricGroup(4)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> pcgs = PcGroup.pcgs
|
||||
>>> collector.exponent_vector(G[0])
|
||||
[1, 0, 0, 0]
|
||||
>>> exp = collector.exponent_vector(G[1])
|
||||
>>> g = Permutation()
|
||||
>>> for i in range(len(exp)):
|
||||
... g = g*pcgs[i]**exp[i] if exp[i] else g
|
||||
>>> assert g == G[1]
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of Computational Group Theory"
|
||||
Section 8.1.1, Definition 8.4
|
||||
|
||||
"""
|
||||
free_group = self.free_group
|
||||
G = PermutationGroup()
|
||||
for g in self.pcgs:
|
||||
G = PermutationGroup([g] + G.generators)
|
||||
gens = G.generator_product(element, original = True)
|
||||
gens.reverse()
|
||||
|
||||
perm_to_free = {}
|
||||
for sym, g in zip(free_group.generators, self.pcgs):
|
||||
perm_to_free[g**-1] = sym**-1
|
||||
perm_to_free[g] = sym
|
||||
w = free_group.identity
|
||||
for g in gens:
|
||||
w = w*perm_to_free[g]
|
||||
|
||||
word = self.collected_word(w)
|
||||
|
||||
index = self.index
|
||||
exp_vector = [0]*len(free_group)
|
||||
word = word.array_form
|
||||
for t in word:
|
||||
exp_vector[index[t[0]]] = t[1]
|
||||
return exp_vector
|
||||
|
||||
def depth(self, element):
|
||||
r"""
|
||||
Return the depth of a given element.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The depth of a given element ``g`` is defined by
|
||||
`\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
|
||||
and `e_i != 0`, where ``e`` represents the exponent-vector.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> G = SymmetricGroup(3)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> collector.depth(G[0])
|
||||
2
|
||||
>>> collector.depth(G[1])
|
||||
1
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of Computational Group Theory"
|
||||
Section 8.1.1, Definition 8.5
|
||||
|
||||
"""
|
||||
exp_vector = self.exponent_vector(element)
|
||||
return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)
|
||||
|
||||
def leading_exponent(self, element):
|
||||
r"""
|
||||
Return the leading non-zero exponent.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The leading exponent for a given element `g` is defined
|
||||
by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> G = SymmetricGroup(3)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> collector.leading_exponent(G[1])
|
||||
1
|
||||
|
||||
"""
|
||||
exp_vector = self.exponent_vector(element)
|
||||
depth = self.depth(element)
|
||||
if depth != len(self.pcgs)+1:
|
||||
return exp_vector[depth-1]
|
||||
return None
|
||||
|
||||
def _sift(self, z, g):
|
||||
h = g
|
||||
d = self.depth(h)
|
||||
while d < len(self.pcgs) and z[d-1] != 1:
|
||||
k = z[d-1]
|
||||
e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
|
||||
e = e % self.relative_order[d-1]
|
||||
h = k**-e*h
|
||||
d = self.depth(h)
|
||||
return h
|
||||
|
||||
def induced_pcgs(self, gens):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
gens : list
|
||||
A list of generators on which polycyclic subgroup
|
||||
is to be defined.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import SymmetricGroup
|
||||
>>> S = SymmetricGroup(8)
|
||||
>>> G = S.sylow_subgroup(2)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> gens = [G[0], G[1]]
|
||||
>>> ipcgs = collector.induced_pcgs(gens)
|
||||
>>> [gen.order() for gen in ipcgs]
|
||||
[2, 2, 2]
|
||||
>>> G = S.sylow_subgroup(3)
|
||||
>>> PcGroup = G.polycyclic_group()
|
||||
>>> collector = PcGroup.collector
|
||||
>>> gens = [G[0], G[1]]
|
||||
>>> ipcgs = collector.induced_pcgs(gens)
|
||||
>>> [gen.order() for gen in ipcgs]
|
||||
[3]
|
||||
|
||||
"""
|
||||
z = [1]*len(self.pcgs)
|
||||
G = gens
|
||||
while G:
|
||||
g = G.pop(0)
|
||||
h = self._sift(z, g)
|
||||
d = self.depth(h)
|
||||
if d < len(self.pcgs):
|
||||
for gen in z:
|
||||
if gen != 1:
|
||||
G.append(h**-1*gen**-1*h*gen)
|
||||
z[d-1] = h
|
||||
z = [gen for gen in z if gen != 1]
|
||||
return z
|
||||
|
||||
def constructive_membership_test(self, ipcgs, g):
|
||||
"""
|
||||
Return the exponent vector for induced pcgs.
|
||||
"""
|
||||
e = [0]*len(ipcgs)
|
||||
h = g
|
||||
d = self.depth(h)
|
||||
for i, gen in enumerate(ipcgs):
|
||||
while self.depth(gen) == d:
|
||||
f = self.leading_exponent(h)*self.leading_exponent(gen)
|
||||
f = f % self.relative_order[d-1]
|
||||
h = gen**(-f)*h
|
||||
e[i] = f
|
||||
d = self.depth(h)
|
||||
if h == 1:
|
||||
return e
|
||||
return False
|
||||
+5459
File diff suppressed because it is too large
Load Diff
+3114
File diff suppressed because it is too large
Load Diff
+1019
File diff suppressed because it is too large
Load Diff
+435
@@ -0,0 +1,435 @@
|
||||
from sympy.core import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.tensor.array import Array
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.utilities.iterables import flatten, iterable
|
||||
from sympy.utilities.misc import as_int
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class Prufer(Basic):
|
||||
"""
|
||||
The Prufer correspondence is an algorithm that describes the
|
||||
bijection between labeled trees and the Prufer code. A Prufer
|
||||
code of a labeled tree is unique up to isomorphism and has
|
||||
a length of n - 2.
|
||||
|
||||
Prufer sequences were first used by Heinz Prufer to give a
|
||||
proof of Cayley's formula.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://mathworld.wolfram.com/LabeledTree.html
|
||||
|
||||
"""
|
||||
_prufer_repr = None
|
||||
_tree_repr = None
|
||||
_nodes = None
|
||||
_rank = None
|
||||
|
||||
@property
|
||||
def prufer_repr(self):
|
||||
"""Returns Prufer sequence for the Prufer object.
|
||||
|
||||
This sequence is found by removing the highest numbered vertex,
|
||||
recording the node it was attached to, and continuing until only
|
||||
two vertices remain. The Prufer sequence is the list of recorded nodes.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).prufer_repr
|
||||
[3, 3, 3, 4]
|
||||
>>> Prufer([1, 0, 0]).prufer_repr
|
||||
[1, 0, 0]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
to_prufer
|
||||
|
||||
"""
|
||||
if self._prufer_repr is None:
|
||||
self._prufer_repr = self.to_prufer(self._tree_repr[:], self.nodes)
|
||||
return self._prufer_repr
|
||||
|
||||
@property
|
||||
def tree_repr(self):
|
||||
"""Returns the tree representation of the Prufer object.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).tree_repr
|
||||
[[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]
|
||||
>>> Prufer([1, 0, 0]).tree_repr
|
||||
[[1, 2], [0, 1], [0, 3], [0, 4]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
to_tree
|
||||
|
||||
"""
|
||||
if self._tree_repr is None:
|
||||
self._tree_repr = self.to_tree(self._prufer_repr[:])
|
||||
return self._tree_repr
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
"""Returns the number of nodes in the tree.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).nodes
|
||||
6
|
||||
>>> Prufer([1, 0, 0]).nodes
|
||||
5
|
||||
|
||||
"""
|
||||
return self._nodes
|
||||
|
||||
@property
|
||||
def rank(self):
|
||||
"""Returns the rank of the Prufer sequence.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> p = Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]])
|
||||
>>> p.rank
|
||||
778
|
||||
>>> p.next(1).rank
|
||||
779
|
||||
>>> p.prev().rank
|
||||
777
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prufer_rank, next, prev, size
|
||||
|
||||
"""
|
||||
if self._rank is None:
|
||||
self._rank = self.prufer_rank()
|
||||
return self._rank
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""Return the number of possible trees of this Prufer object.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer([0]*4).size == Prufer([6]*4).size == 1296
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prufer_rank, rank, next, prev
|
||||
|
||||
"""
|
||||
return self.prev(self.rank).prev().rank + 1
|
||||
|
||||
@staticmethod
|
||||
def to_prufer(tree, n):
|
||||
"""Return the Prufer sequence for a tree given as a list of edges where
|
||||
``n`` is the number of nodes in the tree.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
||||
>>> a.prufer_repr
|
||||
[0, 0]
|
||||
>>> Prufer.to_prufer([[0, 1], [0, 2], [0, 3]], 4)
|
||||
[0, 0]
|
||||
|
||||
See Also
|
||||
========
|
||||
prufer_repr: returns Prufer sequence of a Prufer object.
|
||||
|
||||
"""
|
||||
d = defaultdict(int)
|
||||
L = []
|
||||
for edge in tree:
|
||||
# Increment the value of the corresponding
|
||||
# node in the degree list as we encounter an
|
||||
# edge involving it.
|
||||
d[edge[0]] += 1
|
||||
d[edge[1]] += 1
|
||||
for i in range(n - 2):
|
||||
# find the smallest leaf
|
||||
for x in range(n):
|
||||
if d[x] == 1:
|
||||
break
|
||||
# find the node it was connected to
|
||||
y = None
|
||||
for edge in tree:
|
||||
if x == edge[0]:
|
||||
y = edge[1]
|
||||
elif x == edge[1]:
|
||||
y = edge[0]
|
||||
if y is not None:
|
||||
break
|
||||
# record and update
|
||||
L.append(y)
|
||||
for j in (x, y):
|
||||
d[j] -= 1
|
||||
if not d[j]:
|
||||
d.pop(j)
|
||||
tree.remove(edge)
|
||||
return L
|
||||
|
||||
@staticmethod
|
||||
def to_tree(prufer):
|
||||
"""Return the tree (as a list of edges) of the given Prufer sequence.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> a = Prufer([0, 2], 4)
|
||||
>>> a.tree_repr
|
||||
[[0, 1], [0, 2], [2, 3]]
|
||||
>>> Prufer.to_tree([0, 2])
|
||||
[[0, 1], [0, 2], [2, 3]]
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://hamberg.no/erlend/posts/2010-11-06-prufer-sequence-compact-tree-representation.html
|
||||
|
||||
See Also
|
||||
========
|
||||
tree_repr: returns tree representation of a Prufer object.
|
||||
|
||||
"""
|
||||
tree = []
|
||||
last = []
|
||||
n = len(prufer) + 2
|
||||
d = defaultdict(lambda: 1)
|
||||
for p in prufer:
|
||||
d[p] += 1
|
||||
for i in prufer:
|
||||
for j in range(n):
|
||||
# find the smallest leaf (degree = 1)
|
||||
if d[j] == 1:
|
||||
break
|
||||
# (i, j) is the new edge that we append to the tree
|
||||
# and remove from the degree dictionary
|
||||
d[i] -= 1
|
||||
d[j] -= 1
|
||||
tree.append(sorted([i, j]))
|
||||
last = [i for i in range(n) if d[i] == 1] or [0, 1]
|
||||
tree.append(last)
|
||||
|
||||
return tree
|
||||
|
||||
@staticmethod
|
||||
def edges(*runs):
|
||||
"""Return a list of edges and the number of nodes from the given runs
|
||||
that connect nodes in an integer-labelled tree.
|
||||
|
||||
All node numbers will be shifted so that the minimum node is 0. It is
|
||||
not a problem if edges are repeated in the runs; only unique edges are
|
||||
returned. There is no assumption made about what the range of the node
|
||||
labels should be, but all nodes from the smallest through the largest
|
||||
must be present.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer.edges([1, 2, 3], [2, 4, 5]) # a T
|
||||
([[0, 1], [1, 2], [1, 3], [3, 4]], 5)
|
||||
|
||||
Duplicate edges are removed:
|
||||
|
||||
>>> Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) # a K
|
||||
([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7)
|
||||
|
||||
"""
|
||||
e = set()
|
||||
nmin = runs[0][0]
|
||||
for r in runs:
|
||||
for i in range(len(r) - 1):
|
||||
a, b = r[i: i + 2]
|
||||
if b < a:
|
||||
a, b = b, a
|
||||
e.add((a, b))
|
||||
rv = []
|
||||
got = set()
|
||||
nmin = nmax = None
|
||||
for ei in e:
|
||||
got.update(ei)
|
||||
nmin = min(ei[0], nmin) if nmin is not None else ei[0]
|
||||
nmax = max(ei[1], nmax) if nmax is not None else ei[1]
|
||||
rv.append(list(ei))
|
||||
missing = set(range(nmin, nmax + 1)) - got
|
||||
if missing:
|
||||
missing = [i + nmin for i in missing]
|
||||
if len(missing) == 1:
|
||||
msg = 'Node %s is missing.' % missing.pop()
|
||||
else:
|
||||
msg = 'Nodes %s are missing.' % sorted(missing)
|
||||
raise ValueError(msg)
|
||||
if nmin != 0:
|
||||
for i, ei in enumerate(rv):
|
||||
rv[i] = [n - nmin for n in ei]
|
||||
nmax -= nmin
|
||||
return sorted(rv), nmax + 1
|
||||
|
||||
def prufer_rank(self):
|
||||
"""Computes the rank of a Prufer sequence.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
||||
>>> a.prufer_rank()
|
||||
0
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
rank, next, prev, size
|
||||
|
||||
"""
|
||||
r = 0
|
||||
p = 1
|
||||
for i in range(self.nodes - 3, -1, -1):
|
||||
r += p*self.prufer_repr[i]
|
||||
p *= self.nodes
|
||||
return r
|
||||
|
||||
@classmethod
|
||||
def unrank(self, rank, n):
|
||||
"""Finds the unranked Prufer sequence.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> Prufer.unrank(0, 4)
|
||||
Prufer([0, 0])
|
||||
|
||||
"""
|
||||
n, rank = as_int(n), as_int(rank)
|
||||
L = defaultdict(int)
|
||||
for i in range(n - 3, -1, -1):
|
||||
L[i] = rank % n
|
||||
rank = (rank - L[i])//n
|
||||
return Prufer([L[i] for i in range(len(L))])
|
||||
|
||||
def __new__(cls, *args, **kw_args):
|
||||
"""The constructor for the Prufer object.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
|
||||
A Prufer object can be constructed from a list of edges:
|
||||
|
||||
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
||||
>>> a.prufer_repr
|
||||
[0, 0]
|
||||
|
||||
If the number of nodes is given, no checking of the nodes will
|
||||
be performed; it will be assumed that nodes 0 through n - 1 are
|
||||
present:
|
||||
|
||||
>>> Prufer([[0, 1], [0, 2], [0, 3]], 4)
|
||||
Prufer([[0, 1], [0, 2], [0, 3]], 4)
|
||||
|
||||
A Prufer object can be constructed from a Prufer sequence:
|
||||
|
||||
>>> b = Prufer([1, 3])
|
||||
>>> b.tree_repr
|
||||
[[0, 1], [1, 3], [2, 3]]
|
||||
|
||||
"""
|
||||
arg0 = Array(args[0]) if args[0] else Tuple()
|
||||
args = (arg0,) + tuple(_sympify(arg) for arg in args[1:])
|
||||
ret_obj = Basic.__new__(cls, *args, **kw_args)
|
||||
args = [list(args[0])]
|
||||
if args[0] and iterable(args[0][0]):
|
||||
if not args[0][0]:
|
||||
raise ValueError(
|
||||
'Prufer expects at least one edge in the tree.')
|
||||
if len(args) > 1:
|
||||
nnodes = args[1]
|
||||
else:
|
||||
nodes = set(flatten(args[0]))
|
||||
nnodes = max(nodes) + 1
|
||||
if nnodes != len(nodes):
|
||||
missing = set(range(nnodes)) - nodes
|
||||
if len(missing) == 1:
|
||||
msg = 'Node %s is missing.' % missing.pop()
|
||||
else:
|
||||
msg = 'Nodes %s are missing.' % sorted(missing)
|
||||
raise ValueError(msg)
|
||||
ret_obj._tree_repr = [list(i) for i in args[0]]
|
||||
ret_obj._nodes = nnodes
|
||||
else:
|
||||
ret_obj._prufer_repr = args[0]
|
||||
ret_obj._nodes = len(ret_obj._prufer_repr) + 2
|
||||
return ret_obj
|
||||
|
||||
def next(self, delta=1):
|
||||
"""Generates the Prufer sequence that is delta beyond the current one.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> a = Prufer([[0, 1], [0, 2], [0, 3]])
|
||||
>>> b = a.next(1) # == a.next()
|
||||
>>> b.tree_repr
|
||||
[[0, 2], [0, 1], [1, 3]]
|
||||
>>> b.rank
|
||||
1
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prufer_rank, rank, prev, size
|
||||
|
||||
"""
|
||||
return Prufer.unrank(self.rank + delta, self.nodes)
|
||||
|
||||
def prev(self, delta=1):
|
||||
"""Generates the Prufer sequence that is -delta before the current one.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.prufer import Prufer
|
||||
>>> a = Prufer([[0, 1], [1, 2], [2, 3], [1, 4]])
|
||||
>>> a.rank
|
||||
36
|
||||
>>> b = a.prev()
|
||||
>>> b
|
||||
Prufer([1, 2, 0])
|
||||
>>> b.rank
|
||||
35
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prufer_rank, rank, next, size
|
||||
|
||||
"""
|
||||
return Prufer.unrank(self.rank -delta, self.nodes)
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
from collections import deque
|
||||
from sympy.combinatorics.rewritingsystem_fsm import StateMachine
|
||||
|
||||
class RewritingSystem:
|
||||
'''
|
||||
A class implementing rewriting systems for `FpGroup`s.
|
||||
|
||||
References
|
||||
==========
|
||||
.. [1] Epstein, D., Holt, D. and Rees, S. (1991).
|
||||
The use of Knuth-Bendix methods to solve the word problem in automatic groups.
|
||||
Journal of Symbolic Computation, 12(4-5), pp.397-414.
|
||||
|
||||
.. [2] GAP's Manual on its KBMAG package
|
||||
https://www.gap-system.org/Manuals/pkg/kbmag-1.5.3/doc/manual.pdf
|
||||
|
||||
'''
|
||||
def __init__(self, group):
|
||||
self.group = group
|
||||
self.alphabet = group.generators
|
||||
self._is_confluent = None
|
||||
|
||||
# these values are taken from [2]
|
||||
self.maxeqns = 32767 # max rules
|
||||
self.tidyint = 100 # rules before tidying
|
||||
|
||||
# _max_exceeded is True if maxeqns is exceeded
|
||||
# at any point
|
||||
self._max_exceeded = False
|
||||
|
||||
# Reduction automaton
|
||||
self.reduction_automaton = None
|
||||
self._new_rules = {}
|
||||
|
||||
# dictionary of reductions
|
||||
self.rules = {}
|
||||
self.rules_cache = deque([], 50)
|
||||
self._init_rules()
|
||||
|
||||
|
||||
# All the transition symbols in the automaton
|
||||
generators = list(self.alphabet)
|
||||
generators += [gen**-1 for gen in generators]
|
||||
# Create a finite state machine as an instance of the StateMachine object
|
||||
self.reduction_automaton = StateMachine('Reduction automaton for '+ repr(self.group), generators)
|
||||
self.construct_automaton()
|
||||
|
||||
def set_max(self, n):
|
||||
'''
|
||||
Set the maximum number of rules that can be defined
|
||||
|
||||
'''
|
||||
if n > self.maxeqns:
|
||||
self._max_exceeded = False
|
||||
self.maxeqns = n
|
||||
return
|
||||
|
||||
@property
|
||||
def is_confluent(self):
|
||||
'''
|
||||
Return `True` if the system is confluent
|
||||
|
||||
'''
|
||||
if self._is_confluent is None:
|
||||
self._is_confluent = self._check_confluence()
|
||||
return self._is_confluent
|
||||
|
||||
def _init_rules(self):
|
||||
identity = self.group.free_group.identity
|
||||
for r in self.group.relators:
|
||||
self.add_rule(r, identity)
|
||||
self._remove_redundancies()
|
||||
return
|
||||
|
||||
def _add_rule(self, r1, r2):
|
||||
'''
|
||||
Add the rule r1 -> r2 with no checking or further
|
||||
deductions
|
||||
|
||||
'''
|
||||
if len(self.rules) + 1 > self.maxeqns:
|
||||
self._is_confluent = self._check_confluence()
|
||||
self._max_exceeded = True
|
||||
raise RuntimeError("Too many rules were defined.")
|
||||
self.rules[r1] = r2
|
||||
# Add the newly added rule to the `new_rules` dictionary.
|
||||
if self.reduction_automaton:
|
||||
self._new_rules[r1] = r2
|
||||
|
||||
def add_rule(self, w1, w2, check=False):
|
||||
new_keys = set()
|
||||
|
||||
if w1 == w2:
|
||||
return new_keys
|
||||
|
||||
if w1 < w2:
|
||||
w1, w2 = w2, w1
|
||||
|
||||
if (w1, w2) in self.rules_cache:
|
||||
return new_keys
|
||||
self.rules_cache.append((w1, w2))
|
||||
|
||||
s1, s2 = w1, w2
|
||||
|
||||
# The following is the equivalent of checking
|
||||
# s1 for overlaps with the implicit reductions
|
||||
# {g*g**-1 -> <identity>} and {g**-1*g -> <identity>}
|
||||
# for any generator g without installing the
|
||||
# redundant rules that would result from processing
|
||||
# the overlaps. See [1], Section 3 for details.
|
||||
|
||||
if len(s1) - len(s2) < 3:
|
||||
if s1 not in self.rules:
|
||||
new_keys.add(s1)
|
||||
if not check:
|
||||
self._add_rule(s1, s2)
|
||||
if s2**-1 > s1**-1 and s2**-1 not in self.rules:
|
||||
new_keys.add(s2**-1)
|
||||
if not check:
|
||||
self._add_rule(s2**-1, s1**-1)
|
||||
|
||||
# overlaps on the right
|
||||
while len(s1) - len(s2) > -1:
|
||||
g = s1[len(s1)-1]
|
||||
s1 = s1.subword(0, len(s1)-1)
|
||||
s2 = s2*g**-1
|
||||
if len(s1) - len(s2) < 0:
|
||||
if s2 not in self.rules:
|
||||
if not check:
|
||||
self._add_rule(s2, s1)
|
||||
new_keys.add(s2)
|
||||
elif len(s1) - len(s2) < 3:
|
||||
new = self.add_rule(s1, s2, check)
|
||||
new_keys.update(new)
|
||||
|
||||
# overlaps on the left
|
||||
while len(w1) - len(w2) > -1:
|
||||
g = w1[0]
|
||||
w1 = w1.subword(1, len(w1))
|
||||
w2 = g**-1*w2
|
||||
if len(w1) - len(w2) < 0:
|
||||
if w2 not in self.rules:
|
||||
if not check:
|
||||
self._add_rule(w2, w1)
|
||||
new_keys.add(w2)
|
||||
elif len(w1) - len(w2) < 3:
|
||||
new = self.add_rule(w1, w2, check)
|
||||
new_keys.update(new)
|
||||
|
||||
return new_keys
|
||||
|
||||
def _remove_redundancies(self, changes=False):
|
||||
'''
|
||||
Reduce left- and right-hand sides of reduction rules
|
||||
and remove redundant equations (i.e. those for which
|
||||
lhs == rhs). If `changes` is `True`, return a set
|
||||
containing the removed keys and a set containing the
|
||||
added keys
|
||||
|
||||
'''
|
||||
removed = set()
|
||||
added = set()
|
||||
rules = self.rules.copy()
|
||||
for r in rules:
|
||||
v = self.reduce(r, exclude=r)
|
||||
w = self.reduce(rules[r])
|
||||
if v != r:
|
||||
del self.rules[r]
|
||||
removed.add(r)
|
||||
if v > w:
|
||||
added.add(v)
|
||||
self.rules[v] = w
|
||||
elif v < w:
|
||||
added.add(w)
|
||||
self.rules[w] = v
|
||||
else:
|
||||
self.rules[v] = w
|
||||
if changes:
|
||||
return removed, added
|
||||
return
|
||||
|
||||
def make_confluent(self, check=False):
|
||||
'''
|
||||
Try to make the system confluent using the Knuth-Bendix
|
||||
completion algorithm
|
||||
|
||||
'''
|
||||
if self._max_exceeded:
|
||||
return self._is_confluent
|
||||
lhs = list(self.rules.keys())
|
||||
|
||||
def _overlaps(r1, r2):
|
||||
len1 = len(r1)
|
||||
len2 = len(r2)
|
||||
result = []
|
||||
for j in range(1, len1 + len2):
|
||||
if (r1.subword(len1 - j, len1 + len2 - j, strict=False)
|
||||
== r2.subword(j - len1, j, strict=False)):
|
||||
a = r1.subword(0, len1-j, strict=False)
|
||||
a = a*r2.subword(0, j-len1, strict=False)
|
||||
b = r2.subword(j-len1, j, strict=False)
|
||||
c = r2.subword(j, len2, strict=False)
|
||||
c = c*r1.subword(len1 + len2 - j, len1, strict=False)
|
||||
result.append(a*b*c)
|
||||
return result
|
||||
|
||||
def _process_overlap(w, r1, r2, check):
|
||||
s = w.eliminate_word(r1, self.rules[r1])
|
||||
s = self.reduce(s)
|
||||
t = w.eliminate_word(r2, self.rules[r2])
|
||||
t = self.reduce(t)
|
||||
if s != t:
|
||||
if check:
|
||||
# system not confluent
|
||||
return [0]
|
||||
try:
|
||||
new_keys = self.add_rule(t, s, check)
|
||||
return new_keys
|
||||
except RuntimeError:
|
||||
return False
|
||||
return
|
||||
|
||||
added = 0
|
||||
i = 0
|
||||
while i < len(lhs):
|
||||
r1 = lhs[i]
|
||||
i += 1
|
||||
# j could be i+1 to not
|
||||
# check each pair twice but lhs
|
||||
# is extended in the loop and the new
|
||||
# elements have to be checked with the
|
||||
# preceding ones. there is probably a better way
|
||||
# to handle this
|
||||
j = 0
|
||||
while j < len(lhs):
|
||||
r2 = lhs[j]
|
||||
j += 1
|
||||
if r1 == r2:
|
||||
continue
|
||||
overlaps = _overlaps(r1, r2)
|
||||
overlaps.extend(_overlaps(r1**-1, r2))
|
||||
if not overlaps:
|
||||
continue
|
||||
for w in overlaps:
|
||||
new_keys = _process_overlap(w, r1, r2, check)
|
||||
if new_keys:
|
||||
if check:
|
||||
return False
|
||||
lhs.extend(new_keys)
|
||||
added += len(new_keys)
|
||||
elif new_keys == False:
|
||||
# too many rules were added so the process
|
||||
# couldn't complete
|
||||
return self._is_confluent
|
||||
|
||||
if added > self.tidyint and not check:
|
||||
# tidy up
|
||||
r, a = self._remove_redundancies(changes=True)
|
||||
added = 0
|
||||
if r:
|
||||
# reset i since some elements were removed
|
||||
i = min(lhs.index(s) for s in r)
|
||||
lhs = [l for l in lhs if l not in r]
|
||||
lhs.extend(a)
|
||||
if r1 in r:
|
||||
# r1 was removed as redundant
|
||||
break
|
||||
|
||||
self._is_confluent = True
|
||||
if not check:
|
||||
self._remove_redundancies()
|
||||
return True
|
||||
|
||||
def _check_confluence(self):
|
||||
return self.make_confluent(check=True)
|
||||
|
||||
def reduce(self, word, exclude=None):
|
||||
'''
|
||||
Apply reduction rules to `word` excluding the reduction rule
|
||||
for the lhs equal to `exclude`
|
||||
|
||||
'''
|
||||
rules = {r: self.rules[r] for r in self.rules if r != exclude}
|
||||
# the following is essentially `eliminate_words()` code from the
|
||||
# `FreeGroupElement` class, the only difference being the first
|
||||
# "if" statement
|
||||
again = True
|
||||
new = word
|
||||
while again:
|
||||
again = False
|
||||
for r in rules:
|
||||
prev = new
|
||||
if rules[r]**-1 > r**-1:
|
||||
new = new.eliminate_word(r, rules[r], _all=True, inverse=False)
|
||||
else:
|
||||
new = new.eliminate_word(r, rules[r], _all=True)
|
||||
if new != prev:
|
||||
again = True
|
||||
return new
|
||||
|
||||
def _compute_inverse_rules(self, rules):
|
||||
'''
|
||||
Compute the inverse rules for a given set of rules.
|
||||
The inverse rules are used in the automaton for word reduction.
|
||||
|
||||
Arguments:
|
||||
rules (dictionary): Rules for which the inverse rules are to computed.
|
||||
|
||||
Returns:
|
||||
Dictionary of inverse_rules.
|
||||
|
||||
'''
|
||||
inverse_rules = {}
|
||||
for r in rules:
|
||||
rule_key_inverse = r**-1
|
||||
rule_value_inverse = (rules[r])**-1
|
||||
if (rule_value_inverse < rule_key_inverse):
|
||||
inverse_rules[rule_key_inverse] = rule_value_inverse
|
||||
else:
|
||||
inverse_rules[rule_value_inverse] = rule_key_inverse
|
||||
return inverse_rules
|
||||
|
||||
def construct_automaton(self):
|
||||
'''
|
||||
Construct the automaton based on the set of reduction rules of the system.
|
||||
|
||||
Automata Design:
|
||||
The accept states of the automaton are the proper prefixes of the left hand side of the rules.
|
||||
The complete left hand side of the rules are the dead states of the automaton.
|
||||
|
||||
'''
|
||||
self._add_to_automaton(self.rules)
|
||||
|
||||
def _add_to_automaton(self, rules):
|
||||
'''
|
||||
Add new states and transitions to the automaton.
|
||||
|
||||
Summary:
|
||||
States corresponding to the new rules added to the system are computed and added to the automaton.
|
||||
Transitions in the previously added states are also modified if necessary.
|
||||
|
||||
Arguments:
|
||||
rules (dictionary) -- Dictionary of the newly added rules.
|
||||
|
||||
'''
|
||||
# Automaton variables
|
||||
automaton_alphabet = []
|
||||
proper_prefixes = {}
|
||||
|
||||
# compute the inverses of all the new rules added
|
||||
all_rules = rules
|
||||
inverse_rules = self._compute_inverse_rules(all_rules)
|
||||
all_rules.update(inverse_rules)
|
||||
|
||||
# Keep track of the accept_states.
|
||||
accept_states = []
|
||||
|
||||
for rule in all_rules:
|
||||
# The symbols present in the new rules are the symbols to be verified at each state.
|
||||
# computes the automaton_alphabet, as the transitions solely depend upon the new states.
|
||||
automaton_alphabet += rule.letter_form_elm
|
||||
# Compute the proper prefixes for every rule.
|
||||
proper_prefixes[rule] = []
|
||||
letter_word_array = list(rule.letter_form_elm)
|
||||
len_letter_word_array = len(letter_word_array)
|
||||
for i in range (1, len_letter_word_array):
|
||||
letter_word_array[i] = letter_word_array[i-1]*letter_word_array[i]
|
||||
# Add accept states.
|
||||
elem = letter_word_array[i-1]
|
||||
if elem not in self.reduction_automaton.states:
|
||||
self.reduction_automaton.add_state(elem, state_type='a')
|
||||
accept_states.append(elem)
|
||||
proper_prefixes[rule] = letter_word_array
|
||||
# Check for overlaps between dead and accept states.
|
||||
if rule in accept_states:
|
||||
self.reduction_automaton.states[rule].state_type = 'd'
|
||||
self.reduction_automaton.states[rule].rh_rule = all_rules[rule]
|
||||
accept_states.remove(rule)
|
||||
# Add dead states
|
||||
if rule not in self.reduction_automaton.states:
|
||||
self.reduction_automaton.add_state(rule, state_type='d', rh_rule=all_rules[rule])
|
||||
|
||||
automaton_alphabet = set(automaton_alphabet)
|
||||
|
||||
# Add new transitions for every state.
|
||||
for state in self.reduction_automaton.states:
|
||||
current_state_name = state
|
||||
current_state_type = self.reduction_automaton.states[state].state_type
|
||||
# Transitions will be modified only when suffixes of the current_state
|
||||
# belongs to the proper_prefixes of the new rules.
|
||||
# The rest are ignored if they cannot lead to a dead state after a finite number of transisitons.
|
||||
if current_state_type == 's':
|
||||
for letter in automaton_alphabet:
|
||||
if letter in self.reduction_automaton.states:
|
||||
self.reduction_automaton.states[state].add_transition(letter, letter)
|
||||
else:
|
||||
self.reduction_automaton.states[state].add_transition(letter, current_state_name)
|
||||
elif current_state_type == 'a':
|
||||
# Check if the transition to any new state in possible.
|
||||
for letter in automaton_alphabet:
|
||||
_next = current_state_name*letter
|
||||
while len(_next) and _next not in self.reduction_automaton.states:
|
||||
_next = _next.subword(1, len(_next))
|
||||
if not len(_next):
|
||||
_next = 'start'
|
||||
self.reduction_automaton.states[state].add_transition(letter, _next)
|
||||
|
||||
# Add transitions for new states. All symbols used in the automaton are considered here.
|
||||
# Ignore this if `reduction_automaton.automaton_alphabet` = `automaton_alphabet`.
|
||||
if len(self.reduction_automaton.automaton_alphabet) != len(automaton_alphabet):
|
||||
for state in accept_states:
|
||||
current_state_name = state
|
||||
for letter in self.reduction_automaton.automaton_alphabet:
|
||||
_next = current_state_name*letter
|
||||
while len(_next) and _next not in self.reduction_automaton.states:
|
||||
_next = _next.subword(1, len(_next))
|
||||
if not len(_next):
|
||||
_next = 'start'
|
||||
self.reduction_automaton.states[state].add_transition(letter, _next)
|
||||
|
||||
def reduce_using_automaton(self, word):
|
||||
'''
|
||||
Reduce a word using an automaton.
|
||||
|
||||
Summary:
|
||||
All the symbols of the word are stored in an array and are given as the input to the automaton.
|
||||
If the automaton reaches a dead state that subword is replaced and the automaton is run from the beginning.
|
||||
The complete word has to be replaced when the word is read and the automaton reaches a dead state.
|
||||
So, this process is repeated until the word is read completely and the automaton reaches the accept state.
|
||||
|
||||
Arguments:
|
||||
word (instance of FreeGroupElement) -- Word that needs to be reduced.
|
||||
|
||||
'''
|
||||
# Modify the automaton if new rules are found.
|
||||
if self._new_rules:
|
||||
self._add_to_automaton(self._new_rules)
|
||||
self._new_rules = {}
|
||||
|
||||
flag = 1
|
||||
while flag:
|
||||
flag = 0
|
||||
current_state = self.reduction_automaton.states['start']
|
||||
for i, s in enumerate(word.letter_form_elm):
|
||||
next_state_name = current_state.transitions[s]
|
||||
next_state = self.reduction_automaton.states[next_state_name]
|
||||
if next_state.state_type == 'd':
|
||||
subst = next_state.rh_rule
|
||||
word = word.substituted_word(i - len(next_state_name) + 1, i+1, subst)
|
||||
flag = 1
|
||||
break
|
||||
current_state = next_state
|
||||
return word
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
class State:
|
||||
'''
|
||||
A representation of a state managed by a ``StateMachine``.
|
||||
|
||||
Attributes:
|
||||
name (instance of FreeGroupElement or string) -- State name which is also assigned to the Machine.
|
||||
transisitons (OrderedDict) -- Represents all the transitions of the state object.
|
||||
state_type (string) -- Denotes the type (accept/start/dead) of the state.
|
||||
rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.
|
||||
state_machine (instance of StateMachine object) -- The finite state machine that the state belongs to.
|
||||
'''
|
||||
|
||||
def __init__(self, name, state_machine, state_type=None, rh_rule=None):
|
||||
self.name = name
|
||||
self.transitions = {}
|
||||
self.state_machine = state_machine
|
||||
self.state_type = state_type[0]
|
||||
self.rh_rule = rh_rule
|
||||
|
||||
def add_transition(self, letter, state):
|
||||
'''
|
||||
Add a transition from the current state to a new state.
|
||||
|
||||
Keyword Arguments:
|
||||
letter -- The alphabet element the current state reads to make the state transition.
|
||||
state -- This will be an instance of the State object which represents a new state after in the transition after the alphabet is read.
|
||||
|
||||
'''
|
||||
self.transitions[letter] = state
|
||||
|
||||
class StateMachine:
|
||||
'''
|
||||
Representation of a finite state machine the manages the states and the transitions of the automaton.
|
||||
|
||||
Attributes:
|
||||
states (dictionary) -- Collection of all registered `State` objects.
|
||||
name (str) -- Name of the state machine.
|
||||
'''
|
||||
|
||||
def __init__(self, name, automaton_alphabet):
|
||||
self.name = name
|
||||
self.automaton_alphabet = automaton_alphabet
|
||||
self.states = {} # Contains all the states in the machine.
|
||||
self.add_state('start', state_type='s')
|
||||
|
||||
def add_state(self, state_name, state_type=None, rh_rule=None):
|
||||
'''
|
||||
Instantiate a state object and stores it in the 'states' dictionary.
|
||||
|
||||
Arguments:
|
||||
state_name (instance of FreeGroupElement or string) -- name of the new states.
|
||||
state_type (string) -- Denotes the type (accept/start/dead) of the state added.
|
||||
rh_rule (instance of FreeGroupElement) -- right hand rule for dead state.
|
||||
|
||||
'''
|
||||
new_state = State(state_name, self, state_type, rh_rule)
|
||||
self.states[state_name] = new_state
|
||||
|
||||
def __repr__(self):
|
||||
return "%s" % (self.name)
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
The Schur number S(k) is the largest integer n for which the interval [1,n]
|
||||
can be partitioned into k sum-free sets.(https://mathworld.wolfram.com/SchurNumber.html)
|
||||
"""
|
||||
import math
|
||||
from sympy.core import S
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.function import Function
|
||||
from sympy.core.numbers import Integer
|
||||
|
||||
|
||||
class SchurNumber(Function):
|
||||
r"""
|
||||
This function creates a SchurNumber object
|
||||
which is evaluated for `k \le 5` otherwise only
|
||||
the lower bound information can be retrieved.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.schur_number import SchurNumber
|
||||
|
||||
Since S(3) = 13, hence the output is a number
|
||||
>>> SchurNumber(3)
|
||||
13
|
||||
|
||||
We do not know the Schur number for values greater than 5, hence
|
||||
only the object is returned
|
||||
>>> SchurNumber(6)
|
||||
SchurNumber(6)
|
||||
|
||||
Now, the lower bound information can be retrieved using lower_bound()
|
||||
method
|
||||
>>> SchurNumber(6).lower_bound()
|
||||
536
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def eval(cls, k):
|
||||
if k.is_Number:
|
||||
if k is S.Infinity:
|
||||
return S.Infinity
|
||||
if k.is_zero:
|
||||
return S.Zero
|
||||
if not k.is_integer or k.is_negative:
|
||||
raise ValueError("k should be a positive integer")
|
||||
first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160}
|
||||
if k <= 5:
|
||||
return Integer(first_known_schur_numbers[k])
|
||||
|
||||
def lower_bound(self):
|
||||
f_ = self.args[0]
|
||||
# Improved lower bounds known for S(6) and S(7)
|
||||
if f_ == 6:
|
||||
return Integer(536)
|
||||
if f_ == 7:
|
||||
return Integer(1680)
|
||||
# For other cases, use general expression
|
||||
if f_.is_Integer:
|
||||
return 3*self.func(f_ - 1).lower_bound() - 1
|
||||
return (3**f_ - 1)/2
|
||||
|
||||
|
||||
def _schur_subsets_number(n):
|
||||
|
||||
if n is S.Infinity:
|
||||
raise ValueError("Input must be finite")
|
||||
if n <= 0:
|
||||
raise ValueError("n must be a non-zero positive integer.")
|
||||
elif n <= 3:
|
||||
min_k = 1
|
||||
else:
|
||||
min_k = math.ceil(math.log(2*n + 1, 3))
|
||||
|
||||
return Integer(min_k)
|
||||
|
||||
|
||||
def schur_partition(n):
|
||||
"""
|
||||
|
||||
This function returns the partition in the minimum number of sum-free subsets
|
||||
according to the lower bound given by the Schur Number.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n: a number
|
||||
n is the upper limit of the range [1, n] for which we need to find and
|
||||
return the minimum number of free subsets according to the lower bound
|
||||
of schur number
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
List of lists
|
||||
List of the minimum number of sum-free subsets
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
It is possible for some n to make the partition into less
|
||||
subsets since the only known Schur numbers are:
|
||||
S(1) = 1, S(2) = 4, S(3) = 13, S(4) = 44.
|
||||
e.g for n = 44 the lower bound from the function above is 5 subsets but it has been proven
|
||||
that can be done with 4 subsets.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
For n = 1, 2, 3 the answer is the set itself
|
||||
|
||||
>>> from sympy.combinatorics.schur_number import schur_partition
|
||||
>>> schur_partition(2)
|
||||
[[1, 2]]
|
||||
|
||||
For n > 3, the answer is the minimum number of sum-free subsets:
|
||||
|
||||
>>> schur_partition(5)
|
||||
[[3, 2], [5], [1, 4]]
|
||||
|
||||
>>> schur_partition(8)
|
||||
[[3, 2], [6, 5, 8], [1, 4, 7]]
|
||||
"""
|
||||
|
||||
if isinstance(n, Basic) and not n.is_Number:
|
||||
raise ValueError("Input value must be a number")
|
||||
|
||||
number_of_subsets = _schur_subsets_number(n)
|
||||
if n == 1:
|
||||
sum_free_subsets = [[1]]
|
||||
elif n == 2:
|
||||
sum_free_subsets = [[1, 2]]
|
||||
elif n == 3:
|
||||
sum_free_subsets = [[1, 2, 3]]
|
||||
else:
|
||||
sum_free_subsets = [[1, 4], [2, 3]]
|
||||
|
||||
while len(sum_free_subsets) < number_of_subsets:
|
||||
sum_free_subsets = _generate_next_list(sum_free_subsets, n)
|
||||
missed_elements = [3*k + 1 for k in range(len(sum_free_subsets), (n-1)//3 + 1)]
|
||||
sum_free_subsets[-1] += missed_elements
|
||||
|
||||
return sum_free_subsets
|
||||
|
||||
|
||||
def _generate_next_list(current_list, n):
|
||||
new_list = []
|
||||
|
||||
for item in current_list:
|
||||
temp_1 = [number*3 for number in item if number*3 <= n]
|
||||
temp_2 = [number*3 - 1 for number in item if number*3 - 1 <= n]
|
||||
new_item = temp_1 + temp_2
|
||||
new_list.append(new_item)
|
||||
|
||||
last_list = [3*k + 1 for k in range(len(current_list)+1) if 3*k + 1 <= n]
|
||||
new_list.append(last_list)
|
||||
current_list = new_list
|
||||
|
||||
return current_list
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
from itertools import combinations
|
||||
|
||||
from sympy.combinatorics.graycode import GrayCode
|
||||
|
||||
|
||||
class Subset():
|
||||
"""
|
||||
Represents a basic subset object.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
We generate subsets using essentially two techniques,
|
||||
binary enumeration and lexicographic enumeration.
|
||||
The Subset class takes two arguments, the first one
|
||||
describes the initial subset to consider and the second
|
||||
describes the superset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.next_binary().subset
|
||||
['b']
|
||||
>>> a.prev_binary().subset
|
||||
['c']
|
||||
"""
|
||||
|
||||
_rank_binary = None
|
||||
_rank_lex = None
|
||||
_rank_graycode = None
|
||||
_subset = None
|
||||
_superset = None
|
||||
|
||||
def __new__(cls, subset, superset):
|
||||
"""
|
||||
Default constructor.
|
||||
|
||||
It takes the ``subset`` and its ``superset`` as its parameters.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.subset
|
||||
['c', 'd']
|
||||
>>> a.superset
|
||||
['a', 'b', 'c', 'd']
|
||||
>>> a.size
|
||||
2
|
||||
"""
|
||||
if len(subset) > len(superset):
|
||||
raise ValueError('Invalid arguments have been provided. The '
|
||||
'superset must be larger than the subset.')
|
||||
for elem in subset:
|
||||
if elem not in superset:
|
||||
raise ValueError('The superset provided is invalid as it does '
|
||||
'not contain the element {}'.format(elem))
|
||||
obj = object.__new__(cls)
|
||||
obj._subset = subset
|
||||
obj._superset = superset
|
||||
return obj
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Return a boolean indicating whether a == b on the basis of
|
||||
whether both objects are of the class Subset and if the values
|
||||
of the subset and superset attributes are the same.
|
||||
"""
|
||||
if not isinstance(other, Subset):
|
||||
return NotImplemented
|
||||
return self.subset == other.subset and self.superset == other.superset
|
||||
|
||||
def iterate_binary(self, k):
|
||||
"""
|
||||
This is a helper function. It iterates over the
|
||||
binary subsets by ``k`` steps. This variable can be
|
||||
both positive or negative.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.iterate_binary(-2).subset
|
||||
['d']
|
||||
>>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.iterate_binary(2).subset
|
||||
[]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
next_binary, prev_binary
|
||||
"""
|
||||
bin_list = Subset.bitlist_from_subset(self.subset, self.superset)
|
||||
n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size
|
||||
bits = bin(n)[2:].rjust(self.superset_size, '0')
|
||||
return Subset.subset_from_bitlist(self.superset, bits)
|
||||
|
||||
def next_binary(self):
|
||||
"""
|
||||
Generates the next binary ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.next_binary().subset
|
||||
['b']
|
||||
>>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.next_binary().subset
|
||||
[]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prev_binary, iterate_binary
|
||||
"""
|
||||
return self.iterate_binary(1)
|
||||
|
||||
def prev_binary(self):
|
||||
"""
|
||||
Generates the previous binary ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([], ['a', 'b', 'c', 'd'])
|
||||
>>> a.prev_binary().subset
|
||||
['a', 'b', 'c', 'd']
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.prev_binary().subset
|
||||
['c']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
next_binary, iterate_binary
|
||||
"""
|
||||
return self.iterate_binary(-1)
|
||||
|
||||
def next_lexicographic(self):
|
||||
"""
|
||||
Generates the next lexicographically ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.next_lexicographic().subset
|
||||
['d']
|
||||
>>> a = Subset(['d'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.next_lexicographic().subset
|
||||
[]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
prev_lexicographic
|
||||
"""
|
||||
i = self.superset_size - 1
|
||||
indices = Subset.subset_indices(self.subset, self.superset)
|
||||
|
||||
if i in indices:
|
||||
if i - 1 in indices:
|
||||
indices.remove(i - 1)
|
||||
else:
|
||||
indices.remove(i)
|
||||
i = i - 1
|
||||
while i >= 0 and i not in indices:
|
||||
i = i - 1
|
||||
if i >= 0:
|
||||
indices.remove(i)
|
||||
indices.append(i+1)
|
||||
else:
|
||||
while i not in indices and i >= 0:
|
||||
i = i - 1
|
||||
indices.append(i + 1)
|
||||
|
||||
ret_set = []
|
||||
super_set = self.superset
|
||||
for i in indices:
|
||||
ret_set.append(super_set[i])
|
||||
return Subset(ret_set, super_set)
|
||||
|
||||
def prev_lexicographic(self):
|
||||
"""
|
||||
Generates the previous lexicographically ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([], ['a', 'b', 'c', 'd'])
|
||||
>>> a.prev_lexicographic().subset
|
||||
['d']
|
||||
>>> a = Subset(['c','d'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.prev_lexicographic().subset
|
||||
['c']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
next_lexicographic
|
||||
"""
|
||||
i = self.superset_size - 1
|
||||
indices = Subset.subset_indices(self.subset, self.superset)
|
||||
|
||||
while i >= 0 and i not in indices:
|
||||
i = i - 1
|
||||
|
||||
if i == 0 or i - 1 in indices:
|
||||
indices.remove(i)
|
||||
else:
|
||||
if i >= 0:
|
||||
indices.remove(i)
|
||||
indices.append(i - 1)
|
||||
indices.append(self.superset_size - 1)
|
||||
|
||||
ret_set = []
|
||||
super_set = self.superset
|
||||
for i in indices:
|
||||
ret_set.append(super_set[i])
|
||||
return Subset(ret_set, super_set)
|
||||
|
||||
def iterate_graycode(self, k):
|
||||
"""
|
||||
Helper function used for prev_gray and next_gray.
|
||||
It performs ``k`` step overs to get the respective Gray codes.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([1, 2, 3], [1, 2, 3, 4])
|
||||
>>> a.iterate_graycode(3).subset
|
||||
[1, 4]
|
||||
>>> a.iterate_graycode(-2).subset
|
||||
[1, 2, 4]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
next_gray, prev_gray
|
||||
"""
|
||||
unranked_code = GrayCode.unrank(self.superset_size,
|
||||
(self.rank_gray + k) % self.cardinality)
|
||||
return Subset.subset_from_bitlist(self.superset,
|
||||
unranked_code)
|
||||
|
||||
def next_gray(self):
|
||||
"""
|
||||
Generates the next Gray code ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([1, 2, 3], [1, 2, 3, 4])
|
||||
>>> a.next_gray().subset
|
||||
[1, 3]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_graycode, prev_gray
|
||||
"""
|
||||
return self.iterate_graycode(1)
|
||||
|
||||
def prev_gray(self):
|
||||
"""
|
||||
Generates the previous Gray code ordered subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5])
|
||||
>>> a.prev_gray().subset
|
||||
[2, 3, 4, 5]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_graycode, next_gray
|
||||
"""
|
||||
return self.iterate_graycode(-1)
|
||||
|
||||
@property
|
||||
def rank_binary(self):
|
||||
"""
|
||||
Computes the binary ordered rank.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset([], ['a','b','c','d'])
|
||||
>>> a.rank_binary
|
||||
0
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.rank_binary
|
||||
3
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_binary, unrank_binary
|
||||
"""
|
||||
if self._rank_binary is None:
|
||||
self._rank_binary = int("".join(
|
||||
Subset.bitlist_from_subset(self.subset,
|
||||
self.superset)), 2)
|
||||
return self._rank_binary
|
||||
|
||||
@property
|
||||
def rank_lexicographic(self):
|
||||
"""
|
||||
Computes the lexicographic ranking of the subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.rank_lexicographic
|
||||
14
|
||||
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
|
||||
>>> a.rank_lexicographic
|
||||
43
|
||||
"""
|
||||
if self._rank_lex is None:
|
||||
def _ranklex(self, subset_index, i, n):
|
||||
if subset_index == [] or i > n:
|
||||
return 0
|
||||
if i in subset_index:
|
||||
subset_index.remove(i)
|
||||
return 1 + _ranklex(self, subset_index, i + 1, n)
|
||||
return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n)
|
||||
indices = Subset.subset_indices(self.subset, self.superset)
|
||||
self._rank_lex = _ranklex(self, indices, 0, self.superset_size)
|
||||
return self._rank_lex
|
||||
|
||||
@property
|
||||
def rank_gray(self):
|
||||
"""
|
||||
Computes the Gray code ranking of the subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c','d'], ['a','b','c','d'])
|
||||
>>> a.rank_gray
|
||||
2
|
||||
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6])
|
||||
>>> a.rank_gray
|
||||
27
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_graycode, unrank_gray
|
||||
"""
|
||||
if self._rank_graycode is None:
|
||||
bits = Subset.bitlist_from_subset(self.subset, self.superset)
|
||||
self._rank_graycode = GrayCode(len(bits), start=bits).rank
|
||||
return self._rank_graycode
|
||||
|
||||
@property
|
||||
def subset(self):
|
||||
"""
|
||||
Gets the subset represented by the current instance.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.subset
|
||||
['c', 'd']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
superset, size, superset_size, cardinality
|
||||
"""
|
||||
return self._subset
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""
|
||||
Gets the size of the subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.size
|
||||
2
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
subset, superset, superset_size, cardinality
|
||||
"""
|
||||
return len(self.subset)
|
||||
|
||||
@property
|
||||
def superset(self):
|
||||
"""
|
||||
Gets the superset of the subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.superset
|
||||
['a', 'b', 'c', 'd']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
subset, size, superset_size, cardinality
|
||||
"""
|
||||
return self._superset
|
||||
|
||||
@property
|
||||
def superset_size(self):
|
||||
"""
|
||||
Returns the size of the superset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.superset_size
|
||||
4
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
subset, superset, size, cardinality
|
||||
"""
|
||||
return len(self.superset)
|
||||
|
||||
@property
|
||||
def cardinality(self):
|
||||
"""
|
||||
Returns the number of all possible subsets.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
>>> a.cardinality
|
||||
16
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
subset, superset, size, superset_size
|
||||
"""
|
||||
return 2**(self.superset_size)
|
||||
|
||||
@classmethod
|
||||
def subset_from_bitlist(self, super_set, bitlist):
|
||||
"""
|
||||
Gets the subset defined by the bitlist.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset
|
||||
['c', 'd']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
bitlist_from_subset
|
||||
"""
|
||||
if len(super_set) != len(bitlist):
|
||||
raise ValueError("The sizes of the lists are not equal")
|
||||
ret_set = []
|
||||
for i in range(len(bitlist)):
|
||||
if bitlist[i] == '1':
|
||||
ret_set.append(super_set[i])
|
||||
return Subset(ret_set, super_set)
|
||||
|
||||
@classmethod
|
||||
def bitlist_from_subset(self, subset, superset):
|
||||
"""
|
||||
Gets the bitlist corresponding to a subset.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
'0011'
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
subset_from_bitlist
|
||||
"""
|
||||
bitlist = ['0'] * len(superset)
|
||||
if isinstance(subset, Subset):
|
||||
subset = subset.subset
|
||||
for i in Subset.subset_indices(subset, superset):
|
||||
bitlist[i] = '1'
|
||||
return ''.join(bitlist)
|
||||
|
||||
@classmethod
|
||||
def unrank_binary(self, rank, superset):
|
||||
"""
|
||||
Gets the binary ordered subset of the specified rank.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset
|
||||
['b']
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_binary, rank_binary
|
||||
"""
|
||||
bits = bin(rank)[2:].rjust(len(superset), '0')
|
||||
return Subset.subset_from_bitlist(superset, bits)
|
||||
|
||||
@classmethod
|
||||
def unrank_gray(self, rank, superset):
|
||||
"""
|
||||
Gets the Gray code ordered subset of the specified rank.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset
|
||||
['a', 'b']
|
||||
>>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset
|
||||
[]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
iterate_graycode, rank_gray
|
||||
"""
|
||||
graycode_bitlist = GrayCode.unrank(len(superset), rank)
|
||||
return Subset.subset_from_bitlist(superset, graycode_bitlist)
|
||||
|
||||
@classmethod
|
||||
def subset_indices(self, subset, superset):
|
||||
"""Return indices of subset in superset in a list; the list is empty
|
||||
if all elements of ``subset`` are not in ``superset``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Subset
|
||||
>>> superset = [1, 3, 2, 5, 4]
|
||||
>>> Subset.subset_indices([3, 2, 1], superset)
|
||||
[1, 2, 0]
|
||||
>>> Subset.subset_indices([1, 6], superset)
|
||||
[]
|
||||
>>> Subset.subset_indices([], superset)
|
||||
[]
|
||||
|
||||
"""
|
||||
a, b = superset, subset
|
||||
sb = set(b)
|
||||
d = {}
|
||||
for i, ai in enumerate(a):
|
||||
if ai in sb:
|
||||
d[ai] = i
|
||||
sb.remove(ai)
|
||||
if not sb:
|
||||
break
|
||||
else:
|
||||
return []
|
||||
return [d[bi] for bi in b]
|
||||
|
||||
|
||||
def ksubsets(superset, k):
|
||||
"""
|
||||
Finds the subsets of size ``k`` in lexicographic order.
|
||||
|
||||
This uses the itertools generator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.subsets import ksubsets
|
||||
>>> list(ksubsets([1, 2, 3], 2))
|
||||
[(1, 2), (1, 3), (2, 3)]
|
||||
>>> list(ksubsets([1, 2, 3, 4, 5], 2))
|
||||
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \
|
||||
(2, 5), (3, 4), (3, 5), (4, 5)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
Subset
|
||||
"""
|
||||
return combinations(superset, k)
|
||||
+1189
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+825
@@ -0,0 +1,825 @@
|
||||
from sympy.combinatorics.fp_groups import FpGroup
|
||||
from sympy.combinatorics.coset_table import (CosetTable,
|
||||
coset_enumeration_r, coset_enumeration_c)
|
||||
from sympy.combinatorics.coset_table import modified_coset_enumeration_r
|
||||
from sympy.combinatorics.free_groups import free_group
|
||||
|
||||
from sympy.testing.pytest import slow
|
||||
|
||||
"""
|
||||
References
|
||||
==========
|
||||
|
||||
[1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of Computational Group Theory"
|
||||
|
||||
[2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
|
||||
Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
|
||||
"Implementation and Analysis of the Todd-Coxeter Algorithm"
|
||||
|
||||
"""
|
||||
|
||||
def test_scan_1():
|
||||
# Example 5.1 from [1]
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
||||
c = CosetTable(f, [x])
|
||||
|
||||
c.scan_and_fill(0, x)
|
||||
assert c.table == [[0, 0, None, None]]
|
||||
assert c.p == [0]
|
||||
assert c.n == 1
|
||||
assert c.omega == [0]
|
||||
|
||||
c.scan_and_fill(0, x**3)
|
||||
assert c.table == [[0, 0, None, None]]
|
||||
assert c.p == [0]
|
||||
assert c.n == 1
|
||||
assert c.omega == [0]
|
||||
|
||||
c.scan_and_fill(0, y**3)
|
||||
assert c.table == [[0, 0, 1, 2], [None, None, 2, 0], [None, None, 0, 1]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(0, x**-1*y**-1*x*y)
|
||||
assert c.table == [[0, 0, 1, 2], [None, None, 2, 0], [2, 2, 0, 1]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(1, x**3)
|
||||
assert c.table == [[0, 0, 1, 2], [3, 4, 2, 0], [2, 2, 0, 1], \
|
||||
[4, 1, None, None], [1, 3, None, None]]
|
||||
assert c.p == [0, 1, 2, 3, 4]
|
||||
assert c.n == 5
|
||||
assert c.omega == [0, 1, 2, 3, 4]
|
||||
|
||||
c.scan_and_fill(1, y**3)
|
||||
assert c.table == [[0, 0, 1, 2], [3, 4, 2, 0], [2, 2, 0, 1], \
|
||||
[4, 1, None, None], [1, 3, None, None]]
|
||||
assert c.p == [0, 1, 2, 3, 4]
|
||||
assert c.n == 5
|
||||
assert c.omega == [0, 1, 2, 3, 4]
|
||||
|
||||
c.scan_and_fill(1, x**-1*y**-1*x*y)
|
||||
assert c.table == [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], \
|
||||
[None, 1, None, None], [1, 3, None, None]]
|
||||
assert c.p == [0, 1, 2, 1, 1]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
# Example 5.2 from [1]
|
||||
f = FpGroup(F, [x**2, y**3, (x*y)**3])
|
||||
c = CosetTable(f, [x*y])
|
||||
|
||||
c.scan_and_fill(0, x*y)
|
||||
assert c.table == [[1, None, None, 1], [None, 0, 0, None]]
|
||||
assert c.p == [0, 1]
|
||||
assert c.n == 2
|
||||
assert c.omega == [0, 1]
|
||||
|
||||
c.scan_and_fill(0, x**2)
|
||||
assert c.table == [[1, 1, None, 1], [0, 0, 0, None]]
|
||||
assert c.p == [0, 1]
|
||||
assert c.n == 2
|
||||
assert c.omega == [0, 1]
|
||||
|
||||
c.scan_and_fill(0, y**3)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(0, (x*y)**3)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(1, x**2)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(1, y**3)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]]
|
||||
assert c.p == [0, 1, 2]
|
||||
assert c.n == 3
|
||||
assert c.omega == [0, 1, 2]
|
||||
|
||||
c.scan_and_fill(1, (x*y)**3)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [3, 4, 1, 0], [None, 2, 4, None], [2, None, None, 3]]
|
||||
assert c.p == [0, 1, 2, 3, 4]
|
||||
assert c.n == 5
|
||||
assert c.omega == [0, 1, 2, 3, 4]
|
||||
|
||||
c.scan_and_fill(2, x**2)
|
||||
assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [3, 3, 1, 0], [2, 2, 3, 3], [2, None, None, 3]]
|
||||
assert c.p == [0, 1, 2, 3, 3]
|
||||
assert c.n == 4
|
||||
assert c.omega == [0, 1, 2, 3]
|
||||
|
||||
|
||||
@slow
|
||||
def test_coset_enumeration():
|
||||
# this test function contains the combined tests for the two strategies
|
||||
# i.e. HLT and Felsch strategies.
|
||||
|
||||
# Example 5.1 from [1]
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
||||
C_r = coset_enumeration_r(f, [x])
|
||||
C_r.compress(); C_r.standardize()
|
||||
C_c = coset_enumeration_c(f, [x])
|
||||
C_c.compress(); C_c.standardize()
|
||||
table1 = [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
|
||||
assert C_r.table == table1
|
||||
assert C_c.table == table1
|
||||
|
||||
# E1 from [2] Pg. 474
|
||||
F, r, s, t = free_group("r, s, t")
|
||||
E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2])
|
||||
C_r = coset_enumeration_r(E1, [])
|
||||
C_r.compress()
|
||||
C_c = coset_enumeration_c(E1, [])
|
||||
C_c.compress()
|
||||
table2 = [[0, 0, 0, 0, 0, 0]]
|
||||
assert C_r.table == table2
|
||||
# test for issue #11449
|
||||
assert C_c.table == table2
|
||||
|
||||
# Cox group from [2] Pg. 474
|
||||
F, a, b = free_group("a, b")
|
||||
Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5])
|
||||
C_r = coset_enumeration_r(Cox, [a])
|
||||
C_r.compress(); C_r.standardize()
|
||||
C_c = coset_enumeration_c(Cox, [a])
|
||||
C_c.compress(); C_c.standardize()
|
||||
table3 = [[0, 0, 1, 2],
|
||||
[2, 3, 4, 0],
|
||||
[5, 1, 0, 6],
|
||||
[1, 7, 8, 9],
|
||||
[9, 10, 11, 1],
|
||||
[12, 2, 9, 13],
|
||||
[14, 9, 2, 11],
|
||||
[3, 12, 15, 16],
|
||||
[16, 17, 18, 3],
|
||||
[6, 4, 3, 5],
|
||||
[4, 19, 20, 21],
|
||||
[21, 22, 6, 4],
|
||||
[7, 5, 23, 24],
|
||||
[25, 23, 5, 18],
|
||||
[19, 6, 22, 26],
|
||||
[24, 27, 28, 7],
|
||||
[29, 8, 7, 30],
|
||||
[8, 31, 32, 33],
|
||||
[33, 34, 13, 8],
|
||||
[10, 14, 35, 35],
|
||||
[35, 36, 37, 10],
|
||||
[30, 11, 10, 29],
|
||||
[11, 38, 39, 14],
|
||||
[13, 39, 38, 12],
|
||||
[40, 15, 12, 41],
|
||||
[42, 13, 34, 43],
|
||||
[44, 35, 14, 45],
|
||||
[15, 46, 47, 34],
|
||||
[34, 48, 49, 15],
|
||||
[50, 16, 21, 51],
|
||||
[52, 21, 16, 49],
|
||||
[17, 50, 53, 54],
|
||||
[54, 55, 56, 17],
|
||||
[41, 18, 17, 40],
|
||||
[18, 28, 27, 25],
|
||||
[26, 20, 19, 19],
|
||||
[20, 57, 58, 59],
|
||||
[59, 60, 51, 20],
|
||||
[22, 52, 61, 23],
|
||||
[23, 62, 63, 22],
|
||||
[64, 24, 33, 65],
|
||||
[48, 33, 24, 61],
|
||||
[62, 25, 54, 66],
|
||||
[67, 54, 25, 68],
|
||||
[57, 26, 59, 69],
|
||||
[70, 59, 26, 63],
|
||||
[27, 64, 71, 72],
|
||||
[72, 73, 68, 27],
|
||||
[28, 41, 74, 75],
|
||||
[75, 76, 30, 28],
|
||||
[31, 29, 77, 78],
|
||||
[79, 77, 29, 37],
|
||||
[38, 30, 76, 80],
|
||||
[78, 81, 82, 31],
|
||||
[43, 32, 31, 42],
|
||||
[32, 83, 84, 85],
|
||||
[85, 86, 65, 32],
|
||||
[36, 44, 87, 88],
|
||||
[88, 89, 90, 36],
|
||||
[45, 37, 36, 44],
|
||||
[37, 82, 81, 79],
|
||||
[80, 74, 41, 38],
|
||||
[39, 42, 91, 92],
|
||||
[92, 93, 45, 39],
|
||||
[46, 40, 94, 95],
|
||||
[96, 94, 40, 56],
|
||||
[97, 91, 42, 82],
|
||||
[83, 43, 98, 99],
|
||||
[100, 98, 43, 47],
|
||||
[101, 87, 44, 90],
|
||||
[82, 45, 93, 97],
|
||||
[95, 102, 103, 46],
|
||||
[104, 47, 46, 105],
|
||||
[47, 106, 107, 100],
|
||||
[61, 108, 109, 48],
|
||||
[105, 49, 48, 104],
|
||||
[49, 110, 111, 52],
|
||||
[51, 111, 110, 50],
|
||||
[112, 53, 50, 113],
|
||||
[114, 51, 60, 115],
|
||||
[116, 61, 52, 117],
|
||||
[53, 118, 119, 60],
|
||||
[60, 70, 66, 53],
|
||||
[55, 67, 120, 121],
|
||||
[121, 122, 123, 55],
|
||||
[113, 56, 55, 112],
|
||||
[56, 103, 102, 96],
|
||||
[69, 124, 125, 57],
|
||||
[115, 58, 57, 114],
|
||||
[58, 126, 127, 128],
|
||||
[128, 128, 69, 58],
|
||||
[66, 129, 130, 62],
|
||||
[117, 63, 62, 116],
|
||||
[63, 125, 124, 70],
|
||||
[65, 109, 108, 64],
|
||||
[131, 71, 64, 132],
|
||||
[133, 65, 86, 134],
|
||||
[135, 66, 70, 136],
|
||||
[68, 130, 129, 67],
|
||||
[137, 120, 67, 138],
|
||||
[132, 68, 73, 131],
|
||||
[139, 69, 128, 140],
|
||||
[71, 141, 142, 86],
|
||||
[86, 143, 144, 71],
|
||||
[145, 72, 75, 146],
|
||||
[147, 75, 72, 144],
|
||||
[73, 145, 148, 120],
|
||||
[120, 149, 150, 73],
|
||||
[74, 151, 152, 94],
|
||||
[94, 153, 146, 74],
|
||||
[76, 147, 154, 77],
|
||||
[77, 155, 156, 76],
|
||||
[157, 78, 85, 158],
|
||||
[143, 85, 78, 154],
|
||||
[155, 79, 88, 159],
|
||||
[160, 88, 79, 161],
|
||||
[151, 80, 92, 162],
|
||||
[163, 92, 80, 156],
|
||||
[81, 157, 164, 165],
|
||||
[165, 166, 161, 81],
|
||||
[99, 107, 106, 83],
|
||||
[134, 84, 83, 133],
|
||||
[84, 167, 168, 169],
|
||||
[169, 170, 158, 84],
|
||||
[87, 171, 172, 93],
|
||||
[93, 163, 159, 87],
|
||||
[89, 160, 173, 174],
|
||||
[174, 175, 176, 89],
|
||||
[90, 90, 89, 101],
|
||||
[91, 177, 178, 98],
|
||||
[98, 179, 162, 91],
|
||||
[180, 95, 100, 181],
|
||||
[179, 100, 95, 152],
|
||||
[153, 96, 121, 148],
|
||||
[182, 121, 96, 183],
|
||||
[177, 97, 165, 184],
|
||||
[185, 165, 97, 172],
|
||||
[186, 99, 169, 187],
|
||||
[188, 169, 99, 178],
|
||||
[171, 101, 174, 189],
|
||||
[190, 174, 101, 176],
|
||||
[102, 180, 191, 192],
|
||||
[192, 193, 183, 102],
|
||||
[103, 113, 194, 195],
|
||||
[195, 196, 105, 103],
|
||||
[106, 104, 197, 198],
|
||||
[199, 197, 104, 109],
|
||||
[110, 105, 196, 200],
|
||||
[198, 201, 133, 106],
|
||||
[107, 186, 202, 203],
|
||||
[203, 204, 181, 107],
|
||||
[108, 116, 205, 206],
|
||||
[206, 207, 132, 108],
|
||||
[109, 133, 201, 199],
|
||||
[200, 194, 113, 110],
|
||||
[111, 114, 208, 209],
|
||||
[209, 210, 117, 111],
|
||||
[118, 112, 211, 212],
|
||||
[213, 211, 112, 123],
|
||||
[214, 208, 114, 125],
|
||||
[126, 115, 215, 216],
|
||||
[217, 215, 115, 119],
|
||||
[218, 205, 116, 130],
|
||||
[125, 117, 210, 214],
|
||||
[212, 219, 220, 118],
|
||||
[136, 119, 118, 135],
|
||||
[119, 221, 222, 217],
|
||||
[122, 182, 223, 224],
|
||||
[224, 225, 226, 122],
|
||||
[138, 123, 122, 137],
|
||||
[123, 220, 219, 213],
|
||||
[124, 139, 227, 228],
|
||||
[228, 229, 136, 124],
|
||||
[216, 222, 221, 126],
|
||||
[140, 127, 126, 139],
|
||||
[127, 230, 231, 232],
|
||||
[232, 233, 140, 127],
|
||||
[129, 135, 234, 235],
|
||||
[235, 236, 138, 129],
|
||||
[130, 132, 207, 218],
|
||||
[141, 131, 237, 238],
|
||||
[239, 237, 131, 150],
|
||||
[167, 134, 240, 241],
|
||||
[242, 240, 134, 142],
|
||||
[243, 234, 135, 220],
|
||||
[221, 136, 229, 244],
|
||||
[149, 137, 245, 246],
|
||||
[247, 245, 137, 226],
|
||||
[220, 138, 236, 243],
|
||||
[244, 227, 139, 221],
|
||||
[230, 140, 233, 248],
|
||||
[238, 249, 250, 141],
|
||||
[251, 142, 141, 252],
|
||||
[142, 253, 254, 242],
|
||||
[154, 255, 256, 143],
|
||||
[252, 144, 143, 251],
|
||||
[144, 257, 258, 147],
|
||||
[146, 258, 257, 145],
|
||||
[259, 148, 145, 260],
|
||||
[261, 146, 153, 262],
|
||||
[263, 154, 147, 264],
|
||||
[148, 265, 266, 153],
|
||||
[246, 267, 268, 149],
|
||||
[260, 150, 149, 259],
|
||||
[150, 250, 249, 239],
|
||||
[162, 269, 270, 151],
|
||||
[262, 152, 151, 261],
|
||||
[152, 271, 272, 179],
|
||||
[159, 273, 274, 155],
|
||||
[264, 156, 155, 263],
|
||||
[156, 270, 269, 163],
|
||||
[158, 256, 255, 157],
|
||||
[275, 164, 157, 276],
|
||||
[277, 158, 170, 278],
|
||||
[279, 159, 163, 280],
|
||||
[161, 274, 273, 160],
|
||||
[281, 173, 160, 282],
|
||||
[276, 161, 166, 275],
|
||||
[283, 162, 179, 284],
|
||||
[164, 285, 286, 170],
|
||||
[170, 188, 184, 164],
|
||||
[166, 185, 189, 173],
|
||||
[173, 287, 288, 166],
|
||||
[241, 254, 253, 167],
|
||||
[278, 168, 167, 277],
|
||||
[168, 289, 290, 291],
|
||||
[291, 292, 187, 168],
|
||||
[189, 293, 294, 171],
|
||||
[280, 172, 171, 279],
|
||||
[172, 295, 296, 185],
|
||||
[175, 190, 297, 297],
|
||||
[297, 298, 299, 175],
|
||||
[282, 176, 175, 281],
|
||||
[176, 294, 293, 190],
|
||||
[184, 296, 295, 177],
|
||||
[284, 178, 177, 283],
|
||||
[178, 300, 301, 188],
|
||||
[181, 272, 271, 180],
|
||||
[302, 191, 180, 303],
|
||||
[304, 181, 204, 305],
|
||||
[183, 266, 265, 182],
|
||||
[306, 223, 182, 307],
|
||||
[303, 183, 193, 302],
|
||||
[308, 184, 188, 309],
|
||||
[310, 189, 185, 311],
|
||||
[187, 301, 300, 186],
|
||||
[305, 202, 186, 304],
|
||||
[312, 187, 292, 313],
|
||||
[314, 297, 190, 315],
|
||||
[191, 316, 317, 204],
|
||||
[204, 318, 319, 191],
|
||||
[320, 192, 195, 321],
|
||||
[322, 195, 192, 319],
|
||||
[193, 320, 323, 223],
|
||||
[223, 324, 325, 193],
|
||||
[194, 326, 327, 211],
|
||||
[211, 328, 321, 194],
|
||||
[196, 322, 329, 197],
|
||||
[197, 330, 331, 196],
|
||||
[332, 198, 203, 333],
|
||||
[318, 203, 198, 329],
|
||||
[330, 199, 206, 334],
|
||||
[335, 206, 199, 336],
|
||||
[326, 200, 209, 337],
|
||||
[338, 209, 200, 331],
|
||||
[201, 332, 339, 240],
|
||||
[240, 340, 336, 201],
|
||||
[202, 341, 342, 292],
|
||||
[292, 343, 333, 202],
|
||||
[205, 344, 345, 210],
|
||||
[210, 338, 334, 205],
|
||||
[207, 335, 346, 237],
|
||||
[237, 347, 348, 207],
|
||||
[208, 349, 350, 215],
|
||||
[215, 351, 337, 208],
|
||||
[352, 212, 217, 353],
|
||||
[351, 217, 212, 327],
|
||||
[328, 213, 224, 323],
|
||||
[354, 224, 213, 355],
|
||||
[349, 214, 228, 356],
|
||||
[357, 228, 214, 345],
|
||||
[358, 216, 232, 359],
|
||||
[360, 232, 216, 350],
|
||||
[344, 218, 235, 361],
|
||||
[362, 235, 218, 348],
|
||||
[219, 352, 363, 364],
|
||||
[364, 365, 355, 219],
|
||||
[222, 358, 366, 367],
|
||||
[367, 368, 353, 222],
|
||||
[225, 354, 369, 370],
|
||||
[370, 371, 372, 225],
|
||||
[307, 226, 225, 306],
|
||||
[226, 268, 267, 247],
|
||||
[227, 373, 374, 233],
|
||||
[233, 360, 356, 227],
|
||||
[229, 357, 361, 234],
|
||||
[234, 375, 376, 229],
|
||||
[248, 231, 230, 230],
|
||||
[231, 377, 378, 379],
|
||||
[379, 380, 359, 231],
|
||||
[236, 362, 381, 245],
|
||||
[245, 382, 383, 236],
|
||||
[384, 238, 242, 385],
|
||||
[340, 242, 238, 346],
|
||||
[347, 239, 246, 381],
|
||||
[386, 246, 239, 387],
|
||||
[388, 241, 291, 389],
|
||||
[343, 291, 241, 339],
|
||||
[375, 243, 364, 390],
|
||||
[391, 364, 243, 383],
|
||||
[373, 244, 367, 392],
|
||||
[393, 367, 244, 376],
|
||||
[382, 247, 370, 394],
|
||||
[395, 370, 247, 396],
|
||||
[377, 248, 379, 397],
|
||||
[398, 379, 248, 374],
|
||||
[249, 384, 399, 400],
|
||||
[400, 401, 387, 249],
|
||||
[250, 260, 402, 403],
|
||||
[403, 404, 252, 250],
|
||||
[253, 251, 405, 406],
|
||||
[407, 405, 251, 256],
|
||||
[257, 252, 404, 408],
|
||||
[406, 409, 277, 253],
|
||||
[254, 388, 410, 411],
|
||||
[411, 412, 385, 254],
|
||||
[255, 263, 413, 414],
|
||||
[414, 415, 276, 255],
|
||||
[256, 277, 409, 407],
|
||||
[408, 402, 260, 257],
|
||||
[258, 261, 416, 417],
|
||||
[417, 418, 264, 258],
|
||||
[265, 259, 419, 420],
|
||||
[421, 419, 259, 268],
|
||||
[422, 416, 261, 270],
|
||||
[271, 262, 423, 424],
|
||||
[425, 423, 262, 266],
|
||||
[426, 413, 263, 274],
|
||||
[270, 264, 418, 422],
|
||||
[420, 427, 307, 265],
|
||||
[266, 303, 428, 425],
|
||||
[267, 386, 429, 430],
|
||||
[430, 431, 396, 267],
|
||||
[268, 307, 427, 421],
|
||||
[269, 283, 432, 433],
|
||||
[433, 434, 280, 269],
|
||||
[424, 428, 303, 271],
|
||||
[272, 304, 435, 436],
|
||||
[436, 437, 284, 272],
|
||||
[273, 279, 438, 439],
|
||||
[439, 440, 282, 273],
|
||||
[274, 276, 415, 426],
|
||||
[285, 275, 441, 442],
|
||||
[443, 441, 275, 288],
|
||||
[289, 278, 444, 445],
|
||||
[446, 444, 278, 286],
|
||||
[447, 438, 279, 294],
|
||||
[295, 280, 434, 448],
|
||||
[287, 281, 449, 450],
|
||||
[451, 449, 281, 299],
|
||||
[294, 282, 440, 447],
|
||||
[448, 432, 283, 295],
|
||||
[300, 284, 437, 452],
|
||||
[442, 453, 454, 285],
|
||||
[309, 286, 285, 308],
|
||||
[286, 455, 456, 446],
|
||||
[450, 457, 458, 287],
|
||||
[311, 288, 287, 310],
|
||||
[288, 454, 453, 443],
|
||||
[445, 456, 455, 289],
|
||||
[313, 290, 289, 312],
|
||||
[290, 459, 460, 461],
|
||||
[461, 462, 389, 290],
|
||||
[293, 310, 463, 464],
|
||||
[464, 465, 315, 293],
|
||||
[296, 308, 466, 467],
|
||||
[467, 468, 311, 296],
|
||||
[298, 314, 469, 470],
|
||||
[470, 471, 472, 298],
|
||||
[315, 299, 298, 314],
|
||||
[299, 458, 457, 451],
|
||||
[452, 435, 304, 300],
|
||||
[301, 312, 473, 474],
|
||||
[474, 475, 309, 301],
|
||||
[316, 302, 476, 477],
|
||||
[478, 476, 302, 325],
|
||||
[341, 305, 479, 480],
|
||||
[481, 479, 305, 317],
|
||||
[324, 306, 482, 483],
|
||||
[484, 482, 306, 372],
|
||||
[485, 466, 308, 454],
|
||||
[455, 309, 475, 486],
|
||||
[487, 463, 310, 458],
|
||||
[454, 311, 468, 485],
|
||||
[486, 473, 312, 455],
|
||||
[459, 313, 488, 489],
|
||||
[490, 488, 313, 342],
|
||||
[491, 469, 314, 472],
|
||||
[458, 315, 465, 487],
|
||||
[477, 492, 485, 316],
|
||||
[463, 317, 316, 468],
|
||||
[317, 487, 493, 481],
|
||||
[329, 447, 464, 318],
|
||||
[468, 319, 318, 463],
|
||||
[319, 467, 448, 322],
|
||||
[321, 448, 467, 320],
|
||||
[475, 323, 320, 466],
|
||||
[432, 321, 328, 437],
|
||||
[438, 329, 322, 434],
|
||||
[323, 474, 452, 328],
|
||||
[483, 494, 486, 324],
|
||||
[466, 325, 324, 475],
|
||||
[325, 485, 492, 478],
|
||||
[337, 422, 433, 326],
|
||||
[437, 327, 326, 432],
|
||||
[327, 436, 424, 351],
|
||||
[334, 426, 439, 330],
|
||||
[434, 331, 330, 438],
|
||||
[331, 433, 422, 338],
|
||||
[333, 464, 447, 332],
|
||||
[449, 339, 332, 440],
|
||||
[465, 333, 343, 469],
|
||||
[413, 334, 338, 418],
|
||||
[336, 439, 426, 335],
|
||||
[441, 346, 335, 415],
|
||||
[440, 336, 340, 449],
|
||||
[416, 337, 351, 423],
|
||||
[339, 451, 470, 343],
|
||||
[346, 443, 450, 340],
|
||||
[480, 493, 487, 341],
|
||||
[469, 342, 341, 465],
|
||||
[342, 491, 495, 490],
|
||||
[361, 407, 414, 344],
|
||||
[418, 345, 344, 413],
|
||||
[345, 417, 408, 357],
|
||||
[381, 446, 442, 347],
|
||||
[415, 348, 347, 441],
|
||||
[348, 414, 407, 362],
|
||||
[356, 408, 417, 349],
|
||||
[423, 350, 349, 416],
|
||||
[350, 425, 420, 360],
|
||||
[353, 424, 436, 352],
|
||||
[479, 363, 352, 435],
|
||||
[428, 353, 368, 476],
|
||||
[355, 452, 474, 354],
|
||||
[488, 369, 354, 473],
|
||||
[435, 355, 365, 479],
|
||||
[402, 356, 360, 419],
|
||||
[405, 361, 357, 404],
|
||||
[359, 420, 425, 358],
|
||||
[476, 366, 358, 428],
|
||||
[427, 359, 380, 482],
|
||||
[444, 381, 362, 409],
|
||||
[363, 481, 477, 368],
|
||||
[368, 393, 390, 363],
|
||||
[365, 391, 394, 369],
|
||||
[369, 490, 480, 365],
|
||||
[366, 478, 483, 380],
|
||||
[380, 398, 392, 366],
|
||||
[371, 395, 496, 497],
|
||||
[497, 498, 489, 371],
|
||||
[473, 372, 371, 488],
|
||||
[372, 486, 494, 484],
|
||||
[392, 400, 403, 373],
|
||||
[419, 374, 373, 402],
|
||||
[374, 421, 430, 398],
|
||||
[390, 411, 406, 375],
|
||||
[404, 376, 375, 405],
|
||||
[376, 403, 400, 393],
|
||||
[397, 430, 421, 377],
|
||||
[482, 378, 377, 427],
|
||||
[378, 484, 497, 499],
|
||||
[499, 499, 397, 378],
|
||||
[394, 461, 445, 382],
|
||||
[409, 383, 382, 444],
|
||||
[383, 406, 411, 391],
|
||||
[385, 450, 443, 384],
|
||||
[492, 399, 384, 453],
|
||||
[457, 385, 412, 493],
|
||||
[387, 442, 446, 386],
|
||||
[494, 429, 386, 456],
|
||||
[453, 387, 401, 492],
|
||||
[389, 470, 451, 388],
|
||||
[493, 410, 388, 457],
|
||||
[471, 389, 462, 495],
|
||||
[412, 390, 393, 399],
|
||||
[462, 394, 391, 410],
|
||||
[401, 392, 398, 429],
|
||||
[396, 445, 461, 395],
|
||||
[498, 496, 395, 460],
|
||||
[456, 396, 431, 494],
|
||||
[431, 397, 499, 496],
|
||||
[399, 477, 481, 412],
|
||||
[429, 483, 478, 401],
|
||||
[410, 480, 490, 462],
|
||||
[496, 497, 484, 431],
|
||||
[489, 495, 491, 459],
|
||||
[495, 460, 459, 471],
|
||||
[460, 489, 498, 498],
|
||||
[472, 472, 471, 491]]
|
||||
|
||||
assert C_r.table == table3
|
||||
assert C_c.table == table3
|
||||
|
||||
# Group denoted by B2,4 from [2] Pg. 474
|
||||
F, a, b = free_group("a, b")
|
||||
B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \
|
||||
(a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4])
|
||||
C_r = coset_enumeration_r(B_2_4, [a])
|
||||
C_c = coset_enumeration_c(B_2_4, [a])
|
||||
index_r = 0
|
||||
for i in range(len(C_r.p)):
|
||||
if C_r.p[i] == i:
|
||||
index_r += 1
|
||||
assert index_r == 1024
|
||||
|
||||
index_c = 0
|
||||
for i in range(len(C_c.p)):
|
||||
if C_c.p[i] == i:
|
||||
index_c += 1
|
||||
assert index_c == 1024
|
||||
|
||||
# trivial Macdonald group G(2,2) from [2] Pg. 480
|
||||
M = FpGroup(F, [b**-1*a**-1*b*a*b**-1*a*b*a**-2, a**-1*b**-1*a*b*a**-1*b*a*b**-2])
|
||||
C_r = coset_enumeration_r(M, [a])
|
||||
C_r.compress(); C_r.standardize()
|
||||
C_c = coset_enumeration_c(M, [a])
|
||||
C_c.compress(); C_c.standardize()
|
||||
table4 = [[0, 0, 0, 0]]
|
||||
assert C_r.table == table4
|
||||
assert C_c.table == table4
|
||||
|
||||
|
||||
def test_look_ahead():
|
||||
# Section 3.2 [Test Example] Example (d) from [2]
|
||||
F, a, b, c = free_group("a, b, c")
|
||||
f = FpGroup(F, [a**11, b**5, c**4, (a*c)**3, b**2*c**-1*b**-1*c, a**4*b**-1*a**-1*b])
|
||||
H = [c, b, c**2]
|
||||
table0 = [[1, 2, 0, 0, 0, 0],
|
||||
[3, 0, 4, 5, 6, 7],
|
||||
[0, 8, 9, 10, 11, 12],
|
||||
[5, 1, 10, 13, 14, 15],
|
||||
[16, 5, 16, 1, 17, 18],
|
||||
[4, 3, 1, 8, 19, 20],
|
||||
[12, 21, 22, 23, 24, 1],
|
||||
[25, 26, 27, 28, 1, 24],
|
||||
[2, 10, 5, 16, 22, 28],
|
||||
[10, 13, 13, 2, 29, 30]]
|
||||
CosetTable.max_stack_size = 10
|
||||
C_c = coset_enumeration_c(f, H)
|
||||
C_c.compress(); C_c.standardize()
|
||||
assert C_c.table[: 10] == table0
|
||||
|
||||
def test_modified_methods():
|
||||
'''
|
||||
Tests for modified coset table methods.
|
||||
Example 5.7 from [1] Holt, D., Eick, B., O'Brien
|
||||
"Handbook of Computational Group Theory".
|
||||
|
||||
'''
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**3, y**5, (x*y)**2])
|
||||
H = [x*y, x**-1*y**-1*x*y*x]
|
||||
C = CosetTable(f, H)
|
||||
C.modified_define(0, x)
|
||||
identity = C._grp.identity
|
||||
a_0 = C._grp.generators[0]
|
||||
a_1 = C._grp.generators[1]
|
||||
|
||||
assert C.P == [[identity, None, None, None],
|
||||
[None, identity, None, None]]
|
||||
assert C.table == [[1, None, None, None],
|
||||
[None, 0, None, None]]
|
||||
|
||||
C.modified_define(1, x)
|
||||
assert C.table == [[1, None, None, None],
|
||||
[2, 0, None, None],
|
||||
[None, 1, None, None]]
|
||||
assert C.P == [[identity, None, None, None],
|
||||
[identity, identity, None, None],
|
||||
[None, identity, None, None]]
|
||||
|
||||
C.modified_scan(0, x**3, C._grp.identity, fill=False)
|
||||
assert C.P == [[identity, identity, None, None],
|
||||
[identity, identity, None, None],
|
||||
[identity, identity, None, None]]
|
||||
assert C.table == [[1, 2, None, None],
|
||||
[2, 0, None, None],
|
||||
[0, 1, None, None]]
|
||||
|
||||
C.modified_scan(0, x*y, C._grp.generators[0], fill=False)
|
||||
assert C.P == [[identity, identity, None, a_0**-1],
|
||||
[identity, identity, a_0, None],
|
||||
[identity, identity, None, None]]
|
||||
assert C.table == [[1, 2, None, 1],
|
||||
[2, 0, 0, None],
|
||||
[0, 1, None, None]]
|
||||
|
||||
C.modified_define(2, y**-1)
|
||||
assert C.table == [[1, 2, None, 1],
|
||||
[2, 0, 0, None],
|
||||
[0, 1, None, 3],
|
||||
[None, None, 2, None]]
|
||||
assert C.P == [[identity, identity, None, a_0**-1],
|
||||
[identity, identity, a_0, None],
|
||||
[identity, identity, None, identity],
|
||||
[None, None, identity, None]]
|
||||
|
||||
C.modified_scan(0, x**-1*y**-1*x*y*x, C._grp.generators[1])
|
||||
assert C.table == [[1, 2, None, 1],
|
||||
[2, 0, 0, None],
|
||||
[0, 1, None, 3],
|
||||
[3, 3, 2, None]]
|
||||
assert C.P == [[identity, identity, None, a_0**-1],
|
||||
[identity, identity, a_0, None],
|
||||
[identity, identity, None, identity],
|
||||
[a_1, a_1**-1, identity, None]]
|
||||
|
||||
C.modified_scan(2, (x*y)**2, C._grp.identity)
|
||||
assert C.table == [[1, 2, 3, 1],
|
||||
[2, 0, 0, None],
|
||||
[0, 1, None, 3],
|
||||
[3, 3, 2, 0]]
|
||||
assert C.P == [[identity, identity, a_1**-1, a_0**-1],
|
||||
[identity, identity, a_0, None],
|
||||
[identity, identity, None, identity],
|
||||
[a_1, a_1**-1, identity, a_1]]
|
||||
|
||||
C.modified_define(2, y)
|
||||
assert C.table == [[1, 2, 3, 1],
|
||||
[2, 0, 0, None],
|
||||
[0, 1, 4, 3],
|
||||
[3, 3, 2, 0],
|
||||
[None, None, None, 2]]
|
||||
assert C.P == [[identity, identity, a_1**-1, a_0**-1],
|
||||
[identity, identity, a_0, None],
|
||||
[identity, identity, identity, identity],
|
||||
[a_1, a_1**-1, identity, a_1],
|
||||
[None, None, None, identity]]
|
||||
|
||||
C.modified_scan(0, y**5, C._grp.identity)
|
||||
assert C.table == [[1, 2, 3, 1], [2, 0, 0, 4], [0, 1, 4, 3], [3, 3, 2, 0], [None, None, 1, 2]]
|
||||
assert C.P == [[identity, identity, a_1**-1, a_0**-1],
|
||||
[identity, identity, a_0, a_0*a_1**-1],
|
||||
[identity, identity, identity, identity],
|
||||
[a_1, a_1**-1, identity, a_1],
|
||||
[None, None, a_1*a_0**-1, identity]]
|
||||
|
||||
C.modified_scan(1, (x*y)**2, C._grp.identity)
|
||||
assert C.table == [[1, 2, 3, 1],
|
||||
[2, 0, 0, 4],
|
||||
[0, 1, 4, 3],
|
||||
[3, 3, 2, 0],
|
||||
[4, 4, 1, 2]]
|
||||
assert C.P == [[identity, identity, a_1**-1, a_0**-1],
|
||||
[identity, identity, a_0, a_0*a_1**-1],
|
||||
[identity, identity, identity, identity],
|
||||
[a_1, a_1**-1, identity, a_1],
|
||||
[a_0*a_1**-1, a_1*a_0**-1, a_1*a_0**-1, identity]]
|
||||
|
||||
# Modified coset enumeration test
|
||||
f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
|
||||
C = coset_enumeration_r(f, [x])
|
||||
C_m = modified_coset_enumeration_r(f, [x])
|
||||
assert C_m.table == C.table
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.combinatorics.fp_groups import (FpGroup, low_index_subgroups,
|
||||
reidemeister_presentation, FpSubgroup,
|
||||
simplify_presentation)
|
||||
from sympy.combinatorics.free_groups import (free_group, FreeGroup)
|
||||
|
||||
from sympy.testing.pytest import slow
|
||||
|
||||
"""
|
||||
References
|
||||
==========
|
||||
|
||||
[1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of Computational Group Theory"
|
||||
|
||||
[2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
|
||||
Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
|
||||
"Implementation and Analysis of the Todd-Coxeter Algorithm"
|
||||
|
||||
[3] PROC. SECOND INTERNAT. CONF. THEORY OF GROUPS, CANBERRA 1973,
|
||||
pp. 347-356. "A Reidemeister-Schreier program" by George Havas.
|
||||
http://staff.itee.uq.edu.au/havas/1973cdhw.pdf
|
||||
|
||||
"""
|
||||
|
||||
def test_low_index_subgroups():
|
||||
F, x, y = free_group("x, y")
|
||||
|
||||
# Example 5.10 from [1] Pg. 194
|
||||
f = FpGroup(F, [x**2, y**3, (x*y)**4])
|
||||
L = low_index_subgroups(f, 4)
|
||||
t1 = [[[0, 0, 0, 0]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]],
|
||||
[[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]],
|
||||
[[1, 1, 0, 0], [0, 0, 1, 1]]]
|
||||
for i in range(len(t1)):
|
||||
assert L[i].table == t1[i]
|
||||
|
||||
f = FpGroup(F, [x**2, y**3, (x*y)**7])
|
||||
L = low_index_subgroups(f, 15)
|
||||
t2 = [[[0, 0, 0, 0]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5],
|
||||
[4, 4, 5, 3], [6, 6, 3, 4], [5, 5, 6, 6]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5],
|
||||
[6, 6, 5, 3], [5, 5, 3, 4], [4, 4, 6, 6]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5],
|
||||
[6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11],
|
||||
[11, 11, 9, 6], [9, 9, 6, 8], [12, 12, 11, 7], [8, 8, 7, 10],
|
||||
[10, 10, 13, 14], [14, 14, 14, 12], [13, 13, 12, 13]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5],
|
||||
[6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11],
|
||||
[11, 11, 9, 6], [12, 12, 6, 8], [10, 10, 11, 7], [8, 8, 7, 10],
|
||||
[9, 9, 13, 14], [14, 14, 14, 12], [13, 13, 12, 13]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5],
|
||||
[6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11],
|
||||
[11, 11, 9, 6], [12, 12, 6, 8], [13, 13, 11, 7], [8, 8, 7, 10],
|
||||
[9, 9, 12, 12], [10, 10, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 3, 3], [2, 2, 5, 6]
|
||||
, [7, 7, 6, 4], [8, 8, 4, 5], [5, 5, 8, 9], [6, 6, 9, 7],
|
||||
[10, 10, 7, 8], [9, 9, 11, 12], [11, 11, 12, 10], [13, 13, 10, 11],
|
||||
[12, 12, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 3, 3], [2, 2, 5, 6]
|
||||
, [7, 7, 6, 4], [8, 8, 4, 5], [5, 5, 8, 9], [6, 6, 9, 7],
|
||||
[10, 10, 7, 8], [9, 9, 11, 12], [13, 13, 12, 10], [12, 12, 10, 11],
|
||||
[11, 11, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 4, 4]
|
||||
, [7, 7, 6, 3], [8, 8, 3, 5], [5, 5, 8, 9], [6, 6, 9, 7],
|
||||
[10, 10, 7, 8], [9, 9, 11, 12], [13, 13, 12, 10], [12, 12, 10, 11],
|
||||
[11, 11, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [5, 5, 6, 3], [9, 9, 3, 5], [10, 10, 8, 4], [8, 8, 4, 7],
|
||||
[6, 6, 10, 11], [7, 7, 11, 9], [12, 12, 9, 10], [11, 11, 13, 14],
|
||||
[14, 14, 14, 12], [13, 13, 12, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [6, 6, 6, 3], [5, 5, 3, 5], [8, 8, 8, 4], [7, 7, 4, 7]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [9, 9, 6, 3], [6, 6, 3, 5], [10, 10, 8, 4], [11, 11, 4, 7],
|
||||
[5, 5, 10, 12], [7, 7, 12, 9], [8, 8, 11, 11], [13, 13, 9, 10],
|
||||
[12, 12, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [9, 9, 6, 3], [6, 6, 3, 5], [10, 10, 8, 4], [11, 11, 4, 7],
|
||||
[5, 5, 12, 11], [7, 7, 10, 10], [8, 8, 9, 12], [13, 13, 11, 9],
|
||||
[12, 12, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [9, 9, 6, 3], [10, 10, 3, 5], [7, 7, 8, 4], [11, 11, 4, 7],
|
||||
[5, 5, 9, 9], [6, 6, 11, 12], [8, 8, 12, 10], [13, 13, 10, 11],
|
||||
[12, 12, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [9, 9, 6, 3], [10, 10, 3, 5], [7, 7, 8, 4], [11, 11, 4, 7],
|
||||
[5, 5, 12, 11], [6, 6, 10, 10], [8, 8, 9, 12], [13, 13, 11, 9],
|
||||
[12, 12, 13, 13]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8]
|
||||
, [9, 9, 6, 3], [10, 10, 3, 5], [11, 11, 8, 4], [12, 12, 4, 7],
|
||||
[5, 5, 9, 9], [6, 6, 12, 13], [7, 7, 11, 11], [8, 8, 13, 10],
|
||||
[13, 13, 10, 12]],
|
||||
[[1, 1, 0, 0], [0, 0, 2, 3], [4, 4, 3, 1], [5, 5, 1, 2], [2, 2, 4, 4]
|
||||
, [3, 3, 6, 7], [7, 7, 7, 5], [6, 6, 5, 6]]]
|
||||
for i in range(len(t2)):
|
||||
assert L[i].table == t2[i]
|
||||
|
||||
f = FpGroup(F, [x**2, y**3, (x*y)**7])
|
||||
L = low_index_subgroups(f, 10, [x])
|
||||
t3 = [[[0, 0, 0, 0]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], [4, 4, 5, 3],
|
||||
[6, 6, 3, 4], [5, 5, 6, 6]],
|
||||
[[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], [6, 6, 5, 3],
|
||||
[5, 5, 3, 4], [4, 4, 6, 6]],
|
||||
[[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8],
|
||||
[6, 6, 6, 3], [5, 5, 3, 5], [8, 8, 8, 4], [7, 7, 4, 7]]]
|
||||
for i in range(len(t3)):
|
||||
assert L[i].table == t3[i]
|
||||
|
||||
|
||||
def test_subgroup_presentations():
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**3, y**5, (x*y)**2])
|
||||
H = [x*y, x**-1*y**-1*x*y*x]
|
||||
p1 = reidemeister_presentation(f, H)
|
||||
assert str(p1) == "((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1))"
|
||||
|
||||
H = f.subgroup(H)
|
||||
assert (H.generators, H.relators) == p1
|
||||
|
||||
f = FpGroup(F, [x**3, y**3, (x*y)**3])
|
||||
H = [x*y, x*y**-1]
|
||||
p2 = reidemeister_presentation(f, H)
|
||||
assert str(p2) == "((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0))"
|
||||
|
||||
f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
|
||||
H = [x]
|
||||
p3 = reidemeister_presentation(f, H)
|
||||
assert str(p3) == "((x_0,), (x_0**4,))"
|
||||
|
||||
f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
|
||||
H = [x]
|
||||
p4 = reidemeister_presentation(f, H)
|
||||
assert str(p4) == "((x_0,), (x_0**6,))"
|
||||
|
||||
# this presentation can be improved, the most simplified form
|
||||
# of presentation is <a, b | a^11, b^2, (a*b)^3, (a^4*b*a^-5*b)^2>
|
||||
# See [2] Pg 474 group PSL_2(11)
|
||||
# This is the group PSL_2(11)
|
||||
F, a, b, c = free_group("a, b, c")
|
||||
f = FpGroup(F, [a**11, b**5, c**4, (b*c**2)**2, (a*b*c)**3, (a**4*c**2)**3, b**2*c**-1*b**-1*c, a**4*b**-1*a**-1*b])
|
||||
H = [a, b, c**2]
|
||||
gens, rels = reidemeister_presentation(f, H)
|
||||
assert str(gens) == "(b_1, c_3)"
|
||||
assert len(rels) == 18
|
||||
|
||||
|
||||
@slow
|
||||
def test_order():
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**4, y**2, x*y*x**-1*y])
|
||||
assert f.order() == 8
|
||||
|
||||
f = FpGroup(F, [x*y*x**-1*y**-1, y**2])
|
||||
assert f.order() is S.Infinity
|
||||
|
||||
F, a, b, c = free_group("a, b, c")
|
||||
f = FpGroup(F, [a**250, b**2, c*b*c**-1*b, c**4, c**-1*a**-1*c*a, a**-1*b**-1*a*b])
|
||||
assert f.order() == 2000
|
||||
|
||||
F, x = free_group("x")
|
||||
f = FpGroup(F, [])
|
||||
assert f.order() is S.Infinity
|
||||
|
||||
f = FpGroup(free_group('')[0], [])
|
||||
assert f.order() == 1
|
||||
|
||||
def test_fp_subgroup():
|
||||
def _test_subgroup(K, T, S):
|
||||
_gens = T(K.generators)
|
||||
assert all(elem in S for elem in _gens)
|
||||
assert T.is_injective()
|
||||
assert T.image().order() == S.order()
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x**4, y**2, x*y*x**-1*y])
|
||||
S = FpSubgroup(f, [x*y])
|
||||
assert (x*y)**-3 in S
|
||||
K, T = f.subgroup([x*y], homomorphism=True)
|
||||
assert T(K.generators) == [y*x**-1]
|
||||
_test_subgroup(K, T, S)
|
||||
|
||||
S = FpSubgroup(f, [x**-1*y*x])
|
||||
assert x**-1*y**4*x in S
|
||||
assert x**-1*y**4*x**2 not in S
|
||||
K, T = f.subgroup([x**-1*y*x], homomorphism=True)
|
||||
assert T(K.generators[0]**3) == y**3
|
||||
_test_subgroup(K, T, S)
|
||||
|
||||
f = FpGroup(F, [x**3, y**5, (x*y)**2])
|
||||
H = [x*y, x**-1*y**-1*x*y*x]
|
||||
K, T = f.subgroup(H, homomorphism=True)
|
||||
S = FpSubgroup(f, H)
|
||||
_test_subgroup(K, T, S)
|
||||
|
||||
def test_permutation_methods():
|
||||
F, x, y = free_group("x, y")
|
||||
# DihedralGroup(8)
|
||||
G = FpGroup(F, [x**2, y**8, x*y*x**-1*y])
|
||||
T = G._to_perm_group()[1]
|
||||
assert T.is_isomorphism()
|
||||
assert G.center() == [y**4]
|
||||
|
||||
# DiheadralGroup(4)
|
||||
G = FpGroup(F, [x**2, y**4, x*y*x**-1*y])
|
||||
S = FpSubgroup(G, G.normal_closure([x]))
|
||||
assert x in S
|
||||
assert y**-1*x*y in S
|
||||
|
||||
# Z_5xZ_4
|
||||
G = FpGroup(F, [x*y*x**-1*y**-1, y**5, x**4])
|
||||
assert G.is_abelian
|
||||
assert G.is_solvable
|
||||
|
||||
# AlternatingGroup(5)
|
||||
G = FpGroup(F, [x**3, y**2, (x*y)**5])
|
||||
assert not G.is_solvable
|
||||
|
||||
# AlternatingGroup(4)
|
||||
G = FpGroup(F, [x**3, y**2, (x*y)**3])
|
||||
assert len(G.derived_series()) == 3
|
||||
S = FpSubgroup(G, G.derived_subgroup())
|
||||
assert S.order() == 4
|
||||
|
||||
|
||||
def test_simplify_presentation():
|
||||
# ref #16083
|
||||
G = simplify_presentation(FpGroup(FreeGroup([]), []))
|
||||
assert not G.generators
|
||||
assert not G.relators
|
||||
|
||||
# CyclicGroup(3)
|
||||
# The second generator in <x, y | x^2, x^5, y^3> is trivial due to relators {x^2, x^5}
|
||||
F, x, y = free_group("x, y")
|
||||
G = simplify_presentation(FpGroup(F, [x**2, x**5, y**3]))
|
||||
assert x in G.relators
|
||||
|
||||
def test_cyclic():
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x*y, x**-1*y**-1*x*y*x])
|
||||
assert f.is_cyclic
|
||||
f = FpGroup(F, [x*y, x*y**-1])
|
||||
assert f.is_cyclic
|
||||
f = FpGroup(F, [x**4, y**2, x*y*x**-1*y])
|
||||
assert not f.is_cyclic
|
||||
|
||||
|
||||
def test_abelian_invariants():
|
||||
F, x, y = free_group("x, y")
|
||||
f = FpGroup(F, [x*y, x**-1*y**-1*x*y*x])
|
||||
assert f.abelian_invariants() == []
|
||||
f = FpGroup(F, [x*y, x*y**-1])
|
||||
assert f.abelian_invariants() == [2]
|
||||
f = FpGroup(F, [x**4, y**2, x*y*x**-1*y])
|
||||
assert f.abelian_invariants() == [2, 4]
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
from sympy.combinatorics.free_groups import free_group, FreeGroup
|
||||
from sympy.core import Symbol
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.numbers import oo
|
||||
|
||||
F, x, y, z = free_group("x, y, z")
|
||||
|
||||
|
||||
def test_FreeGroup__init__():
|
||||
x, y, z = map(Symbol, "xyz")
|
||||
|
||||
assert len(FreeGroup("x, y, z").generators) == 3
|
||||
assert len(FreeGroup(x).generators) == 1
|
||||
assert len(FreeGroup(("x", "y", "z"))) == 3
|
||||
assert len(FreeGroup((x, y, z)).generators) == 3
|
||||
|
||||
|
||||
def test_FreeGroup__getnewargs__():
|
||||
x, y, z = map(Symbol, "xyz")
|
||||
assert FreeGroup("x, y, z").__getnewargs__() == ((x, y, z),)
|
||||
|
||||
|
||||
def test_free_group():
|
||||
G, a, b, c = free_group("a, b, c")
|
||||
assert F.generators == (x, y, z)
|
||||
assert x*z**2 in F
|
||||
assert x in F
|
||||
assert y*z**-1 in F
|
||||
assert (y*z)**0 in F
|
||||
assert a not in F
|
||||
assert a**0 not in F
|
||||
assert len(F) == 3
|
||||
assert str(F) == '<free group on the generators (x, y, z)>'
|
||||
assert not F == G
|
||||
assert F.order() is oo
|
||||
assert F.is_abelian == False
|
||||
assert F.center() == {F.identity}
|
||||
|
||||
(e,) = free_group("")
|
||||
assert e.order() == 1
|
||||
assert e.generators == ()
|
||||
assert e.elements == {e.identity}
|
||||
assert e.is_abelian == True
|
||||
|
||||
|
||||
def test_FreeGroup__hash__():
|
||||
assert hash(F)
|
||||
|
||||
|
||||
def test_FreeGroup__eq__():
|
||||
assert free_group("x, y, z")[0] == free_group("x, y, z")[0]
|
||||
assert free_group("x, y, z")[0] is free_group("x, y, z")[0]
|
||||
|
||||
assert free_group("x, y, z")[0] != free_group("a, x, y")[0]
|
||||
assert free_group("x, y, z")[0] is not free_group("a, x, y")[0]
|
||||
|
||||
assert free_group("x, y")[0] != free_group("x, y, z")[0]
|
||||
assert free_group("x, y")[0] is not free_group("x, y, z")[0]
|
||||
|
||||
assert free_group("x, y, z")[0] != free_group("x, y")[0]
|
||||
assert free_group("x, y, z")[0] is not free_group("x, y")[0]
|
||||
|
||||
|
||||
def test_FreeGroup__getitem__():
|
||||
assert F[0:] == FreeGroup("x, y, z")
|
||||
assert F[1:] == FreeGroup("y, z")
|
||||
assert F[2:] == FreeGroup("z")
|
||||
|
||||
|
||||
def test_FreeGroupElm__hash__():
|
||||
assert hash(x*y*z)
|
||||
|
||||
|
||||
def test_FreeGroupElm_copy():
|
||||
f = x*y*z**3
|
||||
g = f.copy()
|
||||
h = x*y*z**7
|
||||
|
||||
assert f == g
|
||||
assert f != h
|
||||
|
||||
|
||||
def test_FreeGroupElm_inverse():
|
||||
assert x.inverse() == x**-1
|
||||
assert (x*y).inverse() == y**-1*x**-1
|
||||
assert (y*x*y**-1).inverse() == y*x**-1*y**-1
|
||||
assert (y**2*x**-1).inverse() == x*y**-2
|
||||
|
||||
|
||||
def test_FreeGroupElm_type_error():
|
||||
raises(TypeError, lambda: 2/x)
|
||||
raises(TypeError, lambda: x**2 + y**2)
|
||||
raises(TypeError, lambda: x/2)
|
||||
|
||||
|
||||
def test_FreeGroupElm_methods():
|
||||
assert (x**0).order() == 1
|
||||
assert (y**2).order() is oo
|
||||
assert (x**-1*y).commutator(x) == y**-1*x**-1*y*x
|
||||
assert len(x**2*y**-1) == 3
|
||||
assert len(x**-1*y**3*z) == 5
|
||||
|
||||
|
||||
def test_FreeGroupElm_eliminate_word():
|
||||
w = x**5*y*x**2*y**-4*x
|
||||
assert w.eliminate_word( x, x**2 ) == x**10*y*x**4*y**-4*x**2
|
||||
w3 = x**2*y**3*x**-1*y
|
||||
assert w3.eliminate_word(x, x**2) == x**4*y**3*x**-2*y
|
||||
assert w3.eliminate_word(x, y) == y**5
|
||||
assert w3.eliminate_word(x, y**4) == y**8
|
||||
assert w3.eliminate_word(y, x**-1) == x**-3
|
||||
assert w3.eliminate_word(x, y*z) == y*z*y*z*y**3*z**-1
|
||||
assert (y**-3).eliminate_word(y, x**-1*z**-1) == z*x*z*x*z*x
|
||||
#assert w3.eliminate_word(x, y*x) == y*x*y*x**2*y*x*y*x*y*x*z**3
|
||||
#assert w3.eliminate_word(x, x*y) == x*y*x**2*y*x*y*x*y*x*y*z**3
|
||||
|
||||
|
||||
def test_FreeGroupElm_array_form():
|
||||
assert (x*z).array_form == ((Symbol('x'), 1), (Symbol('z'), 1))
|
||||
assert (x**2*z*y*x**-2).array_form == \
|
||||
((Symbol('x'), 2), (Symbol('z'), 1), (Symbol('y'), 1), (Symbol('x'), -2))
|
||||
assert (x**-2*y**-1).array_form == ((Symbol('x'), -2), (Symbol('y'), -1))
|
||||
|
||||
|
||||
def test_FreeGroupElm_letter_form():
|
||||
assert (x**3).letter_form == (Symbol('x'), Symbol('x'), Symbol('x'))
|
||||
assert (x**2*z**-2*x).letter_form == \
|
||||
(Symbol('x'), Symbol('x'), -Symbol('z'), -Symbol('z'), Symbol('x'))
|
||||
|
||||
|
||||
def test_FreeGroupElm_ext_rep():
|
||||
assert (x**2*z**-2*x).ext_rep == \
|
||||
(Symbol('x'), 2, Symbol('z'), -2, Symbol('x'), 1)
|
||||
assert (x**-2*y**-1).ext_rep == (Symbol('x'), -2, Symbol('y'), -1)
|
||||
assert (x*z).ext_rep == (Symbol('x'), 1, Symbol('z'), 1)
|
||||
|
||||
|
||||
def test_FreeGroupElm__mul__pow__():
|
||||
x1 = x.group.dtype(((Symbol('x'), 1),))
|
||||
assert x**2 == x1*x
|
||||
|
||||
assert (x**2*y*x**-2)**4 == x**2*y**4*x**-2
|
||||
assert (x**2)**2 == x**4
|
||||
assert (x**-1)**-1 == x
|
||||
assert (x**-1)**0 == F.identity
|
||||
assert (y**2)**-2 == y**-4
|
||||
|
||||
assert x**2*x**-1 == x
|
||||
assert x**2*y**2*y**-1 == x**2*y
|
||||
assert x*x**-1 == F.identity
|
||||
|
||||
assert x/x == F.identity
|
||||
assert x/x**2 == x**-1
|
||||
assert (x**2*y)/(x**2*y**-1) == x**2*y**2*x**-2
|
||||
assert (x**2*y)/(y**-1*x**2) == x**2*y*x**-2*y
|
||||
|
||||
assert x*(x**-1*y*z*y**-1) == y*z*y**-1
|
||||
assert x**2*(x**-2*y**-1*z**2*y) == y**-1*z**2*y
|
||||
|
||||
a = F.identity
|
||||
for n in range(10):
|
||||
assert a == x**n
|
||||
assert a**-1 == x**-n
|
||||
a *= x
|
||||
|
||||
|
||||
def test_FreeGroupElm__len__():
|
||||
assert len(x**5*y*x**2*y**-4*x) == 13
|
||||
assert len(x**17) == 17
|
||||
assert len(y**0) == 0
|
||||
|
||||
|
||||
def test_FreeGroupElm_comparison():
|
||||
assert not (x*y == y*x)
|
||||
assert x**0 == y**0
|
||||
|
||||
assert x**2 < y**3
|
||||
assert not x**3 < y**2
|
||||
assert x*y < x**2*y
|
||||
assert x**2*y**2 < y**4
|
||||
assert not y**4 < y**-4
|
||||
assert not y**4 < x**-4
|
||||
assert y**-2 < y**2
|
||||
|
||||
assert x**2 <= y**2
|
||||
assert x**2 <= x**2
|
||||
|
||||
assert not y*z > z*y
|
||||
assert x > x**-1
|
||||
|
||||
assert not x**2 >= y**2
|
||||
|
||||
|
||||
def test_FreeGroupElm_syllables():
|
||||
w = x**5*y*x**2*y**-4*x
|
||||
assert w.number_syllables() == 5
|
||||
assert w.exponent_syllable(2) == 2
|
||||
assert w.generator_syllable(3) == Symbol('y')
|
||||
assert w.sub_syllables(1, 2) == y
|
||||
assert w.sub_syllables(3, 3) == F.identity
|
||||
|
||||
|
||||
def test_FreeGroup_exponents():
|
||||
w1 = x**2*y**3
|
||||
assert w1.exponent_sum(x) == 2
|
||||
assert w1.exponent_sum(x**-1) == -2
|
||||
assert w1.generator_count(x) == 2
|
||||
|
||||
w2 = x**2*y**4*x**-3
|
||||
assert w2.exponent_sum(x) == -1
|
||||
assert w2.generator_count(x) == 5
|
||||
|
||||
|
||||
def test_FreeGroup_generators():
|
||||
assert (x**2*y**4*z**-1).contains_generators() == {x, y, z}
|
||||
assert (x**-1*y**3).contains_generators() == {x, y}
|
||||
|
||||
|
||||
def test_FreeGroupElm_words():
|
||||
w = x**5*y*x**2*y**-4*x
|
||||
assert w.subword(2, 6) == x**3*y
|
||||
assert w.subword(3, 2) == F.identity
|
||||
assert w.subword(6, 10) == x**2*y**-2
|
||||
|
||||
assert w.substituted_word(0, 7, y**-1) == y**-1*x*y**-4*x
|
||||
assert w.substituted_word(0, 7, y**2*x) == y**2*x**2*y**-4*x
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
"""Test groups defined by the galois module. """
|
||||
|
||||
from sympy.combinatorics.galois import (
|
||||
S4TransitiveSubgroups, S5TransitiveSubgroups, S6TransitiveSubgroups,
|
||||
find_transitive_subgroups_of_S6,
|
||||
)
|
||||
from sympy.combinatorics.homomorphisms import is_isomorphic
|
||||
from sympy.combinatorics.named_groups import (
|
||||
SymmetricGroup, AlternatingGroup, CyclicGroup,
|
||||
)
|
||||
|
||||
|
||||
def test_four_group():
|
||||
G = S4TransitiveSubgroups.V.get_perm_group()
|
||||
A4 = AlternatingGroup(4)
|
||||
assert G.is_subgroup(A4)
|
||||
assert G.degree == 4
|
||||
assert G.is_transitive()
|
||||
assert G.order() == 4
|
||||
assert not G.is_cyclic
|
||||
|
||||
|
||||
def test_M20():
|
||||
G = S5TransitiveSubgroups.M20.get_perm_group()
|
||||
S5 = SymmetricGroup(5)
|
||||
A5 = AlternatingGroup(5)
|
||||
assert G.is_subgroup(S5)
|
||||
assert not G.is_subgroup(A5)
|
||||
assert G.degree == 5
|
||||
assert G.is_transitive()
|
||||
assert G.order() == 20
|
||||
|
||||
|
||||
# Setting this True means that for each of the transitive subgroups of S6,
|
||||
# we run a test not only on the fixed representation, but also on one freshly
|
||||
# generated by the search procedure.
|
||||
INCLUDE_SEARCH_REPS = False
|
||||
S6_randomized = {}
|
||||
if INCLUDE_SEARCH_REPS:
|
||||
S6_randomized = find_transitive_subgroups_of_S6(*list(S6TransitiveSubgroups))
|
||||
|
||||
|
||||
def get_versions_of_S6_subgroup(name):
|
||||
vers = [name.get_perm_group()]
|
||||
if INCLUDE_SEARCH_REPS:
|
||||
vers.append(S6_randomized[name])
|
||||
return vers
|
||||
|
||||
|
||||
def test_S6_transitive_subgroups():
|
||||
"""
|
||||
Test enough characteristics to distinguish all 16 transitive subgroups.
|
||||
"""
|
||||
ts = S6TransitiveSubgroups
|
||||
A6 = AlternatingGroup(6)
|
||||
for name, alt, order, is_isom, not_isom in [
|
||||
(ts.C6, False, 6, CyclicGroup(6), None),
|
||||
(ts.S3, False, 6, SymmetricGroup(3), None),
|
||||
(ts.D6, False, 12, None, None),
|
||||
(ts.A4, True, 12, None, None),
|
||||
(ts.G18, False, 18, None, None),
|
||||
(ts.A4xC2, False, 24, None, SymmetricGroup(4)),
|
||||
(ts.S4m, False, 24, SymmetricGroup(4), None),
|
||||
(ts.S4p, True, 24, None, None),
|
||||
(ts.G36m, False, 36, None, None),
|
||||
(ts.G36p, True, 36, None, None),
|
||||
(ts.S4xC2, False, 48, None, None),
|
||||
(ts.PSL2F5, True, 60, None, None),
|
||||
(ts.G72, False, 72, None, None),
|
||||
(ts.PGL2F5, False, 120, None, None),
|
||||
(ts.A6, True, 360, None, None),
|
||||
(ts.S6, False, 720, None, None),
|
||||
]:
|
||||
for G in get_versions_of_S6_subgroup(name):
|
||||
assert G.is_transitive()
|
||||
assert G.degree == 6
|
||||
assert G.is_subgroup(A6) is alt
|
||||
assert G.order() == order
|
||||
if is_isom:
|
||||
assert is_isomorphic(G, is_isom)
|
||||
if not_isom:
|
||||
assert not is_isomorphic(G, not_isom)
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
from sympy.combinatorics.generators import symmetric, cyclic, alternating, \
|
||||
dihedral, rubik
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
def test_generators():
|
||||
|
||||
assert list(cyclic(6)) == [
|
||||
Permutation([0, 1, 2, 3, 4, 5]),
|
||||
Permutation([1, 2, 3, 4, 5, 0]),
|
||||
Permutation([2, 3, 4, 5, 0, 1]),
|
||||
Permutation([3, 4, 5, 0, 1, 2]),
|
||||
Permutation([4, 5, 0, 1, 2, 3]),
|
||||
Permutation([5, 0, 1, 2, 3, 4])]
|
||||
|
||||
assert list(cyclic(10)) == [
|
||||
Permutation([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
||||
Permutation([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]),
|
||||
Permutation([2, 3, 4, 5, 6, 7, 8, 9, 0, 1]),
|
||||
Permutation([3, 4, 5, 6, 7, 8, 9, 0, 1, 2]),
|
||||
Permutation([4, 5, 6, 7, 8, 9, 0, 1, 2, 3]),
|
||||
Permutation([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]),
|
||||
Permutation([6, 7, 8, 9, 0, 1, 2, 3, 4, 5]),
|
||||
Permutation([7, 8, 9, 0, 1, 2, 3, 4, 5, 6]),
|
||||
Permutation([8, 9, 0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
Permutation([9, 0, 1, 2, 3, 4, 5, 6, 7, 8])]
|
||||
|
||||
assert list(alternating(4)) == [
|
||||
Permutation([0, 1, 2, 3]),
|
||||
Permutation([0, 2, 3, 1]),
|
||||
Permutation([0, 3, 1, 2]),
|
||||
Permutation([1, 0, 3, 2]),
|
||||
Permutation([1, 2, 0, 3]),
|
||||
Permutation([1, 3, 2, 0]),
|
||||
Permutation([2, 0, 1, 3]),
|
||||
Permutation([2, 1, 3, 0]),
|
||||
Permutation([2, 3, 0, 1]),
|
||||
Permutation([3, 0, 2, 1]),
|
||||
Permutation([3, 1, 0, 2]),
|
||||
Permutation([3, 2, 1, 0])]
|
||||
|
||||
assert list(symmetric(3)) == [
|
||||
Permutation([0, 1, 2]),
|
||||
Permutation([0, 2, 1]),
|
||||
Permutation([1, 0, 2]),
|
||||
Permutation([1, 2, 0]),
|
||||
Permutation([2, 0, 1]),
|
||||
Permutation([2, 1, 0])]
|
||||
|
||||
assert list(symmetric(4)) == [
|
||||
Permutation([0, 1, 2, 3]),
|
||||
Permutation([0, 1, 3, 2]),
|
||||
Permutation([0, 2, 1, 3]),
|
||||
Permutation([0, 2, 3, 1]),
|
||||
Permutation([0, 3, 1, 2]),
|
||||
Permutation([0, 3, 2, 1]),
|
||||
Permutation([1, 0, 2, 3]),
|
||||
Permutation([1, 0, 3, 2]),
|
||||
Permutation([1, 2, 0, 3]),
|
||||
Permutation([1, 2, 3, 0]),
|
||||
Permutation([1, 3, 0, 2]),
|
||||
Permutation([1, 3, 2, 0]),
|
||||
Permutation([2, 0, 1, 3]),
|
||||
Permutation([2, 0, 3, 1]),
|
||||
Permutation([2, 1, 0, 3]),
|
||||
Permutation([2, 1, 3, 0]),
|
||||
Permutation([2, 3, 0, 1]),
|
||||
Permutation([2, 3, 1, 0]),
|
||||
Permutation([3, 0, 1, 2]),
|
||||
Permutation([3, 0, 2, 1]),
|
||||
Permutation([3, 1, 0, 2]),
|
||||
Permutation([3, 1, 2, 0]),
|
||||
Permutation([3, 2, 0, 1]),
|
||||
Permutation([3, 2, 1, 0])]
|
||||
|
||||
assert list(dihedral(1)) == [
|
||||
Permutation([0, 1]), Permutation([1, 0])]
|
||||
|
||||
assert list(dihedral(2)) == [
|
||||
Permutation([0, 1, 2, 3]),
|
||||
Permutation([1, 0, 3, 2]),
|
||||
Permutation([2, 3, 0, 1]),
|
||||
Permutation([3, 2, 1, 0])]
|
||||
|
||||
assert list(dihedral(3)) == [
|
||||
Permutation([0, 1, 2]),
|
||||
Permutation([2, 1, 0]),
|
||||
Permutation([1, 2, 0]),
|
||||
Permutation([0, 2, 1]),
|
||||
Permutation([2, 0, 1]),
|
||||
Permutation([1, 0, 2])]
|
||||
|
||||
assert list(dihedral(5)) == [
|
||||
Permutation([0, 1, 2, 3, 4]),
|
||||
Permutation([4, 3, 2, 1, 0]),
|
||||
Permutation([1, 2, 3, 4, 0]),
|
||||
Permutation([0, 4, 3, 2, 1]),
|
||||
Permutation([2, 3, 4, 0, 1]),
|
||||
Permutation([1, 0, 4, 3, 2]),
|
||||
Permutation([3, 4, 0, 1, 2]),
|
||||
Permutation([2, 1, 0, 4, 3]),
|
||||
Permutation([4, 0, 1, 2, 3]),
|
||||
Permutation([3, 2, 1, 0, 4])]
|
||||
|
||||
raises(ValueError, lambda: rubik(1))
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
from sympy.combinatorics.graycode import (GrayCode, bin_to_gray,
|
||||
random_bitstring, get_subset_from_bitstring, graycode_subsets,
|
||||
gray_to_bin)
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
def test_graycode():
|
||||
g = GrayCode(2)
|
||||
got = []
|
||||
for i in g.generate_gray():
|
||||
if i.startswith('0'):
|
||||
g.skip()
|
||||
got.append(i)
|
||||
assert got == '00 11 10'.split()
|
||||
a = GrayCode(6)
|
||||
assert a.current == '0'*6
|
||||
assert a.rank == 0
|
||||
assert len(list(a.generate_gray())) == 64
|
||||
codes = ['011001', '011011', '011010',
|
||||
'011110', '011111', '011101', '011100', '010100', '010101', '010111',
|
||||
'010110', '010010', '010011', '010001', '010000', '110000', '110001',
|
||||
'110011', '110010', '110110', '110111', '110101', '110100', '111100',
|
||||
'111101', '111111', '111110', '111010', '111011', '111001', '111000',
|
||||
'101000', '101001', '101011', '101010', '101110', '101111', '101101',
|
||||
'101100', '100100', '100101', '100111', '100110', '100010', '100011',
|
||||
'100001', '100000']
|
||||
assert list(a.generate_gray(start='011001')) == codes
|
||||
assert list(
|
||||
a.generate_gray(rank=GrayCode(6, start='011001').rank)) == codes
|
||||
assert a.next().current == '000001'
|
||||
assert a.next(2).current == '000011'
|
||||
assert a.next(-1).current == '100000'
|
||||
|
||||
a = GrayCode(5, start='10010')
|
||||
assert a.rank == 28
|
||||
a = GrayCode(6, start='101000')
|
||||
assert a.rank == 48
|
||||
|
||||
assert GrayCode(6, rank=4).current == '000110'
|
||||
assert GrayCode(6, rank=4).rank == 4
|
||||
assert [GrayCode(4, start=s).rank for s in
|
||||
GrayCode(4).generate_gray()] == [0, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9, 10, 11, 12, 13, 14, 15]
|
||||
a = GrayCode(15, rank=15)
|
||||
assert a.current == '000000000001000'
|
||||
|
||||
assert bin_to_gray('111') == '100'
|
||||
|
||||
a = random_bitstring(5)
|
||||
assert type(a) is str
|
||||
assert len(a) == 5
|
||||
assert all(i in ['0', '1'] for i in a)
|
||||
|
||||
assert get_subset_from_bitstring(
|
||||
['a', 'b', 'c', 'd'], '0011') == ['c', 'd']
|
||||
assert get_subset_from_bitstring('abcd', '1001') == ['a', 'd']
|
||||
assert list(graycode_subsets(['a', 'b', 'c'])) == \
|
||||
[[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'],
|
||||
['a', 'c'], ['a']]
|
||||
|
||||
raises(ValueError, lambda: GrayCode(0))
|
||||
raises(ValueError, lambda: GrayCode(2.2))
|
||||
raises(ValueError, lambda: GrayCode(2, start=[1, 1, 0]))
|
||||
raises(ValueError, lambda: GrayCode(2, rank=2.5))
|
||||
raises(ValueError, lambda: get_subset_from_bitstring(['c', 'a', 'c'], '1100'))
|
||||
raises(ValueError, lambda: list(GrayCode(3).generate_gray(start="1111")))
|
||||
|
||||
|
||||
def test_live_issue_117():
|
||||
assert bin_to_gray('0100') == '0110'
|
||||
assert bin_to_gray('0101') == '0111'
|
||||
for bits in ('0100', '0101'):
|
||||
assert gray_to_bin(bin_to_gray(bits)) == bits
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from sympy.combinatorics.group_constructs import DirectProduct
|
||||
from sympy.combinatorics.named_groups import CyclicGroup, DihedralGroup
|
||||
|
||||
|
||||
def test_direct_product_n():
|
||||
C = CyclicGroup(4)
|
||||
D = DihedralGroup(4)
|
||||
G = DirectProduct(C, C, C)
|
||||
assert G.order() == 64
|
||||
assert G.degree == 12
|
||||
assert len(G.orbits()) == 3
|
||||
assert G.is_abelian is True
|
||||
H = DirectProduct(D, C)
|
||||
assert H.order() == 32
|
||||
assert H.is_abelian is False
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
from sympy.combinatorics.group_numbers import (is_nilpotent_number,
|
||||
is_abelian_number, is_cyclic_number, _holder_formula, groups_count)
|
||||
from sympy.ntheory.factor_ import factorint
|
||||
from sympy.ntheory.generate import prime
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy import randprime
|
||||
|
||||
|
||||
def test_is_nilpotent_number():
|
||||
assert is_nilpotent_number(21) == False
|
||||
assert is_nilpotent_number(randprime(1, 30)**12) == True
|
||||
raises(ValueError, lambda: is_nilpotent_number(-5))
|
||||
|
||||
A056867 = [1, 2, 3, 4, 5, 7, 8, 9, 11, 13, 15, 16, 17, 19,
|
||||
23, 25, 27, 29, 31, 32, 33, 35, 37, 41, 43, 45,
|
||||
47, 49, 51, 53, 59, 61, 64, 65, 67, 69, 71, 73,
|
||||
77, 79, 81, 83, 85, 87, 89, 91, 95, 97, 99]
|
||||
for n in range(1, 100):
|
||||
assert is_nilpotent_number(n) == (n in A056867)
|
||||
|
||||
|
||||
def test_is_abelian_number():
|
||||
assert is_abelian_number(4) == True
|
||||
assert is_abelian_number(randprime(1, 2000)**2) == True
|
||||
assert is_abelian_number(randprime(1000, 100000)) == True
|
||||
assert is_abelian_number(60) == False
|
||||
assert is_abelian_number(24) == False
|
||||
raises(ValueError, lambda: is_abelian_number(-5))
|
||||
|
||||
A051532 = [1, 2, 3, 4, 5, 7, 9, 11, 13, 15, 17, 19, 23, 25,
|
||||
29, 31, 33, 35, 37, 41, 43, 45, 47, 49, 51, 53,
|
||||
59, 61, 65, 67, 69, 71, 73, 77, 79, 83, 85, 87,
|
||||
89, 91, 95, 97, 99]
|
||||
for n in range(1, 100):
|
||||
assert is_abelian_number(n) == (n in A051532)
|
||||
|
||||
|
||||
A003277 = [1, 2, 3, 5, 7, 11, 13, 15, 17, 19, 23, 29,
|
||||
31, 33, 35, 37, 41, 43, 47, 51, 53, 59, 61,
|
||||
65, 67, 69, 71, 73, 77, 79, 83, 85, 87, 89,
|
||||
91, 95, 97]
|
||||
|
||||
|
||||
def test_is_cyclic_number():
|
||||
assert is_cyclic_number(15) == True
|
||||
assert is_cyclic_number(randprime(1, 2000)**2) == False
|
||||
assert is_cyclic_number(randprime(1000, 100000)) == True
|
||||
assert is_cyclic_number(4) == False
|
||||
raises(ValueError, lambda: is_cyclic_number(-5))
|
||||
|
||||
for n in range(1, 100):
|
||||
assert is_cyclic_number(n) == (n in A003277)
|
||||
|
||||
|
||||
def test_holder_formula():
|
||||
# semiprime
|
||||
assert _holder_formula({3, 5}) == 1
|
||||
assert _holder_formula({5, 11}) == 2
|
||||
# n in A003277 is always 1
|
||||
for n in A003277:
|
||||
assert _holder_formula(set(factorint(n).keys())) == 1
|
||||
# otherwise
|
||||
assert _holder_formula({2, 3, 5, 7}) == 12
|
||||
|
||||
|
||||
def test_groups_count():
|
||||
A000001 = [0, 1, 1, 1, 2, 1, 2, 1, 5, 2, 2, 1, 5, 1,
|
||||
2, 1, 14, 1, 5, 1, 5, 2, 2, 1, 15, 2, 2,
|
||||
5, 4, 1, 4, 1, 51, 1, 2, 1, 14, 1, 2, 2,
|
||||
14, 1, 6, 1, 4, 2, 2, 1, 52, 2, 5, 1, 5,
|
||||
1, 15, 2, 13, 2, 2, 1, 13, 1, 2, 4, 267,
|
||||
1, 4, 1, 5, 1, 4, 1, 50, 1, 2, 3, 4, 1,
|
||||
6, 1, 52, 15, 2, 1, 15, 1, 2, 1, 12, 1,
|
||||
10, 1, 4, 2]
|
||||
for n in range(1, len(A000001)):
|
||||
try:
|
||||
assert groups_count(n) == A000001[n]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289]
|
||||
for e in range(1, len(A000679)):
|
||||
assert groups_count(2**e) == A000679[e]
|
||||
|
||||
A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645]
|
||||
for e in range(1, len(A090091)):
|
||||
assert groups_count(3**e) == A090091[e]
|
||||
|
||||
A090130 = [1, 1, 2, 5, 15, 77, 684, 34297]
|
||||
for e in range(1, len(A090130)):
|
||||
assert groups_count(5**e) == A090130[e]
|
||||
|
||||
A090140 = [1, 1, 2, 5, 15, 83, 860, 113147]
|
||||
for e in range(1, len(A090140)):
|
||||
assert groups_count(7**e) == A090140[e]
|
||||
|
||||
A232105 = [51, 67, 77, 83, 87, 97, 101, 107, 111, 125, 131,
|
||||
145, 149, 155, 159, 173, 183, 193, 203, 207, 217]
|
||||
for i in range(len(A232105)):
|
||||
assert groups_count(prime(i+1)**5) == A232105[i]
|
||||
|
||||
A232106 = [267, 504, 684, 860, 1192, 1476, 1944, 2264, 2876,
|
||||
4068, 4540, 6012, 7064, 7664, 8852, 10908, 13136]
|
||||
for i in range(len(A232106)):
|
||||
assert groups_count(prime(i+1)**6) == A232106[i]
|
||||
|
||||
A232107 = [2328, 9310, 34297, 113147, 750735, 1600573,
|
||||
5546909, 9380741, 23316851, 71271069, 98488755]
|
||||
for i in range(len(A232107)):
|
||||
assert groups_count(prime(i+1)**7) == A232107[i]
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.combinatorics.homomorphisms import homomorphism, group_isomorphism, is_isomorphic
|
||||
from sympy.combinatorics.free_groups import free_group
|
||||
from sympy.combinatorics.fp_groups import FpGroup
|
||||
from sympy.combinatorics.named_groups import AlternatingGroup, DihedralGroup, CyclicGroup
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
def test_homomorphism():
|
||||
# FpGroup -> PermutationGroup
|
||||
F, a, b = free_group("a, b")
|
||||
G = FpGroup(F, [a**3, b**3, (a*b)**2])
|
||||
|
||||
c = Permutation(3)(0, 1, 2)
|
||||
d = Permutation(3)(1, 2, 3)
|
||||
A = AlternatingGroup(4)
|
||||
T = homomorphism(G, A, [a, b], [c, d])
|
||||
assert T(a*b**2*a**-1) == c*d**2*c**-1
|
||||
assert T.is_isomorphism()
|
||||
assert T(T.invert(Permutation(3)(0, 2, 3))) == Permutation(3)(0, 2, 3)
|
||||
|
||||
T = homomorphism(G, AlternatingGroup(4), G.generators)
|
||||
assert T.is_trivial()
|
||||
assert T.kernel().order() == G.order()
|
||||
|
||||
E, e = free_group("e")
|
||||
G = FpGroup(E, [e**8])
|
||||
P = PermutationGroup([Permutation(0, 1, 2, 3), Permutation(0, 2)])
|
||||
T = homomorphism(G, P, [e], [Permutation(0, 1, 2, 3)])
|
||||
assert T.image().order() == 4
|
||||
assert T(T.invert(Permutation(0, 2)(1, 3))) == Permutation(0, 2)(1, 3)
|
||||
|
||||
T = homomorphism(E, AlternatingGroup(4), E.generators, [c])
|
||||
assert T.invert(c**2) == e**-1 #order(c) == 3 so c**2 == c**-1
|
||||
|
||||
# FreeGroup -> FreeGroup
|
||||
T = homomorphism(F, E, [a], [e])
|
||||
assert T(a**-2*b**4*a**2).is_identity
|
||||
|
||||
# FreeGroup -> FpGroup
|
||||
G = FpGroup(F, [a*b*a**-1*b**-1])
|
||||
T = homomorphism(F, G, F.generators, G.generators)
|
||||
assert T.invert(a**-1*b**-1*a**2) == a*b**-1
|
||||
|
||||
# PermutationGroup -> PermutationGroup
|
||||
D = DihedralGroup(8)
|
||||
p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
|
||||
P = PermutationGroup(p)
|
||||
T = homomorphism(P, D, [p], [p])
|
||||
assert T.is_injective()
|
||||
assert not T.is_isomorphism()
|
||||
assert T.invert(p**3) == p**3
|
||||
|
||||
T2 = homomorphism(F, P, [F.generators[0]], P.generators)
|
||||
T = T.compose(T2)
|
||||
assert T.domain == F
|
||||
assert T.codomain == D
|
||||
assert T(a*b) == p
|
||||
|
||||
D3 = DihedralGroup(3)
|
||||
T = homomorphism(D3, D3, D3.generators, D3.generators)
|
||||
assert T.is_isomorphism()
|
||||
|
||||
|
||||
def test_isomorphisms():
|
||||
|
||||
F, a, b = free_group("a, b")
|
||||
E, c, d = free_group("c, d")
|
||||
# Infinite groups with differently ordered relators.
|
||||
G = FpGroup(F, [a**2, b**3])
|
||||
H = FpGroup(F, [b**3, a**2])
|
||||
assert is_isomorphic(G, H)
|
||||
|
||||
# Trivial Case
|
||||
# FpGroup -> FpGroup
|
||||
H = FpGroup(F, [a**3, b**3, (a*b)**2])
|
||||
F, c, d = free_group("c, d")
|
||||
G = FpGroup(F, [c**3, d**3, (c*d)**2])
|
||||
check, T = group_isomorphism(G, H)
|
||||
assert check
|
||||
assert T(c**3*d**2) == a**3*b**2
|
||||
|
||||
# FpGroup -> PermutationGroup
|
||||
# FpGroup is converted to the equivalent isomorphic group.
|
||||
F, a, b = free_group("a, b")
|
||||
G = FpGroup(F, [a**3, b**3, (a*b)**2])
|
||||
H = AlternatingGroup(4)
|
||||
check, T = group_isomorphism(G, H)
|
||||
assert check
|
||||
assert T(b*a*b**-1*a**-1*b**-1) == Permutation(0, 2, 3)
|
||||
assert T(b*a*b*a**-1*b**-1) == Permutation(0, 3, 2)
|
||||
|
||||
# PermutationGroup -> PermutationGroup
|
||||
D = DihedralGroup(8)
|
||||
p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
|
||||
P = PermutationGroup(p)
|
||||
assert not is_isomorphic(D, P)
|
||||
|
||||
A = CyclicGroup(5)
|
||||
B = CyclicGroup(7)
|
||||
assert not is_isomorphic(A, B)
|
||||
|
||||
# Two groups of the same prime order are isomorphic to each other.
|
||||
G = FpGroup(F, [a, b**5])
|
||||
H = CyclicGroup(5)
|
||||
assert G.order() == H.order()
|
||||
assert is_isomorphic(G, H)
|
||||
|
||||
|
||||
def test_check_homomorphism():
|
||||
a = Permutation(1,2,3,4)
|
||||
b = Permutation(1,3)
|
||||
G = PermutationGroup([a, b])
|
||||
raises(ValueError, lambda: homomorphism(G, G, [a], [a]))
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
from sympy.combinatorics.named_groups import (SymmetricGroup, CyclicGroup,
|
||||
DihedralGroup, AlternatingGroup,
|
||||
AbelianGroup, RubikGroup)
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_SymmetricGroup():
|
||||
G = SymmetricGroup(5)
|
||||
elements = list(G.generate())
|
||||
assert (G.generators[0]).size == 5
|
||||
assert len(elements) == 120
|
||||
assert G.is_solvable is False
|
||||
assert G.is_abelian is False
|
||||
assert G.is_nilpotent is False
|
||||
assert G.is_transitive() is True
|
||||
H = SymmetricGroup(1)
|
||||
assert H.order() == 1
|
||||
L = SymmetricGroup(2)
|
||||
assert L.order() == 2
|
||||
|
||||
|
||||
def test_CyclicGroup():
|
||||
G = CyclicGroup(10)
|
||||
elements = list(G.generate())
|
||||
assert len(elements) == 10
|
||||
assert (G.derived_subgroup()).order() == 1
|
||||
assert G.is_abelian is True
|
||||
assert G.is_solvable is True
|
||||
assert G.is_nilpotent is True
|
||||
H = CyclicGroup(1)
|
||||
assert H.order() == 1
|
||||
L = CyclicGroup(2)
|
||||
assert L.order() == 2
|
||||
|
||||
|
||||
def test_DihedralGroup():
|
||||
G = DihedralGroup(6)
|
||||
elements = list(G.generate())
|
||||
assert len(elements) == 12
|
||||
assert G.is_transitive() is True
|
||||
assert G.is_abelian is False
|
||||
assert G.is_solvable is True
|
||||
assert G.is_nilpotent is False
|
||||
H = DihedralGroup(1)
|
||||
assert H.order() == 2
|
||||
L = DihedralGroup(2)
|
||||
assert L.order() == 4
|
||||
assert L.is_abelian is True
|
||||
assert L.is_nilpotent is True
|
||||
|
||||
|
||||
def test_AlternatingGroup():
|
||||
G = AlternatingGroup(5)
|
||||
elements = list(G.generate())
|
||||
assert len(elements) == 60
|
||||
assert [perm.is_even for perm in elements] == [True]*60
|
||||
H = AlternatingGroup(1)
|
||||
assert H.order() == 1
|
||||
L = AlternatingGroup(2)
|
||||
assert L.order() == 1
|
||||
|
||||
|
||||
def test_AbelianGroup():
|
||||
A = AbelianGroup(3, 3, 3)
|
||||
assert A.order() == 27
|
||||
assert A.is_abelian is True
|
||||
|
||||
|
||||
def test_RubikGroup():
|
||||
raises(ValueError, lambda: RubikGroup(1))
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
from sympy.core.sorting import ordered, default_sort_key
|
||||
from sympy.combinatorics.partitions import (Partition, IntegerPartition,
|
||||
RGS_enum, RGS_unrank, RGS_rank,
|
||||
random_integer_partition)
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.utilities.iterables import partitions
|
||||
from sympy.sets.sets import Set, FiniteSet
|
||||
|
||||
|
||||
def test_partition_constructor():
|
||||
raises(ValueError, lambda: Partition([1, 1, 2]))
|
||||
raises(ValueError, lambda: Partition([1, 2, 3], [2, 3, 4]))
|
||||
raises(ValueError, lambda: Partition(1, 2, 3))
|
||||
raises(ValueError, lambda: Partition(*list(range(3))))
|
||||
|
||||
assert Partition([1, 2, 3], [4, 5]) == Partition([4, 5], [1, 2, 3])
|
||||
assert Partition({1, 2, 3}, {4, 5}) == Partition([1, 2, 3], [4, 5])
|
||||
|
||||
a = FiniteSet(1, 2, 3)
|
||||
b = FiniteSet(4, 5)
|
||||
assert Partition(a, b) == Partition([1, 2, 3], [4, 5])
|
||||
assert Partition({a, b}) == Partition(FiniteSet(a, b))
|
||||
assert Partition({a, b}) != Partition(a, b)
|
||||
|
||||
def test_partition():
|
||||
from sympy.abc import x
|
||||
|
||||
a = Partition([1, 2, 3], [4])
|
||||
b = Partition([1, 2], [3, 4])
|
||||
c = Partition([x])
|
||||
l = [a, b, c]
|
||||
l.sort(key=default_sort_key)
|
||||
assert l == [c, a, b]
|
||||
l.sort(key=lambda w: default_sort_key(w, order='rev-lex'))
|
||||
assert l == [c, a, b]
|
||||
|
||||
assert (a == b) is False
|
||||
assert a <= b
|
||||
assert (a > b) is False
|
||||
assert a != b
|
||||
assert a < b
|
||||
|
||||
assert (a + 2).partition == [[1, 2], [3, 4]]
|
||||
assert (b - 1).partition == [[1, 2, 4], [3]]
|
||||
|
||||
assert (a - 1).partition == [[1, 2, 3, 4]]
|
||||
assert (a + 1).partition == [[1, 2, 4], [3]]
|
||||
assert (b + 1).partition == [[1, 2], [3], [4]]
|
||||
|
||||
assert a.rank == 1
|
||||
assert b.rank == 3
|
||||
|
||||
assert a.RGS == (0, 0, 0, 1)
|
||||
assert b.RGS == (0, 0, 1, 1)
|
||||
|
||||
|
||||
def test_integer_partition():
|
||||
# no zeros in partition
|
||||
raises(ValueError, lambda: IntegerPartition(list(range(3))))
|
||||
# check fails since 1 + 2 != 100
|
||||
raises(ValueError, lambda: IntegerPartition(100, list(range(1, 3))))
|
||||
a = IntegerPartition(8, [1, 3, 4])
|
||||
b = a.next_lex()
|
||||
c = IntegerPartition([1, 3, 4])
|
||||
d = IntegerPartition(8, {1: 3, 3: 1, 2: 1})
|
||||
assert a == c
|
||||
assert a.integer == d.integer
|
||||
assert a.conjugate == [3, 2, 2, 1]
|
||||
assert (a == b) is False
|
||||
assert a <= b
|
||||
assert (a > b) is False
|
||||
assert a != b
|
||||
|
||||
for i in range(1, 11):
|
||||
next = set()
|
||||
prev = set()
|
||||
a = IntegerPartition([i])
|
||||
ans = {IntegerPartition(p) for p in partitions(i)}
|
||||
n = len(ans)
|
||||
for j in range(n):
|
||||
next.add(a)
|
||||
a = a.next_lex()
|
||||
IntegerPartition(i, a.partition) # check it by giving i
|
||||
for j in range(n):
|
||||
prev.add(a)
|
||||
a = a.prev_lex()
|
||||
IntegerPartition(i, a.partition) # check it by giving i
|
||||
assert next == ans
|
||||
assert prev == ans
|
||||
|
||||
assert IntegerPartition([1, 2, 3]).as_ferrers() == '###\n##\n#'
|
||||
assert IntegerPartition([1, 1, 3]).as_ferrers('o') == 'ooo\no\no'
|
||||
assert str(IntegerPartition([1, 1, 3])) == '[3, 1, 1]'
|
||||
assert IntegerPartition([1, 1, 3]).partition == [3, 1, 1]
|
||||
|
||||
raises(ValueError, lambda: random_integer_partition(-1))
|
||||
assert random_integer_partition(1) == [1]
|
||||
assert random_integer_partition(10, seed=[1, 3, 2, 1, 5, 1]
|
||||
) == [5, 2, 1, 1, 1]
|
||||
|
||||
|
||||
def test_rgs():
|
||||
raises(ValueError, lambda: RGS_unrank(-1, 3))
|
||||
raises(ValueError, lambda: RGS_unrank(3, 0))
|
||||
raises(ValueError, lambda: RGS_unrank(10, 1))
|
||||
|
||||
raises(ValueError, lambda: Partition.from_rgs(list(range(3)), list(range(2))))
|
||||
raises(ValueError, lambda: Partition.from_rgs(list(range(1, 3)), list(range(2))))
|
||||
assert RGS_enum(-1) == 0
|
||||
assert RGS_enum(1) == 1
|
||||
assert RGS_unrank(7, 5) == [0, 0, 1, 0, 2]
|
||||
assert RGS_unrank(23, 14) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2]
|
||||
assert RGS_rank(RGS_unrank(40, 100)) == 40
|
||||
|
||||
def test_ordered_partition_9608():
|
||||
a = Partition([1, 2, 3], [4])
|
||||
b = Partition([1, 2], [3, 4])
|
||||
assert list(ordered([a,b], Set._infimum_key))
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.combinatorics.named_groups import SymmetricGroup, AlternatingGroup, DihedralGroup
|
||||
from sympy.matrices import Matrix
|
||||
|
||||
def test_pc_presentation():
|
||||
Groups = [SymmetricGroup(3), SymmetricGroup(4), SymmetricGroup(9).sylow_subgroup(3),
|
||||
SymmetricGroup(9).sylow_subgroup(2), SymmetricGroup(8).sylow_subgroup(2), DihedralGroup(10)]
|
||||
|
||||
S = SymmetricGroup(125).sylow_subgroup(5)
|
||||
G = S.derived_series()[2]
|
||||
Groups.append(G)
|
||||
|
||||
G = SymmetricGroup(25).sylow_subgroup(5)
|
||||
Groups.append(G)
|
||||
|
||||
S = SymmetricGroup(11**2).sylow_subgroup(11)
|
||||
G = S.derived_series()[2]
|
||||
Groups.append(G)
|
||||
|
||||
for G in Groups:
|
||||
PcGroup = G.polycyclic_group()
|
||||
collector = PcGroup.collector
|
||||
pc_presentation = collector.pc_presentation
|
||||
|
||||
pcgs = PcGroup.pcgs
|
||||
free_group = collector.free_group
|
||||
free_to_perm = {}
|
||||
for s, g in zip(free_group.symbols, pcgs):
|
||||
free_to_perm[s] = g
|
||||
|
||||
for k, v in pc_presentation.items():
|
||||
k_array = k.array_form
|
||||
if v != ():
|
||||
v_array = v.array_form
|
||||
|
||||
lhs = Permutation()
|
||||
for gen in k_array:
|
||||
s = gen[0]
|
||||
e = gen[1]
|
||||
lhs = lhs*free_to_perm[s]**e
|
||||
|
||||
if v == ():
|
||||
assert lhs.is_identity
|
||||
continue
|
||||
|
||||
rhs = Permutation()
|
||||
for gen in v_array:
|
||||
s = gen[0]
|
||||
e = gen[1]
|
||||
rhs = rhs*free_to_perm[s]**e
|
||||
|
||||
assert lhs == rhs
|
||||
|
||||
|
||||
def test_exponent_vector():
|
||||
|
||||
Groups = [SymmetricGroup(3), SymmetricGroup(4), SymmetricGroup(9).sylow_subgroup(3),
|
||||
SymmetricGroup(9).sylow_subgroup(2), SymmetricGroup(8).sylow_subgroup(2)]
|
||||
|
||||
for G in Groups:
|
||||
PcGroup = G.polycyclic_group()
|
||||
collector = PcGroup.collector
|
||||
|
||||
pcgs = PcGroup.pcgs
|
||||
# free_group = collector.free_group
|
||||
|
||||
for gen in G.generators:
|
||||
exp = collector.exponent_vector(gen)
|
||||
g = Permutation()
|
||||
for i in range(len(exp)):
|
||||
g = g*pcgs[i]**exp[i] if exp[i] else g
|
||||
assert g == gen
|
||||
|
||||
|
||||
def test_induced_pcgs():
|
||||
G = [SymmetricGroup(9).sylow_subgroup(3), SymmetricGroup(20).sylow_subgroup(2), AlternatingGroup(4),
|
||||
DihedralGroup(4), DihedralGroup(10), DihedralGroup(9), SymmetricGroup(3), SymmetricGroup(4)]
|
||||
|
||||
for g in G:
|
||||
PcGroup = g.polycyclic_group()
|
||||
collector = PcGroup.collector
|
||||
gens = list(g.generators)
|
||||
ipcgs = collector.induced_pcgs(gens)
|
||||
m = []
|
||||
for i in ipcgs:
|
||||
m.append(collector.exponent_vector(i))
|
||||
assert Matrix(m).is_upper
|
||||
+1243
File diff suppressed because it is too large
Load Diff
+564
@@ -0,0 +1,564 @@
|
||||
from itertools import permutations
|
||||
from copy import copy
|
||||
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.singleton import S
|
||||
from sympy.combinatorics.permutations import \
|
||||
Permutation, _af_parity, _af_rmul, _af_rmuln, AppliedPermutation, Cycle
|
||||
from sympy.printing import sstr, srepr, pretty, latex
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
rmul = Permutation.rmul
|
||||
a = Symbol('a', integer=True)
|
||||
|
||||
|
||||
def test_Permutation():
|
||||
# don't auto fill 0
|
||||
raises(ValueError, lambda: Permutation([1]))
|
||||
p = Permutation([0, 1, 2, 3])
|
||||
# call as bijective
|
||||
assert [p(i) for i in range(p.size)] == list(p)
|
||||
# call as operator
|
||||
assert p(list(range(p.size))) == list(p)
|
||||
# call as function
|
||||
assert list(p(1, 2)) == [0, 2, 1, 3]
|
||||
raises(TypeError, lambda: p(-1))
|
||||
raises(TypeError, lambda: p(5))
|
||||
# conversion to list
|
||||
assert list(p) == list(range(4))
|
||||
assert p.copy() == p
|
||||
assert copy(p) == p
|
||||
assert Permutation(size=4) == Permutation(3)
|
||||
assert Permutation(Permutation(3), size=5) == Permutation(4)
|
||||
# cycle form with size
|
||||
assert Permutation([[1, 2]], size=4) == Permutation([[1, 2], [0], [3]])
|
||||
# random generation
|
||||
assert Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1]))
|
||||
|
||||
p = Permutation([2, 5, 1, 6, 3, 0, 4])
|
||||
q = Permutation([[1], [0, 3, 5, 6, 2, 4]])
|
||||
assert len({p, p}) == 1
|
||||
r = Permutation([1, 3, 2, 0, 4, 6, 5])
|
||||
ans = Permutation(_af_rmuln(*[w.array_form for w in (p, q, r)])).array_form
|
||||
assert rmul(p, q, r).array_form == ans
|
||||
# make sure no other permutation of p, q, r could have given
|
||||
# that answer
|
||||
for a, b, c in permutations((p, q, r)):
|
||||
if (a, b, c) == (p, q, r):
|
||||
continue
|
||||
assert rmul(a, b, c).array_form != ans
|
||||
|
||||
assert p.support() == list(range(7))
|
||||
assert q.support() == [0, 2, 3, 4, 5, 6]
|
||||
assert Permutation(p.cyclic_form).array_form == p.array_form
|
||||
assert p.cardinality == 5040
|
||||
assert q.cardinality == 5040
|
||||
assert q.cycles == 2
|
||||
assert rmul(q, p) == Permutation([4, 6, 1, 2, 5, 3, 0])
|
||||
assert rmul(p, q) == Permutation([6, 5, 3, 0, 2, 4, 1])
|
||||
assert _af_rmul(p.array_form, q.array_form) == \
|
||||
[6, 5, 3, 0, 2, 4, 1]
|
||||
|
||||
assert rmul(Permutation([[1, 2, 3], [0, 4]]),
|
||||
Permutation([[1, 2, 4], [0], [3]])).cyclic_form == \
|
||||
[[0, 4, 2], [1, 3]]
|
||||
assert q.array_form == [3, 1, 4, 5, 0, 6, 2]
|
||||
assert q.cyclic_form == [[0, 3, 5, 6, 2, 4]]
|
||||
assert q.full_cyclic_form == [[0, 3, 5, 6, 2, 4], [1]]
|
||||
assert p.cyclic_form == [[0, 2, 1, 5], [3, 6, 4]]
|
||||
t = p.transpositions()
|
||||
assert t == [(0, 5), (0, 1), (0, 2), (3, 4), (3, 6)]
|
||||
assert Permutation.rmul(*[Permutation(Cycle(*ti)) for ti in (t)])
|
||||
assert Permutation([1, 0]).transpositions() == [(0, 1)]
|
||||
|
||||
assert p**13 == p
|
||||
assert q**0 == Permutation(list(range(q.size)))
|
||||
assert q**-2 == ~q**2
|
||||
assert q**2 == Permutation([5, 1, 0, 6, 3, 2, 4])
|
||||
assert q**3 == q**2*q
|
||||
assert q**4 == q**2*q**2
|
||||
|
||||
a = Permutation(1, 3)
|
||||
b = Permutation(2, 0, 3)
|
||||
I = Permutation(3)
|
||||
assert ~a == a**-1
|
||||
assert a*~a == I
|
||||
assert a*b**-1 == a*~b
|
||||
|
||||
ans = Permutation(0, 5, 3, 1, 6)(2, 4)
|
||||
assert (p + q.rank()).rank() == ans.rank()
|
||||
assert (p + q.rank())._rank == ans.rank()
|
||||
assert (q + p.rank()).rank() == ans.rank()
|
||||
raises(TypeError, lambda: p + Permutation(list(range(10))))
|
||||
|
||||
assert (p - q.rank()).rank() == Permutation(0, 6, 3, 1, 2, 5, 4).rank()
|
||||
assert p.rank() - q.rank() < 0 # for coverage: make sure mod is used
|
||||
assert (q - p.rank()).rank() == Permutation(1, 4, 6, 2)(3, 5).rank()
|
||||
|
||||
assert p*q == Permutation(_af_rmuln(*[list(w) for w in (q, p)]))
|
||||
assert p*Permutation([]) == p
|
||||
assert Permutation([])*p == p
|
||||
assert p*Permutation([[0, 1]]) == Permutation([2, 5, 0, 6, 3, 1, 4])
|
||||
assert Permutation([[0, 1]])*p == Permutation([5, 2, 1, 6, 3, 0, 4])
|
||||
|
||||
pq = p ^ q
|
||||
assert pq == Permutation([5, 6, 0, 4, 1, 2, 3])
|
||||
assert pq == rmul(q, p, ~q)
|
||||
qp = q ^ p
|
||||
assert qp == Permutation([4, 3, 6, 2, 1, 5, 0])
|
||||
assert qp == rmul(p, q, ~p)
|
||||
raises(ValueError, lambda: p ^ Permutation([]))
|
||||
|
||||
assert p.commutator(q) == Permutation(0, 1, 3, 4, 6, 5, 2)
|
||||
assert q.commutator(p) == Permutation(0, 2, 5, 6, 4, 3, 1)
|
||||
assert p.commutator(q) == ~q.commutator(p)
|
||||
raises(ValueError, lambda: p.commutator(Permutation([])))
|
||||
|
||||
assert len(p.atoms()) == 7
|
||||
assert q.atoms() == {0, 1, 2, 3, 4, 5, 6}
|
||||
|
||||
assert p.inversion_vector() == [2, 4, 1, 3, 1, 0]
|
||||
assert q.inversion_vector() == [3, 1, 2, 2, 0, 1]
|
||||
|
||||
assert Permutation.from_inversion_vector(p.inversion_vector()) == p
|
||||
assert Permutation.from_inversion_vector(q.inversion_vector()).array_form\
|
||||
== q.array_form
|
||||
raises(ValueError, lambda: Permutation.from_inversion_vector([0, 2]))
|
||||
assert Permutation(list(range(500, -1, -1))).inversions() == 125250
|
||||
|
||||
s = Permutation([0, 4, 1, 3, 2])
|
||||
assert s.parity() == 0
|
||||
_ = s.cyclic_form # needed to create a value for _cyclic_form
|
||||
assert len(s._cyclic_form) != s.size and s.parity() == 0
|
||||
assert not s.is_odd
|
||||
assert s.is_even
|
||||
assert Permutation([0, 1, 4, 3, 2]).parity() == 1
|
||||
assert _af_parity([0, 4, 1, 3, 2]) == 0
|
||||
assert _af_parity([0, 1, 4, 3, 2]) == 1
|
||||
|
||||
s = Permutation([0])
|
||||
|
||||
assert s.is_Singleton
|
||||
assert Permutation([]).is_Empty
|
||||
|
||||
r = Permutation([3, 2, 1, 0])
|
||||
assert (r**2).is_Identity
|
||||
|
||||
assert rmul(~p, p).is_Identity
|
||||
assert (~p)**13 == Permutation([5, 2, 0, 4, 6, 1, 3])
|
||||
assert p.max() == 6
|
||||
assert p.min() == 0
|
||||
|
||||
q = Permutation([[6], [5], [0, 1, 2, 3, 4]])
|
||||
|
||||
assert q.max() == 4
|
||||
assert q.min() == 0
|
||||
|
||||
p = Permutation([1, 5, 2, 0, 3, 6, 4])
|
||||
q = Permutation([[1, 2, 3, 5, 6], [0, 4]])
|
||||
|
||||
assert p.ascents() == [0, 3, 4]
|
||||
assert q.ascents() == [1, 2, 4]
|
||||
assert r.ascents() == []
|
||||
|
||||
assert p.descents() == [1, 2, 5]
|
||||
assert q.descents() == [0, 3, 5]
|
||||
assert Permutation(r.descents()).is_Identity
|
||||
|
||||
assert p.inversions() == 7
|
||||
# test the merge-sort with a longer permutation
|
||||
big = list(p) + list(range(p.max() + 1, p.max() + 130))
|
||||
assert Permutation(big).inversions() == 7
|
||||
assert p.signature() == -1
|
||||
assert q.inversions() == 11
|
||||
assert q.signature() == -1
|
||||
assert rmul(p, ~p).inversions() == 0
|
||||
assert rmul(p, ~p).signature() == 1
|
||||
|
||||
assert p.order() == 6
|
||||
assert q.order() == 10
|
||||
assert (p**(p.order())).is_Identity
|
||||
|
||||
assert p.length() == 6
|
||||
assert q.length() == 7
|
||||
assert r.length() == 4
|
||||
|
||||
assert p.runs() == [[1, 5], [2], [0, 3, 6], [4]]
|
||||
assert q.runs() == [[4], [2, 3, 5], [0, 6], [1]]
|
||||
assert r.runs() == [[3], [2], [1], [0]]
|
||||
|
||||
assert p.index() == 8
|
||||
assert q.index() == 8
|
||||
assert r.index() == 3
|
||||
|
||||
assert p.get_precedence_distance(q) == q.get_precedence_distance(p)
|
||||
assert p.get_adjacency_distance(q) == p.get_adjacency_distance(q)
|
||||
assert p.get_positional_distance(q) == p.get_positional_distance(q)
|
||||
p = Permutation([0, 1, 2, 3])
|
||||
q = Permutation([3, 2, 1, 0])
|
||||
assert p.get_precedence_distance(q) == 6
|
||||
assert p.get_adjacency_distance(q) == 3
|
||||
assert p.get_positional_distance(q) == 8
|
||||
p = Permutation([0, 3, 1, 2, 4])
|
||||
q = Permutation.josephus(4, 5, 2)
|
||||
assert p.get_adjacency_distance(q) == 3
|
||||
raises(ValueError, lambda: p.get_adjacency_distance(Permutation([])))
|
||||
raises(ValueError, lambda: p.get_positional_distance(Permutation([])))
|
||||
raises(ValueError, lambda: p.get_precedence_distance(Permutation([])))
|
||||
|
||||
a = [Permutation.unrank_nonlex(4, i) for i in range(5)]
|
||||
iden = Permutation([0, 1, 2, 3])
|
||||
for i in range(5):
|
||||
for j in range(i + 1, 5):
|
||||
assert a[i].commutes_with(a[j]) == \
|
||||
(rmul(a[i], a[j]) == rmul(a[j], a[i]))
|
||||
if a[i].commutes_with(a[j]):
|
||||
assert a[i].commutator(a[j]) == iden
|
||||
assert a[j].commutator(a[i]) == iden
|
||||
|
||||
a = Permutation(3)
|
||||
b = Permutation(0, 6, 3)(1, 2)
|
||||
assert a.cycle_structure == {1: 4}
|
||||
assert b.cycle_structure == {2: 1, 3: 1, 1: 2}
|
||||
# issue 11130
|
||||
raises(ValueError, lambda: Permutation(3, size=3))
|
||||
raises(ValueError, lambda: Permutation([1, 2, 0, 3], size=3))
|
||||
|
||||
|
||||
def test_Permutation_subclassing():
|
||||
# Subclass that adds permutation application on iterables
|
||||
class CustomPermutation(Permutation):
|
||||
def __call__(self, *i):
|
||||
try:
|
||||
return super().__call__(*i)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
perm_obj = i[0]
|
||||
return [self._array_form[j] for j in perm_obj]
|
||||
except TypeError:
|
||||
raise TypeError('unrecognized argument')
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Permutation):
|
||||
return self._hashable_content() == other._hashable_content()
|
||||
else:
|
||||
return super().__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
p = CustomPermutation([1, 2, 3, 0])
|
||||
q = Permutation([1, 2, 3, 0])
|
||||
|
||||
assert p == q
|
||||
raises(TypeError, lambda: q([1, 2]))
|
||||
assert [2, 3] == p([1, 2])
|
||||
|
||||
assert type(p * q) == CustomPermutation
|
||||
assert type(q * p) == Permutation # True because q.__mul__(p) is called!
|
||||
|
||||
# Run all tests for the Permutation class also on the subclass
|
||||
def wrapped_test_Permutation():
|
||||
# Monkeypatch the class definition in the globals
|
||||
globals()['__Perm'] = globals()['Permutation']
|
||||
globals()['Permutation'] = CustomPermutation
|
||||
test_Permutation()
|
||||
globals()['Permutation'] = globals()['__Perm'] # Restore
|
||||
del globals()['__Perm']
|
||||
|
||||
wrapped_test_Permutation()
|
||||
|
||||
|
||||
def test_josephus():
|
||||
assert Permutation.josephus(4, 6, 1) == Permutation([3, 1, 0, 2, 5, 4])
|
||||
assert Permutation.josephus(1, 5, 1).is_Identity
|
||||
|
||||
|
||||
def test_ranking():
|
||||
assert Permutation.unrank_lex(5, 10).rank() == 10
|
||||
p = Permutation.unrank_lex(15, 225)
|
||||
assert p.rank() == 225
|
||||
p1 = p.next_lex()
|
||||
assert p1.rank() == 226
|
||||
assert Permutation.unrank_lex(15, 225).rank() == 225
|
||||
assert Permutation.unrank_lex(10, 0).is_Identity
|
||||
p = Permutation.unrank_lex(4, 23)
|
||||
assert p.rank() == 23
|
||||
assert p.array_form == [3, 2, 1, 0]
|
||||
assert p.next_lex() is None
|
||||
|
||||
p = Permutation([1, 5, 2, 0, 3, 6, 4])
|
||||
q = Permutation([[1, 2, 3, 5, 6], [0, 4]])
|
||||
a = [Permutation.unrank_trotterjohnson(4, i).array_form for i in range(5)]
|
||||
assert a == [[0, 1, 2, 3], [0, 1, 3, 2], [0, 3, 1, 2], [3, 0, 1,
|
||||
2], [3, 0, 2, 1] ]
|
||||
assert [Permutation(pa).rank_trotterjohnson() for pa in a] == list(range(5))
|
||||
assert Permutation([0, 1, 2, 3]).next_trotterjohnson() == \
|
||||
Permutation([0, 1, 3, 2])
|
||||
|
||||
assert q.rank_trotterjohnson() == 2283
|
||||
assert p.rank_trotterjohnson() == 3389
|
||||
assert Permutation([1, 0]).rank_trotterjohnson() == 1
|
||||
a = Permutation(list(range(3)))
|
||||
b = a
|
||||
l = []
|
||||
tj = []
|
||||
for i in range(6):
|
||||
l.append(a)
|
||||
tj.append(b)
|
||||
a = a.next_lex()
|
||||
b = b.next_trotterjohnson()
|
||||
assert a == b is None
|
||||
assert {tuple(a) for a in l} == {tuple(a) for a in tj}
|
||||
|
||||
p = Permutation([2, 5, 1, 6, 3, 0, 4])
|
||||
q = Permutation([[6], [5], [0, 1, 2, 3, 4]])
|
||||
assert p.rank() == 1964
|
||||
assert q.rank() == 870
|
||||
assert Permutation([]).rank_nonlex() == 0
|
||||
prank = p.rank_nonlex()
|
||||
assert prank == 1600
|
||||
assert Permutation.unrank_nonlex(7, 1600) == p
|
||||
qrank = q.rank_nonlex()
|
||||
assert qrank == 41
|
||||
assert Permutation.unrank_nonlex(7, 41) == Permutation(q.array_form)
|
||||
|
||||
a = [Permutation.unrank_nonlex(4, i).array_form for i in range(24)]
|
||||
assert a == [
|
||||
[1, 2, 3, 0], [3, 2, 0, 1], [1, 3, 0, 2], [1, 2, 0, 3], [2, 3, 1, 0],
|
||||
[2, 0, 3, 1], [3, 0, 1, 2], [2, 0, 1, 3], [1, 3, 2, 0], [3, 0, 2, 1],
|
||||
[1, 0, 3, 2], [1, 0, 2, 3], [2, 1, 3, 0], [2, 3, 0, 1], [3, 1, 0, 2],
|
||||
[2, 1, 0, 3], [3, 2, 1, 0], [0, 2, 3, 1], [0, 3, 1, 2], [0, 2, 1, 3],
|
||||
[3, 1, 2, 0], [0, 3, 2, 1], [0, 1, 3, 2], [0, 1, 2, 3]]
|
||||
|
||||
N = 10
|
||||
p1 = Permutation(a[0])
|
||||
for i in range(1, N+1):
|
||||
p1 = p1*Permutation(a[i])
|
||||
p2 = Permutation.rmul_with_af(*[Permutation(h) for h in a[N::-1]])
|
||||
assert p1 == p2
|
||||
|
||||
ok = []
|
||||
p = Permutation([1, 0])
|
||||
for i in range(3):
|
||||
ok.append(p.array_form)
|
||||
p = p.next_nonlex()
|
||||
if p is None:
|
||||
ok.append(None)
|
||||
break
|
||||
assert ok == [[1, 0], [0, 1], None]
|
||||
assert Permutation([3, 2, 0, 1]).next_nonlex() == Permutation([1, 3, 0, 2])
|
||||
assert [Permutation(pa).rank_nonlex() for pa in a] == list(range(24))
|
||||
|
||||
|
||||
def test_mul():
|
||||
a, b = [0, 2, 1, 3], [0, 1, 3, 2]
|
||||
assert _af_rmul(a, b) == [0, 2, 3, 1]
|
||||
assert _af_rmuln(a, b, list(range(4))) == [0, 2, 3, 1]
|
||||
assert rmul(Permutation(a), Permutation(b)).array_form == [0, 2, 3, 1]
|
||||
|
||||
a = Permutation([0, 2, 1, 3])
|
||||
b = (0, 1, 3, 2)
|
||||
c = (3, 1, 2, 0)
|
||||
assert Permutation.rmul(a, b, c) == Permutation([1, 2, 3, 0])
|
||||
assert Permutation.rmul(a, c) == Permutation([3, 2, 1, 0])
|
||||
raises(TypeError, lambda: Permutation.rmul(b, c))
|
||||
|
||||
n = 6
|
||||
m = 8
|
||||
a = [Permutation.unrank_nonlex(n, i).array_form for i in range(m)]
|
||||
h = list(range(n))
|
||||
for i in range(m):
|
||||
h = _af_rmul(h, a[i])
|
||||
h2 = _af_rmuln(*a[:i + 1])
|
||||
assert h == h2
|
||||
|
||||
|
||||
def test_args():
|
||||
p = Permutation([(0, 3, 1, 2), (4, 5)])
|
||||
assert p._cyclic_form is None
|
||||
assert Permutation(p) == p
|
||||
assert p.cyclic_form == [[0, 3, 1, 2], [4, 5]]
|
||||
assert p._array_form == [3, 2, 0, 1, 5, 4]
|
||||
p = Permutation((0, 3, 1, 2))
|
||||
assert p._cyclic_form is None
|
||||
assert p._array_form == [0, 3, 1, 2]
|
||||
assert Permutation([0]) == Permutation((0, ))
|
||||
assert Permutation([[0], [1]]) == Permutation(((0, ), (1, ))) == \
|
||||
Permutation(((0, ), [1]))
|
||||
assert Permutation([[1, 2]]) == Permutation([0, 2, 1])
|
||||
assert Permutation([[1], [4, 2]]) == Permutation([0, 1, 4, 3, 2])
|
||||
assert Permutation([[1], [4, 2]], size=1) == Permutation([0, 1, 4, 3, 2])
|
||||
assert Permutation(
|
||||
[[1], [4, 2]], size=6) == Permutation([0, 1, 4, 3, 2, 5])
|
||||
assert Permutation([[0, 1], [0, 2]]) == Permutation(0, 1, 2)
|
||||
assert Permutation([], size=3) == Permutation([0, 1, 2])
|
||||
assert Permutation(3).list(5) == [0, 1, 2, 3, 4]
|
||||
assert Permutation(3).list(-1) == []
|
||||
assert Permutation(5)(1, 2).list(-1) == [0, 2, 1]
|
||||
assert Permutation(5)(1, 2).list() == [0, 2, 1, 3, 4, 5]
|
||||
raises(ValueError, lambda: Permutation([1, 2], [0]))
|
||||
# enclosing brackets needed
|
||||
raises(ValueError, lambda: Permutation([[1, 2], 0]))
|
||||
# enclosing brackets needed on 0
|
||||
raises(ValueError, lambda: Permutation([1, 1, 0]))
|
||||
raises(ValueError, lambda: Permutation([4, 5], size=10)) # where are 0-3?
|
||||
# but this is ok because cycles imply that only those listed moved
|
||||
assert Permutation(4, 5) == Permutation([0, 1, 2, 3, 5, 4])
|
||||
|
||||
|
||||
def test_Cycle():
|
||||
assert str(Cycle()) == '()'
|
||||
assert Cycle(Cycle(1,2)) == Cycle(1, 2)
|
||||
assert Cycle(1,2).copy() == Cycle(1,2)
|
||||
assert list(Cycle(1, 3, 2)) == [0, 3, 1, 2]
|
||||
assert Cycle(1, 2)(2, 3) == Cycle(1, 3, 2)
|
||||
assert Cycle(1, 2)(2, 3)(4, 5) == Cycle(1, 3, 2)(4, 5)
|
||||
assert Permutation(Cycle(1, 2)(2, 1, 0, 3)).cyclic_form, Cycle(0, 2, 1)
|
||||
raises(ValueError, lambda: Cycle().list())
|
||||
assert Cycle(1, 2).list() == [0, 2, 1]
|
||||
assert Cycle(1, 2).list(4) == [0, 2, 1, 3]
|
||||
assert Cycle(3).list(2) == [0, 1]
|
||||
assert Cycle(3).list(6) == [0, 1, 2, 3, 4, 5]
|
||||
assert Permutation(Cycle(1, 2), size=4) == \
|
||||
Permutation([0, 2, 1, 3])
|
||||
assert str(Cycle(1, 2)(4, 5)) == '(1 2)(4 5)'
|
||||
assert str(Cycle(1, 2)) == '(1 2)'
|
||||
assert Cycle(Permutation(list(range(3)))) == Cycle()
|
||||
assert Cycle(1, 2).list() == [0, 2, 1]
|
||||
assert Cycle(1, 2).list(4) == [0, 2, 1, 3]
|
||||
assert Cycle().size == 0
|
||||
raises(ValueError, lambda: Cycle((1, 2)))
|
||||
raises(ValueError, lambda: Cycle(1, 2, 1))
|
||||
raises(TypeError, lambda: Cycle(1, 2)*{})
|
||||
raises(ValueError, lambda: Cycle(4)[a])
|
||||
raises(ValueError, lambda: Cycle(2, -4, 3))
|
||||
|
||||
# check round-trip
|
||||
p = Permutation([[1, 2], [4, 3]], size=5)
|
||||
assert Permutation(Cycle(p)) == p
|
||||
|
||||
|
||||
def test_from_sequence():
|
||||
assert Permutation.from_sequence('SymPy') == Permutation(4)(0, 1, 3)
|
||||
assert Permutation.from_sequence('SymPy', key=lambda x: x.lower()) == \
|
||||
Permutation(4)(0, 2)(1, 3)
|
||||
|
||||
|
||||
def test_resize():
|
||||
p = Permutation(0, 1, 2)
|
||||
assert p.resize(5) == Permutation(0, 1, 2, size=5)
|
||||
assert p.resize(4) == Permutation(0, 1, 2, size=4)
|
||||
assert p.resize(3) == p
|
||||
raises(ValueError, lambda: p.resize(2))
|
||||
|
||||
p = Permutation(0, 1, 2)(3, 4)(5, 6)
|
||||
assert p.resize(3) == Permutation(0, 1, 2)
|
||||
raises(ValueError, lambda: p.resize(4))
|
||||
|
||||
|
||||
def test_printing_cyclic():
|
||||
p1 = Permutation([0, 2, 1])
|
||||
assert repr(p1) == 'Permutation(1, 2)'
|
||||
assert str(p1) == '(1 2)'
|
||||
p2 = Permutation()
|
||||
assert repr(p2) == 'Permutation()'
|
||||
assert str(p2) == '()'
|
||||
p3 = Permutation([1, 2, 0, 3])
|
||||
assert repr(p3) == 'Permutation(3)(0, 1, 2)'
|
||||
|
||||
|
||||
def test_printing_non_cyclic():
|
||||
p1 = Permutation([0, 1, 2, 3, 4, 5])
|
||||
assert srepr(p1, perm_cyclic=False) == 'Permutation([], size=6)'
|
||||
assert sstr(p1, perm_cyclic=False) == 'Permutation([], size=6)'
|
||||
p2 = Permutation([0, 1, 2])
|
||||
assert srepr(p2, perm_cyclic=False) == 'Permutation([0, 1, 2])'
|
||||
assert sstr(p2, perm_cyclic=False) == 'Permutation([0, 1, 2])'
|
||||
|
||||
p3 = Permutation([0, 2, 1])
|
||||
assert srepr(p3, perm_cyclic=False) == 'Permutation([0, 2, 1])'
|
||||
assert sstr(p3, perm_cyclic=False) == 'Permutation([0, 2, 1])'
|
||||
p4 = Permutation([0, 1, 3, 2, 4, 5, 6, 7])
|
||||
assert srepr(p4, perm_cyclic=False) == 'Permutation([0, 1, 3, 2], size=8)'
|
||||
|
||||
|
||||
def test_deprecated_print_cyclic():
|
||||
p = Permutation(0, 1, 2)
|
||||
try:
|
||||
Permutation.print_cyclic = True
|
||||
with warns_deprecated_sympy():
|
||||
assert sstr(p) == '(0 1 2)'
|
||||
with warns_deprecated_sympy():
|
||||
assert srepr(p) == 'Permutation(0, 1, 2)'
|
||||
with warns_deprecated_sympy():
|
||||
assert pretty(p) == '(0 1 2)'
|
||||
with warns_deprecated_sympy():
|
||||
assert latex(p) == r'\left( 0\; 1\; 2\right)'
|
||||
|
||||
Permutation.print_cyclic = False
|
||||
with warns_deprecated_sympy():
|
||||
assert sstr(p) == 'Permutation([1, 2, 0])'
|
||||
with warns_deprecated_sympy():
|
||||
assert srepr(p) == 'Permutation([1, 2, 0])'
|
||||
with warns_deprecated_sympy():
|
||||
assert pretty(p, use_unicode=False) == '/0 1 2\\\n\\1 2 0/'
|
||||
with warns_deprecated_sympy():
|
||||
assert latex(p) == \
|
||||
r'\begin{pmatrix} 0 & 1 & 2 \\ 1 & 2 & 0 \end{pmatrix}'
|
||||
finally:
|
||||
Permutation.print_cyclic = None
|
||||
|
||||
|
||||
def test_permutation_equality():
|
||||
a = Permutation(0, 1, 2)
|
||||
b = Permutation(0, 1, 2)
|
||||
assert Eq(a, b) is S.true
|
||||
c = Permutation(0, 2, 1)
|
||||
assert Eq(a, c) is S.false
|
||||
|
||||
d = Permutation(0, 1, 2, size=4)
|
||||
assert unchanged(Eq, a, d)
|
||||
e = Permutation(0, 2, 1, size=4)
|
||||
assert unchanged(Eq, a, e)
|
||||
|
||||
i = Permutation()
|
||||
assert unchanged(Eq, i, 0)
|
||||
assert unchanged(Eq, 0, i)
|
||||
|
||||
|
||||
def test_issue_17661():
|
||||
c1 = Cycle(1,2)
|
||||
c2 = Cycle(1,2)
|
||||
assert c1 == c2
|
||||
assert repr(c1) == 'Cycle(1, 2)'
|
||||
assert c1 == c2
|
||||
|
||||
|
||||
def test_permutation_apply():
|
||||
x = Symbol('x')
|
||||
p = Permutation(0, 1, 2)
|
||||
assert p.apply(0) == 1
|
||||
assert isinstance(p.apply(0), Integer)
|
||||
assert p.apply(x) == AppliedPermutation(p, x)
|
||||
assert AppliedPermutation(p, x).subs(x, 0) == 1
|
||||
|
||||
x = Symbol('x', integer=False)
|
||||
raises(NotImplementedError, lambda: p.apply(x))
|
||||
x = Symbol('x', negative=True)
|
||||
raises(NotImplementedError, lambda: p.apply(x))
|
||||
|
||||
|
||||
def test_AppliedPermutation():
|
||||
x = Symbol('x')
|
||||
p = Permutation(0, 1, 2)
|
||||
raises(ValueError, lambda: AppliedPermutation((0, 1, 2), x))
|
||||
assert AppliedPermutation(p, 1, evaluate=True) == 2
|
||||
assert AppliedPermutation(p, 1, evaluate=False).__class__ == \
|
||||
AppliedPermutation
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.sets.sets import FiniteSet
|
||||
from sympy.combinatorics.polyhedron import (Polyhedron,
|
||||
tetrahedron, cube as square, octahedron, dodecahedron, icosahedron,
|
||||
cube_faces)
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
rmul = Permutation.rmul
|
||||
|
||||
|
||||
def test_polyhedron():
|
||||
raises(ValueError, lambda: Polyhedron(list('ab'),
|
||||
pgroup=[Permutation([0])]))
|
||||
pgroup = [Permutation([[0, 7, 2, 5], [6, 1, 4, 3]]),
|
||||
Permutation([[0, 7, 1, 6], [5, 2, 4, 3]]),
|
||||
Permutation([[3, 6, 0, 5], [4, 1, 7, 2]]),
|
||||
Permutation([[7, 4, 5], [1, 3, 0], [2], [6]]),
|
||||
Permutation([[1, 3, 2], [7, 6, 5], [4], [0]]),
|
||||
Permutation([[4, 7, 6], [2, 0, 3], [1], [5]]),
|
||||
Permutation([[1, 2, 0], [4, 5, 6], [3], [7]]),
|
||||
Permutation([[4, 2], [0, 6], [3, 7], [1, 5]]),
|
||||
Permutation([[3, 5], [7, 1], [2, 6], [0, 4]]),
|
||||
Permutation([[2, 5], [1, 6], [0, 4], [3, 7]]),
|
||||
Permutation([[4, 3], [7, 0], [5, 1], [6, 2]]),
|
||||
Permutation([[4, 1], [0, 5], [6, 2], [7, 3]]),
|
||||
Permutation([[7, 2], [3, 6], [0, 4], [1, 5]]),
|
||||
Permutation([0, 1, 2, 3, 4, 5, 6, 7])]
|
||||
corners = tuple(symbols('A:H'))
|
||||
faces = cube_faces
|
||||
cube = Polyhedron(corners, faces, pgroup)
|
||||
|
||||
assert cube.edges == FiniteSet(*(
|
||||
(0, 1), (6, 7), (1, 2), (5, 6), (0, 3), (2, 3),
|
||||
(4, 7), (4, 5), (3, 7), (1, 5), (0, 4), (2, 6)))
|
||||
|
||||
for i in range(3): # add 180 degree face rotations
|
||||
cube.rotate(cube.pgroup[i]**2)
|
||||
|
||||
assert cube.corners == corners
|
||||
|
||||
for i in range(3, 7): # add 240 degree axial corner rotations
|
||||
cube.rotate(cube.pgroup[i]**2)
|
||||
|
||||
assert cube.corners == corners
|
||||
cube.rotate(1)
|
||||
raises(ValueError, lambda: cube.rotate(Permutation([0, 1])))
|
||||
assert cube.corners != corners
|
||||
assert cube.array_form == [7, 6, 4, 5, 3, 2, 0, 1]
|
||||
assert cube.cyclic_form == [[0, 7, 1, 6], [2, 4, 3, 5]]
|
||||
cube.reset()
|
||||
assert cube.corners == corners
|
||||
|
||||
def check(h, size, rpt, target):
|
||||
|
||||
assert len(h.faces) + len(h.vertices) - len(h.edges) == 2
|
||||
assert h.size == size
|
||||
|
||||
got = set()
|
||||
for p in h.pgroup:
|
||||
# make sure it restores original
|
||||
P = h.copy()
|
||||
hit = P.corners
|
||||
for i in range(rpt):
|
||||
P.rotate(p)
|
||||
if P.corners == hit:
|
||||
break
|
||||
else:
|
||||
print('error in permutation', p.array_form)
|
||||
for i in range(rpt):
|
||||
P.rotate(p)
|
||||
got.add(tuple(P.corners))
|
||||
c = P.corners
|
||||
f = [[c[i] for i in f] for f in P.faces]
|
||||
assert h.faces == Polyhedron(c, f).faces
|
||||
assert len(got) == target
|
||||
assert PermutationGroup([Permutation(g) for g in got]).is_group
|
||||
|
||||
for h, size, rpt, target in zip(
|
||||
(tetrahedron, square, octahedron, dodecahedron, icosahedron),
|
||||
(4, 8, 6, 20, 12),
|
||||
(3, 4, 4, 5, 5),
|
||||
(12, 24, 24, 60, 60)):
|
||||
check(h, size, rpt, target)
|
||||
|
||||
|
||||
def test_pgroups():
|
||||
from sympy.combinatorics.polyhedron import (cube, tetrahedron_faces,
|
||||
octahedron_faces, dodecahedron_faces, icosahedron_faces)
|
||||
from sympy.combinatorics.polyhedron import _pgroup_calcs
|
||||
(tetrahedron2, cube2, octahedron2, dodecahedron2, icosahedron2,
|
||||
tetrahedron_faces2, cube_faces2, octahedron_faces2,
|
||||
dodecahedron_faces2, icosahedron_faces2) = _pgroup_calcs()
|
||||
|
||||
assert tetrahedron == tetrahedron2
|
||||
assert cube == cube2
|
||||
assert octahedron == octahedron2
|
||||
assert dodecahedron == dodecahedron2
|
||||
assert icosahedron == icosahedron2
|
||||
assert sorted(map(sorted, tetrahedron_faces)) == sorted(map(sorted, tetrahedron_faces2))
|
||||
assert sorted(cube_faces) == sorted(cube_faces2)
|
||||
assert sorted(octahedron_faces) == sorted(octahedron_faces2)
|
||||
assert sorted(dodecahedron_faces) == sorted(dodecahedron_faces2)
|
||||
assert sorted(icosahedron_faces) == sorted(icosahedron_faces2)
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
from sympy.combinatorics.prufer import Prufer
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_prufer():
|
||||
# number of nodes is optional
|
||||
assert Prufer([[0, 1], [0, 2], [0, 3], [0, 4]], 5).nodes == 5
|
||||
assert Prufer([[0, 1], [0, 2], [0, 3], [0, 4]]).nodes == 5
|
||||
|
||||
a = Prufer([[0, 1], [0, 2], [0, 3], [0, 4]])
|
||||
assert a.rank == 0
|
||||
assert a.nodes == 5
|
||||
assert a.prufer_repr == [0, 0, 0]
|
||||
|
||||
a = Prufer([[2, 4], [1, 4], [1, 3], [0, 5], [0, 4]])
|
||||
assert a.rank == 924
|
||||
assert a.nodes == 6
|
||||
assert a.tree_repr == [[2, 4], [1, 4], [1, 3], [0, 5], [0, 4]]
|
||||
assert a.prufer_repr == [4, 1, 4, 0]
|
||||
|
||||
assert Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) == \
|
||||
([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7)
|
||||
assert Prufer([0]*4).size == Prufer([6]*4).size == 1296
|
||||
|
||||
# accept iterables but convert to list of lists
|
||||
tree = [(0, 1), (1, 5), (0, 3), (0, 2), (2, 6), (4, 7), (2, 4)]
|
||||
tree_lists = [list(t) for t in tree]
|
||||
assert Prufer(tree).tree_repr == tree_lists
|
||||
assert sorted(Prufer(set(tree)).tree_repr) == sorted(tree_lists)
|
||||
|
||||
raises(ValueError, lambda: Prufer([[1, 2], [3, 4]])) # 0 is missing
|
||||
raises(ValueError, lambda: Prufer([[2, 3], [3, 4]])) # 0, 1 are missing
|
||||
assert Prufer(*Prufer.edges([1, 2], [3, 4])).prufer_repr == [1, 3]
|
||||
raises(ValueError, lambda: Prufer.edges(
|
||||
[1, 3], [3, 4])) # a broken tree but edges doesn't care
|
||||
raises(ValueError, lambda: Prufer.edges([1, 2], [5, 6]))
|
||||
raises(ValueError, lambda: Prufer([[]]))
|
||||
|
||||
a = Prufer([[0, 1], [0, 2], [0, 3]])
|
||||
b = a.next()
|
||||
assert b.tree_repr == [[0, 2], [0, 1], [1, 3]]
|
||||
assert b.rank == 1
|
||||
|
||||
|
||||
def test_round_trip():
|
||||
def doit(t, b):
|
||||
e, n = Prufer.edges(*t)
|
||||
t = Prufer(e, n)
|
||||
a = sorted(t.tree_repr)
|
||||
b = [i - 1 for i in b]
|
||||
assert t.prufer_repr == b
|
||||
assert sorted(Prufer(b).tree_repr) == a
|
||||
assert Prufer.unrank(t.rank, n).prufer_repr == b
|
||||
|
||||
doit([[1, 2]], [])
|
||||
doit([[2, 1, 3]], [1])
|
||||
doit([[1, 3, 2]], [3])
|
||||
doit([[1, 2, 3]], [2])
|
||||
doit([[2, 1, 4], [1, 3]], [1, 1])
|
||||
doit([[3, 2, 1, 4]], [2, 1])
|
||||
doit([[3, 2, 1], [2, 4]], [2, 2])
|
||||
doit([[1, 3, 2, 4]], [3, 2])
|
||||
doit([[1, 4, 2, 3]], [4, 2])
|
||||
doit([[3, 1, 4, 2]], [4, 1])
|
||||
doit([[4, 2, 1, 3]], [1, 2])
|
||||
doit([[1, 2, 4, 3]], [2, 4])
|
||||
doit([[1, 3, 4, 2]], [3, 4])
|
||||
doit([[2, 4, 1], [4, 3]], [4, 4])
|
||||
doit([[1, 2, 3, 4]], [2, 3])
|
||||
doit([[2, 3, 1], [3, 4]], [3, 3])
|
||||
doit([[1, 4, 3, 2]], [4, 3])
|
||||
doit([[2, 1, 4, 3]], [1, 4])
|
||||
doit([[2, 1, 3, 4]], [1, 3])
|
||||
doit([[6, 2, 1, 4], [1, 3, 5, 8], [3, 7]], [1, 2, 1, 3, 3, 5])
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
from sympy.combinatorics.fp_groups import FpGroup
|
||||
from sympy.combinatorics.free_groups import free_group
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_rewriting():
|
||||
F, a, b = free_group("a, b")
|
||||
G = FpGroup(F, [a*b*a**-1*b**-1])
|
||||
a, b = G.generators
|
||||
R = G._rewriting_system
|
||||
assert R.is_confluent
|
||||
|
||||
assert G.reduce(b**-1*a) == a*b**-1
|
||||
assert G.reduce(b**3*a**4*b**-2*a) == a**5*b
|
||||
assert G.equals(b**2*a**-1*b, b**4*a**-1*b**-1)
|
||||
|
||||
assert R.reduce_using_automaton(b*a*a**2*b**-1) == a**3
|
||||
assert R.reduce_using_automaton(b**3*a**4*b**-2*a) == a**5*b
|
||||
assert R.reduce_using_automaton(b**-1*a) == a*b**-1
|
||||
|
||||
G = FpGroup(F, [a**3, b**3, (a*b)**2])
|
||||
R = G._rewriting_system
|
||||
R.make_confluent()
|
||||
# R._is_confluent should be set to True after
|
||||
# a successful run of make_confluent
|
||||
assert R.is_confluent
|
||||
# but also the system should actually be confluent
|
||||
assert R._check_confluence()
|
||||
assert G.reduce(b*a**-1*b**-1*a**3*b**4*a**-1*b**-15) == a**-1*b**-1
|
||||
# check for automaton reduction
|
||||
assert R.reduce_using_automaton(b*a**-1*b**-1*a**3*b**4*a**-1*b**-15) == a**-1*b**-1
|
||||
|
||||
G = FpGroup(F, [a**2, b**3, (a*b)**4])
|
||||
R = G._rewriting_system
|
||||
assert G.reduce(a**2*b**-2*a**2*b) == b**-1
|
||||
assert R.reduce_using_automaton(a**2*b**-2*a**2*b) == b**-1
|
||||
assert G.reduce(a**3*b**-2*a**2*b) == a**-1*b**-1
|
||||
assert R.reduce_using_automaton(a**3*b**-2*a**2*b) == a**-1*b**-1
|
||||
# Check after adding a rule
|
||||
R.add_rule(a**2, b)
|
||||
assert R.reduce_using_automaton(a**2*b**-2*a**2*b) == b**-1
|
||||
assert R.reduce_using_automaton(a**4*b**-2*a**2*b**3) == b
|
||||
|
||||
R.set_max(15)
|
||||
raises(RuntimeError, lambda: R.add_rule(a**-3, b))
|
||||
R.set_max(20)
|
||||
R.add_rule(a**-3, b)
|
||||
|
||||
assert R.add_rule(a, a) == set()
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
from sympy.core import S, Rational
|
||||
from sympy.combinatorics.schur_number import schur_partition, SchurNumber
|
||||
from sympy.core.random import _randint
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
|
||||
def _sum_free_test(subset):
|
||||
"""
|
||||
Checks if subset is sum-free(There are no x,y,z in the subset such that
|
||||
x + y = z)
|
||||
"""
|
||||
for i in subset:
|
||||
for j in subset:
|
||||
assert (i + j in subset) is False
|
||||
|
||||
|
||||
def test_schur_partition():
|
||||
raises(ValueError, lambda: schur_partition(S.Infinity))
|
||||
raises(ValueError, lambda: schur_partition(-1))
|
||||
raises(ValueError, lambda: schur_partition(0))
|
||||
assert schur_partition(2) == [[1, 2]]
|
||||
|
||||
random_number_generator = _randint(1000)
|
||||
for _ in range(5):
|
||||
n = random_number_generator(1, 1000)
|
||||
result = schur_partition(n)
|
||||
t = 0
|
||||
numbers = []
|
||||
for item in result:
|
||||
_sum_free_test(item)
|
||||
"""
|
||||
Checks if the occurrence of all numbers is exactly one
|
||||
"""
|
||||
t += len(item)
|
||||
for l in item:
|
||||
assert (l in numbers) is False
|
||||
numbers.append(l)
|
||||
assert n == t
|
||||
|
||||
x = symbols("x")
|
||||
raises(ValueError, lambda: schur_partition(x))
|
||||
|
||||
def test_schur_number():
|
||||
first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160}
|
||||
for k in first_known_schur_numbers:
|
||||
assert SchurNumber(k) == first_known_schur_numbers[k]
|
||||
|
||||
assert SchurNumber(S.Infinity) == S.Infinity
|
||||
assert SchurNumber(0) == 0
|
||||
raises(ValueError, lambda: SchurNumber(0.5))
|
||||
|
||||
n = symbols("n")
|
||||
assert SchurNumber(n).lower_bound() == 3**n/2 - Rational(1, 2)
|
||||
assert SchurNumber(8).lower_bound() == 5039
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
from sympy.combinatorics.subsets import Subset, ksubsets
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_subset():
|
||||
a = Subset(['c', 'd'], ['a', 'b', 'c', 'd'])
|
||||
assert a.next_binary() == Subset(['b'], ['a', 'b', 'c', 'd'])
|
||||
assert a.prev_binary() == Subset(['c'], ['a', 'b', 'c', 'd'])
|
||||
assert a.next_lexicographic() == Subset(['d'], ['a', 'b', 'c', 'd'])
|
||||
assert a.prev_lexicographic() == Subset(['c'], ['a', 'b', 'c', 'd'])
|
||||
assert a.next_gray() == Subset(['c'], ['a', 'b', 'c', 'd'])
|
||||
assert a.prev_gray() == Subset(['d'], ['a', 'b', 'c', 'd'])
|
||||
assert a.rank_binary == 3
|
||||
assert a.rank_lexicographic == 14
|
||||
assert a.rank_gray == 2
|
||||
assert a.cardinality == 16
|
||||
assert a.size == 2
|
||||
assert Subset.bitlist_from_subset(a, ['a', 'b', 'c', 'd']) == '0011'
|
||||
|
||||
a = Subset([2, 5, 7], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.next_binary() == Subset([2, 5, 6], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.prev_binary() == Subset([2, 5], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.next_lexicographic() == Subset([2, 6], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.prev_lexicographic() == Subset([2, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.next_gray() == Subset([2, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.prev_gray() == Subset([2, 5], [1, 2, 3, 4, 5, 6, 7])
|
||||
assert a.rank_binary == 37
|
||||
assert a.rank_lexicographic == 93
|
||||
assert a.rank_gray == 57
|
||||
assert a.cardinality == 128
|
||||
|
||||
superset = ['a', 'b', 'c', 'd']
|
||||
assert Subset.unrank_binary(4, superset).rank_binary == 4
|
||||
assert Subset.unrank_gray(10, superset).rank_gray == 10
|
||||
|
||||
superset = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
assert Subset.unrank_binary(33, superset).rank_binary == 33
|
||||
assert Subset.unrank_gray(25, superset).rank_gray == 25
|
||||
|
||||
a = Subset([], ['a', 'b', 'c', 'd'])
|
||||
i = 1
|
||||
while a.subset != Subset(['d'], ['a', 'b', 'c', 'd']).subset:
|
||||
a = a.next_lexicographic()
|
||||
i = i + 1
|
||||
assert i == 16
|
||||
|
||||
i = 1
|
||||
while a.subset != Subset([], ['a', 'b', 'c', 'd']).subset:
|
||||
a = a.prev_lexicographic()
|
||||
i = i + 1
|
||||
assert i == 16
|
||||
|
||||
raises(ValueError, lambda: Subset(['a', 'b'], ['a']))
|
||||
raises(ValueError, lambda: Subset(['a'], ['b', 'c']))
|
||||
raises(ValueError, lambda: Subset.subset_from_bitlist(['a', 'b'], '010'))
|
||||
|
||||
assert Subset(['a'], ['a', 'b']) != Subset(['b'], ['a', 'b'])
|
||||
assert Subset(['a'], ['a', 'b']) != Subset(['a'], ['a', 'c'])
|
||||
|
||||
def test_ksubsets():
|
||||
assert list(ksubsets([1, 2, 3], 2)) == [(1, 2), (1, 3), (2, 3)]
|
||||
assert list(ksubsets([1, 2, 3, 4, 5], 2)) == [(1, 2), (1, 3), (1, 4),
|
||||
(1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
|
||||
+560
@@ -0,0 +1,560 @@
|
||||
from sympy.combinatorics.permutations import Permutation, Perm
|
||||
from sympy.combinatorics.tensor_can import (perm_af_direct_product, dummy_sgs,
|
||||
riemann_bsgs, get_symmetric_group_sgs, canonicalize, bsgs_direct_product)
|
||||
from sympy.combinatorics.testutil import canonicalize_naive, graph_certificate
|
||||
from sympy.testing.pytest import skip, XFAIL
|
||||
|
||||
def test_perm_af_direct_product():
|
||||
gens1 = [[1,0,2,3], [0,1,3,2]]
|
||||
gens2 = [[1,0]]
|
||||
assert perm_af_direct_product(gens1, gens2, 0) == [[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]]
|
||||
gens1 = [[1,0,2,3,5,4], [0,1,3,2,4,5]]
|
||||
gens2 = [[1,0,2,3]]
|
||||
assert [[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]]
|
||||
|
||||
def test_dummy_sgs():
|
||||
a = dummy_sgs([1,2], 0, 4)
|
||||
assert a == [[0,2,1,3,4,5]]
|
||||
a = dummy_sgs([2,3,4,5], 0, 8)
|
||||
assert a == [x._array_form for x in [Perm(9)(2,3), Perm(9)(4,5),
|
||||
Perm(9)(2,4)(3,5)]]
|
||||
|
||||
a = dummy_sgs([2,3,4,5], 1, 8)
|
||||
assert a == [x._array_form for x in [Perm(2,3)(8,9), Perm(4,5)(8,9),
|
||||
Perm(9)(2,4)(3,5)]]
|
||||
|
||||
def test_get_symmetric_group_sgs():
|
||||
assert get_symmetric_group_sgs(2) == ([0], [Permutation(3)(0,1)])
|
||||
assert get_symmetric_group_sgs(2, 1) == ([0], [Permutation(0,1)(2,3)])
|
||||
assert get_symmetric_group_sgs(3) == ([0,1], [Permutation(4)(0,1), Permutation(4)(1,2)])
|
||||
assert get_symmetric_group_sgs(3, 1) == ([0,1], [Permutation(0,1)(3,4), Permutation(1,2)(3,4)])
|
||||
assert get_symmetric_group_sgs(4) == ([0,1,2], [Permutation(5)(0,1), Permutation(5)(1,2), Permutation(5)(2,3)])
|
||||
assert get_symmetric_group_sgs(4, 1) == ([0,1,2], [Permutation(0,1)(4,5), Permutation(1,2)(4,5), Permutation(2,3)(4,5)])
|
||||
|
||||
|
||||
def test_canonicalize_no_slot_sym():
|
||||
# cases in which there is no slot symmetry after fixing the
|
||||
# free indices; here and in the following if the symmetry of the
|
||||
# metric is not specified, it is assumed to be symmetric.
|
||||
# If it is not specified, tensors are commuting.
|
||||
|
||||
# A_d0 * B^d0; g = [1,0, 2,3]; T_c = A^d0*B_d0; can = [0,1,2,3]
|
||||
base1, gens1 = get_symmetric_group_sgs(1)
|
||||
dummies = [0, 1]
|
||||
g = Permutation([1,0,2,3])
|
||||
can = canonicalize(g, dummies, 0, (base1,gens1,1,0), (base1,gens1,1,0))
|
||||
assert can == [0,1,2,3]
|
||||
# equivalently
|
||||
can = canonicalize(g, dummies, 0, (base1, gens1, 2, None))
|
||||
assert can == [0,1,2,3]
|
||||
|
||||
# with antisymmetric metric; T_c = -A^d0*B_d0; can = [0,1,3,2]
|
||||
can = canonicalize(g, dummies, 1, (base1,gens1,1,0), (base1,gens1,1,0))
|
||||
assert can == [0,1,3,2]
|
||||
|
||||
# A^a * B^b; ord = [a,b]; g = [0,1,2,3]; can = g
|
||||
g = Permutation([0,1,2,3])
|
||||
dummies = []
|
||||
t0 = t1 = (base1, gens1, 1, 0)
|
||||
can = canonicalize(g, dummies, 0, t0, t1)
|
||||
assert can == [0,1,2,3]
|
||||
# B^b * A^a
|
||||
g = Permutation([1,0,2,3])
|
||||
can = canonicalize(g, dummies, 0, t0, t1)
|
||||
assert can == [1,0,2,3]
|
||||
|
||||
# A symmetric
|
||||
# A^{b}_{d0}*A^{d0, a} order a,b,d0,-d0; T_c = A^{a d0}*A{b}_{d0}
|
||||
# g = [1,3,2,0,4,5]; can = [0,2,1,3,4,5]
|
||||
base2, gens2 = get_symmetric_group_sgs(2)
|
||||
dummies = [2,3]
|
||||
g = Permutation([1,3,2,0,4,5])
|
||||
can = canonicalize(g, dummies, 0, (base2, gens2, 2, 0))
|
||||
assert can == [0, 2, 1, 3, 4, 5]
|
||||
# with antisymmetric metric
|
||||
can = canonicalize(g, dummies, 1, (base2, gens2, 2, 0))
|
||||
assert can == [0, 2, 1, 3, 4, 5]
|
||||
# A^{a}_{d0}*A^{d0, b}
|
||||
g = Permutation([0,3,2,1,4,5])
|
||||
can = canonicalize(g, dummies, 1, (base2, gens2, 2, 0))
|
||||
assert can == [0, 2, 1, 3, 5, 4]
|
||||
|
||||
# A, B symmetric
|
||||
# A^b_d0*B^{d0,a}; g=[1,3,2,0,4,5]
|
||||
# T_c = A^{b,d0}*B_{a,d0}; can = [1,2,0,3,4,5]
|
||||
dummies = [2,3]
|
||||
g = Permutation([1,3,2,0,4,5])
|
||||
can = canonicalize(g, dummies, 0, (base2,gens2,1,0), (base2,gens2,1,0))
|
||||
assert can == [1,2,0,3,4,5]
|
||||
# same with antisymmetric metric
|
||||
can = canonicalize(g, dummies, 1, (base2,gens2,1,0), (base2,gens2,1,0))
|
||||
assert can == [1,2,0,3,5,4]
|
||||
|
||||
# A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5]
|
||||
# T_c = A^{d0 d1}*B_d0*C_d1; can = [0,2,1,3,4,5]
|
||||
base1, gens1 = get_symmetric_group_sgs(1)
|
||||
base2, gens2 = get_symmetric_group_sgs(2)
|
||||
g = Permutation([2,1,0,3,4,5])
|
||||
dummies = [0,1,2,3]
|
||||
t0 = (base2, gens2, 1, 0)
|
||||
t1 = t2 = (base1, gens1, 1, 0)
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [0, 2, 1, 3, 4, 5]
|
||||
|
||||
# A without symmetry
|
||||
# A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5]
|
||||
# T_c = A^{d0 d1}*B_d1*C_d0; can = [0,2,3,1,4,5]
|
||||
g = Permutation([2,1,0,3,4,5])
|
||||
dummies = [0,1,2,3]
|
||||
t0 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [0,2,3,1,4,5]
|
||||
# A, B without symmetry
|
||||
# A^{d1}_{d0}*B_{d1}^{d0}; g = [2,1,3,0,4,5]
|
||||
# T_c = A^{d0 d1}*B_{d0 d1}; can = [0,2,1,3,4,5]
|
||||
t0 = t1 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
dummies = [0,1,2,3]
|
||||
g = Permutation([2,1,3,0,4,5])
|
||||
can = canonicalize(g, dummies, 0, t0, t1)
|
||||
assert can == [0, 2, 1, 3, 4, 5]
|
||||
# A_{d0}^{d1}*B_{d1}^{d0}; g = [1,2,3,0,4,5]
|
||||
# T_c = A^{d0 d1}*B_{d1 d0}; can = [0,2,3,1,4,5]
|
||||
g = Permutation([1,2,3,0,4,5])
|
||||
can = canonicalize(g, dummies, 0, t0, t1)
|
||||
assert can == [0,2,3,1,4,5]
|
||||
|
||||
# A, B, C without symmetry
|
||||
# A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g=[4,2,0,3,5,1,6,7]
|
||||
# T_c=A^{d0 d1}*B_{a d1}*C_{d0 b}; can = [2,4,0,5,3,1,6,7]
|
||||
t0 = t1 = t2 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
dummies = [2,3,4,5]
|
||||
g = Permutation([4,2,0,3,5,1,6,7])
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [2,4,0,5,3,1,6,7]
|
||||
|
||||
# A symmetric, B and C without symmetry
|
||||
# A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g=[4,2,0,3,5,1,6,7]
|
||||
# T_c = A^{d0 d1}*B_{a d0}*C_{d1 b}; can = [2,4,0,3,5,1,6,7]
|
||||
t0 = (base2,gens2,1,0)
|
||||
t1 = t2 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
dummies = [2,3,4,5]
|
||||
g = Permutation([4,2,0,3,5,1,6,7])
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [2,4,0,3,5,1,6,7]
|
||||
|
||||
# A and C symmetric, B without symmetry
|
||||
# A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g=[4,2,0,3,5,1,6,7]
|
||||
# T_c = A^{d0 d1}*B_{a d0}*C_{b d1}; can = [2,4,0,3,1,5,6,7]
|
||||
t0 = t2 = (base2,gens2,1,0)
|
||||
t1 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
dummies = [2,3,4,5]
|
||||
g = Permutation([4,2,0,3,5,1,6,7])
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [2,4,0,3,1,5,6,7]
|
||||
|
||||
# A symmetric, B without symmetry, C antisymmetric
|
||||
# A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g=[4,2,0,3,5,1,6,7]
|
||||
# T_c = -A^{d0 d1}*B_{a d0}*C_{b d1}; can = [2,4,0,3,1,5,7,6]
|
||||
t0 = (base2,gens2, 1, 0)
|
||||
t1 = ([], [Permutation(list(range(4)))], 1, 0)
|
||||
base2a, gens2a = get_symmetric_group_sgs(2, 1)
|
||||
t2 = (base2a, gens2a, 1, 0)
|
||||
dummies = [2,3,4,5]
|
||||
g = Permutation([4,2,0,3,5,1,6,7])
|
||||
can = canonicalize(g, dummies, 0, t0, t1, t2)
|
||||
assert can == [2,4,0,3,1,5,7,6]
|
||||
|
||||
|
||||
def test_canonicalize_no_dummies():
|
||||
base1, gens1 = get_symmetric_group_sgs(1)
|
||||
base2, gens2 = get_symmetric_group_sgs(2)
|
||||
base2a, gens2a = get_symmetric_group_sgs(2, 1)
|
||||
|
||||
# A commuting
|
||||
# A^c A^b A^a; ord = [a,b,c]; g = [2,1,0,3,4]
|
||||
# T_c = A^a A^b A^c; can = list(range(5))
|
||||
g = Permutation([2,1,0,3,4])
|
||||
can = canonicalize(g, [], 0, (base1, gens1, 3, 0))
|
||||
assert can == list(range(5))
|
||||
|
||||
# A anticommuting
|
||||
# A^c A^b A^a; ord = [a,b,c]; g = [2,1,0,3,4]
|
||||
# T_c = -A^a A^b A^c; can = [0,1,2,4,3]
|
||||
g = Permutation([2,1,0,3,4])
|
||||
can = canonicalize(g, [], 0, (base1, gens1, 3, 1))
|
||||
assert can == [0,1,2,4,3]
|
||||
|
||||
# A commuting and symmetric
|
||||
# A^{b,d}*A^{c,a}; ord = [a,b,c,d]; g = [1,3,2,0,4,5]
|
||||
# T_c = A^{a c}*A^{b d}; can = [0,2,1,3,4,5]
|
||||
g = Permutation([1,3,2,0,4,5])
|
||||
can = canonicalize(g, [], 0, (base2, gens2, 2, 0))
|
||||
assert can == [0,2,1,3,4,5]
|
||||
|
||||
# A anticommuting and symmetric
|
||||
# A^{b,d}*A^{c,a}; ord = [a,b,c,d]; g = [1,3,2,0,4,5]
|
||||
# T_c = -A^{a c}*A^{b d}; can = [0,2,1,3,5,4]
|
||||
g = Permutation([1,3,2,0,4,5])
|
||||
can = canonicalize(g, [], 0, (base2, gens2, 2, 1))
|
||||
assert can == [0,2,1,3,5,4]
|
||||
# A^{c,a}*A^{b,d} ; g = [2,0,1,3,4,5]
|
||||
# T_c = A^{a c}*A^{b d}; can = [0,2,1,3,4,5]
|
||||
g = Permutation([2,0,1,3,4,5])
|
||||
can = canonicalize(g, [], 0, (base2, gens2, 2, 1))
|
||||
assert can == [0,2,1,3,4,5]
|
||||
|
||||
def test_no_metric_symmetry():
|
||||
# no metric symmetry
|
||||
# A^d1_d0 * A^d0_d1; ord = [d0,-d0,d1,-d1]; g= [2,1,0,3,4,5]
|
||||
# T_c = A^d0_d1 * A^d1_d0; can = [0,3,2,1,4,5]
|
||||
g = Permutation([2,1,0,3,4,5])
|
||||
can = canonicalize(g, list(range(4)), None, [[], [Permutation(list(range(4)))], 2, 0])
|
||||
assert can == [0,3,2,1,4,5]
|
||||
|
||||
# A^d1_d2 * A^d0_d3 * A^d2_d1 * A^d3_d0
|
||||
# ord = [d0,-d0,d1,-d1,d2,-d2,d3,-d3]
|
||||
# 0 1 2 3 4 5 6 7
|
||||
# g = [2,5,0,7,4,3,6,1,8,9]
|
||||
# T_c = A^d0_d1 * A^d1_d0 * A^d2_d3 * A^d3_d2
|
||||
# can = [0,3,2,1,4,7,6,5,8,9]
|
||||
g = Permutation([2,5,0,7,4,3,6,1,8,9])
|
||||
#can = canonicalize(g, list(range(8)), 0, [[], [list(range(4))], 4, 0])
|
||||
#assert can == [0, 2, 3, 1, 4, 6, 7, 5, 8, 9]
|
||||
can = canonicalize(g, list(range(8)), None, [[], [Permutation(list(range(4)))], 4, 0])
|
||||
assert can == [0, 3, 2, 1, 4, 7, 6, 5, 8, 9]
|
||||
|
||||
# A^d0_d2 * A^d1_d3 * A^d3_d0 * A^d2_d1
|
||||
# g = [0,5,2,7,6,1,4,3,8,9]
|
||||
# T_c = A^d0_d1 * A^d1_d2 * A^d2_d3 * A^d3_d0
|
||||
# can = [0,3,2,5,4,7,6,1,8,9]
|
||||
g = Permutation([0,5,2,7,6,1,4,3,8,9])
|
||||
can = canonicalize(g, list(range(8)), None, [[], [Permutation(list(range(4)))], 4, 0])
|
||||
assert can == [0,3,2,5,4,7,6,1,8,9]
|
||||
|
||||
g = Permutation([12,7,10,3,14,13,4,11,6,1,2,9,0,15,8,5,16,17])
|
||||
can = canonicalize(g, list(range(16)), None, [[], [Permutation(list(range(4)))], 8, 0])
|
||||
assert can == [0,3,2,5,4,7,6,1,8,11,10,13,12,15,14,9,16,17]
|
||||
|
||||
def test_canonical_free():
|
||||
# t = A^{d0 a1}*A_d0^a0
|
||||
# ord = [a0,a1,d0,-d0]; g = [2,1,3,0,4,5]; dummies = [[2,3]]
|
||||
# t_c = A_d0^a0*A^{d0 a1}
|
||||
# can = [3,0, 2,1, 4,5]
|
||||
g = Permutation([2,1,3,0,4,5])
|
||||
dummies = [[2,3]]
|
||||
can = canonicalize(g, dummies, [None], ([], [Permutation(3)], 2, 0))
|
||||
assert can == [3,0, 2,1, 4,5]
|
||||
|
||||
def test_canonicalize1():
|
||||
base1, gens1 = get_symmetric_group_sgs(1)
|
||||
base1a, gens1a = get_symmetric_group_sgs(1, 1)
|
||||
base2, gens2 = get_symmetric_group_sgs(2)
|
||||
base3, gens3 = get_symmetric_group_sgs(3)
|
||||
base2a, gens2a = get_symmetric_group_sgs(2, 1)
|
||||
base3a, gens3a = get_symmetric_group_sgs(3, 1)
|
||||
|
||||
# A_d0*A^d0; ord = [d0,-d0]; g = [1,0,2,3]
|
||||
# T_c = A^d0*A_d0; can = [0,1,2,3]
|
||||
g = Permutation([1,0,2,3])
|
||||
can = canonicalize(g, [0, 1], 0, (base1, gens1, 2, 0))
|
||||
assert can == list(range(4))
|
||||
|
||||
# A commuting
|
||||
# A_d0*A_d1*A_d2*A^d2*A^d1*A^d0; ord=[d0,-d0,d1,-d1,d2,-d2]
|
||||
# g = [1,3,5,4,2,0,6,7]
|
||||
# T_c = A^d0*A_d0*A^d1*A_d1*A^d2*A_d2; can = list(range(8))
|
||||
g = Permutation([1,3,5,4,2,0,6,7])
|
||||
can = canonicalize(g, list(range(6)), 0, (base1, gens1, 6, 0))
|
||||
assert can == list(range(8))
|
||||
|
||||
# A anticommuting
|
||||
# A_d0*A_d1*A_d2*A^d2*A^d1*A^d0; ord=[d0,-d0,d1,-d1,d2,-d2]
|
||||
# g = [1,3,5,4,2,0,6,7]
|
||||
# T_c 0; can = 0
|
||||
g = Permutation([1,3,5,4,2,0,6,7])
|
||||
can = canonicalize(g, list(range(6)), 0, (base1, gens1, 6, 1))
|
||||
assert can == 0
|
||||
can1 = canonicalize_naive(g, list(range(6)), 0, (base1, gens1, 6, 1))
|
||||
assert can1 == 0
|
||||
|
||||
# A commuting symmetric
|
||||
# A^{d0 b}*A^a_d1*A^d1_d0; ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g = [2,1,0,5,4,3,6,7]
|
||||
# T_c = A^{a d0}*A^{b d1}*A_{d0 d1}; can = [0,2,1,4,3,5,6,7]
|
||||
g = Permutation([2,1,0,5,4,3,6,7])
|
||||
can = canonicalize(g, list(range(2,6)), 0, (base2, gens2, 3, 0))
|
||||
assert can == [0,2,1,4,3,5,6,7]
|
||||
|
||||
# A, B commuting symmetric
|
||||
# A^{d0 b}*A^d1_d0*B^a_d1; ord=[a,b,d0,-d0,d1,-d1]
|
||||
# g = [2,1,4,3,0,5,6,7]
|
||||
# T_c = A^{b d0}*A_d0^d1*B^a_d1; can = [1,2,3,4,0,5,6,7]
|
||||
g = Permutation([2,1,4,3,0,5,6,7])
|
||||
can = canonicalize(g, list(range(2,6)), 0, (base2,gens2,2,0), (base2,gens2,1,0))
|
||||
assert can == [1,2,3,4,0,5,6,7]
|
||||
|
||||
# A commuting symmetric
|
||||
# A^{d1 d0 b}*A^{a}_{d1 d0}; ord=[a,b, d0,-d0,d1,-d1]
|
||||
# g = [4,2,1,0,5,3,6,7]
|
||||
# T_c = A^{a d0 d1}*A^{b}_{d0 d1}; can = [0,2,4,1,3,5,6,7]
|
||||
g = Permutation([4,2,1,0,5,3,6,7])
|
||||
can = canonicalize(g, list(range(2,6)), 0, (base3, gens3, 2, 0))
|
||||
assert can == [0,2,4,1,3,5,6,7]
|
||||
|
||||
|
||||
# A^{d3 d0 d2}*A^a0_{d1 d2}*A^d1_d3^a1*A^{a2 a3}_d0
|
||||
# ord = [a0,a1,a2,a3,d0,-d0,d1,-d1,d2,-d2,d3,-d3]
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
# g = [10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13]
|
||||
# T_c = A^{a0 d0 d1}*A^a1_d0^d2*A^{a2 a3 d3}*A_{d1 d2 d3}
|
||||
# can = [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13]
|
||||
g = Permutation([10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13])
|
||||
can = canonicalize(g, list(range(4,12)), 0, (base3, gens3, 4, 0))
|
||||
assert can == [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13]
|
||||
|
||||
# A commuting symmetric, B antisymmetric
|
||||
# A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3
|
||||
# ord = [d0,-d0,d1,-d1,d2,-d2,d3,-d3]
|
||||
# g = [0,2,4,5,7,3,1,6,8,9]
|
||||
# in this esxample and in the next three,
|
||||
# renaming dummy indices and using symmetry of A,
|
||||
# T = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3
|
||||
# can = 0
|
||||
g = Permutation([0,2,4,5,7,3,1,6,8,9])
|
||||
can = canonicalize(g, list(range(8)), 0, (base3, gens3,2,0), (base2a,gens2a,1,0))
|
||||
assert can == 0
|
||||
# A anticommuting symmetric, B anticommuting
|
||||
# A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3
|
||||
# T_c = A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3}
|
||||
# can = [0,2,4, 1,3,6, 5,7, 8,9]
|
||||
can = canonicalize(g, list(range(8)), 0, (base3, gens3,2,1), (base2a,gens2a,1,0))
|
||||
assert can == [0,2,4, 1,3,6, 5,7, 8,9]
|
||||
# A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric
|
||||
# A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3
|
||||
# T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3}
|
||||
# can = [0,2,4, 1,3,6, 5,7, 9,8]
|
||||
can = canonicalize(g, list(range(8)), 1, (base3, gens3,2,1), (base2a,gens2a,1,0))
|
||||
assert can == [0,2,4, 1,3,6, 5,7, 9,8]
|
||||
|
||||
# A anticommuting symmetric, B anticommuting anticommuting,
|
||||
# no metric symmetry
|
||||
# A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3
|
||||
# T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3
|
||||
# can = [0,2,4, 1,3,7, 5,6, 8,9]
|
||||
can = canonicalize(g, list(range(8)), None, (base3, gens3,2,1), (base2a,gens2a,1,0))
|
||||
assert can == [0,2,4,1,3,7,5,6,8,9]
|
||||
|
||||
# Gamma anticommuting
|
||||
# Gamma_{mu nu} * gamma^rho * Gamma^{nu mu alpha}
|
||||
# ord = [alpha, rho, mu,-mu,nu,-nu]
|
||||
# g = [3,5,1,4,2,0,6,7]
|
||||
# T_c = -Gamma^{mu nu} * gamma^rho * Gamma_{alpha mu nu}
|
||||
# can = [2,4,1,0,3,5,7,6]]
|
||||
g = Permutation([3,5,1,4,2,0,6,7])
|
||||
t0 = (base2a, gens2a, 1, None)
|
||||
t1 = (base1, gens1, 1, None)
|
||||
t2 = (base3a, gens3a, 1, None)
|
||||
can = canonicalize(g, list(range(2, 6)), 0, t0, t1, t2)
|
||||
assert can == [2,4,1,0,3,5,7,6]
|
||||
|
||||
# Gamma_{mu nu} * Gamma^{gamma beta} * gamma_rho * Gamma^{nu mu alpha}
|
||||
# ord = [alpha, beta, gamma, -rho, mu,-mu,nu,-nu]
|
||||
# 0 1 2 3 4 5 6 7
|
||||
# g = [5,7,2,1,3,6,4,0,8,9]
|
||||
# T_c = Gamma^{mu nu} * Gamma^{beta gamma} * gamma_rho * Gamma^alpha_{mu nu} # can = [4,6,1,2,3,0,5,7,8,9]
|
||||
t0 = (base2a, gens2a, 2, None)
|
||||
g = Permutation([5,7,2,1,3,6,4,0,8,9])
|
||||
can = canonicalize(g, list(range(4, 8)), 0, t0, t1, t2)
|
||||
assert can == [4,6,1,2,3,0,5,7,8,9]
|
||||
|
||||
# f^a_{b,c} antisymmetric in b,c; A_mu^a no symmetry
|
||||
# f^c_{d a} * f_{c e b} * A_mu^d * A_nu^a * A^{nu e} * A^{mu b}
|
||||
# ord = [mu,-mu,nu,-nu,a,-a,b,-b,c,-c,d,-d, e, -e]
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13
|
||||
# g = [8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15]
|
||||
# T_c = -f^{a b c} * f_a^{d e} * A^mu_b * A_{mu d} * A^nu_c * A_{nu e}
|
||||
# can = [4,6,8, 5,10,12, 0,7, 1,11, 2,9, 3,13, 15,14]
|
||||
g = Permutation([8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15])
|
||||
base_f, gens_f = bsgs_direct_product(base1, gens1, base2a, gens2a)
|
||||
base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1)
|
||||
t0 = (base_f, gens_f, 2, 0)
|
||||
t1 = (base_A, gens_A, 4, 0)
|
||||
can = canonicalize(g, [list(range(4)), list(range(4, 14))], [0, 0], t0, t1)
|
||||
assert can == [4,6,8, 5,10,12, 0,7, 1,11, 2,9, 3,13, 15,14]
|
||||
|
||||
|
||||
def test_riemann_invariants():
|
||||
baser, gensr = riemann_bsgs
|
||||
# R^{d0 d1}_{d1 d0}; ord = [d0,-d0,d1,-d1]; g = [0,2,3,1,4,5]
|
||||
# T_c = -R^{d0 d1}_{d0 d1}; can = [0,2,1,3,5,4]
|
||||
g = Permutation([0,2,3,1,4,5])
|
||||
can = canonicalize(g, list(range(2, 4)), 0, (baser, gensr, 1, 0))
|
||||
assert can == [0,2,1,3,5,4]
|
||||
# use a non minimal BSGS
|
||||
can = canonicalize(g, list(range(2, 4)), 0, ([2, 0], [Permutation([1,0,2,3,5,4]), Permutation([2,3,0,1,4,5])], 1, 0))
|
||||
assert can == [0,2,1,3,5,4]
|
||||
|
||||
"""
|
||||
The following tests in test_riemann_invariants and in
|
||||
test_riemann_invariants1 have been checked using xperm.c from XPerm in
|
||||
in [1] and with an older version contained in [2]
|
||||
|
||||
[1] xperm.c part of xPerm written by J. M. Martin-Garcia
|
||||
http://www.xact.es/index.html
|
||||
[2] test_xperm.cc in cadabra by Kasper Peeters, http://cadabra.phi-sci.com/
|
||||
"""
|
||||
# R_d11^d1_d0^d5 * R^{d6 d4 d0}_d5 * R_{d7 d2 d8 d9} *
|
||||
# R_{d10 d3 d6 d4} * R^{d2 d7 d11}_d1 * R^{d8 d9 d3 d10}
|
||||
# ord: contravariant d_k ->2*k, covariant d_k -> 2*k+1
|
||||
# T_c = R^{d0 d1 d2 d3} * R_{d0 d1}^{d4 d5} * R_{d2 d3}^{d6 d7} *
|
||||
# R_{d4 d5}^{d8 d9} * R_{d6 d7}^{d10 d11} * R_{d8 d9 d10 d11}
|
||||
g = Permutation([23,2,1,10,12,8,0,11,15,5,17,19,21,7,13,9,4,14,22,3,16,18,6,20,24,25])
|
||||
can = canonicalize(g, list(range(24)), 0, (baser, gensr, 6, 0))
|
||||
assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25]
|
||||
|
||||
# use a non minimal BSGS
|
||||
can = canonicalize(g, list(range(24)), 0, ([2, 0], [Permutation([1,0,2,3,5,4]), Permutation([2,3,0,1,4,5])], 6, 0))
|
||||
assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25]
|
||||
|
||||
g = Permutation([0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22,25,27,24,26,29,31,28,30,33,35,32,34,37,39,36,38,1,3,40,41])
|
||||
can = canonicalize(g, list(range(40)), 0, (baser, gensr, 10, 0))
|
||||
assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,24,26,21,23,28,30,25,27,32,34,29,31,36,38,33,35,37,39,40,41]
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_riemann_invariants1():
|
||||
skip('takes too much time')
|
||||
baser, gensr = riemann_bsgs
|
||||
g = Permutation([17, 44, 11, 3, 0, 19, 23, 15, 38, 4, 25, 27, 43, 36, 22, 14, 8, 30, 41, 20, 2, 10, 12, 28, 18, 1, 29, 13, 37, 42, 33, 7, 9, 31, 24, 26, 39, 5, 34, 47, 32, 6, 21, 40, 35, 46, 45, 16, 48, 49])
|
||||
can = canonicalize(g, list(range(48)), 0, (baser, gensr, 12, 0))
|
||||
assert can == [0, 2, 4, 6, 1, 3, 8, 10, 5, 7, 12, 14, 9, 11, 16, 18, 13, 15, 20, 22, 17, 19, 24, 26, 21, 23, 28, 30, 25, 27, 32, 34, 29, 31, 36, 38, 33, 35, 40, 42, 37, 39, 44, 46, 41, 43, 45, 47, 48, 49]
|
||||
|
||||
g = Permutation([0,2,4,6, 7,8,10,12, 14,16,18,20, 19,22,24,26, 5,21,28,30, 32,34,36,38, 40,42,44,46, 13,48,50,52, 15,49,54,56, 17,33,41,58, 9,23,60,62, 29,35,63,64, 3,45,66,68, 25,37,47,57, 11,31,69,70, 27,39,53,72, 1,59,73,74, 55,61,67,76, 43,65,75,78, 51,71,77,79, 80,81])
|
||||
can = canonicalize(g, list(range(80)), 0, (baser, gensr, 20, 0))
|
||||
assert can == [0,2,4,6, 1,8,10,12, 3,14,16,18, 5,20,22,24, 7,26,28,30, 9,15,32,34, 11,36,23,38, 13,40,42,44, 17,39,29,46, 19,48,43,50, 21,45,52,54, 25,56,33,58, 27,60,53,62, 31,51,64,66, 35,65,47,68, 37,70,49,72, 41,74,57,76, 55,67,59,78, 61,69,71,75, 63,79,73,77, 80,81]
|
||||
|
||||
|
||||
def test_riemann_products():
|
||||
baser, gensr = riemann_bsgs
|
||||
base1, gens1 = get_symmetric_group_sgs(1)
|
||||
base2, gens2 = get_symmetric_group_sgs(2)
|
||||
base2a, gens2a = get_symmetric_group_sgs(2, 1)
|
||||
|
||||
# R^{a b d0}_d0 = 0
|
||||
g = Permutation([0,1,2,3,4,5])
|
||||
can = canonicalize(g, list(range(2,4)), 0, (baser, gensr, 1, 0))
|
||||
assert can == 0
|
||||
|
||||
# R^{d0 b a}_d0 ; ord = [a,b,d0,-d0}; g = [2,1,0,3,4,5]
|
||||
# T_c = -R^{a d0 b}_d0; can = [0,2,1,3,5,4]
|
||||
g = Permutation([2,1,0,3,4,5])
|
||||
can = canonicalize(g, list(range(2, 4)), 0, (baser, gensr, 1, 0))
|
||||
assert can == [0,2,1,3,5,4]
|
||||
|
||||
# R^d1_d2^b_d0 * R^{d0 a}_d1^d2; ord=[a,b,d0,-d0,d1,-d1,d2,-d2]
|
||||
# g = [4,7,1,3,2,0,5,6,8,9]
|
||||
# T_c = -R^{a d0 d1 d2}* R^b_{d0 d1 d2}
|
||||
# can = [0,2,4,6,1,3,5,7,9,8]
|
||||
g = Permutation([4,7,1,3,2,0,5,6,8,9])
|
||||
can = canonicalize(g, list(range(2,8)), 0, (baser, gensr, 2, 0))
|
||||
assert can == [0,2,4,6,1,3,5,7,9,8]
|
||||
can1 = canonicalize_naive(g, list(range(2,8)), 0, (baser, gensr, 2, 0))
|
||||
assert can == can1
|
||||
|
||||
# A symmetric commuting
|
||||
# R^{d6 d5}_d2^d1 * R^{d4 d0 d2 d3} * A_{d6 d0} A_{d3 d1} * A_{d4 d5}
|
||||
# g = [12,10,5,2, 8,0,4,6, 13,1, 7,3, 9,11,14,15]
|
||||
# T_c = -R^{d0 d1 d2 d3} * R_d0^{d4 d5 d6} * A_{d1 d4}*A_{d2 d5}*A_{d3 d6}
|
||||
|
||||
g = Permutation([12,10,5,2,8,0,4,6,13,1,7,3,9,11,14,15])
|
||||
can = canonicalize(g, list(range(14)), 0, ((baser,gensr,2,0)), (base2,gens2,3,0))
|
||||
assert can == [0, 2, 4, 6, 1, 8, 10, 12, 3, 9, 5, 11, 7, 13, 15, 14]
|
||||
|
||||
# R^{d2 a0 a2 d0} * R^d1_d2^{a1 a3} * R^{a4 a5}_{d0 d1}
|
||||
# ord = [a0,a1,a2,a3,a4,a5,d0,-d0,d1,-d1,d2,-d2]
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
# can = [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13]
|
||||
# T_c = R^{a0 d0 a2 d1}*R^{a1 a3}_d0^d2*R^{a4 a5}_{d1 d2}
|
||||
g = Permutation([10,0,2,6,8,11,1,3,4,5,7,9,12,13])
|
||||
can = canonicalize(g, list(range(6,12)), 0, (baser, gensr, 3, 0))
|
||||
assert can == [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13]
|
||||
#can1 = canonicalize_naive(g, list(range(6,12)), 0, (baser, gensr, 3, 0))
|
||||
#assert can == can1
|
||||
|
||||
# A^n_{i, j} antisymmetric in i,j
|
||||
# A_m0^d0_a1 * A_m1^a0_d0; ord = [m0,m1,a0,a1,d0,-d0]
|
||||
# g = [0,4,3,1,2,5,6,7]
|
||||
# T_c = -A_{m a1}^d0 * A_m1^a0_d0
|
||||
# can = [0,3,4,1,2,5,7,6]
|
||||
base, gens = bsgs_direct_product(base1, gens1, base2a, gens2a)
|
||||
dummies = list(range(4, 6))
|
||||
g = Permutation([0,4,3,1,2,5,6,7])
|
||||
can = canonicalize(g, dummies, 0, (base, gens, 2, 0))
|
||||
assert can == [0, 3, 4, 1, 2, 5, 7, 6]
|
||||
|
||||
|
||||
# A^n_{i, j} symmetric in i,j
|
||||
# A^m0_a0^d2 * A^n0_d2^d1 * A^n1_d1^d0 * A_{m0 d0}^a1
|
||||
# ordering: first the free indices; then first n, then d
|
||||
# ord=[n0,n1,a0,a1, m0,-m0,d0,-d0,d1,-d1,d2,-d2]
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11]
|
||||
# g = [4,2,10, 0,11,8, 1,9,6, 5,7,3, 12,13]
|
||||
# if the dummy indices m_i and d_i were separated,
|
||||
# one gets
|
||||
# T_c = A^{n0 d0 d1} * A^n1_d0^d2 * A^m0^a0_d1 * A_m0^a1_d2
|
||||
# can = [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 12, 13]
|
||||
# If they are not, so can is
|
||||
# T_c = A^{n0 m0 d0} A^n1_m0^d1 A^{d2 a0}_d0 A_d2^a1_d1
|
||||
# can = [0, 4, 6, 1, 5, 8, 10, 2, 7, 11, 3, 9, 12, 13]
|
||||
# case with single type of indices
|
||||
|
||||
base, gens = bsgs_direct_product(base1, gens1, base2, gens2)
|
||||
dummies = list(range(4, 12))
|
||||
g = Permutation([4,2,10, 0,11,8, 1,9,6, 5,7,3, 12,13])
|
||||
can = canonicalize(g, dummies, 0, (base, gens, 4, 0))
|
||||
assert can == [0, 4, 6, 1, 5, 8, 10, 2, 7, 11, 3, 9, 12, 13]
|
||||
# case with separated indices
|
||||
dummies = [list(range(4, 6)), list(range(6,12))]
|
||||
sym = [0, 0]
|
||||
can = canonicalize(g, dummies, sym, (base, gens, 4, 0))
|
||||
assert can == [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 12, 13]
|
||||
# case with separated indices with the second type of index
|
||||
# with antisymmetric metric: there is a sign change
|
||||
sym = [0, 1]
|
||||
can = canonicalize(g, dummies, sym, (base, gens, 4, 0))
|
||||
assert can == [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 13, 12]
|
||||
|
||||
def test_graph_certificate():
|
||||
# test tensor invariants constructed from random regular graphs;
|
||||
# checked graph isomorphism with networkx
|
||||
import random
|
||||
def randomize_graph(size, g):
|
||||
p = list(range(size))
|
||||
random.shuffle(p)
|
||||
g1a = {}
|
||||
for k, v in g1.items():
|
||||
g1a[p[k]] = [p[i] for i in v]
|
||||
return g1a
|
||||
|
||||
g1 = {0: [2, 3, 7], 1: [4, 5, 7], 2: [0, 4, 6], 3: [0, 6, 7], 4: [1, 2, 5], 5: [1, 4, 6], 6: [2, 3, 5], 7: [0, 1, 3]}
|
||||
g2 = {0: [2, 3, 7], 1: [2, 4, 5], 2: [0, 1, 5], 3: [0, 6, 7], 4: [1, 5, 6], 5: [1, 2, 4], 6: [3, 4, 7], 7: [0, 3, 6]}
|
||||
|
||||
c1 = graph_certificate(g1)
|
||||
c2 = graph_certificate(g2)
|
||||
assert c1 != c2
|
||||
g1a = randomize_graph(8, g1)
|
||||
c1a = graph_certificate(g1a)
|
||||
assert c1 == c1a
|
||||
|
||||
g1 = {0: [8, 1, 9, 7], 1: [0, 9, 3, 4], 2: [3, 4, 6, 7], 3: [1, 2, 5, 6], 4: [8, 1, 2, 5], 5: [9, 3, 4, 7], 6: [8, 2, 3, 7], 7: [0, 2, 5, 6], 8: [0, 9, 4, 6], 9: [8, 0, 5, 1]}
|
||||
g2 = {0: [1, 2, 5, 6], 1: [0, 9, 5, 7], 2: [0, 4, 6, 7], 3: [8, 9, 6, 7], 4: [8, 2, 6, 7], 5: [0, 9, 8, 1], 6: [0, 2, 3, 4], 7: [1, 2, 3, 4], 8: [9, 3, 4, 5], 9: [8, 1, 3, 5]}
|
||||
c1 = graph_certificate(g1)
|
||||
c2 = graph_certificate(g2)
|
||||
assert c1 != c2
|
||||
g1a = randomize_graph(10, g1)
|
||||
c1a = graph_certificate(g1a)
|
||||
assert c1 == c1a
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
from sympy.combinatorics.named_groups import SymmetricGroup, AlternatingGroup,\
|
||||
CyclicGroup
|
||||
from sympy.combinatorics.testutil import _verify_bsgs, _cmp_perm_lists,\
|
||||
_naive_list_centralizer, _verify_centralizer,\
|
||||
_verify_normal_closure
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.core.random import shuffle
|
||||
|
||||
|
||||
def test_cmp_perm_lists():
|
||||
S = SymmetricGroup(4)
|
||||
els = list(S.generate_dimino())
|
||||
other = els.copy()
|
||||
shuffle(other)
|
||||
assert _cmp_perm_lists(els, other) is True
|
||||
|
||||
|
||||
def test_naive_list_centralizer():
|
||||
# verified by GAP
|
||||
S = SymmetricGroup(3)
|
||||
A = AlternatingGroup(3)
|
||||
assert _naive_list_centralizer(S, S) == [Permutation([0, 1, 2])]
|
||||
assert PermutationGroup(_naive_list_centralizer(S, A)).is_subgroup(A)
|
||||
|
||||
|
||||
def test_verify_bsgs():
|
||||
S = SymmetricGroup(5)
|
||||
S.schreier_sims()
|
||||
base = S.base
|
||||
strong_gens = S.strong_gens
|
||||
assert _verify_bsgs(S, base, strong_gens) is True
|
||||
assert _verify_bsgs(S, base[:-1], strong_gens) is False
|
||||
assert _verify_bsgs(S, base, S.generators) is False
|
||||
|
||||
|
||||
def test_verify_centralizer():
|
||||
# verified by GAP
|
||||
S = SymmetricGroup(3)
|
||||
A = AlternatingGroup(3)
|
||||
triv = PermutationGroup([Permutation([0, 1, 2])])
|
||||
assert _verify_centralizer(S, S, centr=triv)
|
||||
assert _verify_centralizer(S, A, centr=A)
|
||||
|
||||
|
||||
def test_verify_normal_closure():
|
||||
# verified by GAP
|
||||
S = SymmetricGroup(3)
|
||||
A = AlternatingGroup(3)
|
||||
assert _verify_normal_closure(S, A, closure=A)
|
||||
S = SymmetricGroup(5)
|
||||
A = AlternatingGroup(5)
|
||||
C = CyclicGroup(5)
|
||||
assert _verify_normal_closure(S, A, closure=A)
|
||||
assert _verify_normal_closure(S, C, closure=A)
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
from sympy.combinatorics.named_groups import SymmetricGroup, DihedralGroup,\
|
||||
AlternatingGroup
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.combinatorics.util import _check_cycles_alt_sym, _strip,\
|
||||
_distribute_gens_by_base, _strong_gens_from_distr,\
|
||||
_orbits_transversals_from_bsgs, _handle_precomputed_bsgs, _base_ordering,\
|
||||
_remove_gens
|
||||
from sympy.combinatorics.testutil import _verify_bsgs
|
||||
|
||||
|
||||
def test_check_cycles_alt_sym():
|
||||
perm1 = Permutation([[0, 1, 2, 3, 4, 5, 6], [7], [8], [9]])
|
||||
perm2 = Permutation([[0, 1, 2, 3, 4, 5], [6, 7, 8, 9]])
|
||||
perm3 = Permutation([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
|
||||
assert _check_cycles_alt_sym(perm1) is True
|
||||
assert _check_cycles_alt_sym(perm2) is False
|
||||
assert _check_cycles_alt_sym(perm3) is False
|
||||
|
||||
|
||||
def test_strip():
|
||||
D = DihedralGroup(5)
|
||||
D.schreier_sims()
|
||||
member = Permutation([4, 0, 1, 2, 3])
|
||||
not_member1 = Permutation([0, 1, 4, 3, 2])
|
||||
not_member2 = Permutation([3, 1, 4, 2, 0])
|
||||
identity = Permutation([0, 1, 2, 3, 4])
|
||||
res1 = _strip(member, D.base, D.basic_orbits, D.basic_transversals)
|
||||
res2 = _strip(not_member1, D.base, D.basic_orbits, D.basic_transversals)
|
||||
res3 = _strip(not_member2, D.base, D.basic_orbits, D.basic_transversals)
|
||||
assert res1[0] == identity
|
||||
assert res1[1] == len(D.base) + 1
|
||||
assert res2[0] == not_member1
|
||||
assert res2[1] == len(D.base) + 1
|
||||
assert res3[0] != identity
|
||||
assert res3[1] == 2
|
||||
|
||||
|
||||
def test_distribute_gens_by_base():
|
||||
base = [0, 1, 2]
|
||||
gens = [Permutation([0, 1, 2, 3]), Permutation([0, 1, 3, 2]),
|
||||
Permutation([0, 2, 3, 1]), Permutation([3, 2, 1, 0])]
|
||||
assert _distribute_gens_by_base(base, gens) == [gens,
|
||||
[Permutation([0, 1, 2, 3]),
|
||||
Permutation([0, 1, 3, 2]),
|
||||
Permutation([0, 2, 3, 1])],
|
||||
[Permutation([0, 1, 2, 3]),
|
||||
Permutation([0, 1, 3, 2])]]
|
||||
|
||||
|
||||
def test_strong_gens_from_distr():
|
||||
strong_gens_distr = [[Permutation([0, 2, 1]), Permutation([1, 2, 0]),
|
||||
Permutation([1, 0, 2])], [Permutation([0, 2, 1])]]
|
||||
assert _strong_gens_from_distr(strong_gens_distr) == \
|
||||
[Permutation([0, 2, 1]),
|
||||
Permutation([1, 2, 0]),
|
||||
Permutation([1, 0, 2])]
|
||||
|
||||
|
||||
def test_orbits_transversals_from_bsgs():
|
||||
S = SymmetricGroup(4)
|
||||
S.schreier_sims()
|
||||
base = S.base
|
||||
strong_gens = S.strong_gens
|
||||
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
||||
result = _orbits_transversals_from_bsgs(base, strong_gens_distr)
|
||||
orbits = result[0]
|
||||
transversals = result[1]
|
||||
base_len = len(base)
|
||||
for i in range(base_len):
|
||||
for el in orbits[i]:
|
||||
assert transversals[i][el](base[i]) == el
|
||||
for j in range(i):
|
||||
assert transversals[i][el](base[j]) == base[j]
|
||||
order = 1
|
||||
for i in range(base_len):
|
||||
order *= len(orbits[i])
|
||||
assert S.order() == order
|
||||
|
||||
|
||||
def test_handle_precomputed_bsgs():
|
||||
A = AlternatingGroup(5)
|
||||
A.schreier_sims()
|
||||
base = A.base
|
||||
strong_gens = A.strong_gens
|
||||
result = _handle_precomputed_bsgs(base, strong_gens)
|
||||
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
||||
assert strong_gens_distr == result[2]
|
||||
transversals = result[0]
|
||||
orbits = result[1]
|
||||
base_len = len(base)
|
||||
for i in range(base_len):
|
||||
for el in orbits[i]:
|
||||
assert transversals[i][el](base[i]) == el
|
||||
for j in range(i):
|
||||
assert transversals[i][el](base[j]) == base[j]
|
||||
order = 1
|
||||
for i in range(base_len):
|
||||
order *= len(orbits[i])
|
||||
assert A.order() == order
|
||||
|
||||
|
||||
def test_base_ordering():
|
||||
base = [2, 4, 5]
|
||||
degree = 7
|
||||
assert _base_ordering(base, degree) == [3, 4, 0, 5, 1, 2, 6]
|
||||
|
||||
|
||||
def test_remove_gens():
|
||||
S = SymmetricGroup(10)
|
||||
base, strong_gens = S.schreier_sims_incremental()
|
||||
new_gens = _remove_gens(base, strong_gens)
|
||||
assert _verify_bsgs(S, base, new_gens) is True
|
||||
A = AlternatingGroup(7)
|
||||
base, strong_gens = A.schreier_sims_incremental()
|
||||
new_gens = _remove_gens(base, strong_gens)
|
||||
assert _verify_bsgs(A, base, new_gens) is True
|
||||
D = DihedralGroup(2)
|
||||
base, strong_gens = D.schreier_sims_incremental()
|
||||
new_gens = _remove_gens(base, strong_gens)
|
||||
assert _verify_bsgs(D, base, new_gens) is True
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.combinatorics.util import _distribute_gens_by_base
|
||||
|
||||
rmul = Permutation.rmul
|
||||
|
||||
|
||||
def _cmp_perm_lists(first, second):
|
||||
"""
|
||||
Compare two lists of permutations as sets.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This is used for testing purposes. Since the array form of a
|
||||
permutation is currently a list, Permutation is not hashable
|
||||
and cannot be put into a set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.permutations import Permutation
|
||||
>>> from sympy.combinatorics.testutil import _cmp_perm_lists
|
||||
>>> a = Permutation([0, 2, 3, 4, 1])
|
||||
>>> b = Permutation([1, 2, 0, 4, 3])
|
||||
>>> c = Permutation([3, 4, 0, 1, 2])
|
||||
>>> ls1 = [a, b, c]
|
||||
>>> ls2 = [b, c, a]
|
||||
>>> _cmp_perm_lists(ls1, ls2)
|
||||
True
|
||||
|
||||
"""
|
||||
return {tuple(a) for a in first} == \
|
||||
{tuple(a) for a in second}
|
||||
|
||||
|
||||
def _naive_list_centralizer(self, other, af=False):
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
"""
|
||||
Return a list of elements for the centralizer of a subgroup/set/element.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This is a brute force implementation that goes over all elements of the
|
||||
group and checks for membership in the centralizer. It is used to
|
||||
test ``.centralizer()`` from ``sympy.combinatorics.perm_groups``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.testutil import _naive_list_centralizer
|
||||
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
||||
>>> D = DihedralGroup(4)
|
||||
>>> _naive_list_centralizer(D, D)
|
||||
[Permutation([0, 1, 2, 3]), Permutation([2, 3, 0, 1])]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.centralizer
|
||||
|
||||
"""
|
||||
from sympy.combinatorics.permutations import _af_commutes_with
|
||||
if hasattr(other, 'generators'):
|
||||
elements = list(self.generate_dimino(af=True))
|
||||
gens = [x._array_form for x in other.generators]
|
||||
commutes_with_gens = lambda x: all(_af_commutes_with(x, gen) for gen in gens)
|
||||
centralizer_list = []
|
||||
if not af:
|
||||
for element in elements:
|
||||
if commutes_with_gens(element):
|
||||
centralizer_list.append(Permutation._af_new(element))
|
||||
else:
|
||||
for element in elements:
|
||||
if commutes_with_gens(element):
|
||||
centralizer_list.append(element)
|
||||
return centralizer_list
|
||||
elif hasattr(other, 'getitem'):
|
||||
return _naive_list_centralizer(self, PermutationGroup(other), af)
|
||||
elif hasattr(other, 'array_form'):
|
||||
return _naive_list_centralizer(self, PermutationGroup([other]), af)
|
||||
|
||||
|
||||
def _verify_bsgs(group, base, gens):
|
||||
"""
|
||||
Verify the correctness of a base and strong generating set.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This is a naive implementation using the definition of a base and a strong
|
||||
generating set relative to it. There are other procedures for
|
||||
verifying a base and strong generating set, but this one will
|
||||
serve for more robust testing.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import AlternatingGroup
|
||||
>>> from sympy.combinatorics.testutil import _verify_bsgs
|
||||
>>> A = AlternatingGroup(4)
|
||||
>>> A.schreier_sims()
|
||||
>>> _verify_bsgs(A, A.base, A.strong_gens)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
|
||||
|
||||
"""
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
strong_gens_distr = _distribute_gens_by_base(base, gens)
|
||||
current_stabilizer = group
|
||||
for i in range(len(base)):
|
||||
candidate = PermutationGroup(strong_gens_distr[i])
|
||||
if current_stabilizer.order() != candidate.order():
|
||||
return False
|
||||
current_stabilizer = current_stabilizer.stabilizer(base[i])
|
||||
if current_stabilizer.order() != 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _verify_centralizer(group, arg, centr=None):
|
||||
"""
|
||||
Verify the centralizer of a group/set/element inside another group.
|
||||
|
||||
This is used for testing ``.centralizer()`` from
|
||||
``sympy.combinatorics.perm_groups``
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import (SymmetricGroup,
|
||||
... AlternatingGroup)
|
||||
>>> from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
>>> from sympy.combinatorics.permutations import Permutation
|
||||
>>> from sympy.combinatorics.testutil import _verify_centralizer
|
||||
>>> S = SymmetricGroup(5)
|
||||
>>> A = AlternatingGroup(5)
|
||||
>>> centr = PermutationGroup([Permutation([0, 1, 2, 3, 4])])
|
||||
>>> _verify_centralizer(S, A, centr)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
_naive_list_centralizer,
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.centralizer,
|
||||
_cmp_perm_lists
|
||||
|
||||
"""
|
||||
if centr is None:
|
||||
centr = group.centralizer(arg)
|
||||
centr_list = list(centr.generate_dimino(af=True))
|
||||
centr_list_naive = _naive_list_centralizer(group, arg, af=True)
|
||||
return _cmp_perm_lists(centr_list, centr_list_naive)
|
||||
|
||||
|
||||
def _verify_normal_closure(group, arg, closure=None):
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
"""
|
||||
Verify the normal closure of a subgroup/subset/element in a group.
|
||||
|
||||
This is used to test
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.normal_closure
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import (SymmetricGroup,
|
||||
... AlternatingGroup)
|
||||
>>> from sympy.combinatorics.testutil import _verify_normal_closure
|
||||
>>> S = SymmetricGroup(3)
|
||||
>>> A = AlternatingGroup(3)
|
||||
>>> _verify_normal_closure(S, A, closure=A)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.normal_closure
|
||||
|
||||
"""
|
||||
if closure is None:
|
||||
closure = group.normal_closure(arg)
|
||||
conjugates = set()
|
||||
if hasattr(arg, 'generators'):
|
||||
subgr_gens = arg.generators
|
||||
elif hasattr(arg, '__getitem__'):
|
||||
subgr_gens = arg
|
||||
elif hasattr(arg, 'array_form'):
|
||||
subgr_gens = [arg]
|
||||
for el in group.generate_dimino():
|
||||
conjugates.update(gen ^ el for gen in subgr_gens)
|
||||
naive_closure = PermutationGroup(list(conjugates))
|
||||
return closure.is_subgroup(naive_closure)
|
||||
|
||||
|
||||
def canonicalize_naive(g, dummies, sym, *v):
|
||||
"""
|
||||
Canonicalize tensor formed by tensors of the different types.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
sym_i symmetry under exchange of two component tensors of type `i`
|
||||
None no symmetry
|
||||
0 commuting
|
||||
1 anticommuting
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
g : Permutation representing the tensor.
|
||||
dummies : List of dummy indices.
|
||||
msym : Symmetry of the metric.
|
||||
v : A list of (base_i, gens_i, n_i, sym_i) for tensors of type `i`.
|
||||
base_i, gens_i BSGS for tensors of this type
|
||||
n_i number of tensors of type `i`
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Returns 0 if the tensor is zero, else returns the array form of
|
||||
the permutation representing the canonical form of the tensor.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.testutil import canonicalize_naive
|
||||
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
>>> g = Permutation([1, 3, 2, 0, 4, 5])
|
||||
>>> base2, gens2 = get_symmetric_group_sgs(2)
|
||||
>>> canonicalize_naive(g, [2, 3], 0, (base2, gens2, 2, 0))
|
||||
[0, 2, 1, 3, 4, 5]
|
||||
"""
|
||||
from sympy.combinatorics.perm_groups import PermutationGroup
|
||||
from sympy.combinatorics.tensor_can import gens_products, dummy_sgs
|
||||
from sympy.combinatorics.permutations import _af_rmul
|
||||
v1 = []
|
||||
for i in range(len(v)):
|
||||
base_i, gens_i, n_i, sym_i = v[i]
|
||||
v1.append((base_i, gens_i, [[]]*n_i, sym_i))
|
||||
size, sbase, sgens = gens_products(*v1)
|
||||
dgens = dummy_sgs(dummies, sym, size-2)
|
||||
if isinstance(sym, int):
|
||||
num_types = 1
|
||||
dummies = [dummies]
|
||||
sym = [sym]
|
||||
else:
|
||||
num_types = len(sym)
|
||||
dgens = []
|
||||
for i in range(num_types):
|
||||
dgens.extend(dummy_sgs(dummies[i], sym[i], size - 2))
|
||||
S = PermutationGroup(sgens)
|
||||
D = PermutationGroup([Permutation(x) for x in dgens])
|
||||
dlist = list(D.generate(af=True))
|
||||
g = g.array_form
|
||||
st = set()
|
||||
for s in S.generate(af=True):
|
||||
h = _af_rmul(g, s)
|
||||
for d in dlist:
|
||||
q = tuple(_af_rmul(d, h))
|
||||
st.add(q)
|
||||
a = list(st)
|
||||
a.sort()
|
||||
prev = (0,)*size
|
||||
for h in a:
|
||||
if h[:-2] == prev[:-2]:
|
||||
if h[-1] != prev[-1]:
|
||||
return 0
|
||||
prev = h
|
||||
return list(a[0])
|
||||
|
||||
|
||||
def graph_certificate(gr):
|
||||
"""
|
||||
Return a certificate for the graph
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
gr : adjacency list
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The graph is assumed to be unoriented and without
|
||||
external lines.
|
||||
|
||||
Associate to each vertex of the graph a symmetric tensor with
|
||||
number of indices equal to the degree of the vertex; indices
|
||||
are contracted when they correspond to the same line of the graph.
|
||||
The canonical form of the tensor gives a certificate for the graph.
|
||||
|
||||
This is not an efficient algorithm to get the certificate of a graph.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.testutil import graph_certificate
|
||||
>>> 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]}
|
||||
>>> 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]}
|
||||
>>> c1 = graph_certificate(gr1)
|
||||
>>> c2 = graph_certificate(gr2)
|
||||
>>> c1
|
||||
[0, 2, 4, 6, 1, 8, 10, 12, 3, 14, 16, 18, 5, 9, 15, 7, 11, 17, 13, 19, 20, 21]
|
||||
>>> c1 == c2
|
||||
True
|
||||
"""
|
||||
from sympy.combinatorics.permutations import _af_invert
|
||||
from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize
|
||||
items = list(gr.items())
|
||||
items.sort(key=lambda x: len(x[1]), reverse=True)
|
||||
pvert = [x[0] for x in items]
|
||||
pvert = _af_invert(pvert)
|
||||
|
||||
# the indices of the tensor are twice the number of lines of the graph
|
||||
num_indices = 0
|
||||
for v, neigh in items:
|
||||
num_indices += len(neigh)
|
||||
# associate to each vertex its indices; for each line
|
||||
# between two vertices assign the
|
||||
# even index to the vertex which comes first in items,
|
||||
# the odd index to the other vertex
|
||||
vertices = [[] for i in items]
|
||||
i = 0
|
||||
for v, neigh in items:
|
||||
for v2 in neigh:
|
||||
if pvert[v] < pvert[v2]:
|
||||
vertices[pvert[v]].append(i)
|
||||
vertices[pvert[v2]].append(i+1)
|
||||
i += 2
|
||||
g = []
|
||||
for v in vertices:
|
||||
g.extend(v)
|
||||
assert len(g) == num_indices
|
||||
g += [num_indices, num_indices + 1]
|
||||
size = num_indices + 2
|
||||
assert sorted(g) == list(range(size))
|
||||
g = Permutation(g)
|
||||
vlen = [0]*(len(vertices[0])+1)
|
||||
for neigh in vertices:
|
||||
vlen[len(neigh)] += 1
|
||||
v = []
|
||||
for i in range(len(vlen)):
|
||||
n = vlen[i]
|
||||
if n:
|
||||
base, gens = get_symmetric_group_sgs(i)
|
||||
v.append((base, gens, n, 0))
|
||||
v.reverse()
|
||||
dummies = list(range(num_indices))
|
||||
can = canonicalize(g, dummies, 0, *v)
|
||||
return can
|
||||
+532
@@ -0,0 +1,532 @@
|
||||
from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul
|
||||
from sympy.ntheory import isprime
|
||||
|
||||
rmul = Permutation.rmul
|
||||
_af_new = Permutation._af_new
|
||||
|
||||
############################################
|
||||
#
|
||||
# Utilities for computational group theory
|
||||
#
|
||||
############################################
|
||||
|
||||
|
||||
def _base_ordering(base, degree):
|
||||
r"""
|
||||
Order `\{0, 1, \dots, n-1\}` so that base points come first and in order.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : the base
|
||||
degree : the degree of the associated permutation group
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
A list ``base_ordering`` such that ``base_ordering[point]`` is the
|
||||
number of ``point`` in the ordering.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import SymmetricGroup
|
||||
>>> from sympy.combinatorics.util import _base_ordering
|
||||
>>> S = SymmetricGroup(4)
|
||||
>>> S.schreier_sims()
|
||||
>>> _base_ordering(S.base, S.degree)
|
||||
[0, 1, 2, 3]
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This is used in backtrack searches, when we define a relation `\ll` on
|
||||
the underlying set for a permutation group of degree `n`,
|
||||
`\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we
|
||||
have `b_i \ll b_j` whenever `i<j` and `b_i \ll a` for all
|
||||
`i\in\{1,2, \dots, k\}` and `a` is not in the base. The idea is developed
|
||||
and applied to backtracking algorithms in [1], pp.108-132. The points
|
||||
that are not in the base are taken in increasing order.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of computational group theory"
|
||||
|
||||
"""
|
||||
base_len = len(base)
|
||||
ordering = [0]*degree
|
||||
for i in range(base_len):
|
||||
ordering[base[i]] = i
|
||||
current = base_len
|
||||
for i in range(degree):
|
||||
if i not in base:
|
||||
ordering[i] = current
|
||||
current += 1
|
||||
return ordering
|
||||
|
||||
|
||||
def _check_cycles_alt_sym(perm):
|
||||
"""
|
||||
Checks for cycles of prime length p with n/2 < p < n-2.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Here `n` is the degree of the permutation. This is a helper function for
|
||||
the function is_alt_sym from sympy.combinatorics.perm_groups.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.util import _check_cycles_alt_sym
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
>>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]])
|
||||
>>> _check_cycles_alt_sym(a)
|
||||
False
|
||||
>>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]])
|
||||
>>> _check_cycles_alt_sym(b)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym
|
||||
|
||||
"""
|
||||
n = perm.size
|
||||
af = perm.array_form
|
||||
current_len = 0
|
||||
total_len = 0
|
||||
used = set()
|
||||
for i in range(n//2):
|
||||
if i not in used and i < n//2 - total_len:
|
||||
current_len = 1
|
||||
used.add(i)
|
||||
j = i
|
||||
while af[j] != i:
|
||||
current_len += 1
|
||||
j = af[j]
|
||||
used.add(j)
|
||||
total_len += current_len
|
||||
if current_len > n//2 and current_len < n - 2 and isprime(current_len):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _distribute_gens_by_base(base, gens):
|
||||
r"""
|
||||
Distribute the group elements ``gens`` by membership in basic stabilizers.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers
|
||||
are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for
|
||||
`i \in\{1, 2, \dots, k\}`.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : a sequence of points in `\{0, 1, \dots, n-1\}`
|
||||
gens : a list of elements of a permutation group of degree `n`.
|
||||
|
||||
Returns
|
||||
=======
|
||||
list
|
||||
List of length `k`, where `k` is the length of *base*. The `i`-th entry
|
||||
contains those elements in *gens* which fix the first `i` elements of
|
||||
*base* (so that the `0`-th entry is equal to *gens* itself). If no
|
||||
element fixes the first `i` elements of *base*, the `i`-th element is
|
||||
set to a list containing the identity element.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
||||
>>> from sympy.combinatorics.util import _distribute_gens_by_base
|
||||
>>> D = DihedralGroup(3)
|
||||
>>> D.schreier_sims()
|
||||
>>> D.strong_gens
|
||||
[(0 1 2), (0 2), (1 2)]
|
||||
>>> D.base
|
||||
[0, 1]
|
||||
>>> _distribute_gens_by_base(D.base, D.strong_gens)
|
||||
[[(0 1 2), (0 2), (1 2)],
|
||||
[(1 2)]]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
_strong_gens_from_distr, _orbits_transversals_from_bsgs,
|
||||
_handle_precomputed_bsgs
|
||||
|
||||
"""
|
||||
base_len = len(base)
|
||||
degree = gens[0].size
|
||||
stabs = [[] for _ in range(base_len)]
|
||||
max_stab_index = 0
|
||||
for gen in gens:
|
||||
j = 0
|
||||
while j < base_len - 1 and gen._array_form[base[j]] == base[j]:
|
||||
j += 1
|
||||
if j > max_stab_index:
|
||||
max_stab_index = j
|
||||
for k in range(j + 1):
|
||||
stabs[k].append(gen)
|
||||
for i in range(max_stab_index + 1, base_len):
|
||||
stabs[i].append(_af_new(list(range(degree))))
|
||||
return stabs
|
||||
|
||||
|
||||
def _handle_precomputed_bsgs(base, strong_gens, transversals=None,
|
||||
basic_orbits=None, strong_gens_distr=None):
|
||||
"""
|
||||
Calculate BSGS-related structures from those present.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The base and strong generating set must be provided; if any of the
|
||||
transversals, basic orbits or distributed strong generators are not
|
||||
provided, they will be calculated from the base and strong generating set.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : the base
|
||||
strong_gens : the strong generators
|
||||
transversals : basic transversals
|
||||
basic_orbits : basic orbits
|
||||
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(transversals, basic_orbits, strong_gens_distr)
|
||||
where *transversals* are the basic transversals, *basic_orbits* are the
|
||||
basic orbits, and *strong_gens_distr* are the strong generators distributed
|
||||
by membership in basic stabilizers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics.named_groups import DihedralGroup
|
||||
>>> from sympy.combinatorics.util import _handle_precomputed_bsgs
|
||||
>>> D = DihedralGroup(3)
|
||||
>>> D.schreier_sims()
|
||||
>>> _handle_precomputed_bsgs(D.base, D.strong_gens,
|
||||
... basic_orbits=D.basic_orbits)
|
||||
([{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)]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
_orbits_transversals_from_bsgs, _distribute_gens_by_base
|
||||
|
||||
"""
|
||||
if strong_gens_distr is None:
|
||||
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
||||
if transversals is None:
|
||||
if basic_orbits is None:
|
||||
basic_orbits, transversals = \
|
||||
_orbits_transversals_from_bsgs(base, strong_gens_distr)
|
||||
else:
|
||||
transversals = \
|
||||
_orbits_transversals_from_bsgs(base, strong_gens_distr,
|
||||
transversals_only=True)
|
||||
else:
|
||||
if basic_orbits is None:
|
||||
base_len = len(base)
|
||||
basic_orbits = [None]*base_len
|
||||
for i in range(base_len):
|
||||
basic_orbits[i] = list(transversals[i].keys())
|
||||
return transversals, basic_orbits, strong_gens_distr
|
||||
|
||||
|
||||
def _orbits_transversals_from_bsgs(base, strong_gens_distr,
|
||||
transversals_only=False, slp=False):
|
||||
"""
|
||||
Compute basic orbits and transversals from a base and strong generating set.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The generators are provided as distributed across the basic stabilizers.
|
||||
If the optional argument ``transversals_only`` is set to True, only the
|
||||
transversals are returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : The base.
|
||||
strong_gens_distr : Strong generators distributed by membership in basic stabilizers.
|
||||
transversals_only : bool, default: False
|
||||
A flag switching between returning only the
|
||||
transversals and both orbits and transversals.
|
||||
slp : bool, default: False
|
||||
If ``True``, return a list of dictionaries containing the
|
||||
generator presentations of the elements of the transversals,
|
||||
i.e. the list of indices of generators from ``strong_gens_distr[i]``
|
||||
such that their product is the relevant transversal element.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import SymmetricGroup
|
||||
>>> from sympy.combinatorics.util import _distribute_gens_by_base
|
||||
>>> S = SymmetricGroup(3)
|
||||
>>> S.schreier_sims()
|
||||
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
|
||||
>>> (S.base, strong_gens_distr)
|
||||
([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
_distribute_gens_by_base, _handle_precomputed_bsgs
|
||||
|
||||
"""
|
||||
from sympy.combinatorics.perm_groups import _orbit_transversal
|
||||
base_len = len(base)
|
||||
degree = strong_gens_distr[0][0].size
|
||||
transversals = [None]*base_len
|
||||
slps = [None]*base_len
|
||||
if transversals_only is False:
|
||||
basic_orbits = [None]*base_len
|
||||
for i in range(base_len):
|
||||
transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
|
||||
base[i], pairs=True, slp=True)
|
||||
transversals[i] = dict(transversals[i])
|
||||
if transversals_only is False:
|
||||
basic_orbits[i] = list(transversals[i].keys())
|
||||
if transversals_only:
|
||||
return transversals
|
||||
else:
|
||||
if not slp:
|
||||
return basic_orbits, transversals
|
||||
return basic_orbits, transversals, slps
|
||||
|
||||
|
||||
def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None):
|
||||
"""
|
||||
Remove redundant generators from a strong generating set.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : a base
|
||||
strong_gens : a strong generating set relative to *base*
|
||||
basic_orbits : basic orbits
|
||||
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
A strong generating set with respect to ``base`` which is a subset of
|
||||
``strong_gens``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import SymmetricGroup
|
||||
>>> from sympy.combinatorics.util import _remove_gens
|
||||
>>> from sympy.combinatorics.testutil import _verify_bsgs
|
||||
>>> S = SymmetricGroup(15)
|
||||
>>> base, strong_gens = S.schreier_sims_incremental()
|
||||
>>> new_gens = _remove_gens(base, strong_gens)
|
||||
>>> len(new_gens)
|
||||
14
|
||||
>>> _verify_bsgs(S, base, new_gens)
|
||||
True
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This procedure is outlined in [1],p.95.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E.
|
||||
"Handbook of computational group theory"
|
||||
|
||||
"""
|
||||
from sympy.combinatorics.perm_groups import _orbit
|
||||
base_len = len(base)
|
||||
degree = strong_gens[0].size
|
||||
if strong_gens_distr is None:
|
||||
strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
|
||||
if basic_orbits is None:
|
||||
basic_orbits = []
|
||||
for i in range(base_len):
|
||||
basic_orbit = _orbit(degree, strong_gens_distr[i], base[i])
|
||||
basic_orbits.append(basic_orbit)
|
||||
strong_gens_distr.append([])
|
||||
res = strong_gens[:]
|
||||
for i in range(base_len - 1, -1, -1):
|
||||
gens_copy = strong_gens_distr[i][:]
|
||||
for gen in strong_gens_distr[i]:
|
||||
if gen not in strong_gens_distr[i + 1]:
|
||||
temp_gens = gens_copy[:]
|
||||
temp_gens.remove(gen)
|
||||
if temp_gens == []:
|
||||
continue
|
||||
temp_orbit = _orbit(degree, temp_gens, base[i])
|
||||
if temp_orbit == basic_orbits[i]:
|
||||
gens_copy.remove(gen)
|
||||
res.remove(gen)
|
||||
return res
|
||||
|
||||
|
||||
def _strip(g, base, orbits, transversals):
|
||||
"""
|
||||
Attempt to decompose a permutation using a (possibly partial) BSGS
|
||||
structure.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This is done by treating the sequence ``base`` as an actual base, and
|
||||
the orbits ``orbits`` and transversals ``transversals`` as basic orbits and
|
||||
transversals relative to it.
|
||||
|
||||
This process is called "sifting". A sift is unsuccessful when a certain
|
||||
orbit element is not found or when after the sift the decomposition
|
||||
does not end with the identity element.
|
||||
|
||||
The argument ``transversals`` is a list of dictionaries that provides
|
||||
transversal elements for the orbits ``orbits``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
g : permutation to be decomposed
|
||||
base : sequence of points
|
||||
orbits : list
|
||||
A list in which the ``i``-th entry is an orbit of ``base[i]``
|
||||
under some subgroup of the pointwise stabilizer of `
|
||||
`base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit
|
||||
in this function since the only information we need is encoded in the orbits
|
||||
and transversals
|
||||
transversals : list
|
||||
A list of orbit transversals associated with the orbits *orbits*.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import Permutation, SymmetricGroup
|
||||
>>> from sympy.combinatorics.util import _strip
|
||||
>>> S = SymmetricGroup(5)
|
||||
>>> S.schreier_sims()
|
||||
>>> g = Permutation([0, 2, 3, 1, 4])
|
||||
>>> _strip(g, S.base, S.basic_orbits, S.basic_transversals)
|
||||
((4), 5)
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The algorithm is described in [1],pp.89-90. The reason for returning
|
||||
both the current state of the element being decomposed and the level
|
||||
at which the sifting ends is that they provide important information for
|
||||
the randomized version of the Schreier-Sims algorithm.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory"
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
|
||||
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random
|
||||
|
||||
"""
|
||||
h = g._array_form
|
||||
base_len = len(base)
|
||||
for i in range(base_len):
|
||||
beta = h[base[i]]
|
||||
if beta == base[i]:
|
||||
continue
|
||||
if beta not in orbits[i]:
|
||||
return _af_new(h), i + 1
|
||||
u = transversals[i][beta]._array_form
|
||||
h = _af_rmul(_af_invert(u), h)
|
||||
return _af_new(h), base_len + 1
|
||||
|
||||
|
||||
def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}):
|
||||
"""
|
||||
optimized _strip, with h, transversals and result in array form
|
||||
if the stripped elements is the identity, it returns False, base_len + 1
|
||||
|
||||
j h[base[i]] == base[i] for i <= j
|
||||
|
||||
"""
|
||||
base_len = len(base)
|
||||
for i in range(j+1, base_len):
|
||||
beta = h[base[i]]
|
||||
if beta == base[i]:
|
||||
continue
|
||||
if beta not in orbits[i]:
|
||||
if not slp:
|
||||
return h, i + 1
|
||||
return h, i + 1, slp
|
||||
u = transversals[i][beta]
|
||||
if h == u:
|
||||
if not slp:
|
||||
return False, base_len + 1
|
||||
return False, base_len + 1, slp
|
||||
h = _af_rmul(_af_invert(u), h)
|
||||
if slp:
|
||||
u_slp = slps[i][beta][:]
|
||||
u_slp.reverse()
|
||||
u_slp = [(i, (g,)) for g in u_slp]
|
||||
slp = u_slp + slp
|
||||
if not slp:
|
||||
return h, base_len + 1
|
||||
return h, base_len + 1, slp
|
||||
|
||||
|
||||
def _strong_gens_from_distr(strong_gens_distr):
|
||||
"""
|
||||
Retrieve strong generating set from generators of basic stabilizers.
|
||||
|
||||
This is just the union of the generators of the first and second basic
|
||||
stabilizers.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
strong_gens_distr : strong generators distributed by membership in basic stabilizers
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.combinatorics import SymmetricGroup
|
||||
>>> from sympy.combinatorics.util import (_strong_gens_from_distr,
|
||||
... _distribute_gens_by_base)
|
||||
>>> S = SymmetricGroup(3)
|
||||
>>> S.schreier_sims()
|
||||
>>> S.strong_gens
|
||||
[(0 1 2), (2)(0 1), (1 2)]
|
||||
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
|
||||
>>> _strong_gens_from_distr(strong_gens_distr)
|
||||
[(0 1 2), (2)(0 1), (1 2)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
_distribute_gens_by_base
|
||||
|
||||
"""
|
||||
if len(strong_gens_distr) == 1:
|
||||
return strong_gens_distr[0][:]
|
||||
else:
|
||||
result = strong_gens_distr[0]
|
||||
for gen in strong_gens_distr[1]:
|
||||
if gen not in result:
|
||||
result.append(gen)
|
||||
return result
|
||||
Reference in New Issue
Block a user