候補の選択(篩)

ysd@KLab > 因数分解 > GNFS > 篩

探索範囲

まずは探索する範囲 (a,b) を決めます。探索量は条件(後述)を満たす候補がFactor Baseの数を超えればいいので、実際には自由に探索してもらって構いません。

一般的には a を [-u, u] の範囲で、 b を [1, v] の範囲で探索するようになってます。これは条件を満たす候補が多いというわけではなく、(コンピュータでの計算として)探索を速く行うことができるという理由によるものだと思います。a が連続(数学的には整数なので離散的ですが)しているので、下の擬似コードのようにループにせず配列に何か記録していけば高速アクセスできるわけです。

for( b = 1 ; (条件をパスした候補の数) < (FBの総数) ; b++ ) {
  for( a = -u ; a ≤ u ; a++ ) { // アルゴリズムの説明のためにループ
    if( check(a, b) ) record(a, b);
  }
}
check(a, b) の中では 有理数のFB(RFB)、代数的数のFB(AFB)、平方剰余について smooth かどうかチェック(詳細は後述)してます。以下は似非実装向き版。
[実装向き ; 長くなるので指数の記録等省略]
for( b = 1 ; (条件をパスした候補の数) < (FBの総数) ; b++ ) {
  double flag[-a:a] = 1.0; // -aからaまでアクセスできる配列
  for( i = 0 ; i < (RFBの数) ; i++ ) {
    j = (b * m - a) % p[i]; // %の結果は正(か0)になるとする
    for( j = -a + j ; j ≤ a ; j = j + p[i] ) flag[j] = flag[j] * p[i];
  }
  for( i = -a ; i ≤ a ; i++ ) {
    if( b * m + i < flag[i] * p[最大] ) // 指数が2以上になるものを捕獲する意味もこめて
      flag[i] = 1.0;
    else
      flag[i] = 0.0;
  }
  for( i = 0 ; i < (AFBの数) ; i++ ) {
    j = (b * q[i].s - a) % q[i].q; // q[i]はi番目のイデアル
    for( j = -a + j ; j ≤ a ; j = j + q[i].q ) flag[j] = flag[j] * q[i].q;
  }
  // ここでまず候補を落とす
  for( j = -a ; j ≤ a ; j++ ) {
    if( flag[j] < 1.01 ) { // ここもちょっと誤差を許してる
      flag[j] = 0.0;
      continue;
    }
    flag[j] = norm( j, b );
  }
  for( i = 0 ; i < (AFBの数) ; i++ ) {
    if( q[i].q == q[i - 1].q ) continue;
    for( j = -a ; j ≤ a ; j++ ) {
      if( flag[j] == 0.0 ) continue; // 既に落ちてるものは計算しない
      for( k = 0 ; flag[j] % q[i].q == 0.0 ; k++ ) // double型だけど % 演算許して
        flag[j] = flag[j] / q[i].q;
    }
  }
}

選択原理

RFB-smooth

a+bm が RFB で smooth な数を選び出す。記録としては、(a,b) とRFBの各要素についての指数が必要。具体的に m=31 の式を使っていて、RFB={2,3,5,7,11,13,17}、(a,b)=(17,3) だとすると
17+3・31 = 110 = 21 30 51 70 111 130 170
これを記録するために
17 3 1010100
を使う。

a+bθ は α の何乗で割れるのか

α←→Q(s,q) という対応が取れていれば、
「αn | a+bθ」 ⇔ 「q | a+bs ∧ qn | N(a+bθ)」
が成り立つので、左の式で n の最大値は求めづらいが、右の式から求められる。 アルゴリズムとしては q|a+bs を確認してから N(a+bθ) を割れるだけ q で割ればOK。