switching to high quality piper tts and added label translations
This commit is contained in:
@@ -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',
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()))
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
File diff suppressed because it is too large
Load Diff
+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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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])
|
||||
@@ -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
|
||||
@@ -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)]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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