Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added kth roots to Permutation #35053

Merged
merged 22 commits into from
Nov 5, 2023
Merged
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions src/sage/combinat/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5383,6 +5383,253 @@ def shifted_shuffle(self, other):
return self.shifted_concatenation(other, "right").\
right_permutohedron_interval(self.shifted_concatenation(other, "left"))

def nth_roots(self, n):
r"""
Return all n-th roots of ``self`` (as a generator).

An n-th root of the permutation ``self`` is a permutation `\gamma` such that `\gamma^n == self`.

Note that the number of n-th roots only depend on the cycle type of ``self``.

EXAMPLES::

sage: Sigma = Permutations(5).identity()
sage: list(Sigma.nth_roots(3))
[[1, 4, 3, 5, 2], [1, 5, 3, 2, 4], [1, 2, 4, 5, 3], [1, 2, 5, 3, 4], [4, 2, 3, 5, 1], [5, 2, 3, 1, 4], [3, 2, 5, 4, 1],
[5, 2, 1, 4, 3], [2, 5, 3, 4, 1], [5, 1, 3, 4, 2], [2, 3, 1, 4, 5], [3, 1, 2, 4, 5], [2, 4, 3, 1, 5], [4, 1, 3, 2, 5],
[3, 2, 4, 1, 5], [4, 2, 1, 3, 5], [1, 3, 4, 2, 5], [1, 4, 2, 3, 5], [1, 3, 5, 4, 2], [1, 5, 2, 4, 3], [1, 2, 3, 4, 5]]

sage: Sigma = Permutation('(1, 3)')
sage: list(Sigma.nth_roots(2))
[]

For n >= 6, this algorithm begins to be more efficient than naive search
(look at all permutations and test their n-th power).

.. SEEALSO::

* :meth:`has_nth_root`
* :meth:`number_of_nth_roots`

TESTS:

We compute the number of square roots of the identity (i.e. involutions in `S_n`, :oeis:`A000085`)::

sage: [len(list(Permutations(n).identity().nth_roots(2))) for n in range(2,8)]
[2, 4, 10, 26, 76, 232]

sage: list(Permutation('(1)').nth_roots(2))
[[1]]

sage: list(Permutation('').nth_roots(2))
[[]]

sage: Sigma = Permutations(6).random_element()
sage: list(Sigma.nth_roots(1)) == [Sigma]
True

sage: list(Permutations(4).identity().nth_roots(-1))
Traceback (most recent call last):
...
ValueError: n must be at least 1
"""
from sage.combinat.partition import Partitions
from sage.combinat.set_partition import SetPartitions
from itertools import product
from sage.arith.misc import divisors, gcd

def merging_cycles(list_of_cycles):
"""
Generate all l-cycles such that its n-th power is the product
of cycles in 'cycles' (which contains gcd(l, n) cycles of length l/gcd(l, n))
fchapoton marked this conversation as resolved.
Show resolved Hide resolved
"""
lC = len(list_of_cycles)
lperm = len(list_of_cycles[0])
l = lC*lperm
perm = [0] * l
for j in range(lperm):
perm[j*lC] = list_of_cycles[0][j]
for p in Permutations(lC-1):
for indices in product(*[range(lperm) for _ in range(lC-1)]):
new_perm = list(perm)
for i in range(lC-1):
for j in range(lperm):
new_perm[(p[i] + (indices[i]+j)*lC) % l] = list_of_cycles[i+1][j]
yield Permutation(tuple(new_perm))

def rewind(L, n):
"""
Construct the list M such that ``M[(j * n) % len(M)] == L[j]``.
"""
M = [0] * len(L)
m = len(M)
for j in range(m):
M[(j * n) % m] = L[j]
return M

if n < 1:
raise ValueError('n must be at least 1')

P = Permutations(self.size())

# Creating dict {length: cycles of this length in the cycle decomposition of Sigma}
cycles = {}
for c in self.cycle_tuples(singletons=True):
lc = len(c)
if lc not in cycles:
cycles[lc] = []
cycles[lc].append(c)

