2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Haskell で試す群論

Last updated at Posted at 2022-07-22

概要

群論を少し勉強したので、Haskell でのコード化を試してみた。
高度なことはしていませんが、数学的な概念が関数設計に反映されています。

どちらかというと、数学的な構造をどうモデル化してコード化するのかといったアイディアの紹介です。

環境

  • GHCi
    version 8.0.2

定義

まずは、半群の定義から行います。

  • 結合律を満たす
    S の各元 a, b, c に対して (ab) • c = a • (bc) を満たす。

群の場合は、さらに次を満たします。

  • 単位元の存在
  • 逆元の存在

設計上のアイディア

次の点がポイントです。この条件を満たすのでリスト構造で数式を管理できます。

  • 結合律を満たす

結合律を満たさない場合、評価順序の違いがでるためツリー構造で表現する必要があります。

コード例

位数2: C2

C2と同型な XOR です。演算表は次のようになります。

0 1
0 0 1
0 1 0

コード例1: 独自に実装

c2_e :: Int
c2_e = 0

c2_op :: Int -> Int -> Int
-- c2_op a b = (a + b) `mod` 2
c2_op 0 0 = 0
c2_op 1 1 = 0
c2_op 0 1 = 1
c2_op 1 0 = 1
c2_op _ _ = error "ERROR invalid arguments."

c2_inv :: Int -> Int
c2_inv x = head [y | y <- [0, 1], c2_op x y == c2_e]

c2_eval :: [Int] -> [Int]
c2_eval [] = []
c2_eval [x] = [x]
c2_eval [x, y] = (c2_op x y):[]
c2_eval (x:y:zs) = c2_eval ((c2_eval [x, y]) ++ zs)

main :: IO ()
main = do
  putStrLn $ "0^(-1) = " ++ show (c2_inv 0)
  putStrLn $ "1^(-1) = " ++ show (c2_inv 1)
  putStrLn $ "1 ⊕ 1 ⊕ 1 = " ++ show (head (c2_eval [1, 1, 1]))
  putStrLn $ "1 ⊕ 1 ⊕ 1 ⊕ 1 = " ++ show (head (c2_eval [1, 1, 1, 1]))

実行結果

$ ghci group_o2_c2_using_int.hs
*Main> main
0^(-1) = 0
1^(-1) = 1
1  1  1 = 1
1  1  1  1 = 0

コード例2: Monoidを使用

data C2 = C2 Int

