Skip to content

Commit 7dd8c79

Browse files
author
Release Manager
committed
sagemathgh-38455: unitary DFT for symmetric group algebra <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies - sagemath#39200: Uses the decomposition. <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#38455 Reported by: Jackson Walters Reviewer(s): Jackson Walters, Travis Scrimshaw
2 parents 7ef5433 + bf00417 commit 7dd8c79

File tree

3 files changed

+605
-10
lines changed

3 files changed

+605
-10
lines changed

src/sage/combinat/symmetric_group_algebra.py

+145-6
Original file line numberDiff line numberDiff line change
@@ -2035,22 +2035,60 @@ def dft(self, form=None, mult='l2r'):
20352035
(:meth:`antipode`) of the seminormal basis instead of
20362036
the seminormal basis.
20372037
2038-
EXAMPLES::
2038+
- ``form`` -- string (default: ``"modular"`` if `p|n!` else ``"seminormal"``);
2039+
one of the following:
20392040
2040-
sage: QS3 = SymmetricGroupAlgebra(QQ, 3)
2041-
sage: QS3.dft()
2041+
* ``"seminormal"`` -- use the seminormal basis
2042+
* ``"modular"`` -- use the modular DFT, which uses the Peirce decomposition
2043+
of the group algebra into blocks with central orthogonal idempotents
2044+
* ``"unitary"`` -- use the unitary DFT, which computes the extended
2045+
Cholesky decomposition of an `S_n`-invariant symmetric bilinear form as
2046+
a change-of-basis matrix (for positive characteristics, it must be
2047+
a finite field of square order)
2048+
2049+
EXAMPLES:
2050+
2051+
The default is the seminormal DFT when the characteristic does not divide the group order::
2052+
2053+
sage: QQ_S3 = SymmetricGroupAlgebra(QQ, 3)
2054+
sage: QQ_S3.dft()
20422055
[ 1 1 1 1 1 1]
20432056
[ 1 1/2 -1 -1/2 -1/2 1/2]
20442057
[ 0 3/4 0 3/4 -3/4 -3/4]
20452058
[ 0 1 0 -1 1 -1]
20462059
[ 1 -1/2 1 -1/2 -1/2 -1/2]
20472060
[ 1 -1 -1 1 1 -1]
20482061
2049-
Over fields of characteristic `p > 0` such that `p \mid n!`, we use the
2062+
The unitary form works in characteristic zero::
2063+
2064+
sage: U = QQ_S3.dft(form='unitary'); U
2065+
[-1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2]
2066+
[ 1/3*sqrt3 1/6*sqrt3 -1/3*sqrt3 -1/6*sqrt3 -1/6*sqrt3 1/6*sqrt3]
2067+
[ 0 1/2 0 1/2 -1/2 -1/2]
2068+
[ 0 1/2 0 -1/2 1/2 -1/2]
2069+
[ 1/3*sqrt3 -1/6*sqrt3 1/3*sqrt3 -1/6*sqrt3 -1/6*sqrt3 -1/6*sqrt3]
2070+
[-1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2]
2071+
sage: U*U.H == 1
2072+
True
2073+
2074+
Over finite fields of square order with characteristic `p > n`, we can perform the unitary DFT::
2075+
2076+
sage: GF25_S3 = SymmetricGroupAlgebra(GF(5**2), 3)
2077+
sage: U = GF25_S3.dft(form='unitary'); U
2078+
[ 1 1 1 1 1 1]
2079+
[2*z2 + 4 z2 + 2 3*z2 + 1 4*z2 + 3 4*z2 + 3 z2 + 2]
2080+
[ 0 2 0 2 3 3]
2081+
[ 0 z2 + 1 0 4*z2 + 4 z2 + 1 4*z2 + 4]
2082+
[2*z2 + 4 4*z2 + 3 2*z2 + 4 4*z2 + 3 4*z2 + 3 4*z2 + 3]
2083+
[ 1 4 4 1 1 4]
2084+
sage: U*U.H == 1
2085+
True
2086+
2087+
Over fields of characteristic `p > 0` such that `p|n!`, we use the
20502088
modular Fourier transform (:issue:`37751`)::
20512089
2052-
sage: GF2S3 = SymmetricGroupAlgebra(GF(2), 3)
2053-
sage: GF2S3.dft()
2090+
sage: GF2_S3 = SymmetricGroupAlgebra(GF(2), 3)
2091+
sage: GF2_S3.dft()
20542092
[1 0 0 0 1 0]
20552093
[0 1 0 0 0 1]
20562094
[0 0 1 0 0 1]
@@ -2066,8 +2104,109 @@ def dft(self, form=None, mult='l2r'):
20662104
return self._dft_seminormal(mult=mult)
20672105
if form == "modular":
20682106
return self._dft_modular()
2107+
if form == "unitary":
2108+
return self._dft_unitary()
20692109
raise ValueError("invalid form (= %s)" % form)
20702110

