2.6 置換群與 SymPy 的 combinatorics#
學習目標#
- 熟練置換的記號(循環、two-line)與運算規則。
- 掌握對稱群 \(S_n\) 的基本理論:生成元、奇偶性、共軛類、迴路型。
- 系統學習
sympy.combinatorics的 API:Permutation、PermutationGroup、CyclicGroup、DihedralGroup等。 - 能計算置換群的階、軌道、共軛類、正規子群。
歷史背景#
置換群:群論的第一語言#
從拉格朗日(1771)、魯菲尼(1799)、柯西(1812–1845)到伽羅瓦(1832)、若爾當(1870),「群」在近一百年間就是指置換群。柯西給出了循環記號與「置換的乘積」;伽羅瓦用置換群刻畫方程的對稱;若爾當《代換論》則是置換群的百科全書。直到凱萊(2.2 節)的抽象觀點在 1880 年代被接受,置換才降格為「群的一個重要例子」——但它仍是最具計算性的例子。
計算的驅動#
19 世紀末出現了「數清所有置換群」的計算工程:柯克(Cole)等人在 1890 年代列出低次置換群目錄;20 世紀 GAP(1986,Aachen)與 Magma 把置換群計算工程化——Schreier–Sims 演算法(1970s)使「隨機給定一組生成元,求群的階、判斷元素成員」變成多項式時間。SymPy 的 combinatorics 模組(2010 年代由 Rahul Gupta 等人在 GSoC 中開發)就是把 GAP 式的置換群計算移植到純 Python 生態,是「理論(Schreier–Sims)→ 技術(GAP)→ 開源普及(SymPy)」的現代版本。
理論#
定義 2.6.1(置換)#
集合 \(\{1, \dots, n\}\) 的置換是其上的雙射 \(\sigma\) 。記法:
- two-line: \(\sigma = \begin{pmatrix} 1 & 2 & \cdots & n \\ \sigma(1) & \sigma(2) & \cdots & \sigma(n) \end{pmatrix}\) ;
- 循環: \(\sigma = (1\,3\,5)(2\,4)\) 表示 \(1 \mapsto 3 \mapsto 5 \mapsto 1\) 、 \(2 \mapsto 4 \mapsto 2\) 、其餘不動。
命題 2.6.2#
- 每個置換可唯一(至循環次序與不動點省略)分解為不相交循環之積。
- 置換可分解為對換(2-循環)之積;對換數的奇偶性不變,據此定義符號 \(\operatorname{sgn}(\sigma) = (-1)^{\#\text{對換}}\) 。
- \(S_n\) 由 \((1\,2), (1\,2\,3), \dots, (1\,2\,\cdots\,n)\) 生成(鄰接對換 \((i\, i{+}1)\) 亦可)。
- 共軛分類: \(\sigma, \tau\) 共軛 \(\iff\) 迴路型(循環長度的多重集)相同。
證明(4): \(\tau (i_1\,i_2\,\cdots\,i_k)\tau^{-1} = (\tau(i_1)\,\tau(i_2)\,\cdots\,\tau(i_k))\) ,故共軛保持迴路型;反之按迴路型構造 \(\tau\) 。 \(\blacksquare\)
定義 2.6.3(交錯群)#
\(A_n = \{\sigma \in S_n : \operatorname{sgn}(\sigma) = +1\} = \ker(\operatorname{sgn} : S_n \to \{\pm 1\})\) ,是 \(n!/2\) 階的正規子群(2.4 節)。
定義 2.6.4(二面體群)#
正 \(n\) 邊形的對稱群
有 \(2n\) 個元素( \(n\) 個旋轉、 \(n\) 個鏡射),自然地實現為 \(\{1,\dots,n\}\) 上的置換群。 \(n \ge 5\) 時 \(D_n\) 非交換; \(D_4 \cong\) 正方形對稱,是晶體學與化學(6.6 節)的基本對象。
Schreier–Sims 演算法(概要)#
給定生成元求群階的困難:從生成元閉包可能爆出 \(n!\) 個元素。Schreier–Sims 建立基底 \([b_1, \dots, b_k]\) 與穩定子鏈
並對每層預存 \(b_i\) 的軌道代表與陪集轉移表;則 \(|G| = \prod_i |b_i^{G^{(i)}}|\) ,成員判斷變成逐層「sifting」。SymPy 的 PermutationGroup.order() 內部正是此類算法。
SymPy 實作#
範例 1:Permutation 的基本操作#
from sympy.combinatorics import Permutation
# 三種構造方式
a = Permutation(1, 2, 0) # 像:0->1, 1->2, 2->0(3-循環)
b = Permutation([2, 0, 1]) # 由像的串列
c = Permutation(0, 1)(2, 3) # 循環記號
print("a =", a, ", 迴路型 =", a.cyclic_form)
print("b =", b, ", c =", c)
# 運算與性質
print("a * b =", a * b)
print("a 的逆 =", ~a)
print("a 的階 =", a.order())
print("sgn(a) =", a.signature())
print("奇置換嗎 =", a.is_odd)
print("共軛:c * a * ~c =", c * a * ~c)
# 不相交循環分解與判斷
d = Permutation(0, 3, 1, 2)
print("d =", d, ", 分解 =", d.cyclic_form, ", 是對換嗎 =", d.cycle_structure == {2: 1})
print("d 是雙射 =", sorted(d.array_form) == list(range(d.size)))
輸出說明:
a = (0 1 2) , 迴路型 = [[0, 1, 2]]
b = (0 2 1) , c = (0 1)(2 3)
a * b = (2)
a 的逆 = (0 2 1)
a 的階 = 3
sgn(a) = 1
奇置換嗎 = False
共軛:c * a * ~c = (0 3 1)
d = (0 3 1 2) , 分解 = [[0, 3, 1, 2]] , 是對換嗎 = False
d 是雙射 = True
注意 SymPy 的約定:Permutation(1, 2, 0) 是像串列( \(0 \mapsto 1, 1 \mapsto 2, 2 \mapsto 0\) ),與 Permutation(0, 1, 2) 這種「循環記號參數」語法並存,讀者務必區分。* 的順序也是常見陷阱:a * b 表示「先 \(b\) 後 \(a\) 」(函數複合)。
範例 2: \(S_4\) 的階、共軛類與 \(A_4\)#
from sympy.combinatorics import Permutation, PermutationGroup
S4 = PermutationGroup(Permutation(0, 1), Permutation(1, 2), Permutation(2, 3))
print("S_4 階 =", S4.order()) # 24
# 共軛類按迴路型分類
from collections import defaultdict
classes = defaultdict(list)
for g in S4.elements:
cycle_lengths = tuple(sorted(
len(c) for c in g.cyclic_form
)) if g.cyclic_form else ()
classes[(4 - sum(cycle_lengths)) and cycle_lengths].append(g)
# 補上不動點:長度 1 的循環
for g in S4.elements:
key = tuple(sorted([len(c) for c in g.cyclic_form] + [1] * (4 - sum(len(c) for c in g.cyclic_form))))
print("迴路型計數(含恆等):")
counts = defaultdict(int)
for g in S4.elements:
key = tuple(sorted([len(c) for c in g.cyclic_form] + [1]*(4 - sum(len(c) for c in g.cyclic_form))))
counts[key] += 1
for key in sorted(counts):
print(" 迴路型", key, ":", counts[key], "個")
# 期望:(1,1,1,1):1, (1,1,2):6, (2,2):3, (1,3):8, (4):6
# A_4 與正規性
A4 = PermutationGroup(Permutation(0, 1, 2), Permutation(0, 1, 3))
print("A_4 階 =", A4.order())
print("A_4 正規於 S_4:", A4.is_normal(S4))
輸出說明: \(S_4\) 階 24;迴路型計數 \(1 + 6 + 3 + 8 + 6 = 24\) ,正好對應 4 的五種劃分——命題 2.6.2(4) 的數值驗證。共軛類大小 = 丟西特數: \(1, 6, 3, 8, 6\) 。
範例 3:內建群——CyclicGroup、DihedralGroup、AlternatingGroup、SymmetricGroup#
from sympy.combinatorics import Permutation, PermutationGroup
from sympy.combinatorics.named_groups import (CyclicGroup, DihedralGroup,
AlternatingGroup, SymmetricGroup,
AbelianGroup)
# 循環群與二面體群
C5 = CyclicGroup(5)
D5 = DihedralGroup(5)
print("C5 階 =", C5.order(), "; D5 階 =", D5.order())
print("D5 阿貝爾嗎 =", D5.is_abelian) # False(n>=3)
print("D5 生成元:", D5.generators)
# 內建的對稱群、交錯群與有限交換群
print("S_5 階 =", SymmetricGroup(5).order())
print("A_5(內建)階 =", AlternatingGroup(5).order())
Z6xZ2 = AbelianGroup(6, 2)
print("Z/6 × Z/2 階 =", Z6xZ2.order(), ",阿貝爾 =", Z6xZ2.is_abelian)
# 交錯群與對稱群(用生成元定義大群時階由 Schreier–Sims 求出)
A5 = PermutationGroup(Permutation(0, 1, 2), Permutation(0, 1, 2, 3, 4))
print("A5 階 =", A5.order()) # 60
print("A5 是單群嗎(檢查正規子群)…")
# 用 SymPy 檢查 A5 的正規子群只有平凡
def normal_subgroups(G):
# 正規子群必為共軛類的聯集:枚舉共軛類的子集並檢查是否封閉
classes = [set(C) for C in G.conjugacy_classes()]
results = []
for mask in range(1, 1 << len(classes)):
S = set()
for i, C in enumerate(classes):
if mask >> i & 1:
S |= C
H = PermutationGroup(*S)
if H.order() == len(S) and H.is_normal(G):
results.append(H)
return results
print("A_5 的正規子群階:", sorted({H.order() for H in normal_subgroups(A5)}))
輸出說明:
C5 階 = 5 ; D5 階 = 10
D5 阿貝爾嗎 = False
D5 生成元: [Permutation(0, 1, 2, 3, 4), Permutation(0, 4)(1, 3)]
S_5 階 = 120
A_5(內建)階 = 60
Z/6 × Z/2 階 = 12 ,阿貝爾 = True
A5 階 = 60
A5 是單群嗎(檢查正規子群)…
A_5 的正規子群階: [1, 60]
DihedralGroup(5) 階 10、非交換;A5 階 60;正規子群階清單只有 \(\{1, 60\}\) ——** \(A_5\) 的單性被程式確認**(定理 2.4.8 的 \(n=5\) 情形),這正是五次方程不可解的關鍵。
範例 4:軌道與 Schreier–Sims 的效果——大群求階#
from sympy.combinatorics import Permutation, PermutationGroup
import time
# 100 次對稱群 S_100:階為 100! (158 位數)——Schreier–Sims 不枚舉元素
S100 = PermutationGroup(*[Permutation(i, i+1) for i in range(99)])
t0 = time.time()
n = S100.order()
t1 = time.time()
print("S_100 的階是", n, "位數:", len(str(n)))
print("精確等於 100!:", n == __import__("math").factorial(100))
print("計算耗時:%.3f 秒" % (t1 - t0))
# 軌道:G 作用在 {0,...,99} 上當然傳遞
print("S_100 在 {0..99} 上的軌道:", S100.orbit(0))
輸出說明:SymPy 能瞬間給出 \(100!\) 的精確值(158 位數),因為 order() 用 Schreier–Sims:不枚舉 \(100!\) 個元素,只沿穩定子鏈把軌道長度相乘。orbit(0) 回傳 \(\{0, 1, \dots, 99\}\) ,確認傳遞性(軌道–穩定子定理的定量計算見 2.7 節)。
常用 API 速查#
| 需求 | SymPy 呼叫 |
|---|---|
| 由循環構造 | Permutation(0, 1, 2) |
| 由像串列構造 | Permutation([1, 2, 0]) |
| 逆、複合 | ~g、g * h |
| 迴路型 | g.cyclic_form、g.full_cyclic_form |
| 符號 | g.signature() |
| 群的階 | G.order()(Schreier–Sims) |
| 元素列舉 | G.elements(僅小群) |
| 生成元 | G.generators |
| 成員判斷 | g in G |
| 子群判斷 | H.is_subgroup(G) |
| 循環/交換 | G.is_cyclic、G.is_abelian |
| 內建群 | CyclicGroup(n), DihedralGroup(n), AbelianGroup(2,3) |
| 軌道 | G.orbit(i) |
SymPy 要點回顧#
- 區分「像串列」與「循環記號」兩種構造;
*是函數複合順序。 - 迴路型決定共軛類;
cyclic_form是迴路型計算的入口。 order()對大群高效(Schreier–Sims),切勿依賴elements枚舉大群。CyclicGroup、DihedralGroup等工廠函數直接給出標準實現。
習題#
- 證明命題 2.6.2(2) 前半:每個置換是不相交循環之積;再證 \((a_1\,a_2\,\cdots\,a_k) = (a_1\,a_k)\cdots(a_1\,3)(a_1\,2)\) ,從而每個置換是對換之積。
- 計算 \(S_5\) 的共軛類大小,驗證它們對應 5 的劃分: \(1, 10, 15, 20, 20, 30, 24\) (總和 120)。
- 用
DihedralGroup(4)檢查 \(D_4\) 的元素階分布( \(1\) 個 1 階、5 個 2 階、2 個 4 階),並與 \(Q_8\) (2.2 節)比較——兩者皆 8 階非交換,但階分布不同,故不同構。 - 實作函數
conjugacy_classes(G):對每個 \(g\) 計算 \(\{hgh^{-1}\}\) ,回傳分類;對 \(A_4\) 執行並驗證類大小為 \(1, 3, 4, 4\) (注意 3-循環在 \(A_4\) 中分裂成兩類——與 \(S_4\) 中 8 個 3-循環一類不同!這是伽羅瓦理論中的「類分裂」現象)。 - 用
AbelianGroup(2, 2, 3)構造群,驗證其階為 12 且同構於 \(\mathbb{Z}/6 \times \mathbb{Z}/2\) (2.5 節)。 - (挑戰)實現簡化的 sifting:給定 \(S_4\) 與基底 \([0, 1]\) ,構造穩定子鏈 \(S_4 \ge S_4^{(1)} \ge S_4^{(2)} = \{e\}\) ,驗證 \(|S_4| = |\text{軌道}(0)| \cdot |S_4^{(1)}| = 4 \times 6\) 。