back to table · edit · history · where entries came from · files · download
5101 bytes, as of the version from 2026-10-27 13:15 (current). Recorded here, not run.
"""Unitary Weingarten functions -- numberdb.org/T549.
Run with SageMath (install numberdb once with sage -pip install numberdb):
sage -python generate.py # verify every stored entry
sage -python checks.py # independent exact checks
sage -python generate.py --fill # write entries to this draft
Writing needs NUMBERDB_API_KEY. It fills the existing table; it does not
publish or offer the draft. NUMBERDB_KEY_FROM_STDIN=1 reads the key from stdin,
and NUMBERDB_PUBLISH=1 is the campaign wrapper's equivalent of --fill.
Matsumoto (2013), arXiv:1301.5401, equation (2.1):
Wg_N(mu) = 1/k! sum_{lambda |- k, len(lambda)<=N}
f^lambda chi^lambda(mu) / product_{(i,j) in lambda}(N+j-i).
Characters are computed by the Murnaghan-Nakayama rule, removing connected
skew rim hooks with no 2 by 2 square, with sign (-1)^(height-1).
Every division has a Sage rational operand. No decimal approximation occurs.
The default selection and its noncanonical finite bounds are justified in
argument-selection.md. The formula also supports positive N below k, using
only partitions of length at most N (the pseudoinverse convention).
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from functools import lru_cache
from math import factorial, prod
import os
import sys
@lru_cache(None)
def partitions(n, ceiling=None):
"""All partitions in reverse lexicographic order, including () for zero."""
if n == 0:
return ((),)
bound = n if ceiling is None else min(n, ceiling)
return tuple((first,) + tail for first in range(bound, 0, -1)
for tail in partitions(n - first, first))
@lru_cache(None)
def cells(shape):
return frozenset((i, j) for i, row in enumerate(shape)
for j in range(row))
@lru_cache(None)
def character(shape, cycle_type):
"""An irreducible symmetric-group character, by rim-hook removal."""
if not cycle_type:
return int(not shape)
r, tail = cycle_type[0], cycle_type[1:]
result = 0
for inner in partitions(sum(shape) - r):
if len(inner) > len(shape) or any(a > b for a, b in zip(inner, shape)):
continue
skew = cells(shape) - cells(inner)
if any({(i, j+1), (i+1, j), (i+1, j+1)} <= skew for i, j in skew):
continue
todo = [next(iter(skew))]
reached = set(todo)
while todo:
i, j = todo.pop()
for neighbor in ((i-1,j), (i+1,j), (i,j-1), (i,j+1)):
if neighbor in skew and neighbor not in reached:
reached.add(neighbor)
todo.append(neighbor)
if len(reached) != r:
continue
height = len({i for i, j in skew})
result += (-1)**(height - 1) * character(inner, tail)
return result
def dimension(shape):
hooks = prod(shape[i] - j + sum(row > j for row in shape[i+1:])
for i, j in cells(shape))
numerator = factorial(sum(shape))
assert numerator % hooks == 0
return numerator // hooks
def weingarten(N, mu):
N = int(N)
mu = tuple(int(x) for x in mu)
if N < 1 or not mu or any(x <= 0 for x in mu) or tuple(sorted(mu, reverse=True)) != mu:
raise ValueError('N must be positive and mu a nonempty decreasing partition')
k = sum(mu)
result = QQ(0)
for shape in partitions(k):
if len(shape) > N:
continue
denominator = prod(N + j - i for i, j in cells(shape))
result += QQ(dimension(shape) * character(shape, mu)) / QQ(denominator)
return result / QQ(factorial(k))
class UnitaryWeingarten(numberdb.Generator):
table = 'T549'
parameters = ('N', 'mu')
type = 'Q'
rigour = 'exact'
digits = 100 # Exact fractions are never rounded.
files = ('generate.py', 'checks.py', 'check.py', 'program.py', 'argument-selection.md',
'numerical-evidence.md', 'skill-manifest.json', 'checks-output.json')
def enumerate(self, max_dimension=12, max_degree=6):
for N in range(1, max_dimension + 1):
for k in range(1, min(max_degree, N) + 1):
for mu in partitions(k):
yield {'N': str(N), 'mu': ','.join(map(str, mu))}
def value(self, params, digits=100):
return weingarten(int(params['N']), tuple(map(int, params['mu'].split(','))))
def configure_key():
if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
key = sys.stdin.read().strip()
if '=' in key and key.split('=', 1)[0].isidentifier():
key = key.split('=', 1)[1].strip().strip('\"\'')
numberdb.configure(api_key=key)
if __name__ == '__main__':
configure_key()
generator = UnitaryWeingarten()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(generator.publish(message='Fill 242 exact unitary Weingarten coefficients; independently checked by Gram inversion'))
else:
report = generator.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)