前の記事では、群準同型を GroupHomomorphism[G, H] として表しました。今回は、群の中にある部分構造を扱います。
数学的には、部分群 $H \leq G$ は、台集合 $H$ とその群構造、および $G$ への包含写像の組として捉えられます。Pythonでもこの対応を保つため、要素集合だけでなく、入力側・出力側の群と包含写像を明示します。
コード例は Python 3.12 以降を前提にしています。
部分群の定義
群 $G$ の部分群 $H$ は、包含写像 $i : H \to G$ が群準同型となるような群です。有限モデルでは、検証対象の要素を別途列挙して確認します。
from __future__ import annotations
from collections.abc import Iterable
from dataclasses import dataclass
from group import Group
from homomorphism import InjectiveGroupHomomorphism
@dataclass(frozen=True, slots=True)
class Subgroup[G, H]:
inclusion: InjectiveGroupHomomorphism[H, G]
def assert_inclusion(self, samples: Iterable[H]) -> None:
samples = tuple(samples)
assert self.inclusion.source.identity in samples
self.inclusion.assert_injective(samples)
self.inclusion.assert_preserves_identity()
for element in samples:
self.inclusion.assert_preserves_inverse(element)
for left in samples:
for right in samples:
self.inclusion.assert_preserves_operation(left, right)
def assert_normal(
self,
ambient_samples: Iterable[G],
subgroup_samples: Iterable[H],
) -> None:
subgroup_samples = tuple(subgroup_samples)
self.assert_inclusion(subgroup_samples)
image = {self.inclusion(element) for element in subgroup_samples}
assert all(
self.inclusion.target.op(
self.inclusion.target.op(ambient_element, self.inclusion(element)),
self.inclusion.target.inv(ambient_element),
)
in image
for ambient_element in ambient_samples
for element in subgroup_samples
)
Subgroup[G, H] は、H が G のPythonサブクラスであることを要求していません。包含写像を明示することで、異なるPython型で表された台集合の間にも部分群関係を表現できます。包含写像の型を InjectiveGroupHomomorphism[H, G] とすることで、部分群の包含に必要な単射性も構造に反映しています。有限集合をフィールドに持たせていないため、無限群にも同じ構造を使えます。
assert_inclusion() と assert_normal() は、有限モデルの全要素、または検証対象のサンプルを引数として受け取ります。渡された要素について、包含写像が群準同型であることや正規性を確認します。
部分群の具体例
置換群 $S_3$ の偶置換全体 $A_3$ を考えます。A3Element を用意し、包含写像で Permutation へ埋め込みます。
from dataclasses import dataclass
from group import Group
from homomorphism import InjectiveGroupHomomorphism
from permutation import Permutation, symmetric_group
from subgroup import Subgroup
S3 = frozenset(
Permutation(values)
for values in (
(0, 1, 2),
(0, 2, 1),
(1, 0, 2),
(1, 2, 0),
(2, 0, 1),
(2, 1, 0),
)
)
def sign(permutation: Permutation) -> int:
inversions = sum(
permutation.values[i] > permutation.values[j]
for i in range(len(permutation.values))
for j in range(i + 1, len(permutation.values))
)
return -1 if inversions % 2 else 1
S3_group = symmetric_group(3)
@dataclass(frozen=True, slots=True)
class A3Element:
value: Permutation
A3_group = Group[A3Element](
identity=A3Element(S3_group.identity),
inv=lambda element: A3Element(S3_group.inv(element.value)),
op=lambda left, right: A3Element(
S3_group.op(left.value, right.value)
),
)
A3_elements = frozenset(
A3Element(permutation)
for permutation in S3
if sign(permutation) == 1
)
A3 = Subgroup(
inclusion=InjectiveGroupHomomorphism(
f=lambda element: element.value,
source=A3_group,
target=S3_group,
),
)
A3.assert_inclusion(A3_elements)
assert len(A3_elements) == 3
A3_group は A3Element 上の群構造であり、inclusion が A3Element を Permutation へ埋め込みます。有限モデルの要素は A3_elements として、検証時に渡しています。Subgroup の対象群と部分群は、包含写像の target と source から取得できます。
正規部分群
部分群 $N \leq G$ が正規部分群であるとは、任意の $g \in G$ と $n \in N$ に対して、共役元 $gng^{-1}$ が再び $N$ に含まれることです。
gng^{-1} \in N
A3 は S3 の正規部分群なので、次のように確認できます。
A3.assert_normal(S3, A3_elements)
assert_normal() は、有限モデルとして与えた ambient_samples 全体について共役を計算します。無限群の正規性を、この方法で一般に証明するものではありません。
交換子部分群
群の交換子を、ここでは次の規約で定義します。
[a,b] = a^{-1}b^{-1}ab
交換子部分群 $[G,G]$ は、すべての交換子で生成される部分群です。
from collections.abc import Iterable
from group import Group
def commutator[G](group: Group[G], a: G, b: G) -> G:
return group.op(
group.op(group.inv(a), group.inv(b)),
group.op(a, b),
)
def generated_subgroup[G](
group: Group[G],
ambient_elements: frozenset[G],
generators: Iterable[G],
) -> frozenset[G]:
elements = {group.identity, *generators}
while True:
expanded = elements | {group.inv(element) for element in elements}
expanded |= {
group.op(left, right)
for left in elements
for right in elements
}
if expanded == elements:
return frozenset(elements)
if not expanded <= ambient_elements:
raise ValueError("the finite carrier is not closed")
elements = expanded
def commutator_subgroup[G](
group: Group[G],
ambient_elements: frozenset[G],
) -> frozenset[G]:
commutators = {
commutator(group, left, right)
for left in ambient_elements
for right in ambient_elements
}
return generated_subgroup(group, ambient_elements, commutators)
generated_subgroup() は、有限群で閉包が安定するまで元を追加します。無限群では停止するとは限らないため、有限モデルを対象とした実装です。
S_3 の交換子部分群を計算する
from subgroup_example import A3_elements, S3, S3_group
from commutator_subgroup import commutator_subgroup
derived_elements = commutator_subgroup(S3_group, S3)
assert derived_elements == {
element.value for element in A3_elements
}
この計算から、具体的な有限群 $S_3$ では
[S_3,S_3] = A_3
となることを確認できます。交換子部分群を含む剰余群 $G/[G,G]$ は可換群になります。この構成は、次の記事で扱う剰余群と第一同型定理につながります。
まとめ
- 部分群を台集合・群構造・包含写像の組として表した
- 包含写像が群準同型であることを具体例について検証した
- 正規性を共役によって判定した
- $S_3$ の交換子部分群が $A_3$ になることを観察した
次の記事では、正規部分群による剰余群を実装し、第一同型定理を具体例で確認します。