instance Eq C2 where
  (C2 x) == (C2 x') = x == x'

instance Show C2 where
  show a = case a of
    C2 0 -> "0"
    C2 1 -> "1"
    _    -> "?"

instance Monoid C2 where
  mempty = C2 0
  mappend (C2 x) (C2 y) = C2 ((x + y) `mod` 2)

c2_inv :: C2 -> C2
c2_inv (C2 x) = head [C2 y | y <- [0..2], (C2 x) `mappend` (C2 y) == (mempty)]

main :: IO ()
main = do
  putStrLn $ "0^(-1) = " ++ show (c2_inv (C2 0))
  putStrLn $ "1^(-1) = " ++ show (c2_inv (C2 1))
  putStrLn $ "1 ⊕ 1 ⊕ 1 = " ++ show (mconcat [C2 1, C2 1, C2 1])
  putStrLn $ "1 ⊕ 1 ⊕ 1 ⊕ 1 = " ++ show (mconcat [C2 1, C2 1, C2 1, C2 1])

位数4: K4

クラインの四元群(klein four-group)です。

K4は、C2×C2 の直積で表現することができます。(a, b) ∘ (c, d) = (a c, b d) のように計算できます。

e=(0,0) p=(0,1) q=(1,0) r=(1,1)
e=(0,0) e p q r
p=(0,1) p e r q
q=(1,0) q r e p
r=(1,1) r q p e

コード

k4_sym :: String -> (Int, Int)
k4_sym "e" = (0, 0)
k4_sym "p" = (0, 1)
k4_sym "q" = (1, 0)
k4_sym "r" = (1, 1)
k4_sym  _  = error "ERROR: Invalid argument."

showTuple :: (Int, Int) -> String
showTuple (0, 0) = "e"
showTuple (0, 1) = "p"
showTuple (1, 0) = "q"
showTuple (1, 1) = "r"
showTuple _ = error "ERROR: Invalid argument."

c2_op :: Int -> Int -> Int
c2_op x y = (x + y) `mod` 2

k4_op :: (Int, Int) -> (Int, Int) -> (Int, Int)
k4_op (x1, y1) (x2, y2) = (c2_op x1 x2, c2_op y1 y2)

k4_inv :: (Int, Int) -> (Int, Int)
k4_inv (x, y) = head [(p, q) | p <- [0..2], q <- [0..2], k4_op (x, y) (p, q) == k4_sym "e"]

k4_eval :: [(Int, Int)] -> [(Int, Int)]
k4_eval [] = []
k4_eval [x] = [x]
k4_eval [x, y] = (k4_op x y):[]
k4_eval (x:y:zs) = k4_eval ((k4_eval [x, y]) ++ zs)

main :: IO ()
main = do
  putStrLn $ "p^(-1) = " ++ showTuple (k4_inv (k4_sym "p"))
  putStrLn $ "r^(-1) = " ++ showTuple (k4_inv (k4_sym "r"))
  putStrLn $ "p q = " ++ showTuple (head (k4_eval [k4_sym "p", k4_sym "q"]))
  putStrLn $ "p q r = " ++ showTuple (head (k4_eval [k4_sym "p", k4_sym "q", k4_sym "r"]))

実行結果

$ ghci src/group_k4.hs
*Main> main
p^(-1) = p
r^(-1) = r
p q = r
p q r = e

位数6: D3

特徴

  • 最小の非可換群
  • 二面体群 D3 と置換群 S3 は同型
  • 直積では表現できない (半直積では表現できる)
演算表
e r r2 s rs r2s

e

e

r

r2

s

rs

r2s

r

r

r2

e

rs

r2s

s

r2

r2

e

r

r2s

s

rs

s

s

r2s

rs

e

r2

r

rs

rs

s

r2s

r

e

r2

r2s

r2s

rs

s

r2

r

e

上記の自明でない正規部分群は N={e, r, r2} となります。
部分群 H={e, s} に対して、 N ∩ H = { e } ですから、半直積の必要条件を満たしています。

半直積

次の形で表されるものです。直積の場合はϕ(x)(y) = er です。

(r1, s1) ∘ (r2, s2) = (r1 r2 ϕ(s1)(r2), s1 s2)

D3における半直積

正規部分群 N と部分群 H とで次のように順序列で表すことができます。
直積では表現できないので、例えば直積としての計算結果 (r1, s1) ∘ (r2, s0) = (r1+2, s1+0) = (r0, s1) ですが、 D3では (r1, s1) ∘ (r2, s0) =(r2, s1) ≠ (r0, s1) です。

D3 = NH の演算表

(r0, s0) (r1, s0) (r2, s0) (r0, s1) (r1, s1) (r2, s1)

(r0, s0)

(r0, s0)

(r1, s0)

(r2, s0)

(r0, s1)

(r1, s1)

(r2, s1)

(r1, s0)

(r1, s0)

(r2, s0)

(r0, s0)

(r1, s1)

(r2, s1)

(r0, s1)

(r2, s0)

(r2, s0)

(r0, s0)

(r1, s0)

(r2, s1)

(r0, s1)

(r1, s1)

(r0, s1)

(r0, s1)

(r2, s1)

(r1, s1)

(r0, s0)

(r2, s0)

(r0, s1)

(r1, s1)

(r1, s1)

(r0, s1)

(r2, s1)

(r1, s0)

(r0, s0)

(r2, s0)

(r2, s1)

(r2, s1)

(r1, s1)

(r0, s1)

(r2, s0)

(r1, s0)

(r0, s0)

上記 ϕ(x)(y) を探してみると、次のように表現できることが分かります。

\phi(s)(r^{k}) = 
\begin{cases}
  r^{0} & \text{if $(s = s^{0})$} \\
  r^{k} & \text{if $(s = s^{1})$}
\end{cases}

コード例

上記を踏まえるとコードは次のようになります。

GHC 8.4.4 を使用しています。GHC 8.4 から Monoid の親クラスが Semigroup となる非互換があり、それ以前の GHC では動かせないことに注意します。

-- An example of Semidirect products
data D3 = C3xdC2 Int Int

instance Eq D3 where
  (C3xdC2 x y) == (C3xdC2 x' y') = x == x' && y == y'

d3_sym :: String -> D3
d3_sym "e"   = C3xdC2 0 0
d3_sym "r"   = C3xdC2 1 0
d3_sym "rr"  = C3xdC2 2 0
d3_sym "s"   = C3xdC2 0 1
d3_sym "rs"  = C3xdC2 1 1
d3_sym "rrs" = C3xdC2 2 1
d3_sym  _  = error "ERROR: Invalid argument."

d3_all :: [D3]
d3_all = [d3_sym "e", d3_sym "r", d3_sym "rr", d3_sym "s", d3_sym "rs", d3_sym "rrs"]

instance Show D3 where
  show a = case a of
    C3xdC2 0 0 -> "e"
    C3xdC2 1 0 -> "r"
    C3xdC2 2 0 -> "rr"
    C3xdC2 0 1 -> "s"
    C3xdC2 1 1 -> "rs"
    C3xdC2 2 1 -> "rrs"
    C3xdC2 r s -> "(" ++ show r ++ "," ++ show s ++ ")"

phi :: Int -> Int -> Int
phi 0 _  = 0
phi 1 r' = r'
phi _ _  = error "ERROR: Invalid arguments."

instance Semigroup D3 where
  -- semidirect products: (r_x, s_x) <> (r_y, s_y) = (r_x * r_y * phi(s_x)(r_y), s_x * s_y)
  (C3xdC2 r s) <> (C3xdC2 r' s') = C3xdC2 ((r + r' + (phi s r')) `mod` 3) ((s + s') `mod` 2)

instance Monoid D3 where
  mempty = C3xdC2 0 0

d3_inv :: D3 -> D3
d3_inv (C3xdC2 r_x s_x) = head [C3xdC2 r_y s_y | r_y <- [0, 1, 2], s_y <- [0, 1], (C3xdC2 r_x s_x) <> (C3xdC2 r_y s_y) == (mempty)]

main :: IO ()
main = do
  print $ [(d3_sym "e")   <> y | y <- d3_all] == [  d3_sym "e",   d3_sym "r",  d3_sym "rr",   d3_sym "s",  d3_sym "rs", d3_sym "rrs"]
  print $ [(d3_sym "r")   <> y | y <- d3_all] == [  d3_sym "r",  d3_sym "rr",   d3_sym "e",  d3_sym "rs", d3_sym "rrs",   d3_sym "s"]
  print $ [(d3_sym "rr")  <> y | y <- d3_all] == [ d3_sym "rr",   d3_sym "e",   d3_sym "r", d3_sym "rrs",   d3_sym "s",  d3_sym "rs"]
  print $ [(d3_sym "s")   <> y | y <- d3_all] == [  d3_sym "s", d3_sym "rrs",  d3_sym "rs",   d3_sym "e",  d3_sym "rr",   d3_sym "r"]
  print $ [(d3_sym "rs")  <> y | y <- d3_all] == [ d3_sym "rs",   d3_sym "s", d3_sym "rrs",   d3_sym "r",   d3_sym "e",  d3_sym "rr"]
  print $ [(d3_sym "rrs") <> y | y <- d3_all] == [ d3_sym "rrs",  d3_sym "rs",  d3_sym "s",  d3_sym "rr",   d3_sym "r",   d3_sym "e"]
  print $ mconcat [d3_sym "s", d3_sym "r", d3_sym "s"] == d3_sym "rr"
  putStrLn $ "s * r * s = " ++ show (mconcat [d3_sym "s", d3_sym "r", d3_sym "s"]) -- == d3_sym "rr"
  print $ d3_inv (d3_sym "rrs") == d3_sym "rrs"
  putStrLn $ "(rrs)^(-1) = " ++ show (d3_inv (d3_sym "rrs")) -- == d3_sym "rrs"

実行例

非可換であることを確認。

*Main> d3_sym "s" <> d3_sym "r"
rrs
*Main> d3_sym "r" <> d3_sym "s"
rs

srs を計算する

*Main> mconcat [d3_sym "s", d3_sym "r", d3_sym "s"]
rr

逆元の計算

*Main> d3_inv (d3_sym "e")
e
*Main> d3_inv (d3_sym "r")
rr
*Main> d3_inv (d3_sym "rr")
r
*Main> d3_inv (d3_sym "s")
s
*Main> d3_inv (d3_sym "rs")
rs
*Main> d3_inv (d3_sym "rrs")
rrs

位数8: Q8

最後は Quaternion group の位数 8 の奴です。

特徴

  • 非可換群
  • 直積や半直積で表現できない群
    • 適当な正規部分群と部分で N ∩ H {e, s} ≠ { e } である
      重なりがあると直積または半直積の形に表すことができない

演算表:Q8

今回は sijk の4変数を組み合わせて各元を表現しています。

実は、2変数でも表現できるのですが、群の中心 {e, s} が割と分かりやすいので、今回は4変数で表現しています。

e s i si j sj k sk

e

e

s

i

si

j

sj

k

sk

s

s

e

si

i

sj

j

sk

k

i

i

si

s

e

k

sk

sj

j

si

si

i

e

s

sk

k

j

sj

j

j

sj

sk

k

s

e

i

si

sj

sj

j

k

sk

e

s

si

i

k

k

sk

j

sj

si

i

s

e

sk

sk

k

sj

j

i

si

e

s

コード1: 群の中心を考慮しない

演算表をそのまま書き起こすだけで済むので、群の構造についての情報を知らずともコード化できるので簡単です。

import Debug.Trace

q8_e :: String
q8_e = "e"

q8_all :: [String]
q8_all = [q8_e, "s", "i", "si", "j", "sj", "k", "sk"]

q8_dot :: String -> String -> String
q8_dot x y | x == q8_e = y
q8_dot x y | y == q8_e = x
q8_dot x y | x == "s" && y == "s" = q8_e
q8_dot x y | x == "i" && y == "i" = "s"
q8_dot x y | x == "j" && y == "j" = "s"
q8_dot x y | x == "k" && y == "k" = "s"

q8_dot x y | x == "s" && y == "i" = "si"
q8_dot x y | x == "s" && y == "si" = "i"
q8_dot x y | x == "s" && y == "j" = "sj"
q8_dot x y | x == "s" && y == "sj" = "j"
q8_dot x y | x == "s" && y == "k" = "sk"
q8_dot x y | x == "s" && y == "sk" = "k"

q8_dot x y | x == "i" && y == "s" = "si"
q8_dot x y | x == "i" && y == "si" = q8_e
q8_dot x y | x == "i" && y == "j" = "k"
q8_dot x y | x == "i" && y == "sj" = "sk"
q8_dot x y | x == "i" && y == "k" = "sj"
q8_dot x y | x == "i" && y == "sk" = "j"

q8_dot x y | x == "si" && y == "s" = "i"
q8_dot x y | x == "si" && y == "i" = q8_e
q8_dot x y | x == "si" && y == "si" = "s"
q8_dot x y | x == "si" && y == "j" = "sk"
q8_dot x y | x == "si" && y == "sj" = "k"
q8_dot x y | x == "si" && y == "k" = "j"
q8_dot x y | x == "si" && y == "sk" = "sj"

q8_dot x y | x == "j" && y == "s" = "sj"
q8_dot x y | x == "j" && y == "i" = "sk"
q8_dot x y | x == "j" && y == "si" = "k"
q8_dot x y | x == "j" && y == "sj" = q8_e
q8_dot x y | x == "j" && y == "k" = "i"
q8_dot x y | x == "j" && y == "sk" = "si"

q8_dot x y | x == "sj" && y == "s" = "j"
q8_dot x y | x == "sj" && y == "i" = "k"
q8_dot x y | x == "sj" && y == "si" = "sk"
q8_dot x y | x == "sj" && y == "j" = q8_e
q8_dot x y | x == "sj" && y == "sj" = "s"
q8_dot x y | x == "sj" && y == "k" = "si"
q8_dot x y | x == "sj" && y == "sk" = "i"

q8_dot x y | x == "k" && y == "s" = "sk"
q8_dot x y | x == "k" && y == "i" = "j"
q8_dot x y | x == "k" && y == "si" = "sj"
q8_dot x y | x == "k" && y == "j" = "si"
q8_dot x y | x == "k" && y == "sj" = "i"
q8_dot x y | x == "k" && y == "sk" = q8_e

q8_dot x y | x == "sk" && y == "s" = "k"
q8_dot x y | x == "sk" && y == "i" = "sj"
q8_dot x y | x == "sk" && y == "si" = "j"
q8_dot x y | x == "sk" && y == "j" = "i"
q8_dot x y | x == "sk" && y == "sj" = "si"
q8_dot x y | x == "sk" && y == "k" = q8_e
q8_dot x y | x == "sk" && y == "sk" = "s"

q8_dot x y = trace ("DEBUG: x=" ++ show x ++ ", y=" ++ show y) "?"

q8 :: [String] -> [String]
q8 [] = []
q8 [x] = [x]
q8 (x:xs) = q8 ([q8_dot x (head xs)] ++ (tail xs))

q8_inv :: String -> String
q8_inv x = head [x | y <- [q8_e, "s", "i", "si", "j", "sj", "k", "sk"], q8_dot x y == q8_e]

main :: IO ()
main = do
  print $ [q8_dot "e"  y | y <- q8_all] == [ "e", "s", "i","si", "j","sj", "k","sk"]
  print $ [q8_dot "s"  y | y <- q8_all] == [ "s", "e","si", "i","sj", "j","sk", "k"]
  print $ [q8_dot "i"  y | y <- q8_all] == [ "i","si", "s", "e", "k","sk","sj", "j"]
  print $ [q8_dot "si" y | y <- q8_all] == ["si", "i", "e", "s","sk", "k", "j","sj"]
  print $ [q8_dot "j"  y | y <- q8_all] == [ "j","sj","sk", "k", "s", "e", "i","si"]
  print $ [q8_dot "sj" y | y <- q8_all] == ["sj", "j", "k","sk", "e", "s","si", "i"]
  print $ [q8_dot "k"  y | y <- q8_all] == [ "k","sk", "j","sj","si", "i", "s", "e"]
  print $ [q8_dot "sk" y | y <- q8_all] == ["sk", "k","sj", "j", "i","si", "e", "s"]
  putStrLn $ "si * si = " ++ head (q8 ["si", "si"])
  putStrLn $ " i * sk = " ++ head (q8 ["i", "sj"])
  putStrLn $ "si * si  = " ++ head (q8 ["si", "si"])
  putStrLn $ "si * i * j * k = " ++ head (q8 ["si", "i", "j", "k"])
  putStrLn $ "(si)^(-1) = " ++ q8_inv "si"

実行結果

$ ghci group_o8_q8_nc.hs
*Main> main
True
True
True
True
True
True
True
True
si * si = s
 i * sk = sk
si * si  = s
si * i * j * k = i
(si)^(-1) = si

コード2: 群の中心を考慮する

s が群の中心であることを踏まえるともう少しガード条件を減らせます。

s はすべての元について可換なので、次のように si は順序を変えて計算できます(i jj i であることに注意)。

$$
(s \circ i) \circ (s \circ j) = s \circ (i \circ s) \circ j = s \circ (s \circ i) \circ j = (s \circ s) \circ (i \circ j) = e \circ k = k
$$

s s = e であるから、一か所にまとめると対消滅できます。

今回は si, sj, sk を群を構成する要素として扱っているが、sisj = k で見られるように、他の元と可換であることを踏まえて sxsy = (ss) ∘ (xy) とできる。

このように、s が中心群の要素であることを考慮し、 s を先頭に移動させて偶数個の s を対消滅させる。

import Debug.Trace

q8_e :: String
q8_e = "e"

q8_all :: [String]
q8_all = [q8_e, "s", "i", "si", "j", "sj", "k", "sk"]

q8_dot :: String -> String -> String
q8_dot x y | x == q8_e = y
q8_dot x y | y == q8_e = x
q8_dot x y | x == "s" && y == "s" = q8_e
q8_dot x y | x == "i" && y == "i" = "s"
q8_dot x y | x == "j" && y == "j" = "s"
q8_dot x y | x == "k" && y == "k" = "s"

q8_dot x y | x == "s" && y == "i" = "si"
q8_dot x y | x == "s" && y == "j" = "sj"
q8_dot x y | x == "s" && y == "k" = "sk"

q8_dot x y | x == "i" && y == "j" = "k"
q8_dot x y | x == "i" && y == "k" = "sj"

q8_dot x y | x == "j" && y == "i" = "sk"
q8_dot x y | x == "j" && y == "k" = "i"

q8_dot x y | x == "k" && y == "i" = "j"
q8_dot x y | x == "k" && y == "j" = "si"

-- `s` is element of the center of a group Q8
q8_dot x s       | x /= "s" && s == "s" = q8_dot s x
q8_dot x (s:ys)  | x /= "s" && s == 's' = q8_dot [s] (q8_dot x ys)
q8_dot s (s2:ys) | s == "s" && s2 == 's' = ys
q8_dot (s:xs) y  | s == 's' = q8_dot [s] (q8_dot xs y)

q8_dot x y = trace ("DEBUG: x=" ++ show x ++ ", y=" ++ show y) "?"

q8 :: [String] -> [String]
q8 [] = []
q8 [x] = [x]
q8 (x:xs) = q8 ([q8_dot x (head xs)] ++ (tail xs))

q8_inv :: String -> String
q8_inv x = head [x | y <- q8_all, q8_dot x y == q8_e]

main :: IO ()
main = do
  print $ [q8_dot "e"  y | y <- q8_all] == [ "e", "s", "i","si", "j","sj", "k","sk"]
  print $ [q8_dot "s"  y | y <- q8_all] == [ "s", "e","si", "i","sj", "j","sk", "k"]
  print $ [q8_dot "i"  y | y <- q8_all] == [ "i","si", "s", "e", "k","sk","sj", "j"]
  print $ [q8_dot "si" y | y <- q8_all] == ["si", "i", "e", "s","sk", "k", "j","sj"]
  print $ [q8_dot "j"  y | y <- q8_all] == [ "j","sj","sk", "k", "s", "e", "i","si"]
  print $ [q8_dot "sj" y | y <- q8_all] == ["sj", "j", "k","sk", "e", "s","si", "i"]
  print $ [q8_dot "k"  y | y <- q8_all] == [ "k","sk", "j","sj","si", "i", "s", "e"]
  print $ [q8_dot "sk" y | y <- q8_all] == ["sk", "k","sj", "j", "i","si", "e", "s"]
  putStrLn $ "si * si = " ++ head (q8 ["si", "si"])
  putStrLn $ " i * sk = " ++ head (q8 ["i", "sj"])
  putStrLn $ "si * si  = " ++ head (q8 ["si", "si"])
  putStrLn $ "si * i * j * k = " ++ head (q8 ["si", "i", "j", "k"])
  putStrLn $ "(si)^(-1) = " ++ q8_inv "si"

実行結果

$ group_o8_q8_c.hs
*Main> main
True
True
True
True
True
True
True
True
si * si = s
 i * sk = sk
si * si  = s
si * i * j * k = i
(si)^(-1) = si
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?