跳轉到

2.6 置換群與 SymPy 的 combinatorics#

學習目標#

  1. 熟練置換的記號(循環、two-line)與運算規則。
  2. 掌握對稱群 \(S_n\) 的基本理論:生成元、奇偶性、共軛類、迴路型。
  3. 系統學習 sympy.combinatorics 的 API:Permutation、PermutationGroup、CyclicGroup、DihedralGroup 等。
  4. 能計算置換群的階、軌道、共軛類、正規子群。

歷史背景#

置換群:群論的第一語言#

從拉格朗日(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#

  1. 每個置換可唯一(至循環次序與不動點省略)分解為不相交循環之積。
  2. 置換可分解為對換(2-循環)之積;對換數的奇偶性不變,據此定義符號 \(\operatorname{sgn}(\sigma) = (-1)^{\#\text{對換}}\) 。
  3. \(S_n\) 由 \((1\,2), (1\,2\,3), \dots, (1\,2\,\cdots\,n)\) 生成(鄰接對換 \((i\, i{+}1)\) 亦可)。
  4. 共軛分類: \(\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\) 邊形的對稱群

\[ D_n = \langle r, s : r^n = s^2 = e,\ srs = r^{-1} \rangle \]

有 \(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]\) 與穩定子鏈

\[ G = G^{(0)} \ge G^{(1)} \ge \cdots \ge G^{(k)} = \{e\}, \qquad G^{(i)} = \{g : g \text{ 固定 } b_1, \dots, b_i\}, \]

並對每層預存 \(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 等工廠函數直接給出標準實現。

習題#

  1. 證明命題 2.6.2(2) 前半:每個置換是不相交循環之積;再證 \((a_1\,a_2\,\cdots\,a_k) = (a_1\,a_k)\cdots(a_1\,3)(a_1\,2)\) ,從而每個置換是對換之積。
  2. 計算 \(S_5\) 的共軛類大小,驗證它們對應 5 的劃分: \(1, 10, 15, 20, 20, 30, 24\) (總和 120)。
  3. 用 DihedralGroup(4) 檢查 \(D_4\) 的元素階分布( \(1\) 個 1 階、5 個 2 階、2 個 4 階),並與 \(Q_8\) (2.2 節)比較——兩者皆 8 階非交換,但階分布不同,故不同構。
  4. 實作函數 conjugacy_classes(G):對每個 \(g\) 計算 \(\{hgh^{-1}\}\) ,回傳分類;對 \(A_4\) 執行並驗證類大小為 \(1, 3, 4, 4\) (注意 3-循環在 \(A_4\) 中分裂成兩類——與 \(S_4\) 中 8 個 3-循環一類不同!這是伽羅瓦理論中的「類分裂」現象)。
  5. 用 AbelianGroup(2, 2, 3) 構造群,驗證其階為 12 且同構於 \(\mathbb{Z}/6 \times \mathbb{Z}/2\) (2.5 節)。
  6. (挑戰)實現簡化的 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\) 。

前後節連結#