前の記事では、正規部分群による剰余群を実装しました。今回は、2つの群から直積群を構成します。
直積群は、群の要素を組として持ち、各成分を別々に演算する構成です。Group[G] を使うと、要素の型が異なる群も組み合わせられます。
コード例は Python 3.12 以降を前提にしています。
直積群の定義
群 $G$ と $H$ の直積群 $G \times H$ は、集合としては直積集合を持ち、演算を成分ごとに定義します。
(g_1, h_1)(g_2, h_2) = (g_1g_2, h_1h_2)
単位元と逆元も成分ごとに定まります。
e_{G \times H} = (e_G, e_H),\qquad
(g,h)^{-1} = (g^{-1},h^{-1})
直積群を実装する
from __future__ import annotations
from group import Group
from homomorphism import GroupHomomorphism
def product_group[G, H](
left: Group[G],
right: Group[H],
) -> Group[tuple[G, H]]:
return Group(
identity=(left.identity, right.identity),
inv=lambda value: (
left.inv(value[0]),
right.inv(value[1]),
),
op=lambda a, b: (
left.op(a[0], b[0]),
right.op(a[1], b[1]),
),
)
def product_homomorphism[K, G, H](
left: GroupHomomorphism[K, G],
right: GroupHomomorphism[K, H],
) -> GroupHomomorphism[K, tuple[G, H]]:
if left.source is not right.source:
raise ValueError("homomorphisms must have the same source")
return GroupHomomorphism(
f=lambda value: (left(value), right(value)),
source=left.source,
target=product_group(left.target, right.target),
)
product_group() は Group[G] と Group[H] を受け取り、Group[tuple[G, H]] を返します。直積の要素は単なるタプルですが、演算・単位元・逆元は新しい群構造としてまとめられています。
直積群の普遍性
直積群には、単に要素を組にする以上の性質があります。群 $K$ から $G$ と $H$ への群準同型
f : K \to G, \qquad g : K \to H
が与えられると、次の写像が直積群への群準同型になります。
\langle f, g \rangle : K \to G \times H,
\qquad k \mapsto (f(k), g(k))
さらに、直積群から各成分への射影
\pi_G : G \times H \to G, \qquad
\pi_H : G \times H \to H
との合成は、元の準同型に戻ります。
\pi_G \circ \langle f, g \rangle = f,
\qquad
\pi_H \circ \langle f, g \rangle = g
この性質によって $G \times H$ への準同型は特徴づけられます。つまり、同じ2つの合成を持つ準同型は $\langle f, g \rangle$ と一致します。これが直積群の普遍性です。
product_homomorphism() は、2本の群準同型からこの組み合わせ写像を構成します。
有限巡回群の直積
整数を法 $n$ で割った余りの加法群 $\mathbb{Z}/n\mathbb{Z}$を、直積群の具体例として使います。
from __future__ import annotations
from dataclasses import dataclass, InitVar
from group import Group
@dataclass(frozen=True, slots=True)
class Cyclic:
modulus: int
value: InitVar[int]
def __post_init__(self, value: int) -> None:
if self.modulus <= 0:
raise ValueError("modulus must be positive")
self.value = value % self.modulus
def _check(self, other: Cyclic) -> None:
if self.modulus != other.modulus:
raise ValueError("moduli must be equal")
def __add__(self, other: Cyclic) -> Cyclic:
self._check(other)
return type(self)(self.modulus, self.value + other.value)
def __neg__(self) -> Cyclic:
return type(self)(self.modulus, -self.value)
def cyclic_group(modulus: int) -> Group[Cyclic]:
return Group(
identity=Cyclic(modulus, 0),
inv=lambda value: -value,
op=lambda left, right: left + right,
)
$\mathbb{Z}/4\mathbb{Z}$ と $\mathbb{Z}/3\mathbb{Z}$ の直積を作ります。
from cyclic import cyclic_group
from direct_product import product_group
z4 = cyclic_group(4)
z3 = cyclic_group(3)
product = product_group(z4, z3)
zero = product.identity
element = (z4.identity, z3.identity)
assert element == zero
assert product.op(element, zero) == element
assert product.inv(element) == zero
直積の要素は (Cyclic(4, ...), Cyclic(3, ...)) のような組になります。左右の群は同じ要素型である必要がなく、各成分の群構造を別々に渡せます。
普遍性を具体例で観察する
$\mathbb{Z}/6\mathbb{Z}$ から $\mathbb{Z}/4\mathbb{Z}$ と $\mathbb{Z}/3\mathbb{Z}$ への準同型を用意し、直積群への組み合わせ写像を作ります。
from cyclic import Cyclic, cyclic_group
from direct_product import product_homomorphism
from homomorphism import GroupHomomorphism
z6 = cyclic_group(6)
z4 = cyclic_group(4)
z3 = cyclic_group(3)
to_z4 = GroupHomomorphism(
f=lambda value: Cyclic(4, 2 * value.value),
source=z6,
target=z4,
)
to_z3 = GroupHomomorphism(
f=lambda value: Cyclic(3, value.value),
source=z6,
target=z3,
)
pairing = product_homomorphism(to_z4, to_z3)
product = pairing.target
z6_elements = frozenset(Cyclic(6, value) for value in range(6))
for value in z6_elements:
assert pairing(value) == (to_z4(value), to_z3(value))
projection_z4 = GroupHomomorphism(
f=lambda value: value[0],
source=product,
target=z4,
)
projection_z3 = GroupHomomorphism(
f=lambda value: value[1],
source=product,
target=z3,
)
for value in z6_elements:
assert projection_z4(pairing(value)) == to_z4(value)
assert projection_z3(pairing(value)) == to_z3(value)
pairing.assert_preserves_identity()
for left in z6_elements:
for right in z6_elements:
pairing.assert_preserves_operation(left, right)
この例では、pairing の各成分が to_z4 と to_z3 に一致することを確認しています。したがって、直積群への写像を各射影との合成で特徴づける普遍性を、有限モデル上で観察できます。任意の群についての一意性をPythonの実行だけで証明しているわけではありません。
直積群の公理を検証する
直積群の公理は、各成分の公理から成り立ちます。有限群の具体例では、Hypothesisで要素の組を生成して確認できます。
from hypothesis import given, strategies as st
from cyclic import Cyclic, cyclic_group
from direct_product import product_group
z4 = cyclic_group(4)
z3 = cyclic_group(3)
product = product_group(z4, z3)
elements = st.tuples(
st.integers(min_value=0, max_value=3).map(lambda value: Cyclic(4, value)),
st.integers(min_value=0, max_value=2).map(lambda value: Cyclic(3, value)),
)
@given(elements, elements, elements)
def test_associativity(left, middle, right) -> None:
product.assert_associative(left, middle, right)
@given(elements)
def test_identity(value) -> None:
product.assert_left_identity(value)
product.assert_right_identity(value)
@given(elements)
def test_inverse(value) -> None:
product.assert_inverse(value)
直積群の実装では、左右の群の演算を組み合わせて新しい演算を作っています。Group の公理検証メソッドを再利用できるため、構成した群も同じテストの枠組みで確認できます。
まとめ
- 直積群の要素は2つの群の要素の組である
- 演算・単位元・逆元を成分ごとに定義できる
- 異なる要素型の群も直積にできる
- 直積群の公理を
Groupの検証メソッドとHypothesisで確認した - 2本の準同型から直積群への準同型を構成し、普遍性を観察した
次の記事では、生成元から自由群を構成し、その普遍性をPythonで観察します。