2111+
def _dft_unitary(self):
2112+
"""
2113+
Return the unitary form of the discrete Fourier transform for ``self``.
2114+
2115+
EXAMPLES::
2116+
2117+
sage: QQ_S3 = SymmetricGroupAlgebra(QQ, 3)
2118+
sage: QQ_S3._dft_unitary()
2119+
[-1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2]
2120+
[ 1/3*sqrt3 1/6*sqrt3 -1/3*sqrt3 -1/6*sqrt3 -1/6*sqrt3 1/6*sqrt3]
2121+
[ 0 1/2 0 1/2 -1/2 -1/2]
2122+
[ 0 1/2 0 -1/2 1/2 -1/2]
2123+
[ 1/3*sqrt3 -1/6*sqrt3 1/3*sqrt3 -1/6*sqrt3 -1/6*sqrt3 -1/6*sqrt3]
2124+
[-1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 -1/6*sqrt3*sqrt2 1/6*sqrt3*sqrt2]
2125+
sage: GF49_S3 = SymmetricGroupAlgebra(GF(7**2), 3)
2126+
sage: GF49_S3._dft_unitary()
2127+
[5*z2 + 5 5*z2 + 5 5*z2 + 5 5*z2 + 5 5*z2 + 5 5*z2 + 5]
2128+
[2*z2 + 5 z2 + 6 5*z2 + 2 6*z2 + 1 6*z2 + 1 z2 + 6]
2129+
[ 0 4*z2 + 5 0 4*z2 + 5 3*z2 + 2 3*z2 + 2]
2130+
[ 0 3*z2 + 2 0 4*z2 + 5 3*z2 + 2 4*z2 + 5]
2131+
[2*z2 + 5 6*z2 + 1 2*z2 + 5 6*z2 + 1 6*z2 + 1 6*z2 + 1]
2132+
[5*z2 + 5 2*z2 + 2 2*z2 + 2 5*z2 + 5 5*z2 + 5 2*z2 + 2]
2133+
2134+
TESTS::
2135+
2136+
sage: QQ_S3 = SymmetricGroupAlgebra(QQ, 3)
2137+
sage: U = QQ_S3._dft_unitary()
2138+
sage: U*U.H == 1
2139+
True
2140+
sage: GF25_S3 = SymmetricGroupAlgebra(GF(5**2), 3)
2141+
sage: U = GF25_S3._dft_unitary()
2142+
sage: U*U.H == 1
2143+
True
2144+
sage: GF5_S3 = SymmetricGroupAlgebra(GF(5), 3)
2145+
sage: U = GF5_S3._dft_unitary()
2146+
Traceback (most recent call last):
2147+
...
2148+
ValueError: the base ring must be a finite field of square order
2149+
sage: Z5_S3 = SymmetricGroupAlgebra(Integers(5), 3)
2150+
sage: U = Z5_S3._dft_unitary()
2151+
Traceback (most recent call last):
2152+
...
2153+
ValueError: the base ring must be a finite field of square order
2154+
sage: F.<x> = FunctionField(GF(3))
2155+
sage: GF3_x_S3 = SymmetricGroupAlgebra(F, 5)
2156+
sage: U = GF3_x_S3._dft_unitary()
2157+
Traceback (most recent call last):
2158+
...
2159+
ValueError: the base ring must be a finite field of square order
2160+
sage: GF9_S3 = SymmetricGroupAlgebra(GF(3**2), 3)
2161+
sage: U = GF9_S3._dft_unitary()
2162+
Traceback (most recent call last):
2163+
...
2164+
NotImplementedError: not implemented when p|n!; dimension of invariant forms may be greater than one
2165+
"""
2166+
from sage.matrix.special import diagonal_matrix
2167+
F = self.base_ring()
2168+
G = self.group()
2169+
2170+
if F.characteristic() == 0:
2171+
from sage.misc.functional import sqrt
2172+
from sage.rings.number_field.number_field import NumberField
2173+
dft_matrix = self.dft()
2174+
n = dft_matrix.nrows()
2175+
diag = [sum(dft_matrix[i, j] * dft_matrix[i, j].conjugate() for j in range(n))
2176+
for i in range(n)]
2177+
primes_needed = {factor for d in diag for factor, _ in d.squarefree_part().factor()}
2178+
names = [f"sqrt{factor}" for factor in primes_needed]
2179+
x = PolynomialRing(QQ, 'x').gen()
2180+
K = NumberField([x**2 - d for d in primes_needed], names=names)
2181+
dft_matrix = dft_matrix.change_ring(K)
2182+
for i, d in enumerate(diag):
2183+
dft_matrix[i] *= ~sqrt(K(d))
2184+
return dft_matrix
2185+
2186+
# positive characteristic case
2187+
assert F.characteristic() > 0, "F must have positive characteristic"
2188+
if not (F.is_field() and F.is_finite() and F.order().is_square()):
2189+
raise ValueError("the base ring must be a finite field of square order")
2190+
if F.characteristic().divides(G.cardinality()):
2191+
raise NotImplementedError("not implemented when p|n!; dimension of invariant forms may be greater than one")
2192+
q = F.order().sqrt()
2193+
2194+
def conj_square_root(u):
2195+
if not u:
2196+
return F.zero()
2197+
z = F.multiplicative_generator()
2198+
k = u.log(z)
2199+
if k % (q+1) != 0:
2200+
raise ValueError(f"unable to factor as {u} is not in base field GF({q})")
2201+
return z ** ((k//(q+1)) % (q-1))
2202+
2203+
dft_matrix = self.dft()
2204+
n = dft_matrix.nrows()
2205+
for i in range(n):
2206+
d = sum(dft_matrix[i, j] * dft_matrix[i, j].conjugate() for j in range(n))
2207+
dft_matrix[i] *= ~conj_square_root(d)
2208+
return dft_matrix
2209+
20712210
def _dft_seminormal(self, mult='l2r'):
20722211
"""
20732212
Return the seminormal form of the discrete Fourier transform for ``self``.

0 commit comments

Comments
 (0)