SymPy 张量正则化(Tensor Canonicalization)完全指南:tensor_can 模块与 Butler-Portugal 算法解析
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
SymPy 的sympy.combinatorics.tensor_can模块实现了一套完整的**张量正则化(tensor canonicalization)**框架:给定一个由多个分量张量、自由指标与哑指标(dummy indices)构成的张量表达式,它会利用槽对称性(slot symmetry)、哑指标对称性与度规对称性,把表达式归约到一个确定性的"最小规范形",从而在指数级增长的可能性空间中快速判断两个张量是否相等、是否为零。本文将围绕官方文档 tensor_can.rst 所挂接的四个核心接口——canonicalize、double_coset_can_rep、get_symmetric_group_sgs、bsgs_direct_product——结合 tensor_can.py 的源码实现与 test_tensor_can.py 的测试用例,完整讲解其数学模型、调用约定、可运行示例与在sympy.tensor模块中的实际集成方式。读完本文,你将掌握如何用排列(Permutation)表示张量、如何构造对称群 BSGS、如何调用canonicalize完成含多种指标类型的张量正则化,并理解其背后的双陪集(double coset)与 Butler-Portugal 算法原理。
一、文档与模块定位
官方文档 tensor_can.rst 位于组合学模块文档 combinatorics/index.rst 的模块清单中,页面本身通过四个autofunction指令挂接源码 docstring:
canonicalize—— 张量正则化的总入口;double_coset_can_rep—— Butler-Portugal 算法核心实现;get_symmetric_group_sgs—— 生成(反)对称张量的极小 BSGS;bsgs_direct_product—— 两个 BSGS 的直积。
也就是说,该文档页面的实质内容来自 tensor_can.py(约 1190 行)中这些函数的完整 docstring,包括算法说明、数学推导与 doctest 示例。模块顶部还列出了四篇参考文献,其中 [1][2] 来自 R. Portugal 关于张量符号化简的系列论文,[4] 是 XPerm 中xperm.c引入的"末两位表示符号"技巧,本文后文会反复用到这些约定。
二、核心思想:张量如何用排列表示
要理解整个模块,首先要建立"一个张量 = 一个排列"的映射关系。该约定在double_coset_can_rep的 docstring 中有非常详尽的推导:
- 索引排序:设张量共有
n = len(ind)个指标(包含逆变与协变)。指标名按顺序排列,例如d1, -d1, d2, -d2, d3, -d3(-表示协变指标),哑指标按"逆变、协变成对"的顺序出现。 - 排列表示:张量
t = T(ind[g[0]], …, ind[g[n-1]]),其中g是大小为n + 2的排列。最后两个位置专门用于记录整体符号(该技巧来自 XPerm [4]):末尾两位为[n, n+1]表示正号,[n+1, n]表示负号。 - 乘法约定:本文档与
Permutation类默认的乘法方向相反,约定(p*q)(i) = p[q[i]],即复合时先作用右侧排列。 - 两类对称:
- 槽对称(slot symmetry):作用在槽位上,
s: t -> T(ind[(g*s)[0]], …, ind[(g*s)[n-1]]),对应分量张量自身指标置换,且可携带符号; - 哑指标对称(dummy symmetry):作用在指标名上,
d: t -> T(ind[(d*g)[0]], …, ind[(d*g)[n-1]]),对应收缩指标的换名与上下互换。
- 槽对称(slot symmetry):作用在槽位上,
- 双陪集:张量在两类对称下等价的所有形式恰为
g -> d*g*s,即双陪集D*g*S中的全体排列。于是"求张量规范形"转化为"求双陪集代表元",这正是 Butler 双陪集算法的用武之地。
docstring 用实例验证了左右作用方向的区别:对g = [4,2,0,1,3,5,6,7],哑指标变换d = [1,0,2,3,4,5,6,7]满足d*g = _af_rmul(d, g) = [4,2,1,0,3,5,6,7],而g*d结果不同;槽对称s = [2,1,0,3,4,5,7,6]满足g*s = [0,2,4,1,3,5,7,6]。
三、总入口canonicalize:参数、流程与校验
canonicalize(g, dummies, msym, *v)是面向最终用户的函数,其完整签名与参数含义如下:
| 参数 | 类型 | 含义 |
|---|---|---|
g | Permutation | 表示张量的排列,大小为n + 2,末两位记录符号 |
dummies | list | 哑指标列表;可以是单一类型指标的扁平列表,也可以是每种指标类型一个子列表的嵌套列表;指标顺序为[d0, -d0, d1, -d1, …],且必须排在自由指标之后 |
msym | int或list | 指标度规(metric)对称性;单个整数表示所有类型相同,列表则按类型逐一给出。取值0=对称(可交换)、1=反对称(反交换)、None=无对称 |
v | tuple序列 | 每个分量张量类型一个四元组(base_i, gens_i, n_i, sym_i):base_i, gens_i为该类型张量的 BSGS,n_i为该类型张量个数,sym_i为该类型张量彼此交换时的对称性(None/0/1) |
返回值:若张量恒为零返回0,否则返回规范形排列的数组形式(array form)。
3.1 算法总流程
源码 tensor_can.py 中canonicalize的执行步骤清晰可循:
- 参数校验:
msym及其列表元素必须是0/1/None,dummies与msym类型数必须一致,否则抛出ValueError;同时校验扁平化后的哑指标必须是连续区间,否则报'dummies is not valid'。 - BSGS 极小性检查:对每个分量张量的
(base_i, gens_i)调用_is_minimal_bsgs检查是否具有字典序极小基。若不是,尝试用get_minimal_bsgs重新计算;若仍失败,则回退到 testutil.py 中的canonicalize_naive(暴力法,速度慢得多)。 - 槽对称下求极小元:用
gens_products组装全部张量的槽对称 BSGS,再调用canonical_free在自由指标上按字典序求最小形式g1,并保存符号sign。 - 固定自由指标:找出自由指标占据的槽位,用
tensor_gens计算保持自由指标固定的残差槽对称群。 - 消除自由指标并归约:把排列去掉自由指标得到
g1_red,哑指标同步平移得到dummies_red,然后交给double_coset_can_rep完成哑指标维度的正则化,得到g2;若g2 == 0直接返回0。 - 回填自由指标:通过
_lift_sgens把自由指标重新嵌入,得到最终规范形g3。
3.2 可直接运行的示例
docstring 给出了三类典型用例,全部可作为 doctest 直接执行:
例 1:单一指标类型,A_{a b}、B_{a b}反对称且可交换
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize >>> from sympy.combinatorics import Permutation >>> base2a, gens2a = get_symmetric_group_sgs(2, 1) >>> t0 = (base2a, gens2a, 1, 0) >>> t1 = (base2a, gens2a, 2, 0) >>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7]) >>> canonicalize(g, range(6), 0, t0, t1) 0T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}的规范形为零,原因是两个反对称的B因子交换会引入符号变化,最终与自身相差一个负号。
例 2:同一表达式但B反交换(anticommuting)
>>> t1 = (base2a, gens2a, 2, 1) >>> canonicalize(g, range(6), 0, t0, t1) [0, 2, 1, 4, 3, 5, 7, 6]此时规范形为-A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2},末两位[7, 6]即表示整体负号。
例 3:两种指标类型([a,b,c,d,e,f]与[m,n],度规均可交换):f^{a b c}反对称可交换,A_{m a}无对称可交换,对T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}求规范形:
>>> from sympy.combinatorics.tensor_can import bsgs_direct_product >>> base_f, gens_f = get_symmetric_group_sgs(3, 1) >>> base1, gens1 = get_symmetric_group_sgs(1) >>> 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) >>> dummies = [range(2, 10), range(10, 14)] >>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15]) >>> canonicalize(g, dummies, [0, 0], t0, t1) [0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14]结果对应规范形T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}。注意这里dummies与msym都是列表形式,且A的 BSGS 正是由bsgs_direct_product构造的(两个一指标张量 BSGS 的直积)。
四、算法内核double_coset_can_rep:Butler-Portugal 双陪集方法
double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g)实现 Butler-Portugal 算法,是canonicalize消除自由指标后调用的核心例程。其参数比canonicalize更低层:
| 参数 | 含义 |
|---|---|
dummies | 哑指标列表的列表,每种指标类型一个子列表,顺序为[d0, -d0, d1, -d1, …] |
sym | 每种指标度规的对称性列表:0对称、1反对称、None无对称 |
b_S | 槽对称群(slot symmetry)极小 BSGS 的基 |
sgens | 槽对称 BSGS 的强生成元 |
S_transversals | 槽 BSGS 的 transversal(陪集横截集) |
g | 表示张量的排列 |
返回值:0表示张量为零;否则返回规范形排列的数组形式。
4.1 算法思想
docstring 给出了完整的数学推导,核心脉络如下:
- 问题等价于求
rep = min(D*g*S),即在双陪集中取按指标字典序([d1, -d1, d2, -d2, d3, -d3])最小的排列。 - 指标逐个固定:先为槽 0 选最小可用指标
p_0,再为槽 1 选剩余最小指标p_1,依次得到稳定子链S -> S_{b0} -> S_{b0,b1} -> …与D -> D_{p0} -> D_{p0,p1} -> …。 - 槽群
S的强基生成元只需用 Schreier-Sims 算法计算一次(稳定子群的强生成元即强基生成元的稳定子);而哑指标群D的基[p0, p1, …]一般不是字典序的,因此每步都要重新生成,好在dummy_sgs能直接构造、无需 Schreier-Sims。 - 算法维护一个 TAB 表,元素为三元组
(s_i, d_i, h_i),其中h_i = d_i*g*s_i,并满足前i个槽h_i[j] = p_j。每一步解方程d_{i+1}*g*s_{i+1}*b_i = p_i,通过_trace_S(在槽 transversal 中查找s[h[b]] == j的代表元)与_trace_D(在哑指标 transversal 中查找h[gj] == p_i的代表元)求解。 - 判零规则:迭代结束后对 TAB 中的
h排序;若存在两个相邻h仅末位(符号)不同,说明张量与自己相差负号,返回0;若完全相同则去重保留一个。 - 与原始算法的两点差异(docstring 明确说明):规范形取字典序极小;BSGS 取字典序极小基;TAB 中的相等
h会被消除。
4.2 完整示例(含手工推导)
考虑张量T^{d3 d2 d1}{}_{d1 d2 d3},槽对称性为T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}与T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5},度规对称。指标序为d1, -d1, d2, -d2, d3, -d3,则该张量对应g = [4, 2, 0, 1, 3, 5, 6, 7]:
sgens[0] = Permutation(0, 2)(6, 7)表示槽对称-(0, 2);sgens[1] = Permutation(0, 4)(6, 7)表示槽对称-(0, 4);- 哑指标群 D 由强生成元
[(0,1), (2,3), (4,5), (0,2)(1,3), (0,4)(1,5)]生成(前三者交换d1 <-> -d1等上下标,后两者换名)。
docstring 的完整可运行版本:
>>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals >>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]] >>> base = [0, 2] >>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7]) >>> transversals = get_transversals(base, gens) >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g) [0, 1, 2, 3, 4, 5, 7, 6]结果对应规范形-T^{d1 d2 d3}{}_{d1 d2 d3}(末两位[7, 6]表负号)。docstring 还给出了判零示例:T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}在两次槽对称与对称度规作用后得到与自身仅差符号的表达式,故结果为:
>>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7]) >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g) 0五、BSGS 构建与辅助函数
5.1get_symmetric_group_sgs(n, antisym=False):对称/反对称张量的极小 BSGS
对于秩为n的完全对称或完全反对称张量,该函数直接返回其字典序极小的 BSGS(base, gens):
>>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs >>> get_symmetric_group_sgs(3) ([0, 1], [(4)(0 1), (4)(1 2)]) >>> get_symmetric_group_sgs(3, 1) ([0, 1], [(0 1)(3 4), (1 2)(3 4)])antisym=False(默认):对称张量,生成元为相邻对换(i, i+1)且不改符号(末尾[n, n+1]);antisym=True:反对称张量,生成元为(i, i+1)且翻转符号(末尾[n+1, n])。
测试 test_tensor_can.py 覆盖了n = 2, 3, 4三种秩、对称与反对称共六种组合,例如get_symmetric_group_sgs(4, 1) == ([0,1,2], [Permutation(0,1)(4,5), Permutation(1,2)(4,5), Permutation(2,3)(4,5)])。它是构造canonicalize参数v的最常用工具。
5.2bsgs_direct_product(base1, gens1, base2, gens2, signed=True):BSGS 直积
把两个 BSGS 组合成一个(适合拼接不同类型张量的槽对称群):
>>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product) >>> base1, gens1 = get_symmetric_group_sgs(1) >>> base2, gens2 = get_symmetric_group_sgs(2) >>> bsgs_direct_product(base1, gens1, base2, gens2) ([1], [(4)(1 2)])底层由perm_af_direct_product完成生成元的块状拼接(signed标志控制是否保留末尾两位符号槽),并在tensor_gens、gens_products中反复被调用以逐层组装整个表达式的槽对称 BSGS。
5.3 其他配套函数
dummy_sgs(dummies, sym, n):返回哑指标群的强生成元,支持sym = None/0/1,在double_coset_can_rep中每轮都会重新调用;docstring 示例dummy_sgs(list(range(2, 8)), 0, 8)会给出 5 个生成元。get_transversals(base, gens):由 BSGS 计算 transversal,内部复用sympy.combinatorics.util的_distribute_gens_by_base与_orbits_transversals_from_bsgs。tensor_gens(base, gens, list_free_indices, sym=0):为n个同类型张量生成带固定自由指标的 BSGS;sym控制张量间的(反)交换。gens_products(*v):组合不同类型张量的槽对称 BSGS,是canonicalize组装槽对称群的直接调用点。canonical_free(base, gens, g, num_free):仅在自由指标上求字典序最小形式(只用槽对称)。docstring 给出了 Riemann 张量积的示例:T = R^{a}_{d0}^{d1,d2} * R_{d2,d1}^{d0,b}在riemann_bsgs下被规范化并重排。riemann_bsgs:模块级常量,([0, 2], [Permutation(0,1)(4,5), Permutation(2,3)(4,5), Permutation(5)(0,2)(1,3)]),即 Riemann 张量的槽对称 BSGS,被canonical_free示例和sympy.tensor直接引用。_is_minimal_bsgs/get_minimal_bsgs:检查与计算字典序极小 BSGS,前者在canonicalize入口被调用以决定是否回退canonicalize_naive。
六、在sympy.tensor中的集成:canon_bp
tensor_can并非孤立工具。在张量模块 tensor.py 中,第 44-45 行直接导入get_symmetric_group_sgs, bsgs_direct_product, canonicalize, riemann_bsgs;其 docstring 开篇即说明"Tensors are put in canonical form usingcanon_bp, which uses Butler-Portugal canonicalization"。
TensMul.canon_bp()(tensor.py)与TensExpr.canon_bp()(tensor.py)的实现路径是:先把表达式转化为(g, dummies, msym, v)四元组(见_IndexStructure.perm2tensor与模块级perm2tensor函数),再调用canonicalize(g, dummies, msym, *v)得到规范形排列,最后用perm2tensor恢复为规范形式的张量对象。模块级函数canon_bp(p)(tensor.py)则是对外的一站式入口。docstring 给出了交互示例:
>>> (GH(i1)*G(i0)).canon_bp()因此,组合学模块提供的canonicalize正是sympy.tensor中张量相等性判定(TensExpr.__eq__会先对两侧做canon_bp)、指标重排与自动化简的底层引擎。
七、测试覆盖与验证方式
模块测试位于 test_tensor_can.py(约 560 行),可运行pytest sympy/combinatorics/tests/test_tensor_can.py验证。测试按场景分层组织,与本文各节一一对应:
test_perm_af_direct_product、test_dummy_sgs:验证直积与哑指标强生成元的数组形式;test_get_symmetric_group_sgs:验证(反)对称 BSGS 的基与生成元;test_canonicalize_no_slot_sym:覆盖"固定自由指标后无剩余槽对称"的退化情形,包括无对称/对称/反对称张量、A*B单重收缩、三重指标等大量组合;test_canonicalize_no_dummies:覆盖无哑指标(纯槽对称重排)的情形,验证交换/反交换张量的符号处理;- 后续测试还包含对 Riemann BSGS、
canonicalize_naive对照等场景。
测试中大量用例把canonicalize的结果与手工推导的规范形(含末两位符号)逐一比对,是理解返回值编码的最佳参考素材。
八、边界条件与使用要点
- 指标顺序约束:哑指标必须连续且排在自由指标之后,顺序为逆变、协变成对出现;否则
canonicalize会抛出ValueError('dummies is not valid')。 msym取值:只能是0(对称/可交换)、1(反对称/反交换)、None(无对称);列表形式时长度必须与dummies的类型数一致。- BSGS 极小性:为获得正确且高效的结果,分量张量的 BSGS 应是字典序极小的;
canonicalize会自动检查并在必要时重算,实在失败才回退到canonicalize_naive(其速度显著更慢)。 - 符号编码:返回值最后两位编码整体符号——
[n, n+1]为正、[n+1, n]为负;返回0表示张量恒为零,这是canonicalize与double_coset_can_rep共同采用的约定。 - 适用前提:本模块处理的是"由排列对称性描述的张量",即具有完全(反)对称性或一般槽置换对称性的分量张量积;其复杂度随指标数呈指数级增长的问题空间正是该算法要压制的对象(docstring 明确说明:不带高效算法时,含大量哑指标的张量相等性判定会"computationally very slow")。
综上,tensor_can以排列群与双陪集为数学骨架,把"张量化简"这一物理与数学中的经典问题转化为可高效计算的组合学问题。无论是直接在组合学层面调用canonicalize,还是经由 sympy/tensor/tensor.py 的canon_bp使用,本模块都是 SymPy 张量规范化能力的事实标准实现,其完整契约以 doctest 形式固化在 tensor_can.py 中,可随时作为参考与回归基准。
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考