4

Frobenius 疑似素数アルゴリズムは、Miller-Rabin 素数性テストよりも実行に 3 倍の時間がかかりますが、解像度は 7 倍になると誰かが私に言いました。したがって、前者を 10 回実行し、後者を 30 回実行すると、両方の実行に同じ時間がかかりますが、前者は約 233% 高い分析能力を提供します。テストを実行する方法を見つけようとしているときに、最後にアルゴリズムが記載された次の論文が発見されました。

フロベニウスの擬素数検定の簡単な導出

以下のアルゴリズムを実装しようとしていますが、プログラムは数値を出力しません。数学表記法やアルゴリズムに詳しい人は、何が起こっているのかを確認してもらえますか?

編集 1:以下のコードには修正が追加されていますが、実装がcompute_wm_wm1ありません。誰かがアルゴリズムの観点から再帰的な定義を説明できますか? 私にとっては「クリック」ではありません。

編集 2:誤ったコードが削除され、compute_wm_wm1関数の実装が以下に追加されました。機能しているように見えますが、実用化するにはさらに最適化が必要になる場合があります。

from random import SystemRandom
from fractions import gcd
random = SystemRandom().randrange

def find_prime_number(bits, test):
    number = random((1 << bits - 1) + 1, 1 << bits, 2)
    while True:
        for _ in range(test):
            if not frobenius_pseudoprime(number):
                break
        else:
            return number
        number += 2

def frobenius_pseudoprime(integer):
    assert integer & 1 and integer >= 3
    a, b, d = choose_ab(integer)
    w1 = (a ** 2 * extended_gcd(b, integer)[0] - 2) % integer
    m = (integer - jacobi_symbol(d, integer)) >> 1
    wm, wm1 = compute_wm_wm1(w1, m, integer)
    if w1 * wm != 2 * wm1 % integer:
        return False
    b = pow(b, (integer - 1) >> 1, integer)
    return b * wm % integer == 2

def choose_ab(integer):
    a, b = random(1, integer), random(1, integer)
    d = a ** 2 - 4 * b
    while is_square(d) or gcd(2 * d * a * b, integer) != 1:
        a, b = random(1, integer), random(1, integer)
        d = a ** 2 - 4 * b
    return a, b, d