# for each length m, collects all product of cycles which n-th power gives the product prod(cycles[l])
possibilities = [[] for m in cycles]
for i, m in enumerate(cycles):
N = len(cycles[m])
parts = [x for x in divisors(n) if gcd(m*x, n) == x]
b = False
for X in Partitions(N, parts_in=parts):
for partition in SetPartitions(N, X):
b = True
poss = [P.identity()]
for pa in partition:
poss = [p*q for p in poss
for q in merging_cycles([rewind(cycles[m][i-1], n//len(pa)) for i in pa])]
possibilities[i] += poss
if not b:
return

#Product of Possibilities (i.e. final result)
for L in product(*possibilities):
yield P.prod(L)

def has_nth_root(self, n):
r"""
Decide if ``self`` has n-th roots.

An n-th root of the permutation ``self`` is a permutation `\gamma` such that `\gamma^n == self`.
Copy link
Collaborator

@mantepse mantepse Oct 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\gamma^n == self doesn't come out properly in the documentation. Not sure what it should be, possibly

``gamma`` such that ``gamma^n == self``

or

of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`.

I don't know how to do it properly.

The same comment applies to the docstrings of n_th_roots and number_of_nth_roots.

I'm sorry :-(

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`.

This is the one I would do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done already


Note that the number of n-th roots only depend on the cycle type of ``self``.

EXAMPLES::

sage: Sigma = Permutations(5).identity()
fchapoton marked this conversation as resolved.
Show resolved Hide resolved
sage: Sigma.has_nth_root(3)
True

sage: Sigma = Permutation('(1, 3)')
sage: Sigma.has_nth_root(2)
False

.. SEEALSO::

* :meth:`nth_roots`
* :meth:`number_of_nth_roots`

TESTS:

We compute the number of permutations that have square roots (i.e. squares in `S_n`, :oeis:`A003483`)::

sage: [len([p for p in Permutations(n) if p.has_nth_root(2)]) for n in range(2, 7)]
[1, 3, 12, 60, 270]

sage: Permutation('(1)').has_nth_root(2)
True

sage: Permutation('').has_nth_root(2)
True

sage: Sigma = Permutations(6).random_element()
sage: Sigma.has_nth_root(1)
True

sage: Permutations(4).identity().has_nth_root(-1)
Traceback (most recent call last):
...
ValueError: n must be at least 1
"""
from sage.combinat.partition import Partitions
from sage.arith.misc import divisors, gcd

if n < 1:
raise ValueError('n must be at least 1')

cycles = self.cycle_type().to_exp_dict()

# for each length m, check if the number of m-cycles can come from a n-th power
# (i.e. if you can partitionate m*Cycles[m] into parts of size l with l = m*gcd(l, n))
fchapoton marked this conversation as resolved.
Show resolved Hide resolved
for m, N in cycles.items():
parts = [x for x in divisors(n) if gcd(m*x, n) == x]
if Partitions(N, parts_in=parts).is_empty():
return False
return True

def number_of_nth_roots(self, n):
r"""
Return the number of n-th roots of ``self``.

An n-th root of the permutation ``self`` is a permutation `\gamma` such that `\gamma^n == self`.

Note that the number of n-th roots only depend on the cycle type of ``self``.

EXAMPLES::

sage: Sigma = Permutations(5).identity()
fchapoton marked this conversation as resolved.
Show resolved Hide resolved
sage: Sigma.number_of_nth_roots(3)
21

sage: Sigma = Permutation('(1, 3)')
sage: Sigma.number_of_nth_roots(2)
0

.. SEEALSO::

* :meth:`nth_roots`
* :meth:`has_nth_root`

TESTS:

We compute the number of square roots of the identity (i.e. involutions in `S_n`, :oeis:`A000085`), then the number of cubic roots::

sage: [Permutations(n).identity().number_of_nth_roots(2) for n in range(2, 10)]
[2, 4, 10, 26, 76, 232, 764, 2620]

sage: [Permutations(n).identity().number_of_nth_roots(3) for n in range(2, 10)]
[1, 3, 9, 21, 81, 351, 1233, 5769]

sage: Permutation('(1)').number_of_nth_roots(2)
1

sage: Permutation('').number_of_nth_roots(2)
1

sage: Sigma = Permutations(6).random_element()
sage: Sigma.number_of_nth_roots(1)
1

sage: Permutations(4).identity().number_of_nth_roots(-1)
Traceback (most recent call last):
...
ValueError: n must be at least 1
"""
from sage.combinat.partition import Partitions
from sage.combinat.set_partition import SetPartitions
from sage.arith.misc import divisors, gcd
from sage.misc.misc_c import prod

if n < 1:
raise ValueError('n must be at least 1')

cycles = self.cycle_type().to_exp_dict()
result = 1
for m, N in cycles.items():
parts = [x for x in divisors(n) if gcd(m*x, n) == x]
result *= sum(SetPartitions(N, pa).cardinality() *
prod(factorial(x-1) * m**(x-1) for x in pa)
for pa in Partitions(N, parts_in=parts))

if not result:
return 0

return result

def _tableau_contribution(T):
r"""
Expand Down
Loading