最小原始根の計算
今回は,整数に関する話です。最小原始根と呼ばれるものを考えます。
最小原始根
定義
説明の都合上,群論の基本的な用語である「位数」を定義します。すなわち,元の位数とは「その回数だけ累乗して初めて1になるもの」と考えることができます。ここでは証明しませんが,有限群の元の位数はその群の要素の数の約数になります*2。また,これもここでは証明はしませんが,次の命題も成立します。
例: について,の値を表にまとめると下の表のようになる。したがって,この表から
\begin{align}
(\mathbb{Z}/5 \mathbb{Z})^{\times} = \{ 2^{n} \,|\,n=1,\dots,4\} = \{ 3^{n} \,|\,n=1,\dots,4\}
\end{align}であることが分かる。
\ | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | 1 | 1 | 1 | 1 |
2 | 2 | 4 | 3 | 1 |
3 | 3 | 4 | 2 | 1 |
4 | 4 | 1 | 4 | 1 |
と書きましたが,難しければ単にで割ったときの余りの話をしていると思えばよいでしょう。今回はややふんわりした言葉で定義しましたが,群論の言葉を使ってしっかりと述べれば,原始根とは「乗法群の生成元」ということです。言い換えればこれは,乗しないと1にならない数であるということです*4。以下ではをと書いてしまい,その元も整数と同じ記号で書いてしまうことにします*5。
原始根の求め方
上の例で述べたように乗法表を全て埋めなければ原始根は求まらないのでしょうか。答えはNoです。例えば,がより小さい数でであるとき,と表される数はみなを満たします。このことから,が原始根でなければ,の累乗の形の元も原始根ではありません。実は次の命題が成り立ちます。
証明
: はを満たす整数である。がを法とする原始根である,つまり,乗しないと1にならないことから,である。
: 対偶を示すことにする。が原始根でないとする。の位数はの約数なので,素因数分解すると,()と書くことができる。とし,,とおく。ならばでありは原始根となるので矛盾。よって,あるが存在して,である。いま,より,であることなどに気をつけると,は整数である。また,
\begin{align}
\operatorname{ord}(a) \cdot \ell = \left( \prod_{j \in A} p_{j}^{e_{j}^{\prime}} \right) \left( \prod_{j \in B} p_{j}^{e_{j}^{\prime}} \right) p_{i}^{e_{i} - e_{i}^{\prime} - 1} \left( \prod_{j \in A \setminus \{i\}} p_{j}^{e_{j} - e_{j}^{\prime}} \right) = \frac{p-1}{p_{i}}.
\end{align}これより,
\begin{align}
1 \equiv (a^{\operatorname{ord}(a)})^{\ell} \equiv a^{\frac{p-1}{p_{i}}} \pmod p.
\end{align}証明終
この命題を用いると,最小原始根を計算するアルゴリズムが導かれます。
最小原始根を計算するアルゴリズム
出力: の最小原始根
Step 1: の素因数を求める。
Step 2: とする。
Step 3: に対してかどうかを調べる。もしそのようながあればとしてStep 3をもう一度行う。
Step 4: を最小原始根とする。
これを実際にPythonのコードで書いてみたものが下のコードです。今回は素因数分解をするためにSympyを使っています。試し割りをすれば手製のものも作れるのでしょうが,手間を省きました*6。
import numpy as np import sympy as sp def mod_pow(a,b,mod): # a^{b} b = int(b) x = 1 for times in range(0,b): x = x * a % mod return x # 計算のために,乱数を使って適当な素数pを選ぶ p = sp.prime(int(np.random.rand()*100+1)) # p-1 の素因数を求める prime_factor_list = sp.primefactors(p-1) # ありがとうSympy k = len(prime_factor_list) # ここからが上のアルゴリズムのStep 3にあたる a = 2 i = 0 while (i!=k): if (mod_pow(a, (p-1)//prime_factor_list[i], p) == 1): a += 1 i = 0 else: i += 1 print(a) # a がZ/pZの最小原始根
このコードを用いて実際に最小原始根を計算すると,次の表のようになりました。この表では,のの値と対応する最小原始根の値を並べました。は2から15番目の素数までについて計算しています*7。
3 | 5 | 7 | 11 | 13 | 17 | 19 | 23 | 29 | 31 | 37 | 41 | 43 | 47 | |
2 | 2 | 3 | 2 | 2 | 3 | 2 | 5 | 2 | 3 | 2 | 6 | 3 | 5 |
参考文献
『工学基礎 代数系とその応用』(著 平林隆一,数理工学社)p.96*2:こののことを群の位数と呼びます。ややこしい……。
*3:このようにある群が一つの元の累乗という形で尽くされるとき,その群は巡回群 (cyclic group) であるといいます。
*4:群の元によって生成されるの部分群をと書くと,このの位数は元の位数と等しくなります。ただし,共に有限でない場合は濃度の違いを無視して単に無限だから等しいということにします。
*5:同型になることから後者の集合と同一視することにします。
*6:なんなら原始根を求める関数もSympyにはあるとかないとか……。この実装の意味とはいかに。
*7:この計算を行うときは,の値を乱数を使うのではなくそのままの値を代入しました。