def is_square(integer):
    if integer < 0:
        return False
    if integer < 2:
        return True
    x = integer >> 1
    seen = set([x])
    while x * x != integer:
        x = (x + integer // x) >> 1
        if x in seen:
            return False
        seen.add(x)
    return True

def extended_gcd(n, d):
    x1, x2, y1, y2 = 0, 1, 1, 0
    while d:
        n, (q, d) = d, divmod(n, d)
        x1, x2, y1, y2 = x2 - q * x1, x1, y2 - q * y1, y1
    return x2, y2

def jacobi_symbol(n, d):
    j = 1
    while n:
        while not n & 1:
            n >>= 1
            if d & 7 in {3, 5}:
                j = -j
        n, d = d, n
        if n & 3 == 3 == d & 3:
            j = -j
        n %= d
    return j if d == 1 else 0

def compute_wm_wm1(w1, m, n):
    a, b = 2, w1
    for shift in range(m.bit_length() - 1, -1, -1):
        if m >> shift & 1:
            a, b = (a * b - w1) % n, (b * b - 2) % n
        else:
            a, b = (a * a - 2) % n, (a * b - w1) % n
    return a, b

print('Probably prime:\n', find_prime_number(300, 10))
4

1 に答える 1

14

表記に慣れていないため、アルゴリズムを完全に誤解しているようです。

def frobenius_pseudoprime(integer):
    assert integer & 1 and integer >= 3
    a, b, d = choose_ab(integer)
    w1 = (a ** 2 // b - 2) % integer

それはラインから来る

W 0 ≡ 2 (mod n) および W 1 ≡ a 2 b −1 − 2 (mod n)

しかし、ここでは b -1は意味しませんが、moduloの剰余逆、つまりの整数です。そのような を最も簡単に見つけるには、を分数展開するか、拡張ユークリッド アルゴリズム を使用します。あなたはおそらく連分数に慣れていないので、後者をお勧めします。1/bbncb·c ≡ 1 (mod n)cb/n

    m = (integer - d // integer) // 2

から来た

n − (Δ/n) = 2m

また、ヤコビ記号を分数/除算と誤解しています (確かに、ここではさらに分数のように表示していますが、このサイトは LaTeX レンダリングをサポートしていないため、対応する必要があります)。ヤコビ記号は、ルジャンドル記号を一般化したものであり、同じように示されます。これは、ある数が奇素数を法とする二次剰余であるかどうかを示します (nが を法とする二次剰余である場合p、つまり、kk^2 ≡ n (mod p)ありn、 の倍数でないp場合(n/p) = 1nの倍数でpある場合(n/p) = 0、それ以外の場合(n/p) = -1)。ヤコビ記号は、「分母」が奇素数であるという制限を解除し、「分母」として任意の奇数を許可します。nその値は、 (多重度に従って)割るすべての素数に対して同じ「分子」を持つルジャンドル記号の積です。詳細と、リンクされた記事で効率的にヤコビ記号を計算する方法について説明します。行は正しく読み取る必要があります

m = (integer - jacobi_symbol(d,integer)) // 2

次の行は、論理的に完全に理解できません。ここでは、再帰を使用したW mと W m+1の計算に従う必要があります。

W 2j ≡ W j 2 − 2 (mod n)

W 2j+1 ≡ W j W j+1 − W 1 (mod n)

その再帰を使用して必要な値を計算する効率的な方法は、PDF の式 (11) の周りに示されています。

    w_m0 = w1 * 2 // m % integer
    w_m1 = w1 * 2 // (m + 1) % integer
    w_m2 = (w_m0 * w_m1 - w1) % integer

関数の残りの部分はほぼ正しいですが、もちろん、以前の誤解のために間違ったデータが取得されるようになりました。

    if w1 * w_m0 != 2 * w_m2:

ここでの (不) 等式は、モジュロinteger、つまりでなければなりませんif (w1*w_m0 - 2*w_m2) % integer != 0

        return False
    b = pow(b, (integer - 1) // 2, integer)
    return b * w_m0 % integer == 2

ただし、 がn素数の場合、

b^((n-1)/2) ≡ (b/n) (mod n)

ここ(b/n)で、 はルジャンドル (またはヤコビ) 記号です (素数の「分母」の場合、ヤコビ記号ルジャンドル記号です)、したがってb^((n-1)/2) ≡ ±1 (mod n). したがって、W mが 2 または でない場合n-2nそれを追加のチェックとして使用できます。b^((n-1)/2) (mod n)n-1

おそらくb^((n-1)/2) (mod n)最初に計算し、それが 1 かどうかをチェックするのn-1が良い考えです。なぜなら、そのチェックが失敗した場合 (ちなみに、これはEuler 疑似素数検定です)、他の安価な計算はもう必要なく、成功した場合、とにかくそれを計算する必要がある可能性が非常に高いです。

修正に関しては、以前に見落としていたグリッチをさらに悪化させたものを除いて、正しいようです。

if w1 * wm != 2 * wm1 % integer:

これは、モジュラスを にのみ適用し2 * wm1ます。

W jの再帰については、最初にコピーと貼り付けを簡単にするために、機能する実装で説明するのが最善だと思います。

def compute_wm_wm1(w1,m,n):
    a, b = 2, w1
    bits = int(log(m,2)) - 2
    if bits < 0:
        bits = 0
    mask = 1 << bits
    while mask <= m:
        mask <<= 1
    mask >>= 1
    while mask > 0:
        if (mask & m) != 0:
            a, b = (a*b-w1)%n, (b*b-2)%n
        else:
            a, b = (a*a-2)%n, (a*b-w1)%n
        mask >>= 1
    return a, b

次に、説明を挟んで:

def compute_wm_wm1(w1,m,n):

W 1の値、目的の数値のインデックス、およびモジュラスを入力として取得する数値が必要です。値 W 0は常に 2 なので、パラメーターとしては必要ありません。

次のように呼び出します

wm, wm1 = compute_wm_wm1(w1,m,integer)

frobenius_pseudoprime(余談ですが、良い名前ではありません。返される数値のほとんどはTrue実素数です)。

    a, b = 2, w1

aとをそれぞれbW 0と W 1に初期化します。各点で、 W jの値とW j+1aの値を保持します。ここで、 はこれまでに消費された のビットの値です。たとえば、 、、の値は次のように展開します。bjmm = 13jab

consumed remaining  j    a    b
           1101     0   w_0  w_1
   1        101     1   w_1  w_2
   11        01     3   w_3  w_4
   110        1     6   w_6  w_7
   1101            13  w_13  w_14

ビットは左から右に消費されるため、最初に設定されたビットを見つけて、そのm直前に「ポインタ」を配置する必要があります

    bits = int(log(m,2)) - 2
    if bits < 0:
        bits = 0
    mask = 1 << bits

計算された対数からビットを差し引いたのは、浮動小数点エラーにだまされないようにするためです (ちなみに、使用logすると、最大で 1024 ビット、10 進数で約 308 桁の数値に制限されます。より大きな数を扱うにmは、別の方法で の底 2 の対数を見つける必要があります。使用するlogのが最も簡単な方法であり、これは単なる概念実証であるため、ここではそれを使用しました)。

    while mask <= m:
        mask <<= 1

より大きくなるまでマスクをシフトし、設定ビットが の最初の設定ビットのm直前を指すようにします。m次に、位置を 1 つ戻して、ビットをポイントします。

    mask >>= 1
    while mask > 0:
        if (mask & m) != 0:
            a, b = (a*b-w1)%n, (b*b-2)%n

次のビットが設定されている場合、 の消費されたビットの最初の部分の値はmからjに移動する2*j+1ため、必要な W シーケンスの次の値は、 の場合は W 2j+1、 の場合はaW 2j+2ですb。上記の再帰式により、

W_{2j+1} = W_j * W_{j+1} - W_1 (mod n)
W_{2j+2} = W_{j+1}^2 - 2 (mod n)

aは W jで、bW j+1だったので、 にaなり(a*b - W_1) % n、 にbなり(b * b - 2) % nます。

        else:
            a, b = (a*a-2)%n, (a*b-w1)%n

次のビットが設定されていない場合、 の消費されたビットの最初の部分の値はmからjになる2*jため、aW 2j = (W j 2 - 2) (mod n) となり、bW 2j+1 = (W j * W j+1 - W 1 ) (mod n)。

        mask >>= 1

ポインタを次のビットに移動します。最終ビットを過ぎると、maskが 0 になり、ループが終了します。の消費されたビットの最初の部分は のmすべてmのビットであるため、値はもちろんmです。それならできる

    return a, b

いくつかの追加のコメント:

def find_prime_number(bits, test):
    while True:
        number = random(3, 1 << bits, 2)
        for _ in range(test):
            if not frobenius_pseudoprime(number):
                break
        else:
            return number

素数は大きな数の中であまり頻繁ではないため、乱数を選択するだけで、1 つにヒットするまでに多くの試行が必要になる可能性があります。乱数を 1 つ選び、候補を順番に確認すると、素数 (または推定素数) をより早く見つけることができます。

もう 1 つのポイントは、Frobenius 検定のような検定は、たとえば 3 の倍数が複合であることを見つけるのに不釣り合いに費用がかかるということです。そのような検定 (または Miller-Rabin 検定、Lucas 検定、または Euler 検定など) を使用する前に、ほとんどの複合材料を取り除き、どこでのみ作業を行うために、少し試行分割を行う必要があります。それはそれだけの価値があるという戦いのチャンスがあります。

ああ、is_square関数は 2 未満の引数を処理する準備ができていません。ゼロ除算エラーがそこに潜んでいます。

def is_square(integer):
    if integer < 0:
        return False
    if integer < 2:
        return True
    x = integer // 2

助けるべきです。

于 2012-05-18T19:16:26.143 に答える