Blogs

uninInitiveation的线性反馈移位寄存器,部分XVIII:原始多项式生成

杰森萨赫斯2018年8月6日2评论

上次我们弄明白了 未知CRC计算的反向工程师参数 通过提供采样输入并分析相应的输出。 我们发现的一个是多项式\(x ^ {16} + x ^ {12} + x ^ {12} + x ^ 5 + 1 \)在16位x.25 crc中使用不是原始的—这只是意味着相应的商圈中的所有非零元素都可以’t由\(x \)的电源生成,因此相应的16位LFSR与位0,5和12中的抽头不具有\(2 ^ {16} - 1 \)的最大段。

一些应用程序确实需要使用原始多项式,而今天我们’重新找到它们。在本文中,我为原始多项式生成提出了不同方法的大小。真的,我们只需要一个—所以,如果它看起来就像我一样’你试图击败一匹死马,你’右转。或者使用另一个怪诞的隐喻,有一种以上的皮肤猫。 (没有动物在制定本文时受到伤害。)我们’重新开始简单,然后从天真到聪明的方式工作。与本系列的最后几篇文章不同,原始多项式一代是主要是学术练习,而且您可能赢了’t找到了任何实际用途,但是什么是哎呀,我喜欢这个话题,我’有一个非常酷的算法分享—下降的土狼算法— that I haven’在其他地方看见。

t!

有时它就没有 ’使用原始多项式的问题—您只需要任何指定程度的原始多项式。在这种情况下,您只能在桌子中查看一个。有许多好消息来源。

  • Zierler和Brillhart.’s 在原始三组(Mod 2)上 (1968)列出了许多不可缩短的三组,其中许多是原始的。

    • 这些由两个术语指定:\(n \)和\(k \)。例如,\(n = 28 \)行包含\(k = 1,3,9,13 \),其中所有但\(k = 1 \)是斜体的,指示\(x ^ {28} + x + 1 \)是不可减少的(不是要素)但不是原始的,而(x ^ {28} + x ^ 3 + 1 \),\(x ^ {28} + x ^ 9 + 1 \),\ (x ^ {28} + x ^ {13} + 1 \)是原始的。
    • 这样的表通常排除互补多项式:采取任何不可可动化或原始多项式并扭转系数,以及您’LL获得另一个不可缩短的或原始多项式,例如\(x ^ {28} + x ^ {27} + 1 \)是不可缩短的,\(x ^ {28} + x ^ {25} + 1), \(x ^ {28} + x ^ {19} + 1 \),\(x ^ {28} + x ^ {15} + 1)是原始的。
    • 那里 are no irreducible or primitive trinomials of degrees that are multiples of 8. This is a result of 天鹅’s Theorem,这可以命名为八位字节定理,因为您可以’T使用三组用于任何LFSR,它充分利用8位字节进行状态存储。
    • 理查德布伦特也有很大的追捕 原始三组织超过\(gf(2)\)。这对你来说并不意味着什么,但他’■数值分析的大师之一。如果你’使用根发现或最大发现算法,它是机会’基于他的研究。
  • 沃森 ’s 原始多项式(MOD 2) (1962) —这列列出了每度的一个原始多项式,最多\(n = 100 \),并且还抛出\(n = 107 \)和\(n = 127 \)以获得良好的措施。

  • Živković‘s 原始二元多项式表 (1994) —这列出了三组,五宣传和斜度。

  • 阿拉什派生’s website —这将所有原始多项式列出高达8度,以及许多用于更高度的原始多项式。

  • Philip Koopman’s website —在这里,众多和许多原始多项式高达64度。只要小心你可以正确地解释列表。例如, 学位-5多项式列表 contains 12 14 17 1B 1D 1E , 和 this is a different notation than I’ve been using, as it leaves out the unity term, so 12 hex = 10010 binary stands for the binary sequence 100101 = \( x^5 + x^2 + 1 \).

有时,桌子不是’T足够好,或者您想从表中仔细检查信息,或类似的东西。

站在巨人的肩膀上(偶尔犯错误)

在我第一次听到LFSR的几年后,2004年或2005年的某个时候,这一原始的多项式业务正在困扰我。最初我无法’T查找任何明确的陈述原始多项式的任何信息来源,或者如何找到它们。然后我遇到了一篇最近被两位斯坦福教授萨克雷纳发表的纸张& McCluskey, called 原始多项式生成算法实现与性能分析。我以为这是世界上最好的事情,因为我不仅可以理解它的大部分,而且它得到了相当易于遵循的说明如何去寻找原始多项式,所有你需要有效地做到这一点矩阵数学。这里’s the paper’s abstract:

两种算法的性能分析, MatrixPower.因子发动机,生成所有\(\ varphi(2 ^ r-1)/ r \) 学位 - \(r \)原始多项式(\(\ varphi \)是欧拉’呈现了Perient函数)。 MatrixPower在\(o(r ^ 4)中生成每个新的度 - \(r \)原始多项式 \ sim o(r ^ 4 \ ln r)\)时间。 因子发动机在\(o(r ^ 4)\ sim O(kr ^ 4 \ ln r)\)时间内产生每个新的度压原始多项式 其中\(k \)是\(2 ^ r - 1 \)的不同素数的数量。两个矩阵到 因子发动机需要\(o(r ^ 2)\)存储。产生原始的复杂性分析 提出了多项式。这项工作增加了以前发表了原始列表 多项式并提供快速计算框架来搜索原始 具有特殊属性的多项式。

本文写得很好,涵盖了四种算法, 龄岛 , 一段时间 , MatrixPower. , 和 因子发动机。一世’m将简要描述这些内容。不幸的是,萨克拿& McCluskey’S分析,虽然彻底纠正,但是错过了一些简单的改进,这些算法都不是那么大。但是让我们’s give them a look.

详尽的方法

前两个, 龄岛 一段时间 ,很糟糕。这些通过疲惫地找到原始多项式,基本上试图直接找到周期\(2 ^ n - 1 \)。 (这里我’m going to revert to my terminology where \( N \), not \( r \), is the degree of the polynomial or size of the LFSR.) You could, for example, use one of the C implementations of lfsr_step() in 第七部分 as follows:

  • 初始化LFSR状态 0000...0001
  • Count the number of times \( P \) you have to call lfsr_step() until you reach 0000...0001 again.
  • 如果\(p = 2 ^ n-1 \),它’是一个原始多项式!

这很简单,但你’重新将为32位LFSR循环4亿次。 33位LFSR的80亿次。它’S一个指数时间算法,每个多项式它检查都有最糟糕的运行时\(O(2 ^ n)\)。那么为什么烦恼?

现在 龄岛 算法,最初在另一个作者中描述 ’工作,不仅是可怕的,因为它有\(O(2 ^ n)\)运行时,但是因为而不是将lfsr内容表示为整数的位,而不是数组字节,每个字节,每个字节,一个字节,它实现斐波纳契表示,需要奇偶校验计算。哦,它’勉强隐秘勉强评论前的ANSI C.并且它使用全局变量。

#define N 32
#include <stdio.h>
int i,n,xx, degree; char d[N];
void see () {
 for (i=n; i>=0; i--) printf ("%c",d[i]+'0');
 printf("\n");
}
char s[N],t,j,f; unsigned long c,max;
void visit () {
 for (i=0;i<n;i++) s[i]=1; c=0;
 do
 {c++;
 for (i=t=0;i<n;i++)
 t = (t^(s[i]&d[i]));
 for (i=0;i<n-1;i++)
 s[i]=s[i+1];
 s[n-1]=t;
 for (i=f=0;i<n;i++)
 if (!s[i]) {f=1; break;}
 }
 while (f);
 if (c==max) see ();
}
void gp (l) char l; {
 if (!l) visit();
 else {
 d[l] = 0; gp (l-1);
 d[l] = 1; gp (l-1);
 }
}
void gc (l,rw) char l, rw; {
 char q;
 if (rw==2) { visit(); return;}
 for (q=1;q>=rw-2;q--) {
 d[q]=1; gc(q-1,rw-1);d[q]=0;
 }
}
void main(int argc, char *argv[]) {
 printf("\n");
 degree = atoi(argv[1]);
 for (n=degree;n<=degree;n++) {printf("%d\n",n);
 for (xx=max=1;xx<=n;xx++) max=2*max; max--;
 d[n] = d[0] = 1;
 gp(n-1); /* use gp(n-1) if all prim polys are desired */
 printf("\n");
 }
}

As far as I can tell, gp(l) sets bit l (that’s the letter l, not the number 1) of the array d[] to both possibilities, 0 and 1, and recurses, calling visit() whenever it bubbles all the way down to bit zero. The array d[] represents each candidate polynomial, whereas the array s[] represents shift register contents, used by visit() to simulate the shift register updates until it contains the same pattern it is initialized to, namely the all-ones pattern. There’s a loop in main that multiplies a number N times by 2; I guess its author never heard of the left-shift operator.

blah。

一段时间 算法有点好,但不是很多。它’s静止\(o(2 ^ n)\)。它确实使用位矢量表示,它不仅在内存中紧凑而是更快… as long as you’重新处理LFSR状态,拟合成像素的原始整数类型,如32位或64位整数。它’也很难读取,包括一些功能

pred(x,y)
unsigned int x,y;
{
extern unsigned int poly,mask;
 if ( (y & mask) > 0 )
 {
 if (x == 1)
 return((y << 1) & (2*mask-1));
 else
 return(((y << 1) ^ poly) & (2*mask-1));
 }
 else
 {
 if (x == 0)
 return((y << 1) & (2*mask-1));
 else
 return(((y << 1) ^ poly) & (2*mask-1));
 }
}

在蒙克与全局变量蒙克,唐’t bother using comments, and use cryptic short names like x, y , 和 pred (predicate? predecessor?) to do our dirty work. Same overall approach as 龄岛 ,将初始值加载到状态内容中,然后重复更新LFSR,直到它重复该初始值。

详尽的方法效率低下,我们可以做得更好。在所罗门戈尔姆’s 移位寄存器序列,有两种类型的计算原始多项式的技术,称为 筛子方法合成方法。筛分方法依次分析每个多项式,并滤除不是原始的多项式。合成方法使用一些巧妙的数学构建原始多项式。

筛子方法

使用Primitivity检查过滤

回到Saxena和McCluskey’s paper: the 因子发动机 算法非常像我的 checkPeriod() 我在第二部分讨论的功能,因为它依赖于具有\(2 ^ n-1)的分解。我展示多项式是原始的方式是检查是否有各种值\(q \)的\(x ^ q \ bmod p(x)= 1 \)。首先,我们检查\(q = 2 ^ n-1 \),如果我们不’T获取\(1 \),因此多项式是可还原的,因此不原始。否则,它’不可减少,它可能是原始的;序列\(x ^ k \ bmod p(x)\)有一个划分\(2 ^ n-1 \)的时段,我们只需确保时间不小于\(2 ^ n-1 \ )。所以我们检查\(q =(2 ^ n-1)/ p_i \)\(2 ^ n-1 \)的每个素数\(p_i \),如果在这些情况下,则\(x ^ q \ BMOD P(x)\ neq 1 \),然后我们知道\(p(x)\)是一个原始的多项式。耶!

(注意:这部分的其余部分有更多的技术,所以如果你想要的话,只需跳到“部分”部分 合成方法 。)

在Saxena和McCluskey’s paper, for 因子发动机 它们使用矩阵代替有限的现场算法,以便最耗时的步骤,并采取以下方法:

  • 检查是否无广场,换句话说,它不能写入某些多项式\(v(v(x))^ 2w(x)\)。(v (x)\)学位1或更高。检查方法是检查是否\(\ gcd(p(x),p’(x))= 1 \)如果是,则\(p(x)\)是方形的,我们可以在那里结束。逐\(p’(x) \) we mean the 正式衍生 \(p(x)\)我们遵循微积分规则,假装采取\(\ frac {d} {dx} p(x),好像它是一个连续的函数,即使我们’重新使用有限田地。例如,如果\(p(x)= a_4x ^ 4 + a_3x ^ 3 + a_2x ^ 2 + a_1x + a_0 \),然后\(p’(x)= 4a_4x ^ 3 + 3a_3x ^ 2 + 2a_2x + a_1 = a_3x ^ 2 + a_1 \)—所有偶数位置系数都消失了。 Saxena和McCluskey Cite Knuth’S计算机编程艺术卷。 1,说明此检查的执行时间是\(o(n ^ 2)\)。

  • 检查是否通过使用\(p(x)\)不可缩短 Berlekamp.’S分解算法,它仅适用于无广方多项式,Saxena和McCluskey状态是\(O(n ^ 3)\),它与矩阵的一些操作和它的空缺,也许我会想到的。对不起,我可以’t帮助你。

  • 如果\(2 ^ n-1 \)不是素数,那么对于\(2 ^ n-1 \)的每个素数\(p_i \),请检查是否\(\ mathbf {a} ^ {p_i} = \ mathbf {i} \),其中\(\ mathbf {a})是 伴侣矩阵 多项式\(p(x)\)。我谈到了这个话题 第八部分 。 Saxena和McCluskey状态为每个Prime因子\(O(n ^ 4)\)为每个主要因子,或\(o(kn ^ 4)\)总共,其中\(k)是主要因素的数量。

此算法中的瓶颈由最后两个步骤共享。对于实际显示为原始的每个多项式,我们必须检查\(n \ ln n \)多项式的顺序。 IRREUCIBIBLIBIB算法在每个原始多项式识别的每个原始多项式的\(n \ ln n \)次的顺序上运行,而检查 - 每个主要因素步骤在\(\ ln n \)次的顺序上运行(这似乎是Saxena的一个错字& McCluskey’s paper; there’SA术语在第17页,状态\(O(1)\ time O(kr ^ 4)\)和\(o(1)\)与不可缩短的多项式的数量与原始多项式的数量的比率不一致前面句子中提到的\(= o(\ ln r)\)。

用来检查 libgf2.gf2.checkPeriod

My implementation of libgf2.gf2.checkPeriod, which we can use to test whether polynomials are primitive, is slightly more efficient. The irreducibility check whether \( x^Q \bmod p(x) = 1 \) for \( Q = 2^N-1 \) uses a raise-to-the-power algorithm; 朝第二部分结束 我提到了\(o(n ^ 2 \ ln q)\)操作,并且用\(q = 2 ^ n-1)这是\(o(n ^ 3)\)(与berlekamp相同’S算法)。当我们检查因素\(p_i \ mid 2 ^ n-1 \)和compute \(x ^ q \ bmod p(x)\)for \(q =(2 ^ n-1)/ p_i \),它使用相同的提升到功率算法,为每个主要因素进行\(O(n ^ 3)\)。以下与Saxena和McCluskey类似的步骤 ’S分析,因为我们计算了不可缩短的Irreacibity \(O(n \ ln n)\)次,但仅仅是\(o(\ ln n)\)次,执行时间应该是\(o(n ^ 4 \ ln n) + o(kn ^ 3 \ ln n)\)具有覆盖不可缩短的术语和第二项覆盖原始性的术语。无限制的测试主导,因此总执行时间应该是\(o(n ^ 4 \ ln n)\),而不管原因的数量如何。

无论如何,我们可以在Zierler中提到的28级多项式上运行它& Brillhart’s table:

from libgf2.gf2 import checkPeriod, GF2QuotientRing

n = 28
period = (1<<n) - 1
for k in [1,3,9,13,15,19,25,27]:
    p = (1<<n) + (1<<k) + 1
    print "k=%d, primitive=%s" % (k, checkPeriod(p,period)==period)
k=1, primitive=False
k=3, primitive=True
k=9, primitive=True
k=13, primitive=True
k=15, primitive=True
k=19, primitive=True
k=25, primitive=True
k=27, primitive=False

因此,我们可以通过以蛮力方式测试它们来找到所有原始多项式。 (We’LL在结束时没有\(1 \)术语的情况下跳过所有多项式,因为这些是通过\(x \)无线的。我们可以计算多项式的奇偶校验’S位矢量表示,这很容易消除任何可分地的多项式\(x + 1 \),但我没有’t bothered.)

from libgf2.gf2 import checkPeriod, GF2QuotientRing

def enumeratePrimPoly(N):
    expectedPeriod = (1<<N) - 1
    for poly in xrange((1<<N)+1, 1<<(N+1), 2):
        if checkPeriod(poly, expectedPeriod) == expectedPeriod:
            yield poly
            
for N in [2,3,4,5,6,7,8]:            
    nN = 0
    for poly in enumeratePrimPoly(N):
        nN += 1
        print "%d %3x %s" % (N,poly, "{0:b}".format(poly)) 
    print "---- degree %d: there are %d primitive polynomials" % (N, nN) 
2   7 111
---- degree 2: there are 1 primitive polynomials
3   b 1011
3   d 1101
---- degree 3: there are 2 primitive polynomials
4  13 10011
4  19 11001
---- degree 4: there are 2 primitive polynomials
5  25 100101
5  29 101001
5  2f 101111
5  37 110111
5  3b 111011
5  3d 111101
---- degree 5: there are 6 primitive polynomials
6  43 1000011
6  5b 1011011
6  61 1100001
6  67 1100111
6  6d 1101101
6  73 1110011
---- degree 6: there are 6 primitive polynomials
7  83 10000011
7  89 10001001
7  8f 10001111
7  91 10010001
7  9d 10011101
7  a7 10100111
7  ab 10101011
7  b9 10111001
7  bf 10111111
7  c1 11000001
7  cb 11001011
7  d3 11010011
7  d5 11010101
7  e5 11100101
7  ef 11101111
7  f1 11110001
7  f7 11110111
7  fd 11111101
---- degree 7: there are 18 primitive polynomials
8 11d 100011101
8 12b 100101011
8 12d 100101101
8 14d 101001101
8 15f 101011111
8 163 101100011
8 165 101100101
8 169 101101001
8 171 101110001
8 187 110000111
8 18d 110001101
8 1a9 110101001
8 1c3 111000011
8 1cf 111001111
8 1e7 111100111
8 1f5 111110101
---- degree 8: there are 16 primitive polynomials

不错,但它对于\(n \)的大值变得耗时。我们’重新仔细地处理这一点,预先计算\(2 ^ n-1)的辅助actor,所以我们不’T必须重做每次计算该计算。

Oh –现在我们要计算比特奇偶校验:这消除了一半的多项式,即将\(x + 1 \)作为一个因素的一半,因为这些具有偶数的非零系数,而且’s a quick test.

from libgf2.gf2 import _calculateCofactors
from libgf2.util import parity
import primefac

def factorize_mersenne_candidate(N):
    """
    Finds factors of (2^N)-1
    taking advantage of the identity 2^(2N)-1 = ((2^N)-1) * ((2^N)+1)
    """
    N = int(N)
    if (N&1) == 1:  # odd -- no speedup
        return primefac.factorint((1<<N)-1)

    # even number speedup
    halfN = N//2
    result = factorize_mersenne_candidate(halfN)
    for k,v in primefac.factorint((1<<halfN)+1).iteritems():
        if k in result:
            result[k] += v
        else:
            result[k] = v
    return result

def test_factorize_mersenne_candidates():
    import time
    for N in xrange(8,64):
        t0 = time.time()
        f = factorize_mersenne_candidate(N)
        t1 = time.time()
        fcheck = primefac.factorint((1<<N)-1)
        assert fcheck == f
        print N, t1-t0, f 

def prim_sieve_1(N, maxcount=None, p1=None):
    """
    Find up to maxcount primitive polynomials of degree N.
    Approach: For each candidate polynomial p(x) with unity coefficients a0 and aN,
      determine the order of x in the quotient ring GF(2)[x]/p(x); p(x) is primitive
      if and only if the order of x is 2^N-1. Skip polynomials with an even number
      of nonzero coefficients since these are divisible by $x+1$.
      (This is the Alanen-Knuth algorithm.) Primitivity determination is stateless.
    """
    expectedPeriod = (1<<N) - 1
    factor_map = factorize_mersenne_candidate(N)
    factors = factor_map.keys()
    multiplicity = [factor_map[k] for k in factors]
    cofactors = _calculateCofactors(factors, multiplicity)
    count = 0
    for k in xrange(4,1<<N,2):
        candidate = expectedPeriod + k
        if parity(candidate) == 0:
            continue        
        if checkPeriod(candidate, expectedPeriod, cofactors) == expectedPeriod:
            yield None, candidate
            count += 1
            if count == maxcount:
                break

count = 0
for _, c in prim_sieve_1(8,1000):
    print '%x' % c
    count += 1
print('%d primitive polynomials found' % count)
11d
12b
12d
14d
15f
163
165
169
171
187
18d
1a9
1c3
1cf
1e7
1f5
16 primitive polynomials found

合成方法

寻找原始多项式的第三种方法是建设性的;我们合成原始多项式…什么?稀薄的空气?嗯,抓住是我们需要一个原始多项式开始。

首先,我们必须识别所需程度的一个原始多项式\(p_1(x)\)(n \)— we could use a sieve technique like libgf2.gf2.checkPeriod(). But then once we have \( p_1(x) \), all the other primitive polynomials \( p_j(x) \) of the same degree are isomorphic to \( p_1(x) \), and we can compute them one after the other. There are at least three equivalent methods of doing this:

  • 计算伴侣矩阵的\(j \)电力的特征多项式\(\ mathbf {a} \)
  • 计算产生的LFSR的特征多项式,其产生的输出比特序列从LFSR与特征多项式\(P_1(x)\)中的比率\(j \)抽取
  • 计算有限字段中的\(x ^ j \)的最小多项式\(gf(2)[x] / p_1(x)\)

不是每一个值\(j \)就足够了,但是产生原始多项式的一些非常简单的标准:

  • \(j \)和周期\(2 ^ n-1 \)必须相对素质,即\(\ gcd(j,2 ^ n-1)= 1 \)
  • 值\(j,2j,4j,\ ldots,2 ^ {n-1} j \)都产生相同的原始多项式(这些是在同一个紧固轴中)

Saxena和McCluskey.’S纸使用伴侣矩阵方法 MatrixPower. 算法。

我们以比例比例的方式暗示 部分IX. ,这是我喜欢的那个。

I’不熟悉最少的多项式方法,所以这个主题的下面是一个业余的实施。

伴侣矩阵

伴侣矩阵方法非常简单。 \(p_1(x)\)之后,如果要列出\(n \)的所有原始多项式,则按如下方式进行以下操作:

  • 打印\(p_1(x)\)。
  • 计算伴侣矩阵\(\ mathbf {a})
  • 计算\(\ mathbf {a} ^ 2 \)
  • 对于每个\(j \)从3到\(2 ^ {n-1})的步骤:
    • 计算\(\ mathbf {A} ^ J = \ mathbf {A} ^ 2 \ mathbf {A} ^ {J-2} \),也就是,我们乘上一候选由矩阵\(\ mathbf {A} ^ 2 \)
    • Check all conjugates of \( j \) by performing a bitwise rotation of \( j \) for each of possible \( N \) values; for example, if \( N=5 \) and \( j=9 \) then the possible bitwise rotations are (in binary) 01001, 10010, 00101, 01010 , 和 10100。一世f any of these numbers are less than \( j \), then if \( p_j(x) \) is primitive, we’ve already found it in an earlier iteration, and we can skip this iteration of the loop. (in the \( N=5, j=9 \) example, the bitwise rotation 00101 = 5 will have been found earlier and will produce the same polynomial, so we don’需要打扰\(j = 9 \)。)
    • 检查是否\(\ gcd(j,2 ^ n-1)= 1 \)。如果没有,请跳过此迭代的循环。
    • 计算\(p_j(x)\)作为矩阵的特征多项式,并打印它。

我们可以停止\(2 ^ {n-1})而不是\(2 ^ n-2 \)的原因是任何\(n \) - 位值,其中1作为其最重要的位,除了所有值\(2 ^ n-1 \),在位旋转下具有较低值共轭。基本上,只要那里’零位某处,我们可以将该零位旋转到最有效位中,并提出较小的\(j \)值,从而产生相同的原始多项式。

让’做它!计算矩阵的特征多项式可以使用 numpy.poly.

import numpy as np

def companion_matrix_with_char_poly(poly):
    n = GF2QuotientRing(poly).degree

    # Construct companion matrix
    C = np.zeros((n,n), dtype=np.uint8)
    k = np.arange(n-1)
    C[k+1,k] = 1          # subdiagonal
    C[:,n-1] = [(poly >> k) & 1 for k in xrange(n)]
    return C

def gcd(a,b):
    while b != 0:
        q,r = divmod(a, b)
        a,b = b,r
    return a

def charpoly_gf2(A):
    """ compute the characteristic polynomial of matrix A in GF(2)[x] """
    pvector = np.round(np.poly(A&1)).astype(int) & 1
    N = len(pvector)-1
    return sum(c<<(N-i) for i,c in enumerate(pvector))

def smallest_conjugate(j,N):
    jrotate = j
    jconj = j
    for _ in xrange(N-1):
        jrotate = (jrotate >> 1) | ((jrotate & 1) << (N-1))
        if jrotate < jconj:
            jconj = jrotate
    return jconj

def prim_construct_1(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Compute the characteristic polynomial of
      the jth power of the companion matrix A to the first
      primitive polynomial p1(x) we can find.
      Requires an initial primitive polynomial p1(x).
      Stateful: runs through odd values of j, multiplies by
      A^2 each time, to be faster than calculating A^j.
    """
    m = (1<<N)-1
    A = companion_matrix_with_char_poly(p1)
    A2 = np.dot(A,A) & 1
    Aj = A
    count = 1
    yield (1,p1)
    for j in xrange(3,1<<(N-1),2):
        Aj = np.dot(Aj,A2) & 1
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = charpoly_gf2(Aj)
        yield (j,pj)
        count += 1
        if count == maxcount:
            break

def find_first_primitive_polynomial(N):
    """ Find first primitive polynomial of degree N """
    m = (1<<N)-1
    for k in xrange(2,1<<N,2):
        p1 = m+k
        if checkPeriod(p1, m) == m:
            return p1
            
def collect_prim_poly(algorithm, N, maxcount=None, p1=None, verbose=False):
    m = (1<<N)-1
    ndigits = len('%d' % (1<<(N-1))) 
    # number of digits needed to print j
    
    if p1 is None:
        p1 = find_first_primitive_polynomial(N)
    count = 0
    result = []
    for j, pj in algorithm(N,p1=p1,maxcount=maxcount):
        count += 1
        result.append(pj)
        if verbose:
            if j is None:
                print '{0:x}'.format(pj)
            else:
                print 'j = {0:{ndigits}d}: {1:x}'.format(j, pj, ndigits=ndigits)
    if verbose:
        print "%d primitive polynomials found" % count
    return result

collect_prim_poly(prim_construct_1,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

It’s the same result we found using checkPeriod(), only the polynomials are in order of \( j \), not in natural order of their coefficients.

Saxena和McCluskey的状态是特征多项式计算是\(O(n ^ 3)\)最坏情况,所以你’d思考可能是这种算法的成本\(O(n ^ 3)\)每个原始多项式,但是使用矩阵乘以更新\(\ mathbf {a} ^ j \)矩阵的成本是\(o(n ^ 3 )\)我们必须每一种原始多项式执行此方法(O(n \ ln n)\)次数,因此这种方法的计算成本是\(o(n ^ 4 \ ln n)\)。

改善矩阵杆:放弃心爱的历史渣滓

这里 is the first stone that Saxena and McCluskey left unturned.

提醒 第八部分 我们提到了Elwyn Berlekamp’在标题的一段中的矩阵上的差异言论 心爱的历史渣滓 在那里他说他们在有限场计算中的使用需要更长的时间并使用更多的存储空间:

简而言之,矩阵方法现在已经过时,而不是思考年轻人的人使用。

我们还提到了第八部分的矩阵的另一个方面,如果你仔细看,你可能能够在萨克拉通知& McCluskey’s Table 6:

看看\(\ mathbf {a},\ mathbf {a} ^ 3,\ mathbf {a} ^ 5 \),\(\ mathbf {a} ^ 7 \)。每次乘以\(\ mathbf {a} \)时,最左边的列会消失,可以通过LFSR更新从前列列获得最右边的列,因为最右边的列只是\(x ^ {j +的系数(n-1)}}} \)在相应的有限场\(gf(2)[x] / p_1(x)\)中。

所以我们 大学教师 ’t need to compute \( \mathbf{A}^j \) with a matrix multiply, which costs \( O(N^3) \) every time we increase \( j \) by 2. Instead, we can just perform a pair of LFSR updates, which cost either \( O(N) \) or \( O(1) \) depending on how strict you are in accounting for computation cost. (If you use fixed-size integers like uint64_t or uint128_t then LFSR update amounts to a shift and conditional XOR in \( O(1) \); if you use an array of bytes to deal with large \( N \) then the LFSR update is \( O(N) \). Of course, the whole idea of big-O notation is to summarize asymptotic behavior as \( N \) becomes very large…但这取决于典型的数字范围;有时\(n = 100 \)非常大,有时它是不是’t.)

这应该将执行时间的瓶颈移位到每个原始多项式的\(O(n ^ 3)\),用于特征多项式计算。矩阵更新不太重要;最多它是\(O(n)\)每次增加\(j \),而且因为我们这样做\(o(n \ ln n))平均,每个原始多项式的矩阵更新成本应该是最多\(o(n ^ 2 \ log n)\)。

让’s do it:

def prim_construct_2(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Compute the characteristic polynomial of
      the jth power of the companion matrix to the first
      primitive polynomial p1(x) we can find, but use
      LFSR updates instead of matrix multiplication.
      Stateful: relies on previous matrix content for speedup.
    """
    m = (1<<N)-1
    A = companion_matrix_with_char_poly(p1)
    Aj = A
    count = 1
    qr = GF2QuotientRing(p1)
    u = qr.lshiftraw(1,N)
    yield 1, p1
    for j in xrange(3,1<<(N-1),2):
        # Update A^j
        Aj = np.roll(Aj,-2,axis=1)
        for c in [-2,-1]:
            u = qr.lshiftraw1(u)
            for i in xrange(N):
                Aj[i,c] = (u >> i) & 1
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = charpoly_gf2(Aj)
        yield j,pj
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_2,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

抽取

我们可以使用\(j \)作为LFSR的比例比来完成相同的事情。如第IX部分所述,这涉及两个步骤:

  • 创建抽取的输出序列\({y_j} = y [0],y [j],y [2j],\ ldots,y [(2n-2)j],y [(2n-1)j] \)基于拍摄输出序列的每个\(j \)th元素\(y [k] \),这是\​​(x ^ k \ bmod p_1(x)\)最高有效位的。
  • 使用 Berlekamp.-Massey algorithm 弄清楚该序列的特征多项式。
from libgf2.util import state_from_reversed_output, berlekamp_massey
from libgf2.gf2 import GF2Element

def decimate_LFSR(field, j):
    N = field.degree
    # Construct decimated sequence from the most significant bit (= bit N-1)
    decimated_sequence = [(field.lshiftraw(1,j*k)) >> (N-1) for k in xrange(2*N)]
    # Figure out the minimal polynomial of the decimated sequence 
    poly, _ = berlekamp_massey(decimated_sequence)
    return poly

def prim_construct_3(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the characteristic polynomial of the LFSR,
      using Berlekamp-Massey, that decimates the output 
      of the base LFSR with characteristic polynomial p1(x), 
      the first primitive polynomial we can find.
      Stateless; computes a finite field power
      for each bit in the decimated sequence.
    """
    m = (1<<N)-1
    qr = GF2QuotientRing(p1)
    count = 1
    yield 1,p1
    for j in xrange(3,1<<(N-1),2):
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = decimate_LFSR(qr, j)
        yield j,pj
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_3,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

此实现实际上比基于矩阵的方法慢。找到\(x ^ {k} \ bmod p(x)\)涉及升级到一个电源,它需要\(o(n ^ 2 \ ln k)\),如果\(k \)接近\( n \)然后我们’谈论\(o(n ^ 3)\)。创建有问题的位序列需要计算\(x \)的这些权力的\(2n \),这样’s \(o(n ^ 4)\),我们丢失了。

但是\(x \)的力量是\(1,x ^ j,x ^ {2j},x ^ {3j},…所以每次需要在比特序列中需要一个新的术语时,我们真的应该只需要一旦计算\(x ^ j \),然后为序列中的每个新输出位而需要提升电量,按\(x ^ j \)执行另一个乘法。这将完成每个原始多项式的\(O(n ^ 3)\)来构建比特序列。 (\(o(n ^ 3)\)对于初始\(x ^ j \)计算一次,并且每个乘以\(o(n ^ 2)\),为每个\(2n \)位重复。)

Berlekamp.-Massey has a \(O(n ^ 2)\)的最坏情况执行时间,那么’t占据了很多计算。

因此,如果我们重组位序列计算,我们应该能够缩小到每一种原始多项式的\(O(n ^ 3)\):

def LFSR_bit_sequence(field, a, nbits):
    """
    generate the high bit of 1, a, a^2, ...
    """
    u = 1
    for _ in xrange(nbits):
        yield (u >> (field.degree - 1)) & 1
        u = field.mulraw(u,a)

def find_decimated_LFSR_polynomial(field, j):
    uj = field.lshiftraw(1,j)
    
    decimated_sequence = list(LFSR_bit_sequence(field, uj, 2*field.degree))
    # Return the minimal polynomial of the decimated sequence 
    poly, _ = berlekamp_massey(decimated_sequence)
    return poly

def prim_construct_4(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the characteristic polynomial of the LFSR,
      using Berlekamp-Massey, that decimates the output 
      of the base LFSR with characteristic polynomial p1(x), 
      the first primitive polynomial we can find.
      Stateless; computes a single finite field power = x^j 
      for each value of j, and a finite field multiply for
      each of 2N bits in the decimated sequence.
    """
    m = (1<<N)-1
    qr = GF2QuotientRing(p1)
    count = 1
    yield 1, p1
    for j in xrange(3,1<<(N-1),2):
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = find_decimated_LFSR_polynomial(qr, j)
        yield j, pj
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_4,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

直接计算最小多项式

那里 is a way to calculate the minimal polynomial directly; if we have some element \( u=x^j \) in the finite field \( GF(2)[x]/p_1(x) \), then we can compute the polynomial

$$ p_j(x)=(x-u)(x-u ^ 2)(x-u ^ 4)\ ldots(x-u ^ {2 ^ {n-1}})$$

这种多项式的系数始终通过有限字段,0或1的魔法,即使它们是有效的有限场元件的产品的总和。这很简单利用:

def minimal_polynomial_from_factors(field, u):
    N = field.degree
    p = [1,u]
    # p is our polynomial, starting with (x-u)
    # and we want to calculate (x-u)(x-u^2)(x-u^4)...
    upow = u
    for i in xrange(1,N):
        # invariant at top of loop: degree(p) = i
        upow = field.mulraw(upow,upow)
        # multiply by (x-upow)
        # first multiply by x
        xp = p[:]+[0]
        p = [0]+p
        # then multiply by upow and add
        for j in xrange(i+2):
            p[j] = field.mulraw(p[j],upow) ^ xp[j]
    # p should now be a list of zeros and ones
    pvec = 0
    for i in xrange(N+1):
        assert p[i] == 0 or p[i] == 1
        pvec = (pvec << 1) ^ p[i] 
    return pvec

def prim_construct_5(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the minimal polynomial of u=x^j directly
    by computing (x-u)(x-u^2)(x-u^4)...(x-u^(2^(N-1))).
    Stateful: steps through odd values of j, updates
    u=x^j by two left shifts each iteration.
    """
    m = (1<<N)-1
    qr = GF2QuotientRing(p1)
    count = 1
    yield 1, p1
    xj = 2
    for j in xrange(3,1<<(N-1),2):
        xj = qr.lshiftraw(xj,2)
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = minimal_polynomial_from_factors(qr, xj)
        yield j, pj
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_5,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

奇迹般有效。不幸的是,这里所示的最小多项式计算使用了运行\((n ^ 2 + 3n-4)/ 2 \)次的循环中的有限字段乘法(\(o(n ^ 2)\)),所以它看起来与每个原始多项式一样,它具有\(O(n ^ 4)\)的整体执行时间。

使用线性代数和基本元素的最小多项式

那里’如果我们,则更有效地确定最小多项式的方法’愿意转向一些线性代数来帮助。我们采取\(u = x ^ j \)并表达\(1,u,u ^ 2,u ^ 3,\ ldots,u ^ n \)在基础上,u ^ n \)( 1,x,x ^ 2,x ^ 3,\ ldots,x ^ {n-1} \)—我们在使用位矢量表示时免费获得,因为这是我们已经使用的基础—并解决不完全零的系数\(c_i \),这样\(c_0 + c_1u + c_2u ^ 2 + c_3u ^ 3 + \ ldots + c_ {n-1} u ^ {n-1} + c_ { n} u ^ n = 0 \)。

例如,假设我们有我们的例子’已经使用,\(n = 8 \)和\(p_1(x)= x ^ 8 + x ^ 4 + x ^ 3 + x ^ 2 + 1 \),\(j = 7 \)。然后这里是\(u \)的权力:

u = GF2Element(1,0x11d) << 7
for i in xrange(9):
    print "u^%d = %s" % (i, u**i)
u^0 = GF2Element(0b00000001,0x11d)
u^1 = GF2Element(0b10000000,0x11d)
u^2 = GF2Element(0b00010011,0x11d)
u^3 = GF2Element(0b01110101,0x11d)
u^4 = GF2Element(0b00011000,0x11d)
u^5 = GF2Element(0b10011100,0x11d)
u^6 = GF2Element(0b10110101,0x11d)
u^7 = GF2Element(0b10001100,0x11d)
u^8 = GF2Element(0b01011101,0x11d)

我们可以看到哪些元素在其最低有效位中有1位, 这意味着\(c_0 + c_2 + c_3 + c_6 + c_8 = 0 \)。同样,如果我们查看每个元素中的每一个的位1,则意味着\(c_2 = 0 \)。这为我们提供了八个方程,其中九个未知数…除了我们知道\(c_8 = 1 \)(否则它会’T是8学位的原始多项式,我们可以解决它。基本上我们使用\(^ 8 \)元素来解决\(\ mathbf {a} c = u ^ 8 \)其中\(\ mathbf {a} \)是由\(u ^的系数形成的矩阵0,u ^ 1,\ ldots u ^ 7 \)。

for \(j = 7 \)我们有一个具体的例子

$$ \ begin {align} \mathbf{A} &= \begin{bmatrix} 0&0&0&0&0&0&0&1 \cr 1&0&0&0&0&0&0&0 \cr 0&0&0&1&0&0&1&1 \cr 0&1&1&1&0&1&0&1 \cr 0&0&0&1&1&0&0&0 \cr 1&0&0&1&1&1&0&0 \cr 1&0&1&1&0&1&0&1 \cr 1&0&0&0&1&1&0&0 \结束{bmatrix} ^ {ht} \ cr u^8 &= \begin{bmatrix}0&1&0&1&1&1&0&1\end{bmatrix}^{HT} \end{align}$$

在上标\(h \)指的是水平镜像的情况下,因为我们希望按升序的系数。这个等式\(\ mathbf {a} c = u ^ 8 \)有解决方案\(c = \ begin {bmatrix} 1&0&0&1&0&1&1&0\end{bmatrix}^T \) indicating \( 1 + x^3 + x^5 + x^6 + x^8 \) or 0x169.

A = np.fliplr(np.matrix(
              [[0,0,0,0,0,0,0,1],
               [1,0,0,0,0,0,0,0],
               [0,0,0,1,0,0,1,1],
               [0,1,1,1,0,1,0,1],
               [0,0,0,1,1,0,0,0],
               [1,0,0,1,1,1,0,0],
               [1,0,1,1,0,1,0,1],
               [1,0,0,0,1,1,0,0]])).T
b = np.fliplr(np.matrix(np.array([0,1,0,1,1,1,0,1]))).T
c = np.linalg.solve(A,b).astype(int) & 1
print ("c.T=%s" % c.T)
print ("poly=0x%x" % (sum(ci<<i for i,ci in enumerate(c)) + (1<<len(c))))
c.T=[[1 0 0 1 0 1 1 0]]
poly=0x169

我们不’在这里需要numpy图书馆;我们可以从头开始应用这个:

def gf2_solve(A,b,N):
    """ Solve Ac=b in mask-encoded form 
    A is a list of N bit vectors 
    b is a bit vector
    """
    remaining_pivots = [1]*N
    permutation = [0]*N
    for i in xrange(N):
        # Find pivot
        pivot = -1
        mask = 1<<i
        for j in xrange(N):
            if remaining_pivots[j] == 0:
                continue
            if A[j] & mask:
                pivot = j
                break
        if pivot == -1:
            raise ValueError('No solution')
        permutation[i] = pivot
        remaining_pivots[pivot] = 0
        # Eliminate pivot
        for j in xrange(N):
            if j == pivot:
                continue
            if A[j] & mask:
                A[j] ^= A[pivot]
                b ^= ((b >> pivot) & 1) << j
    # Now find solution
    return sum(1<<i for i in xrange(N) if (b >> permutation[i]) & 1)    

def minimal_polynomial_from_basis(field, u):
    """ Finds minimal polynomial from basis using linear algebra,
    assuming it is an Nth degree polynomial.
    (If we relax this restriction, this function
    needs to be more complicated, and then we can try lower degree polynomials.)
    
    This implementation uses Gaussian elimination; by using
    bit masks, we can do the elimination part in O(N^2).
    But the construction of the matrix A requires N finite field multiplications
    for a total of O(N^3).
    """
    N = field.degree
    # Determine u^i from i=0 to i=N (inclusive) 
    elements = []
    ui = 1
    for i in xrange(N):
        elements.append(ui)
        ui = field.mulraw(ui, u)
    uN = ui
    # Transpose the element vectors into a matrix of bit masks
    A = [0]*N
    for i in xrange(N):
        mask = 1<<i
        for j in xrange(N):
            if elements[j] & mask:
                A[i] ^= (1<<j)
    c = gf2_solve(A,uN,N)
    c ^= (1<<N)
    # Verify solution
    y = 0
    for i in xrange(N):
        if (c >> i) & 1:
            y ^= elements[i]
    if y != uN:
        raise ValueError("Could not verify solution")
    return c
                
def prim_construct_6(N, p1, maxcount=None):
    """ 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the minimal polynomial of x^j using linear algebra.
    Stateful: steps through odd values of j, updates
    u=x^j by two left shifts each iteration.
    """
    m = (1<<N)-1
    qr = GF2QuotientRing(p1)
    count = 1
    yield 1, p1
    xj = 2
    for j in xrange(3,1<<(N-1),2):
        xj = qr.lshiftraw(xj,2)
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        pj = minimal_polynomial_from_basis(qr, xj)
        yield j, pj
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_6,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

其他方法

如果我们转向LFSR和有限字段的权威参考,我们会看到此建议:

  • 戈尔仑 ’s 移位寄存器序列:Golomm致力于全部部分(“计算技巧”)鉴定原始多项式。讨论的筛分方法或多或少与萨克拉和麦克伦单相同’s 因子发动机。对于合成方法,戈尔仑提到了“袜子叠加”找到所有原始多项式的方法,使用与计算规范移位寄存器序列的系数矩阵有关(移位为此\(a [k] = a [2 ^ wk] \),即序列是不变的向每个相应的紧固轴中的两个)的幂的抽取值— which doesn’对于我来说,对我有任何意义,首先是因为我不’完全理解它(戈尔环’写作是隐秘的,有时是模糊的,让读者猜测某些非正式陈述的提示的含义,而第二,因为患者的数量增加为\(\ varphi(2 ^ n-1)\ sim o(2 ^ n /(n \ ln n))\),因此即使您只想找到一部分原始多项式,您必须为每个原始多项式做一些事情的前正常成本,您可能会在以后生成。

  • 炸 ’s 计算机科学家和工程师的有限领域:MECERIES有一章关于\(m \) - 序列,并表明通过抽取可以从相同程度的任何其他程度导出的任何最大长度LFSR序列。

  • Lidl.& Niederreiter’s 有限字段及其应用介绍:有一个叫做的部分“Irreafible多项式的构建”这引用了三种合成方法:

    • 计算\(p_j(x)\)这样\(p_j(x ^ j)= \ prod \ limits_ {i = 1} ^ jp_1(\ omega_ix)\)其中\(\ omega_i \)是其中一个\( j \)统一的根— this doesn’T似乎平化为一个有效的计算技术
    • 计算伴侣矩阵的权力的特征多项式,如我们’ve already seen
    • 计算最小多项式,如我们’ve already seen
  • 约翰克林 ’s 有限字段中的计算:虽然Kerl的部分’S文件闻名地深入,易于遵循,他花了三个段落概述了在进入另一个主题之前检查原始性的筛查方法。

其他论文:

  • Živković 描述了一种优化的筛分方法,用于找到具有低系数计数的原始多项式\(t \)
  • 米特拉 描述了一种筛分方法,但它需要\(o(2 ^ n)\)计算时间,所以似乎很差思考
  • di porto,guida和montolivo 描述使用LFSR抽取和Berlekamp-Massey算法的合成方法。然而,它可以扭曲到速度。我称之为圣艾夫斯算法,我’LL下面更详细地描述。
  • 戈登 描述了一种用于直接计算最小多项式的合成技术。
  • sh 描述了一种聪明的合成技术,也使用LFSR抽取和Berlekamp-Massey算法,但它完全不同,而且,再次,我’LL描述如下。

我们可以做得更好!

那里 are four improvements for synthetic methods, which are minor variations, but they all accomplish key improvements. Three of them are based around methods using the Berlekamp-Massey算法 to process decimations of an LFSR bit sequence. The fourth, Gordon’S方法使用洞察力,使其能够轻松地直接计算最小多项式。

圣艾夫斯算法

使用问题 抽取 和 Berlekamp-Massey is that it takes so long to construct bit sequences as input to Berlekamp-Massey. In prim_construct_4() we came up with an optimization where we computed \( 1, x^j, x^{2j}, x^{3j}, \ldots, x^{(2N-1)j} \) as repeated finite field multiplications of \( x^j \). Each multiplication takes \( O(N^2) \) operations, and since we do this \( 2N-1 \) times, constructing the whole bit sequences takes \( O(N^3) \).

我在写这篇文章时想到了这一点。我们通过将所需的操作从提升到电源降低到乘以来降低运行时。也许我们可以做一些类似的事情,并从乘法到LFSR更新中减少所需的操作,这需要(取决于您的否则如何)\(O(1)\)或\(O(n)\)执行时间?例如,我们可以通过以1开始和执行\(j \)lfsr更新,然后使用另一个\(j \)lfsr更新,然后计算\(x ^ {2j})来计算\(x ^ j \)。所以,完成后完成整个所需的位序列\((2n-1)j \)更新—这是\(o(jn)\)或\(o(o(jn ^ 2)\)操作,具体取决于您定义的东西。唯一的问题是,如果\(j \)很大,则需要更长时间。但是’不是一个大问题。我们只需要找到比较素质的最小值,即\(2 ^ n-1 \):

candidates = [3,5,7,11,13,17,19,23,29]
for N in xrange(2,33):
    m = (1<<N) - 1
    for j in candidates:
        if gcd(j,m) == 1:
            print "N=%2d, j=%d" % (N,j)
            break
    else:
        print "N=%2d, j>30 (UH OH!)" % N
N= 2, j=5
N= 3, j=3
N= 4, j=7
N= 5, j=3
N= 6, j=5
N= 7, j=3
N= 8, j=7
N= 9, j=3
N=10, j=5
N=11, j=3
N=12, j=11
N=13, j=3
N=14, j=5
N=15, j=3
N=16, j=7
N=17, j=3
N=18, j=5
N=19, j=3
N=20, j=7
N=21, j=3
N=22, j=5
N=23, j=3
N=24, j=11
N=25, j=3
N=26, j=5
N=27, j=3
N=28, j=7
N=29, j=3
N=30, j=5
N=31, j=3
N=32, j=7

大多数时候,事实上,只要\(n \)是奇数,我们就可以逃脱\(j = 3 \)。如果\(n \)甚至,则\(j \)至少为\(5 \)。如果\(n \)是4的倍数,则\(j \)至少为7.(但是\(j = 7 \)为2.)如果\(n \)是多个12,然后\(j \)至少为11.需要多于\(j = 11 \)是非常罕见的:

candidates = [3,5,7,11,13,17,19,23,29]
for N in xrange(2,2000):
    m = (1<<N) - 1
    for j in candidates:
        if gcd(j,m) == 1:
            if j > 11:
                print "N=%2d, j=%d" % (N,j)
            break
    else:
        print "N=%2d, j>30 (UH OH!)" % N
N=60, j=17
N=120, j=19
N=180, j=17
N=240, j=19
N=300, j=17
N=360, j=23
N=420, j=17
N=480, j=19
N=540, j=17
N=600, j=19
N=660, j=17
N=720, j=23
N=780, j=17
N=840, j=19
N=900, j=17
N=960, j=19
N=1020, j=17
N=1080, j=23
N=1140, j=17
N=1200, j=19
N=1260, j=17
N=1320, j=19
N=1380, j=17
N=1440, j=23
N=1500, j=17
N=1560, j=19
N=1620, j=17
N=1680, j=19
N=1740, j=17
N=1800, j=23
N=1860, j=17
N=1920, j=19
N=1980, j=17

It’■只有60的倍数,您需要\(j \ ge 17 \),以及360的倍数,您需要\(j \ ge 23 \),以及您需要的3960的倍数\(j \ ge 29 \ )。一世’m不可能使用\(n>64 \),所以\(j = 17 \)足以满足所有小值。

以便’SENE和DANDY,但是当我们需要超过2个原始多项式时会发生什么?我们必须从筛子方法(或其他别人)获得的第一个’在一张桌子中的工作),第二个我们可以使用这种逐渐抽取的这种方法,然后使用Berlekamp-Massey,但是随着我们需要增加的方式发生了什么\(j \)?对于\(n = 8 \)所需的\(j \)值序列为\(1,7,11,13,19,23,29,31,37,43,47,53,59,61,91,127 \),\(n \)的值较大,最大\(j \)值将是\(2 ^ {n-1} - 1),这不小。

这里的关键是在整数的乘法组\(\ bmod 2 ^ n-1 \)中查看\(j \)作为一个可能的生成器,相对\(2 ^ n-1 \):

N = 8
j0 = 7
m = (1<<N) - 1
j = 1
for i in xrange(16):
    print("j = %d^%2d mod 255 = %3d -> smallest conjugate %d" %
          (j0, i, j, smallest_conjugate(j,N)))
    j = (j * j0) % m
j = 7^ 0 mod 255 =   1 -> smallest conjugate 1
j = 7^ 1 mod 255 =   7 -> smallest conjugate 7
j = 7^ 2 mod 255 =  49 -> smallest conjugate 19
j = 7^ 3 mod 255 =  88 -> smallest conjugate 11
j = 7^ 4 mod 255 = 106 -> smallest conjugate 53
j = 7^ 5 mod 255 = 232 -> smallest conjugate 29
j = 7^ 6 mod 255 =  94 -> smallest conjugate 47
j = 7^ 7 mod 255 = 148 -> smallest conjugate 37
j = 7^ 8 mod 255 =  16 -> smallest conjugate 1
j = 7^ 9 mod 255 = 112 -> smallest conjugate 7
j = 7^10 mod 255 =  19 -> smallest conjugate 19
j = 7^11 mod 255 = 133 -> smallest conjugate 11
j = 7^12 mod 255 = 166 -> smallest conjugate 53
j = 7^13 mod 255 = 142 -> smallest conjugate 29
j = 7^14 mod 255 = 229 -> smallest conjugate 47
j = 7^15 mod 255 =  73 -> smallest conjugate 37

那 generated half of the values we need. What about the other ones? We find the smallest factor not used yet, which is 13 in this case, and use it:

N = 8
j0 = 7
j1 = 13
m = (1<<N) - 1
j = 1
i0 = 0
i1 = 0
for i in xrange(16):
    c = smallest_conjugate(j,N)
    if i > 1 and c == 1:
        i1 += 1
        j = (j * j1) % m
        c = smallest_conjugate(j,N)
    print("j = (%d^%2d)*(%d^%2d) mod 255 = %3d -> smallest conjugate %d" %
          (j0, i0, j1, i1, j, c))
    j = (j * j0) % m
    i0 += 1
j = (7^ 0)*(13^ 0) mod 255 =   1 -> smallest conjugate 1
j = (7^ 1)*(13^ 0) mod 255 =   7 -> smallest conjugate 7
j = (7^ 2)*(13^ 0) mod 255 =  49 -> smallest conjugate 19
j = (7^ 3)*(13^ 0) mod 255 =  88 -> smallest conjugate 11
j = (7^ 4)*(13^ 0) mod 255 = 106 -> smallest conjugate 53
j = (7^ 5)*(13^ 0) mod 255 = 232 -> smallest conjugate 29
j = (7^ 6)*(13^ 0) mod 255 =  94 -> smallest conjugate 47
j = (7^ 7)*(13^ 0) mod 255 = 148 -> smallest conjugate 37
j = (7^ 8)*(13^ 1) mod 255 = 208 -> smallest conjugate 13
j = (7^ 9)*(13^ 1) mod 255 = 181 -> smallest conjugate 91
j = (7^10)*(13^ 1) mod 255 = 247 -> smallest conjugate 127
j = (7^11)*(13^ 1) mod 255 = 199 -> smallest conjugate 31
j = (7^12)*(13^ 1) mod 255 = 118 -> smallest conjugate 59
j = (7^13)*(13^ 1) mod 255 =  61 -> smallest conjugate 61
j = (7^14)*(13^ 1) mod 255 = 172 -> smallest conjugate 43
j = (7^15)*(13^ 1) mod 255 = 184 -> smallest conjugate 23

我称之为st的原因是,如果\(n \ ge 4 \)是2的力量,\(j \)最终是7的力量,就像苗圃押韵一样, 正如我要去圣艾夫斯.

我很失望地发现Di Porto,Guida和Montolivo已经在1992年的论文中提出了这种方法,更感到失望,发现你可以’依靠一个值\(j \),正如我们上面所看到的那样。没有’似乎是一种简单的方法,概括了我们上面看过的任意\(n \)的方法,而Di Porto等’如果您想生成,则讨论围绕此限制的方法 全部 原始多项式。

所以我’我试图找到一种方式。

那里 is a way to “help”手动如果你知道的话 生成集合 整数的乘法组\(\ BMOD 2 ^ n-1 \)相对\(2 ^ n-1 \)。如果我们可以提供执行的操作计划,那么也许我们可以找到所有原始多项式。

def multiplicative_plan(generators, N):
    m = (1<<N) - 1
    v = 1
    vc = 1
    ngen = len(generators)
    progress = [0]*ngen
    count = 0
    while count < 100:
        count += 1
        for k in xrange(ngen):
            j,nj = generators[k]
            progress[k] += 1
            vnext = (v*j) % m
            vcnext = smallest_conjugate(vnext, N)
            yield k, j, vc, v
            v = vnext
            vc = vcnext
            if progress[k] < nj:
                break
            progress[k] = 0
        else:
            break

print "k  j  vc   v"
for k,a,jc,j in multiplicative_plan([(7,8),(13,2)], 8):
    print "%d %2d %3d %3d" % (k,a,jc,j)
k  j  vc   v
0  7   1   1
0  7   7   7
0  7  19  49
0  7  11  88
0  7  53 106
0  7  29 232
0  7  47  94
0  7  37 148
1 13   1  16
0  7  13 208
0  7  91 181
0  7 127 247
0  7  31 199
0  7  59 118
0  7  61  61
0  7  43 172
0  7  23 184
1 13  13  13

在上表中,在每个步骤中:

  • \(k = \)索引进入生成设置使用
  • \(j = \)生成集中的相应元素
  • \(v = \)有效的抽取率:在每个步骤中,\(v \ leftarrow vj \ bmod 2 ^ n-1 \)
  • \(v_c = \)“canonical”\(v \)的值(位旋转下的最小缀合物)

当然,可能难以提前找到这样的生成。这里’■尝试查找我们去的发电机。这个想法是如下:

  1. compute \(r = \ varphi(2 ^ n-1)\),它是整数的乘法组mod \(2 ^ n-1 \)的顺序相对\(2 ^ n-1 \)。
  2. 设置\(r = r / n \),它是循环比特旋转下的不同值的数量,并且这也是学位的原始多项式的数量\(n \)。
  3. 创建低值奇数indes的列表\(l \)\(3,5,7,\ ldots,p_ {max}。\)(\(p_ {max} = 997)可能工作。)
  4. 从\(l \)中删除\(2 ^ n-1 \)的所有除数。
  5. 创建空列表\(j \)。
  6. 输出元组\((1,0,1)\)。
  7. 设置\(v = 1,s = 1,n = 0。\)\(v \)的值将是每个步骤中使用的抽取比率; s的值将是由\(j \)元素生成的组的顺序以及到目前为止已输出的值数; n的值将是\(j \)的大小。
  8. 设置\(j = \)\(l \)的最低剩余元素。
  9. set \(n \ refrearrow n + 1。\)
  10. set \((v,r_j)= \ operatorname {erate}(v,j,l,j,0,n-1,j)\)。
  11. 将对\((j,r_j)\)添加到列表\(j \)。
  12. set \(s \ refrearrow r_js \)
  13. 如果\(s<r \)转到步骤8,否则我们完成了,我们应该有\(s = r \)。 (如果没有,我们’ve made a mistake.)

迭代算法如下工作,给定值\(v,j,l,j,r_j,n,j_ {top} \);值\(r_j \)为零(并且我们必须弄清楚乘速度乘以\(j \),直到可到达某些功率)或大于1,我们需要乘以\(j \ )多次。

  1. 设置\(t = 0 \)如果\(j = j_ {top} \),否则设置\(t = -1 \)。 (这使得算法在顶级延长了一个较少的时间,因为以前的顶级呼叫迭代已经执行; \(v \)应该等于\({j_0} ^ {0} {j_1} ^ {r_1此时,{j_2} ^ {r_2-1} \ ldots \)。)
  2. set \(w = 0 \)
  3. set \(v \ refrearrow jv \ bmod 2 ^ n-1 \)
  4. set \(t \ refrearrow t + 1 \)
  5. 如果\(n> 0 \), goto step 11
  6. (\(n=0 \)) Output the tuple \( (v,j,1) \)
  7. compute \(v’= \)\(v \)的最小位旋转,如果存在,请从\(l \)中删除它
  8. 如果\(r_j>0 \)和\(j_ {top} v \ bmod 2 ^ n-1)是两个,set \(w = 1 \)的力量
  9. 如果\(r_j= 0 \) and \( v’= 1 \)然后返回\((v,t)\)
  10. 如果\(t<r_j \)然后转到第3步
  11. 否则返回((v,w)\)
  12. (\(n>0 \))输出元组\((v,j,0)\)
  13. set \((v,w_ {sub})= \ operatorname {erate}(v,j,l,j [n-1] [0],j [n-1] [1],j_ {top})\ )
  14. 如果\(w_ {sub} = 1 \)然后设置\(w \ refrearrow 1 \)
  15. 如果\(t<r_j \)然后转到步骤2
  16. 如果\(t = r_j \)然后返回\((v,w)\)
  17. 如果\(w = 0 \)然后转到步骤2
  18. 否则返回\((v,t)\)

这里的想法是我们有一些数量的乘法器\(j_0,j_1,\ ldots j_ {n-1},每个乘数都有循环计数\(r_0,r_1,\ ldots r_ {n-1} \ )。最上面的乘数\(j_ {top} = j_ {n-1})我们需要弄清楚\(r_ {n-1} \),我们通过0,循环直到\(n = 0 \)级别将在最顶层乘数的下一个周期上达到1,于是我们设置\(w = 1 \),以指示这是最后一个外周期。例程返回两个数字; \(v \)是其更新的值,我们要么返回\(w \)(对于较低级别)或最外层的循环计数\(t \)。

输出元组由\(v \),每个步骤中的乘数\(j \)组成,以及一个“usage”位\(u \);如果\(u \)为1,则应使用所得\(v \)的结果来查找合适的原始多项式,否则应跳过。

我无法’想到一个干净的方法来处理有一个事实“output” and a “return value” in the algorithm above. In Python, at first I tried to yield the output and return the return value, but Python won’让你做两者,所以我不得不重写一点。这是一个Python实现:

def yield_primitive_polynomial_search_plan(N,J=None):
    factors = factorize_mersenne_candidate(N)
    phi = 1
    for k,v in factors.iteritems():
        phi *= k**(v-1) * (k-1)
    #print "phi=%d,phi/N=%d" % (phi, phi//N)
    def primes_up_to(N):
        L = [3,5,7,11]
        pmin = L[-1]+2
        while pmin<N:
            pmax = min(L[-1]**2, N+1)
            for p in xrange(pmin,pmax,2):
                isprime = True
                for d in L:
                    if d*d > p:
                        break
                    if p % d == 0:
                        isprime = False
                        break
                if isprime:
                    L.append(p)
            pmin = p+2
        return L

    M = (1<<N)-1
    Np = 1000
    L = set(primes_up_to(Np))
    for k in factors:
        if k < Np:
            L.remove(k)
    if J is None:
        J = []

    def iterate(v,j,rj,n,jtop):
        t=0 if j == jtop else -1
        w=0
        while rj == 0 or (t+1)<rj:
            v = (v*j)%M 
            t += 1
            if n == 0:
                if rj > 0:
                    vnext = (v*jtop)%M
                    if (vnext & (vnext-1)) == 0:   # power of 2
                        w = 1
                vc = smallest_conjugate(v,N)
                if vc < Np:
                    L.discard(vc)
                if rj > 0:
                    if t >= rj:
                        yield (v,j,0,w)
                        break
                else: # rj == 0
                    if vc == 1:
                        break
                yield (v,j,1,w)
            else:  # n > 0
                if rj == 0 and w == 1:
                    break
                if rj > 0 and t >= rj:
                    break
                yield (v,j,0,w)
                for y in iterate(v,J[n-1][0],J[n-1][1],n-1,jtop):
                    v,_,_,wsub = y
                    w = wsub | w
                    yield y
                    
    s = 1
    v = 1
    yield (1,0,1,0)
    n = 0
    while s*N < phi:
        span = s
        n += 1
        Lmin = min(L)
        J.append((Lmin,0))
        for y in iterate(v,Lmin,0,n-1,Lmin):
            v,j,v_use,w = y
            s += v_use
            yield y
        t = s//span
        J[-1] = (Lmin,t)
        #print "s=%d, j=%d, t=%d" % (s,Lmin,t)
    #print "jdict=%s,J=%s" % (jdict,J)
def show_primitive_polynomial_search_plan(N,smax=3000):
    print "Primitive polynomial search plan, N=%d" % N
    s=0
    s_use = 0
    jdict = {}
    for y in yield_primitive_polynomial_search_plan(N):
        s+=1
        if s > smax:
            break
        v,j,u,w = y
        s_use += u
        if j > 0:
            jdict[j] = jdict.get(j,0) + 1
        print("s=%3d su=%3d v=%5d j=%2d u=%d w=%d vc=%5d %s"
              % (s,s_use,v,j,u,w, smallest_conjugate(v,N), jdict))

show_primitive_polynomial_search_plan(8)
show_primitive_polynomial_search_plan(9)
Primitive polynomial search plan, N=8
s=  1 su=  1 v=    1 j= 0 u=1 w=0 vc=    1 {}
s=  2 su=  2 v=    7 j= 7 u=1 w=0 vc=    7 {7: 1}
s=  3 su=  3 v=   49 j= 7 u=1 w=0 vc=   19 {7: 2}
s=  4 su=  4 v=   88 j= 7 u=1 w=0 vc=   11 {7: 3}
s=  5 su=  5 v=  106 j= 7 u=1 w=0 vc=   53 {7: 4}
s=  6 su=  6 v=  232 j= 7 u=1 w=0 vc=   29 {7: 5}
s=  7 su=  7 v=   94 j= 7 u=1 w=0 vc=   47 {7: 6}
s=  8 su=  8 v=  148 j= 7 u=1 w=0 vc=   37 {7: 7}
s=  9 su=  8 v=  139 j=13 u=0 w=0 vc=   23 {13: 1, 7: 7}
s= 10 su=  9 v=  208 j= 7 u=1 w=0 vc=   13 {13: 1, 7: 8}
s= 11 su= 10 v=  181 j= 7 u=1 w=0 vc=   91 {13: 1, 7: 9}
s= 12 su= 11 v=  247 j= 7 u=1 w=0 vc=  127 {13: 1, 7: 10}
s= 13 su= 12 v=  199 j= 7 u=1 w=0 vc=   31 {13: 1, 7: 11}
s= 14 su= 13 v=  118 j= 7 u=1 w=1 vc=   59 {13: 1, 7: 12}
s= 15 su= 14 v=   61 j= 7 u=1 w=1 vc=   61 {13: 1, 7: 13}
s= 16 su= 15 v=  172 j= 7 u=1 w=1 vc=   43 {13: 1, 7: 14}
s= 17 su= 16 v=  184 j= 7 u=1 w=1 vc=   23 {13: 1, 7: 15}
Primitive polynomial search plan, N=9
s=  1 su=  1 v=    1 j= 0 u=1 w=0 vc=    1 {}
s=  2 su=  2 v=    3 j= 3 u=1 w=0 vc=    3 {3: 1}
s=  3 su=  3 v=    9 j= 3 u=1 w=0 vc=    9 {3: 2}
s=  4 su=  4 v=   27 j= 3 u=1 w=0 vc=   27 {3: 3}
s=  5 su=  5 v=   81 j= 3 u=1 w=0 vc=   37 {3: 4}
s=  6 su=  6 v=  243 j= 3 u=1 w=0 vc=  111 {3: 5}
s=  7 su=  7 v=  218 j= 3 u=1 w=0 vc=  109 {3: 6}
s=  8 su=  8 v=  143 j= 3 u=1 w=0 vc=   61 {3: 7}
s=  9 su=  9 v=  429 j= 3 u=1 w=0 vc=  183 {3: 8}
s= 10 su= 10 v=  265 j= 3 u=1 w=0 vc=   19 {3: 9}
s= 11 su= 11 v=  284 j= 3 u=1 w=0 vc=   57 {3: 10}
s= 12 su= 12 v=  341 j= 3 u=1 w=0 vc=  171 {3: 11}
s= 13 su= 12 v=  172 j= 5 u=0 w=0 vc=   43 {3: 11, 5: 1}
s= 14 su= 13 v=    5 j= 3 u=1 w=0 vc=    5 {3: 12, 5: 1}
s= 15 su= 14 v=   15 j= 3 u=1 w=0 vc=   15 {3: 13, 5: 1}
s= 16 su= 15 v=   45 j= 3 u=1 w=0 vc=   45 {3: 14, 5: 1}
s= 17 su= 16 v=  135 j= 3 u=1 w=0 vc=   29 {3: 15, 5: 1}
s= 18 su= 17 v=  405 j= 3 u=1 w=0 vc=   87 {3: 16, 5: 1}
s= 19 su= 18 v=  193 j= 3 u=1 w=0 vc=   11 {3: 17, 5: 1}
s= 20 su= 19 v=   68 j= 3 u=1 w=0 vc=   17 {3: 18, 5: 1}
s= 21 su= 20 v=  204 j= 3 u=1 w=0 vc=   51 {3: 19, 5: 1}
s= 22 su= 21 v=  101 j= 3 u=1 w=0 vc=   83 {3: 20, 5: 1}
s= 23 su= 22 v=  303 j= 3 u=1 w=0 vc=   95 {3: 21, 5: 1}
s= 24 su= 23 v=  398 j= 3 u=1 w=0 vc=   59 {3: 22, 5: 1}
s= 25 su= 24 v=  172 j= 3 u=1 w=0 vc=   43 {3: 23, 5: 1}
s= 26 su= 24 v=  349 j= 5 u=0 w=0 vc=  187 {3: 23, 5: 2}
s= 27 su= 25 v=   25 j= 3 u=1 w=0 vc=   25 {3: 24, 5: 2}
s= 28 su= 26 v=   75 j= 3 u=1 w=0 vc=   75 {3: 25, 5: 2}
s= 29 su= 27 v=  225 j= 3 u=1 w=0 vc=   23 {3: 26, 5: 2}
s= 30 su= 28 v=  164 j= 3 u=1 w=0 vc=   41 {3: 27, 5: 2}
s= 31 su= 29 v=  492 j= 3 u=1 w=0 vc=  123 {3: 28, 5: 2}
s= 32 su= 30 v=  454 j= 3 u=1 w=0 vc=   55 {3: 29, 5: 2}
s= 33 su= 31 v=  340 j= 3 u=1 w=0 vc=   85 {3: 30, 5: 2}
s= 34 su= 32 v=  509 j= 3 u=1 w=0 vc=  255 {3: 31, 5: 2}
s= 35 su= 33 v=  505 j= 3 u=1 w=0 vc=  127 {3: 32, 5: 2}
s= 36 su= 34 v=  493 j= 3 u=1 w=0 vc=  223 {3: 33, 5: 2}
s= 37 su= 35 v=  457 j= 3 u=1 w=0 vc=   79 {3: 34, 5: 2}
s= 38 su= 36 v=  349 j= 3 u=1 w=0 vc=  187 {3: 35, 5: 2}
s= 39 su= 36 v=  212 j= 5 u=0 w=0 vc=   53 {3: 35, 5: 3}
s= 40 su= 37 v=  125 j= 3 u=1 w=0 vc=  125 {3: 36, 5: 3}
s= 41 su= 38 v=  375 j= 3 u=1 w=0 vc=  239 {3: 37, 5: 3}
s= 42 su= 39 v=  103 j= 3 u=1 w=1 vc=  103 {3: 38, 5: 3}
s= 43 su= 40 v=  309 j= 3 u=1 w=1 vc=  107 {3: 39, 5: 3}
s= 44 su= 41 v=  416 j= 3 u=1 w=1 vc=   13 {3: 40, 5: 3}
s= 45 su= 42 v=  226 j= 3 u=1 w=1 vc=   39 {3: 41, 5: 3}
s= 46 su= 43 v=  167 j= 3 u=1 w=1 vc=  117 {3: 42, 5: 3}
s= 47 su= 44 v=  501 j= 3 u=1 w=1 vc=  191 {3: 43, 5: 3}
s= 48 su= 45 v=  481 j= 3 u=1 w=1 vc=   31 {3: 44, 5: 3}
s= 49 su= 46 v=  421 j= 3 u=1 w=1 vc=   93 {3: 45, 5: 3}
s= 50 su= 47 v=  241 j= 3 u=1 w=1 vc=   47 {3: 46, 5: 3}
s= 51 su= 48 v=  212 j= 3 u=1 w=1 vc=   53 {3: 47, 5: 3}

这有效地通过晶格结构形成路径,为发电机中的每个元件具有一个尺寸。以下是\(n = 8 \)的结果路径:

这里,用于\(j = 13 \)的虚线表示用于确定原始多项式的目的的过渡;结果值(在这种情况下,比例比例23)将在序列中稍后拾取。

下面示出了此乘法组中的所有\(j = 7 \)和\(j = 13 \)转换的完整图片,具体行表示\(j = 7 \)和虚线表示乘法的乘法(j = 13 \):

您可以在此处看到\(j = 7 \)有订单8(通过循环需要8个转换),\(j = 13 \)有订单4(例如:循环,1,13,53,59 )但它们并不完全正交,因为乘以\(j = 13 \)之字形来回乘法在两个脚纤木之间(\(j = 7 \)的较低和上周期))。

我不知道数学家如何形成非环群;这是一个相对简单的一个’甚至表现出由其两个元素产生的周期甚至忙碌。

无论如何,这种方法似乎很好;一世 ’在一堆不同的\(n \)的束上运行它,包括完整的速度达到\(n = 25 \),并且似乎找到了所有完整运行的一组小型次要媒体。 (可选的j参数由对\((j,r_j)\)填充,它是抽取比率乘数\(j \)及其运行长度\(r_j \); \(r_j \)的值是初始化的稍后纠正,一旦到目前为止识别的生成集的元素实现了完整的周期。例如,我们可以使用\(j = 3 \)来获得450个原语的周期多项式和\(j = {3,5 \} \)来获得\(450 \ times 60 = 27000 \)原始多项式的周期; \(j = {3,5,7 \} \)颠簸它高达648000,\(j = {3,5,7,111 \})达到全套1296000原始多项式,用于\(n = 25 \)。)

for N in xrange(3,64):
    su = 0
    J=[]
    for y in yield_primitive_polynomial_search_plan(N,J):
        su += y[2]
        nmax = 1300000 if N <= 25 else 25000 
        if su >= nmax:
            break
    print "N=%d, su=%d, J=%s" % (N,su,J)
N=3, su=2, J=[(3, 2)]
N=4, su=2, J=[(7, 2)]
N=5, su=6, J=[(3, 6)]
N=6, su=6, J=[(5, 6)]
N=7, su=18, J=[(3, 18)]
N=8, su=16, J=[(7, 8), (13, 2)]
N=9, su=48, J=[(3, 12), (5, 4)]
N=10, su=60, J=[(5, 30), (13, 2)]
N=11, su=176, J=[(3, 88), (5, 2)]
N=12, su=144, J=[(11, 12), (17, 6), (23, 2)]
N=13, su=630, J=[(3, 70), (5, 3), (7, 3)]
N=14, su=756, J=[(5, 42), (7, 18)]
N=15, su=1800, J=[(3, 150), (5, 6), (11, 2)]
N=16, su=2048, J=[(7, 128), (11, 8), (13, 2)]
N=17, su=7710, J=[(3, 7710)]
N=18, su=7776, J=[(5, 72), (11, 18), (17, 3), (23, 2)]
N=19, su=27594, J=[(3, 27594)]
N=20, su=24000, J=[(7, 120), (13, 10), (17, 10), (19, 2)]
N=21, su=84672, J=[(3, 504), (5, 84), (13, 2)]
N=22, su=120032, J=[(5, 1364), (7, 44), (19, 2)]
N=23, su=356960, J=[(3, 178480), (5, 2)]
N=24, su=276480, J=[(11, 24), (19, 48), (23, 10), (29, 2), (31, 2), (37, 6)]
N=25, su=1296000, J=[(3, 450), (5, 60), (7, 24), (11, 2)]
N=26, su=25000, J=[(5, 2730), (7, 0)]
N=27, su=25000, J=[(3, 14592), (5, 0)]
N=28, su=25000, J=[(7, 252), (11, 0)]
N=29, su=25000, J=[(3, 0)]
N=30, su=25000, J=[(5, 1650), (13, 0)]
N=31, su=25000, J=[(3, 0)]
N=32, su=25000, J=[(7, 0)]
N=33, su=25000, J=[(3, 0)]
N=34, su=25000, J=[(5, 0)]
N=35, su=25000, J=[(3, 0)]
N=36, su=25000, J=[(11, 216), (17, 36), (23, 0)]
N=37, su=25000, J=[(3, 0)]
N=38, su=25000, J=[(5, 0)]
N=39, su=25000, J=[(3, 0)]
N=40, su=25000, J=[(7, 0)]
N=41, su=25000, J=[(3, 0)]
N=42, su=25000, J=[(5, 0)]
N=43, su=25000, J=[(3, 0)]
N=44, su=25000, J=[(7, 0)]
N=45, su=25000, J=[(3, 0)]
N=46, su=25000, J=[(5, 0)]
N=47, su=25000, J=[(3, 0)]
N=48, su=25000, J=[(11, 1344), (19, 0)]
N=49, su=25000, J=[(3, 0)]
N=50, su=25000, J=[(5, 8100), (7, 0)]
N=51, su=25000, J=[(3, 0)]
N=52, su=25000, J=[(7, 0)]
N=53, su=25000, J=[(3, 0)]
N=54, su=25000, J=[(5, 0)]
N=55, su=25000, J=[(3, 0)]
N=56, su=25000, J=[(7, 0)]
N=57, su=25000, J=[(3, 0)]
N=58, su=25000, J=[(5, 0)]
N=59, su=25000, J=[(3, 0)]
N=60, su=25000, J=[(17, 6600), (19, 0)]
N=61, su=25000, J=[(3, 0)]
N=62, su=25000, J=[(5, 0)]
N=63, su=25000, J=[(3, 0)]

好的,但我们避风港’实际上发现了任何原始多项式。我们 ’刚刚发现覆盖全套的抽取比率。为了找到多项式本身,我们必须采取LFSR样品并通过Berlekamp-Massey运行它们。请记住,St.Ives算法收集\(j(2n-1)+ 1 \)样本,所以对于\(n = 8 \)和\(j = 7 \),我们需要生成位\(b [0 ] \)通过\(b [105] \)并保留样品\(b [0],b [7],B [14],B [21],B [28],B [35],B [42] ],\ Ldots,B [98],B [105] \)进入Berlekamp-Massey。

从搜索计划算法的使用位输出只是告诉您新的抽取率是否会产生我们应该报告的原始多项式;我们仍然必须运行Berlekamp-Massey,以便我们为以下抽取比具有新的原始多项式。

让’s do it!

def prim_construct_st_ives(N,p1,maxcount=None):
    """
    Implement the St. Ives algorithm
    (di porto,guida和montolivo, by computing 1+(2N-1)j bits
    in order to decimate by a small ratio j, varying j when necessary)
    Stateful: depends on previous polynomial each time.
    """
    qr = GF2QuotientRing(p1)
    poly= p1
    count = 0
    for v,j,usage_bit,w in yield_primitive_polynomial_search_plan(N):
        if j == 0:
            # we get this the very beginning; 
            # just output the original polynomial
            yield v,poly
            count += 1
            continue
        # Generate enough bits in the LFSR output sequence,
        # decimated by j
        e = 1
        bits = [0]
        for _ in xrange(2*N-1):
            for i in xrange(j):
                e = qr.lshiftraw1(e)
            bits.append(e>>(N-1))
        poly,N_expected = berlekamp_massey(bits)
        qr = GF2QuotientRing(poly)
        
        # Only report the polynomial if the usage bit is 1
        if usage_bit:
            yield v,poly
            count += 1
            if count == maxcount:
                break

st_ives_polynomials = sorted(collect_prim_poly(
                              prim_construct_st_ives, 8, verbose=True))
prim_construct_1_polynomials = sorted(collect_prim_poly(
                              prim_construct_1, 8))

print st_ives_polynomials
print prim_construct_1_polynomials
print("Match? "+("YES" 
                if st_ives_polynomials == prim_construct_1_polynomials 
                else "NO"))
j =   1: 11d
j =   7: 169
j =  49: 165
j =  88: 1e7
j = 106: 187
j = 232: 18d
j =  94: 1a9
j = 148: 15f
j = 208: 12b
j = 181: 1f5
j = 247: 171
j = 199: 12d
j = 118: 14d
j =  61: 1cf
j = 172: 1c3
j = 184: 163
16 primitive polynomials found
[285, 299, 301, 333, 351, 355, 357, 361, 369, 391, 397, 425, 451, 463, 487, 501]
[285, 299, 301, 333, 351, 355, 357, 361, 369, 391, 397, 425, 451, 463, 487, 501]
Match? YES

哼哼,它’当事情正确地解决时,很无聊。

只是为了踢,让’s在\(n = 20 \)上尝试它,应该生成24000个不同的原始多项式,并看看它需要多长时间。

import time

def collect_algorithm_stats(algorithm, N, maxcount=None):
    p1 = find_first_primitive_polynomial(N)
    t0 = time.time()
    result = collect_prim_poly(algorithm, N, p1=p1, maxcount=maxcount)
    t1 = time.time()
    print "Elapsed time: %fs" % (t1-t0)
    print "%d polynomials collected" % len(result)
    print ("%d of them are degree N" %
           sum(1 for p in result if (p >> N) == 1))
    print "%d of them are unique" % len(set(result))
    return result

collect_algorithm_stats(prim_construct_st_ives, 20);
Elapsed time: 16.821543s
24000 polynomials collected
24000 of them are degree N
24000 of them are unique

戈登’s Method

( 新的 —添加到本文2018年8月26日)

1976年,J.A.Gordon提出了一种聪明的技术,用于直接计算最小多项式。这可能是所有这些技术的最简单。这里’s my take on it:

We already mentioned (and used in prim_construct_5) the identity that the minimum polynomial of \( u=x^j \) is

$$ p_j(x)=(x-u)(x-u ^ 2)(x-u ^ 4)\ ldots(x-u ^ {2 ^ {n-1}})$$

我们去了它作为多项式的\(x \)。那里’不过,这里是一个问题。变量\(x \)是字段中的一个元素\(gf(2)[x] / p_1(x)\)— it’我们指定的特殊元素,以便能够描述此字段中的所有非零元素,无论是\(x \)的权力,还是作为\(x \)的权力的总和,或者\((n- 1)\)电力。但它’仍然是一个元素。一种 众所周知 元素。如果我们想描述一个未知变量的多项式,我们应该严格地说,使用不同的变量:

$$ p_j(y)=(y-u)(y-u ^ 2)(y-u ^ 4)\ ldots(y-u ^ {2 ^ {n-1}})$$

其中\(y \)是我们可以替换的占位符:\(y = 0,y = 1,y = u,y = x ^ n \)等。

通过将\(y \)保持为占位符,当我们执行代数来弄清楚\(y \)的系数,\(p_j(y)= a_0 + a_1y + a_2y ^ 2 + a_3y ^ 3 + \ ldots a_ny ^ n \),我们必须在\((n + 1)\)元素向量中解释这些系数作为单独的数字。例如,\((yu)(yu ^ 2)= y ^ 2 +(u + u ^ 2)y + u ^ 3 \)涉及通过使用\(y \)计算二次多项式的三个系数好老 箔法 to multiply two linear terms. Each coefficient is an element in \( GF(2)[x]/p_1(x) \), namely \( a_2 = 1, a_1 = u+u^2, a_0=u^3 \). In prim_construct_5 we maintain a list of coefficients, getting longer by one each time we multiply by a new linear term, until we get the \( N+1 \) coefficients of the \( N \)th degree polynomial we are looking for.

戈登’S Insight是替代\(y = x \)并直接评估多项式。换句话说,拍摄字段元素\((x-u)\)并乘以元素\((x-u ^ 2)\),然后乘以元素\((x-u ^ 4)\)等。我们不’T必须制作一份不断变长的元素列表,相反,我们只需维护一个部分产品,它是\(GF(2)[x] / p_1(x))的元素。它’S类类似于评估\(p(2)=(2 + 6)(2 + 36)= 304 \)而不是\(p(x)=(x + 6)(x + 36)= x ^ 2 + 42x + 216 \)我们必须维护这三个单独的系数并假装\(x \)可以是任何东西。更多的会计方式。

这里的思想吹入元素是我们最终得到了结果,这是\(gf(2)[x] / p_1(x))的元素。它’不是一个未知占位符的多项式。它’s a polynomial of a known element \( x \) of the field. For example if we do this with \( p_1(x) = x^8 + x^4 + x^3 + x^2 + 1 \) (our good old 11d polynomial) and \( u=x^7 \), calculating \( p_j(x) = (x-u)(x-u^2)(x-u^4)(x-u^8)(x-u^{16})(x-u^{32})(x-u^{64})(x-u^{128}) \) and simplify in terms of powers \( 1,x,x^2,x^3,x^4,x^5,x^6,x^7 \) then we’ll get the element \( p_j(x) = x^6 + x^5 + x^4 + x^2 \) (hex 74). But we’re looking for an 8th-degree polynomial. Since the field calculations are \( \bmod p_1(x) \), we just add in \( p_1(x) = x^8 + x^4 + x^3 + x^2 + 1 \) and come up with \( p_j(x) = x^8 + x^6 + x^5 + x^3 + 1 \) (hex 169).

It’如此思维 - 吹你甚至可能无法实现它’s mind-blowing. Let’尝试一些不同的东西:考虑复杂的数字\(1 + 2J。\)这是一个 数字 。值\(j = \ sqrt {-1} \)只是我们以符号形式携带的东西,以便复杂的数字具有两度自由度。但它是一个数字,具有\(\ sqrt {5} \)的大小。现在有人来找你并说“嘿,拍摄该号码并替换\(j = x \)以获得多项式\(1 + 2x \)。或者更好,只要写入\(1 + 2J \)并将其视为\(j \)中的多项式,即使\(j \)只是一个 数字 ,除了现在’s not, it’在线性多项式的一个未知的占位符。”

戈登’s method works for 任何 \(u \)的值,不仅仅是产生原始多项式的那些。只要我们没有,我们将继续循环通过\(u,u ^ 2,\ ldots \)’重复一个值,当我们这样做时,我们停下来。因此,原始多项式将是学位\(n \),但不产生原始多项式的元素可以是较小的程度\(m \),如果\(u = u ^ {2 ^ ^ m} \)对于一些\(M.<n \)。如果我们确实使它进行学位\(n \),我们需要在\(n \)th度多项式\(p_1(x)\)中添加,以便结果是\(n \)th度多项式。我们甚至可以使用\(u = 0 \)(其具有最小多项式\(x \))或\(u ​​= 1 \)(具有最小多项式\(x + 1 \))。但是对于原始多项式一代,我们’始终将获得程度的多项式\(n \)。

戈登的执行时间’对于每个原始多项式的方法大致\(O(n ^ 3)\):我们有\(n-1 \)乘法到正方形\(u \)总共\(n-1 \)次,以及\(n-1 \)乘法计算结果\(n \)术语的乘积;每个乘法都需要大致\(O(n ^ 2)\)时间。

无论如何,它在这里行动:

def minimal_polynomial_gordon(qr, u):
    """
    Gordon's method for calculating minimal polynomial of u:
    (u+x)(u^2+x)(u^4+x) ... (u^(2^(M-1))+x)
    where M is the order of this sequence, = N if u is primitive.
    """
    N = qr.degree
    uk = u
    p = uk^2  
    # "2" represents x, and ^ is XOR which represents addition,
    # so we're really just adding x to some power of u.
    for M in xrange(N-1):
        uk = qr.mulraw(uk, uk)
        if uk == u:
            break
        p = qr.mulraw(p, uk^2)
    else:
        p ^= qr.coeffs
    return p

def prim_construct_gordon(N,p1,maxcount=None):
    """
    Use Gordon's method for calculating minimal polynomial.
    Stateful, but only in the updating of u=x^j
    by x^2 each iteration.
    """
    qr = GF2QuotientRing(p1)
    poly= p1
    count = 1
    yield (1,p1)
    u = 2 
    m = (1<<N) - 1
    for j in xrange(3,1<<(N-1),2):
        u = qr.lshiftraw1(qr.lshiftraw1(u))
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
            
        yield (j, minimal_polynomial_gordon(qr, u))
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_gordon,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found
collect_algorithm_stats(prim_construct_gordon, 20);
Elapsed time: 22.713530s
24000 polynomials collected
24000 of them are degree N
24000 of them are unique

sh’s Algorithm

维克多犬疼 —谁的画面让我想起了大卫·拜尔的谈话头—是纽约州教授和在离散数学和密码学区的着名的数学家。他于1999年发表了一篇论文,而IBM在瑞士的研究科学家称为 有效地区代数扩展中最小多项式的高效计算。如果您已经遵循了迄今为止有限的现场数学,您应该能够遵循大部分群’纸。纸张的目标是给出一个元素\(\ sigma \中的k [\ alpha] \),找到最小的\(\ sigma \)的多项式,即声音多项式\(g(x)\)最小程度\(g(\ sigma)= 0 \)。 (提示:他使用比我使用的略微不同的符号,即\(k [\ alpha] \)作为商圈的速写\(k [x] /(p(x))\),其中\ (k \)可以是任何有限的字段,不仅仅是\(gf(2)\)我们’一直在使用。符号\(k [x] \)指的是包含\(k)中的系数的所有多项式的多项式环,而\(k [\ alpha] \)表示有限场\(k [x] /(p (x))\)其中\(\ alpha \)是\(p(x)\)的原始根。了解? \(k [\ alpha] \)=有限字段; \(k [x] \)=多项式环。 sh’S纸还进入了两个可变的字段元素,我稍微触及 第十一部分 ,并且是表单\(k [\ alpha] [\ beta] \),其中\(\ beta \)是字段的原始根目录,表示在字段中具有系数的Modulo \(p_2(x)\) (k [\ alpha] \)。让我的头部受伤。)

我在寻找计算最小多项式的算法时发现了本文,虽然它通过一些非常聪明而简单的数学完成,但在大约第四次读取之后,我终于意识到了为二进制字段\(k = gf(2)\ ),所有他’谈论的是相当于通过某些因子\(j \)在其中\(\ sigma = x ^ j \)的\(\ sigma = x ^ \)抽出LFSR输出序列,并计算抽取序列的足够元素以运行Berlekamp-Massey并恢复最小多项式。数学是比商品戒指更复杂 不是 fields, but here we’谈论原始多项式,这些多项式确实定义了有限田地,所以我们可以保持简单的事情。

关键洞察力基于另一个聪明 Paper,于1973年由Paterson和Stockmeyer发布,这找到了评估有限场元素的多项式的方法,并最大限度地减少该有限场中的乘法数。让’S表示你想评估\(U ^ 8 + U ^ 5 + U ^ 3 + u + 1. \)现在,使用Horner的正常方法’S规则将是评估\(U(U(U(U(U)+ 1))+ 1)+ 1)+ 1),其需要7乘以和4个添加。它’非常系统化;你从领先的多项式系数开始(在这种情况下,它以来 ’S Monic),以及彼此多项式系数,包括具有零系数的术语,乘以\(u \)并增加系数。 \(n \)乘以\(n \)th度多项式。但在有限的领域,繁殖与加法相比一点贵,并且减少这个数字很好。 Paterson和Stockmeyer.’S技术涉及构建一种体育形成; for \(p(u)= \ sum \ limits_ {i = 0} ^ {8} a_iu ^ i \)我们会写的

$$ \ begin {对齐} p(u)= u ^ 3(u ^ 3(&a_8u ^ 2 + a_7u + a_6)\ cr + (&A_5U ^ 2 + A_4U + A_3))\ CR + (&a_2u^2 + a_1u + a_0) \end{align}$$

这使得一个方形矩阵从系数出来。然后我们需要2个乘法来计算\(1,1,u,u ^ 2,u ^ 3 \),另一个两个乘以前两行的\(u ^ 3 \)乘以乘以\(^ 3 \)。在这种情况下,使用\(n = 8 \),我们只需要4个乘法,而不是Horner的7’统治。通常,乘数总数是\(o(\ sqrt {n})\)其中\(n \)是多项式的程度;如果\(n + 1 \)是一个完美的正方形,则需要完全\(2(\ sqrt {n + 1} -1)倍数,否则它有点贵,但并不多。

sh cites a paper by Brent and Kung for this technique, but they got it from Paterson and Stockmeyer’纸张和添加了一些东西。

sh uses the same approach for LFSR decimation —虽然他从未使用过LFSR或抽取的术语;群体中的条款’s paper are 电力投影转乘乘法,你不’需要担心这里。假设我们想要用抽取比率计算16个抽取位序列\(j \)。我们会需要:

$$ \ begin {matrix} \ {b [0],& b[j], &b[2j], &b[3j], \cr b[4j], & b[5j], &b[6j], &b[7j], \cr b[8j], &b[9j], &b[10j],&b[11j], \cr b[12j], &b[13j], &b[14j], &b[15j]\}. \end{matrix}$$

好吧,大交易,一切都在广场上漂亮。所以呢?

好吧,在 部分IX. 我谈到了跟踪奇偶校验和痕迹模式。这个想法是,您可以使用某个状态\(s = x ^ {ka} \)和一个代表时间提升的适当掩码\(m_a \)计算LFSR \(b [k] \)的输出。 (a \)单位;我将\(s \)的系数作为位向量,采用按位和\(m_a \),并采取结果的奇偶校验:\(b [k] = \ pi(s \ otimes m_a)\ )如果奇偶函数\(\ pi(x)\)如果其参数具有奇数为1位,则返回1,如果其参数具有1位的偶数,则为0。这是在使用\(GF(2)\)的非常快速的操作;它’s一个按位and,\(\ log_2 n \)偏移和xors来计算奇偶校验。

矩阵的第一行可以通过\(\ pi(1 \ otimes m_0),\ pi(x ^ j \ otimes m_0),\ pi(x ^ {2j} \ otimes m_0),\ pi(x ^ {3J} \ otimes m_0)。\)

相同的方法适用于 全部 矩阵的行,可以通过\(\ pi(1 \ otimes m_i),\ pi(x ^ j \ otimes m_i),\ pi(x ^ {2j} \ otimes m_i),\ pi(x ^ {3J} \ otimes m_i),\)其中\(i \)th行(从零计数计数)的掩码\(m_i \)是表示\(4Ji \)单位的提升的掩码。一世’我将在那里宣称(但不证明)’s一个简单的功能\(m(u)\)计算\(m_a = m(x ^ a),\)实际上我们’ll在几行Python中写下它,以便通过\(a \)单位延迟,我们只需要计算\(x ^ a \)然后计算\(m(x ^ a)\)。对于这个特殊的4×4矩阵意味着计算\(m(1),m(x ^ {4j}),m(x ^ {8j}),\)和\(m(x ^ {12j})\)。所有这一切的净效应是,如果我们被给出\(u = x ^ j \),我们只需要计算5个有限的字段乘法以获取我们需要运行Berlekamp-Massey的序列的所有16位:

  • 2乘以计算\(u ^ 2,u ^ 3 \)所以我们有\(\ {1,x ^ j,x ^ {2j},x ^ {3j},x ^ {3j} \)为每行,
  • 3乘以计算\(U ^ 4,u ^ 8,u ^ {12} \)所以我们可以为每个计算masks \(m_0,m_ {4j},m_ {8j},m_ {12j},\)一个排。

(如果您想知道这是如何对应的疾病’S纸:掩蔽和奇偶校验\(\ pi(u \ otimes m)\)类似于疾病’S POWER投影\(\左< M, u\right> \) —除了他使用\(v \)和\(\ sigma \)而不是\(m \)和\(u \)。掩模计算\(m = m(u)\)大致对应于剪切’s转置乘法\(v \ mapsto \ tau \ circ v \),虽然在我的情况下,而不是以前掩码的函数计算新掩码\(m_ {i + 1} = m(m_i,x ^ j )\),我只是乘以添加另一个因素\(x ^ {4j} \)和compute \(m_ {4ji} = m(x ^ {4ji})\))

一般来说,而不是4的行,我们使用行的行(k = \ lfloor \ sqrt {l} \ rfloor \),其中\(l = 2n \)是我们需要的比特数,以及我们需要的比特数,以及我们不’t必须计算平方根,我们只是通过循环弄清楚\(k \)的值,直到\((k + 1)^ 2>l \)当我们计算行权力\(\ {1,x ^ j,x ^ {2j},x ^ {3j},\ ldots,x ^ {(k-1)j} \} \)。)。)

让’s do it!

from libgf2.util import parity

def mask_from_lfsr_initial_state(field, s):
    """
    Computes mask M from initial state s=x^d.
    This allows b[k+d] = parity(M&(x^k))
    which will effectively advance a bit sequence
    by d units.
    
    This same function is built into the method
    libgf2.gf2.GF2TracePattern._mask_from_lfsr_initial_state
    """
    n = field.degree
    mask = 0
    for k in xrange(n):
        mask |= (s>>(n-1)) << k
        s = field.lshiftraw1(s)
    return mask

qr = GF2QuotientRing(0x11d)
u = 1
d = 3
states = [qr.lshiftraw(u,d*k) for k in xrange(8)]
print("init_states S=%s" % ' '.join('%02x' % s for s in states))
masks = [mask_from_lfsr_initial_state(qr, s) for s in states] 
print("mask M=       %s" % ' '.join('%02x' % m for m in masks))
for k in xrange(25):
    print("k=%2d u=%02x %d %s" % 
          (k,
           u,
           u>>7,
           ' '.join('%d'%parity(u&m) for m in masks[1:])))
    u = qr.lshiftraw1(u)
init_states S=01 08 40 3a cd 26 2d 75
mask M=       80 10 e2 1c 23 a4 74 0e
k= 0 u=01 0 0 0 0 1 0 0 0
k= 1 u=02 0 0 1 0 1 0 0 1
k= 2 u=04 0 0 0 1 0 1 1 1
k= 3 u=08 0 0 0 1 0 0 0 1
k= 4 u=10 0 1 0 1 0 0 1 0
k= 5 u=20 0 0 1 0 1 1 1 0
k= 6 u=40 0 0 1 0 0 0 1 0
k= 7 u=80 1 0 1 0 0 1 0 0
k= 8 u=1d 0 1 0 1 1 1 0 0
k= 9 u=3a 0 1 0 0 0 1 0 0
k=10 u=74 0 1 0 0 1 0 0 1
k=11 u=e8 1 0 1 1 1 0 0 1
k=12 u=cd 1 0 0 0 1 0 0 0
k=13 u=87 1 0 0 1 0 0 1 0
k=14 u=13 0 1 1 1 0 0 1 1
k=15 u=26 0 0 0 1 0 0 0 0
k=16 u=4c 0 0 1 0 0 1 0 0
k=17 u=98 1 1 1 0 0 1 1 1
k=18 u=2d 0 0 1 0 0 0 0 0
k=19 u=5a 0 1 0 0 1 0 0 0
k=20 u=b4 1 1 0 0 1 1 1 1
k=21 u=75 0 1 0 0 0 0 0 1
k=22 u=ea 1 0 0 1 0 0 0 0
k=23 u=c9 1 0 0 1 1 1 1 1
k=24 u=8f 1 0 0 0 0 0 1 1

这里 we have computed 8 different bit sequences, each one advanced by \( d=3 \) places beyond the previous one, and we do this by calculating masks \( M = m(x^{3i}) \) for \( i=0,1,2,3,4,5,6,7 \) where \( m(u) \) is the mask_from_lfsr_initial_state function. Now let’s使用此功能来帮助加速抽取,并将结果与​​直接但不太有效的抽取执行进行比较:

from libgf2.util import parity

def shoup_decimate(qr,j=None,u=None):
    n = qr.degree
    L = 2*n
    c = []
    # We're going to gather L decimated LFSR output bits
    if u is None:
        u = qr.lshiftraw(1,j)
    # construct powers of u=x^j from 1,u,u^2,...,u^k-1
    # where k = floor(sqrt(L))
    upowers = [1,u]
    k = 1
    uk = u
    while (k+1)*(k+1) <= L:
        k += 1
        uk = qr.mulraw(uk,u)
        upowers.append(uk)
    upowers.pop()
    # find nrow = ceil(n/k)
    nrow = (L+k-1)//k
    c = []
    uik = 1
    for i in xrange(nrow):
        # Each row uses a different mask
        # to represent a delay of i*j*k units
        mask = mask_from_lfsr_initial_state(qr,uik)
        for up in upowers:
            c.append(parity(mask&up))
            if len(c) >= L:
                break
        uik = qr.mulraw(uik,uk)
    return c

def simple_decimate(qr,j):
    n = qr.degree
    u = qr.lshiftraw(1,j)
    uk = 1
    result = []
    for k in xrange(2*n):
        result.append(uk >> (n-1))
        uk = qr.mulraw(uk,u)
    return result

for poly in [0x11d, 0x211]:
    qr = GF2QuotientRing(poly)
    for j in [7,13,19]:
        print "qr=%s (poly=%x), j=%d" % (qr,poly,j)
        print simple_decimate(qr,j)
        print shoup_decimate(qr,j=j)
qr=GF2QuotientRing(0b100011101) (poly=11d), j=7
[0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0]
[0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0]
qr=GF2QuotientRing(0b100011101) (poly=11d), j=13
[0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0]
[0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0]
qr=GF2QuotientRing(0b100011101) (poly=11d), j=19
[0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0]
[0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0]
qr=GF2QuotientRing(0b1000010001) (poly=211), j=7
[0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1]
[0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1]
qr=GF2QuotientRing(0b1000010001) (poly=211), j=13
[0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1]
[0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1]
qr=GF2QuotientRing(0b1000010001) (poly=211), j=19
[0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0]
[0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0]

完美的!现在让步’S使用它来找到一些原始的多项式:

def prim_construct_shoup(N,p1,maxcount=None):
    """
    Use Shoup's decimation technique.
    Stateful, but only in the updating of u=x^j
    by x^2 each iteration.
    """
    qr = GF2QuotientRing(p1)
    poly= p1
    count = 1
    yield (1,p1)
    u = 2 
    m = (1<<N) - 1
    for j in xrange(3,1<<(N-1),2):
        u = qr.lshiftraw1(qr.lshiftraw1(u))
        if j > smallest_conjugate(j,N):
            continue
        if gcd(j,m) != 1:
            continue
        bits = shoup_decimate(qr, u=u)
        pj,nj = berlekamp_massey(bits)
        assert nj == N
        yield (j,pj)
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_shoup,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

就像其他人一样!

collect_algorithm_stats(prim_construct_shoup, 20);
Elapsed time: 25.085867s
24000 polynomials collected
24000 of them are degree N
24000 of them are unique

下降的土狼算法

最后,这是我尝试寻找原始多项式的速度改进,我称之为下降的土狼算法。这是一种合成方法,所以让’S看看相同的旧示例:\(n = 8 \)和\(p_1(x)= x ^ 8 + x ^ 4 + x ^ 3 + x ^ 2 + 1 \)。我们’重新使用BERLEKAMP-MASSEY算法确定给定针对从LFSR输出的每个\(j \)输出的特定比特序列的最小多项式。(p_1(x)\)。到目前为止,没有什么新鲜事。所以我们有办法在这里展示这一点’S辅助框架,用于显示序列的HTML表。

class Table(object):
    def __init__(self, content=None):
        self.content = content
    def html_content(self):
        return '' if self.content is None else self.content.html_content()
    def _repr_html_(self):
        return "<table>"+self.html_content()+"</table>"
    
class Row(object):
    def __init__(self, content=None, cssclass=None):
        self.content = content
        self.cssclass = cssclass
    def _repr_html_(self):
        return Table(self)._repr_html_()
    def html_content(self):
        try:
            content = self.content._repr_html_()
        except:
            content = '' if self.content is None else self.content
        return self.opentag+content+'</tr>'
    @property
    def opentag(self):
        return '<tr>' if self.cssclass is None else '<tr class="%s">' % self.cssclass
    def __add__(self, other):
        return Rows.concat(self, other)
    
class Rows(object):
    def __init__(self, rows):
        self.rows = tuple(rows)
    def _repr_html_(self):
        return Table(self)._repr_html_()
    def html_content(self):
        return '\n'.join(row.html_content() for row in self.rows)
    def __add__(self, other):
        return Rows.concat(self, other)
    def __iter__(self):
        return iter(self.rows)
    @staticmethod
    def concat(*items):
        return Rows(row for item in items for row in (item if isinstance(item,Rows) else [item]))
    
class Sequence(Row):
    def __init__(self, sequence=(), header=None, cssclass=None):
        self.sequence = sequence
        self.cssclass = cssclass
        self.header = header
    def html_content(self):
        return self.opentag+(
            '<th style="min-width:60px">'+self.header+'</th>' if self.header is not None else ''
        )+'<td>'+'</td><td>'.join('%d' % i for i in self.sequence)+'</td></tr>'
    
Sequence(xrange(16), header=r'\(j=1\)')
\(j = 1 \) 0123456789101112131415

以上的数字表示二进制位序列中的指标\(b [0],b [1],b [2],\ ldots b [15] \)。如果我们想要将这个序列占据了两倍,继续直到我们有16个样本\(b [0],b [2],b [4],\ ldots b [30] \),它看起来像这样:

Sequence(xrange(0,32,2), header=r'\(j=2\)')
\(j = 2 \) 024681012141618202224262830

出于我们的目的,此表格(显示指数)比位序列本身更具启发性,尽管我们也可以表明:

def bitseq(field, nbits, start_element=1, bitnum=None):
    if bitnum is None:
        bitnum = field.degree - 1
    e = start_element
    for _ in xrange(nbits):
        yield (e >> bitnum) & 1
        e = field.lshiftraw1(e)
        
qr = GF2QuotientRing(0x11d)
Sequence(list(bitseq(qr, 16)), header=r'\(j=1\)')
\(j = 1 \) 0000000100011100

使用Berlekamp-Massey的一般合成算法,给定一些抽取比率\(j \),必须计算与\(1,x ^ j,x ^ {2j},x ^ {3j},\ ldots \ )在喂食进入Berlekamp-Massey之前。让’S只是说我们想要使用的那一刻\(j = 1 \):

poly, n = berlekamp_massey(bitseq(qr,16))
print "j=%d: poly=%x, n=%d" % (1, poly, n)
j=1: poly=11d, n=8

那’琐事。如果我们想要减少\(j>1 \),一种简单但低效的方法(适用于小\(j \),正如我们在Stives算法中所看到的那样,只是计算\(j(2n-1)\)位并保持每个\ (j \)th bit:

def poly_from_inefficient_decimation(seq, j):
    items = []
    i = 0
    for item in seq:
        if (i % j) == 0:
            items.append(item)
        i += 1
    poly, n = berlekamp_massey(items)
    print "j=%2d: poly=%x, n=%d" % (j, poly, n)
    
def show_poly_and_table(field, j, **kwargs):
    n = field.degree
    seq = bitseq(field, 2*n*j)
    poly_from_inefficient_decimation(seq, j)
    return Sequence(xrange(0,2*n*j,j), header=r'\(j=%d\)'%j, **kwargs)
        
show_poly_and_table(qr, 1)
j= 1: poly=11d, n=8
\(j = 1 \) 0123456789101112131415
show_poly_and_table(qr, 7)
j= 7: poly=169, n=8
\(j = 7 \) 0714212835424956637077849198105
show_poly_and_table(qr, 13)
j=13: poly=12b, n=8
\(j = 13 \) 013263952657891104117130143156169182195

知道了?现在让步’s忽略了一瞬间,通常要求\(\ gcd(j,2 ^ n-1)= 1 \),只需在每行中显示奇数奇值:

Rows(show_poly_and_table(qr,j,cssclass = 'emphasis_rows') for j in xrange(1,16,2))
j= 1: poly=11d, n=8
j= 3: poly=177, n=8
j= 5: poly=1f3, n=8
j= 7: poly=169, n=8
j= 9: poly=1bd, n=8
j=11: poly=1e7, n=8
j=13: poly=12b, n=8
j=15: poly=1d7, n=8
\(j = 1 \) 0123456789101112131415
\(j = 3 \) 0369121518212427303336394245
\(j = 5 \) 051015202530354045505560657075
\(j = 7 \) 0714212835424956637077849198105
\(j = 9 \) 0918273645546372819099108117126135
\(j = 11 \) 0112233445566778899110121132143154165
\(j = 13 \) 013263952657891104117130143156169182195
\(j = 15 \) 0153045607590105120135150165180195210225

并非所有这些都会产生原始多项式;再次,我们需要\(\ gcd(j,2 ^ n-1)= 1 \)。但是,如果我们制作桌子,而不是查看表格的行,那就看出会发生什么:

Rows(show_poly_and_table(qr,j,cssclass = 'emphasis_columns') for j in xrange(1,16,2))
j= 1: poly=11d, n=8
j= 3: poly=177, n=8
j= 5: poly=1f3, n=8
j= 7: poly=169, n=8
j= 9: poly=1bd, n=8
j=11: poly=1e7, n=8
j=13: poly=12b, n=8
j=15: poly=1d7, n=8
\(j = 1 \) 0123456789101112131415
\(j = 3 \) 0369121518212427303336394245
\(j = 5 \) 051015202530354045505560657075
\(j = 7 \) 0714212835424956637077849198105
\(j = 9 \) 0918273645546372819099108117126135
\(j = 11 \) 0112233445566778899110121132143154165
\(j = 13 \) 013263952657891104117130143156169182195
\(j = 15 \) 0153045607590105120135150165180195210225

索引同等间隔,具有间隔\(0,2,4,6,8,\ ldots \),这意味着可以从原始移位寄存器的适当抽取来获得每列的比特。

下降的Coyote算法工作方式是首先水平工作,收集足够的初步信息来创建\(2n \)列,每个列的一个lfsr。 (我们’LL在一下显示我们实际上只需要\(n \)lfsrs。)

然后我们垂直工作,一次更新LFSR并一次生成一行。如果该行具有\(j \)值,例如\(\ gcd(j,2 ^ n-1)= 1 \),并且\(j \)的比特旋转处于其最小值,则我们运行berlekamp -Massey对每行生成的LFSR位。

那里 are three optimizations we can make here.

第一个是,而不是使用\(x ^ 0 = 1 \)启动LFSR状态,而是可以使用“natural”状态\(x ^ a \),使得\(x ^ ax ^ k \)=高比特\(x ^ ax ^ {2k} \)。我们暗示了这一点 在谈论跟踪奇偶阶段时,IX部分,提及在抽取时未改变的序列。如果我们这样做,则所有偶数列(第0列除外)将与早期奇数对应物具有相同的位序列,因此而不是需要跟踪\(2n \)不同的跟踪LFSR,我们只需要\(n \)不同的LFSR和钻头“natural”索引0.列2,4和8与列1相同;列6和12与柱3相同,依据。

大学教师’记住怎么办?和 libgf2 在Python中,我们可以使用 libgf2.gf2.GF2TracePattern.from_pattern(field, 1) 找到LFSR初始状态\(x ^ a \),其在抽取下的幂下具有两个的功率:

from libgf2.gf2 import GF2TracePattern

trp1 = GF2TracePattern.from_pattern(qr, 1)
s = trp1.lfsr_initial_state
n = qr.degree
print "LFSR(poly=%x) with init_state=%x" % (qr.coeffs, s)
for k in xrange(20):
    bk = qr.lshiftraw(s,k) >> (n-1)
    b2k = qr.lshiftraw(s,2*k) >> (n-1)
    b4k = qr.lshiftraw(s,4*k) >> (n-1)    
    print "k=%2d, b[k] = %d, b[2k] = %d, b[4k] = %d" % (k, bk, b2k, b4k)
LFSR(poly=11d) with init_state=4
k= 0, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 1, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 2, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 3, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 4, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 5, b[k] = 1, b[2k] = 1, b[4k] = 1
k= 6, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 7, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 8, b[k] = 0, b[2k] = 0, b[4k] = 0
k= 9, b[k] = 1, b[2k] = 1, b[4k] = 1
k=10, b[k] = 1, b[2k] = 1, b[4k] = 1
k=11, b[k] = 1, b[2k] = 1, b[4k] = 1
k=12, b[k] = 0, b[2k] = 0, b[4k] = 0
k=13, b[k] = 0, b[2k] = 0, b[4k] = 0
k=14, b[k] = 0, b[2k] = 0, b[4k] = 0
k=15, b[k] = 1, b[2k] = 1, b[4k] = 1
k=16, b[k] = 0, b[2k] = 0, b[4k] = 0
k=17, b[k] = 0, b[2k] = 0, b[4k] = 0
k=18, b[k] = 1, b[2k] = 1, b[4k] = 1
k=19, b[k] = 0, b[2k] = 0, b[4k] = 0

第二优化是,而不是从索引中获取我们的原始序列,而不是从索引\(1,3,5,7,\ ldots \)(每次新行为两次更新LFSR),而是使用状态\(x ^ b \)其中\(b = a + 2 ^ {n-1} \),因为来自\(x ^ b,x ^ + 1},x ^ {b + 2},x的相应高位^ {b + 3},\ ldots \)是\(x ^ {a + 2 ^ {n-1}},x ^ {a + 2 ^ {n-1} +1},x ^ {a + 2 ^ {n-1} +2},x ^ {a + 2 ^ {n-1} +3},\ ldots \),并且因为高比特\(x ^ {a +} \)是与\(x ^ {a + 2k} \)相同,这意味着我们的序列与\(x ^ {a + 2 ^ n}的高比特相同,x ^ {a + 2 ^ n +2},x ^ {a + 2 ^ n + 4},x ^ {a + 2 ^ n + 6},\ ldots \),并且由于整个序列重复modulo \(2 ^ n-1 \),这与高比特(x ^ {a + 1},x ^ {a + 3},x ^ {a + 5},x ^ {a + 7}相同。… \)

实际上,我们在我们的序列中使用了这种方程式的\(k \ \)th位,其中“hb”代表位矢量表示的高位:

$$ \ begin {align} b[k] &= \ operatorName {hb}(x ^ {a + 2 ^ {n-1} + k})\ cr &= \ OperatorName {Hb}(x ^ {a + 2(2 ^ {n-1} + k)})\ cr &= \ OperatorName {Hb}(x ^ {a + 2 ^ n + 2k})\ cr &= \ operatorName {hb}(x ^ {a + 2k + 1})\ cr \end{align}$$

我们可以尝试两种“direct”计算方法\(x ^ {a + 2k + 1} \)以及“efficient”使用高位的方法\(x ^ {a + 2 ^ {n-1} + k} \)(“efficient”由于我们可以通过每次\(k \)增加1)通过执行一次LFSR更新来计算此问题:

sshift = qr.lshiftraw(s, 1<<(n-1))
for k in xrange(1,40,2):
    bka = qr.lshiftraw(s,k) >> (n-1)
    bkb = sshift >> (n-1)
    sshift = qr.lshiftraw1(sshift)
    print "k=%2d, b[k] direct = %d, b[k] efficient = %d" % (k, bka, bkb)
k= 1, b[k] direct = 0, b[k] efficient = 0
k= 3, b[k] direct = 0, b[k] efficient = 0
k= 5, b[k] direct = 1, b[k] efficient = 1
k= 7, b[k] direct = 0, b[k] efficient = 0
k= 9, b[k] direct = 1, b[k] efficient = 1
k=11, b[k] direct = 1, b[k] efficient = 1
k=13, b[k] direct = 0, b[k] efficient = 0
k=15, b[k] direct = 1, b[k] efficient = 1
k=17, b[k] direct = 0, b[k] efficient = 0
k=19, b[k] direct = 0, b[k] efficient = 0
k=21, b[k] direct = 1, b[k] efficient = 1
k=23, b[k] direct = 0, b[k] efficient = 0
k=25, b[k] direct = 0, b[k] efficient = 0
k=27, b[k] direct = 0, b[k] efficient = 0
k=29, b[k] direct = 1, b[k] efficient = 1
k=31, b[k] direct = 0, b[k] efficient = 0
k=33, b[k] direct = 1, b[k] efficient = 1
k=35, b[k] direct = 0, b[k] efficient = 0
k=37, b[k] direct = 0, b[k] efficient = 0
k=39, b[k] direct = 1, b[k] efficient = 1

第三种优化相对较小;我们将初始状态乘以\(x \),以便我们可以采用LFSR状态的低位,而不是取出LFSR状态的高位。 (计算地存在’s的差异很小,但是一些处理器更容易拍摄低位,特别是如果有问题的LFSR需要几个机器词来表示。)这对于具有统一系数的多项式的Galois-Decionds LFSR始终如此(对于所有原始多项式,其中包括),因为当您具有高位的LFSR(b [k] \)时,在下次更新后,低位\(b_0 [k + 1] \)是始终等于\(b [k] \)。就有限字段元素而言,\(\ OperatorName {Hb}(u)= \ OperatorName {LB}(UX)\)。

qr = GF2QuotientRing(0x11d)

S0 = 0x1
S1 = 0x2
print "\nVerifying:"

for k in xrange(20):
    print("k=%2d, S0<<k=%02x, S1<<k=%02x, hb(S0<<k)=%d, lb(S1<<k)=%d"
          % (k, S0, S1, S0 >> 7, S1 &1))
    S1 = qr.lshiftraw1(S1)
    S0 = qr.lshiftraw1(S0)
Verifying:
k= 0, S0<<k=01, S1<<k=02, hb(S0<<k)=0, lb(S1<<k)=0
k= 1, S0<<k=02, S1<<k=04, hb(S0<<k)=0, lb(S1<<k)=0
k= 2, S0<<k=04, S1<<k=08, hb(S0<<k)=0, lb(S1<<k)=0
k= 3, S0<<k=08, S1<<k=10, hb(S0<<k)=0, lb(S1<<k)=0
k= 4, S0<<k=10, S1<<k=20, hb(S0<<k)=0, lb(S1<<k)=0
k= 5, S0<<k=20, S1<<k=40, hb(S0<<k)=0, lb(S1<<k)=0
k= 6, S0<<k=40, S1<<k=80, hb(S0<<k)=0, lb(S1<<k)=0
k= 7, S0<<k=80, S1<<k=1d, hb(S0<<k)=1, lb(S1<<k)=1
k= 8, S0<<k=1d, S1<<k=3a, hb(S0<<k)=0, lb(S1<<k)=0
k= 9, S0<<k=3a, S1<<k=74, hb(S0<<k)=0, lb(S1<<k)=0
k=10, S0<<k=74, S1<<k=e8, hb(S0<<k)=0, lb(S1<<k)=0
k=11, S0<<k=e8, S1<<k=cd, hb(S0<<k)=1, lb(S1<<k)=1
k=12, S0<<k=cd, S1<<k=87, hb(S0<<k)=1, lb(S1<<k)=1
k=13, S0<<k=87, S1<<k=13, hb(S0<<k)=1, lb(S1<<k)=1
k=14, S0<<k=13, S1<<k=26, hb(S0<<k)=0, lb(S1<<k)=0
k=15, S0<<k=26, S1<<k=4c, hb(S0<<k)=0, lb(S1<<k)=0
k=16, S0<<k=4c, S1<<k=98, hb(S0<<k)=0, lb(S1<<k)=0
k=17, S0<<k=98, S1<<k=2d, hb(S0<<k)=1, lb(S1<<k)=1
k=18, S0<<k=2d, S1<<k=5a, hb(S0<<k)=0, lb(S1<<k)=0
k=19, S0<<k=5a, S1<<k=b4, hb(S0<<k)=0, lb(S1<<k)=0

如果我们组合所有三个优化,则启动LFSR状态是\(x ^ {a + 1 + 2 ^ {n-1} \),所以我们可以计算

$$ \ begin {align} \ operatorname {lb}(x ^ {a + 1 + 2 ^ {n-1}} x ^ k)&= \ OperatorName {Hb}(x ^ {a + 2 ^ {n-1}} x ^ k)\ cr &= \ OperatorName {Hb}(x ^ {a + 2 ^ {n}} x ^ {2k})\ cr &= \ OperatorName {Hb}(x ^ ax ^ {2k + 1})。 \end{align}$$

这将启动每个LFSR,以便当我们采取状态的低位时,然后更新,我们将产生序列\(b [1],b [3],b [5],\ ldots \)。但到我们拥有所有\(2n \)列,我们’已经将所有多项式识别到\(j = 2n-1 \),所以我们想跳过这个序列的第一个\(n \)条款并以\(b [2n + 1] \)开始“vertical”落钴算法的阶段。 (在取出状态的低位之前,下面更新的实现,并跳过第一个\(n-1 \)术语,因此起始值是\(x ^ {a + n + 2 ^ {n-1}}。 \))

So overall,下降的土狼算法works like this:

  • 给定:程度的多项式\(p_1(x)\)(n \)
  • 第1步:(开始“horizontal”阶段)找到LFSR的第一个\(1+(2N-1)^ 2 \)输出位,具有特征多项式\(p_1(x)\)
  • 第2步:初始化\(j = 1 \)
  • 第3步:如果\(j> 2N \) goto step 7
  • 第4步:如果\(j = 1 \)那么我们已经知道了\(p_1(x)\);否则使用Berkelamp-Massey算法计算在步骤1中计算的输出位(P_J(x)\)
  • 步骤5:如果\(\ gcd(j,2 ^ n-1)= 1 \)和\(j \)是\(n \)位的按位旋转下的最小值(保证if \(j<2 ^ {\ lfloor n / 2 \ rfloor} \)),然后报告\(p_j(x)\)作为原始多项式
  • 第6步:SET \(j \ refrearrow j + 2 \)和goto step 3
  • 步骤7:对于每个\(p_j(x)\):
    • 计算商圈中的自然\(x ^ a \)\(gf(2)[x] / p_j(x)\),使得高比特\(x ^ ax ^ k \)=高比特\(x ^ ax ^ {2k} \)全部\(k \)
    • 如果\(j = 1 \)然后设置\(b_0 \)=高位\(x ^ a \)
    • 初始化lfsr状态\(s_j = x ^ {a + n + 2 ^ {n-1}} \)
  • 第8步:(开始“vertical” phase) Goto step 10
  • 第9步:SET \(J \ Lettrarrow J + 2 \)
  • 第10步:if \(j> 2^{N-1} \), stop.
  • 步骤11:更新所有奇数的状态\(s_i \)(i< 2N \)
  • 步骤12:如果\(\ gcd(j,2 ^ n-1)= 1 \)和\(j \)是\(n \)位的按位旋转下的最小值,则转到步骤13,否则\(j \)不对应于原始多项式,或者我们’在之前已经遇到过,我们转到步骤9。
  • 第13步:为所有奇数计算\(b_i \)(i<2n \)作为低点\(s_i \)
  • 第14步:所有非零偶联的SET \(b_i = b _ {(i / 2)} \)(i \ leq 2n \)
  • 步骤15:计算\(p_j \)在所有\(b_i \)上运行berlekamp-massey算法
  • 步骤16:报告\(p_j \)作为原始多项式
  • 第17步:转到步骤9
def prim_construct_falling_coyote(N, p1, maxcount=None):
    """
    Implement the Falling Coyote Algorithm.
    Finds N LFSRs from Berlekamp-Massey in order
    to construct a series of bit sequences that,
    when sampled in parallel, yield 2N bits that can
    be used to find the minimal polynomial of u=x^j
    using Berlekamp-Massey.
    Stateful: relies on updating each of the LFSRs
    once per iteration through odd values of j.
    """
    qr1 = GF2QuotientRing(p1)
    assert N == qr1.degree
    # Begin horizontal phase
    yield (1,p1)
    M = (1<<N)-1
    qr = [None]*N    # this p[i] = p_(2i+1) in the algorithm above
    qr[0] = qr1
    bits = [0]*((2*N-1)**2+1)
    S = 1
    bits[0] = S&1   # sample a bit of the LFSR (doesn't matter which one)
    i = 0
    count = 1
    for j in xrange(1,2*N):
        for k in xrange(2*N-1):
            i += 1
            S = qr1.lshiftraw1(S)
            bits[i] = S&1
        # We now have all bits up to j(2n-1)
        if j == 1 or (j & 1) == 0:
            continue
        # j is odd, j > 1
        poly, npoly = berlekamp_massey([bits[k*j] for k in xrange(2*N)])
        if gcd(j,M) == 1:
            if j == smallest_conjugate(j,N):
                count += 1
                yield (j, poly)
        qr[j//2] = GF2QuotientRing(poly)
        
    # Get ready for vertical phase
    S = [0]*N
    Sa = [0]*N   # "自然" state
    b0 = 0
    for i in xrange(N):
        trp = GF2TracePattern.from_pattern(qr[i], 1)
        Si = trp.lfsr_initial_state
        Sa[i] = Si
        if i == 0:
            b0 = Si >> (N-1)
        Si = qr[i].lshiftraw(Si,N)
        Si = qr[i].lshiftraw(Si,1<<(N-1))
        S[i] = Si
        
    # Begin vertical phase
    for j in xrange(2*N+1, 1<<(N-1), 2):
        # Update state
        for i in xrange(N):
            S[i] = qr[i].lshiftraw1(S[i])

        if gcd(j,M) != 1:
            continue
        if j > smallest_conjugate(j,N):
            continue

        bits = [b0]*(2*N)
        for i in xrange(N):
            bits[2*i+1] = S[i] & 1
        for i in xrange(1,N):
            bits[2*i] = bits[i]
        if False:
            print "j=%d" % j
            print bits
            print [qr1.lshiftraw(Sa[0],j*k)>>(N-1) for k in xrange(2*N)]

        poly, npoly = berlekamp_massey(bits)
        yield (j,poly)
        count += 1
        if count == maxcount:
            break

collect_prim_poly(prim_construct_falling_coyote,8,verbose=True);
j =   1: 11d
j =   7: 169
j =  11: 1e7
j =  13: 12b
j =  19: 165
j =  23: 163
j =  29: 18d
j =  31: 12d
j =  37: 15f
j =  43: 1c3
j =  47: 1a9
j =  53: 187
j =  59: 14d
j =  61: 1cf
j =  91: 1f5
j = 127: 171
16 primitive polynomials found

让’s试一试它,就像我们为圣艾夫斯算法所做的那样:

collect_algorithm_stats(prim_construct_falling_coyote, 20);
Elapsed time: 18.317701s
24000 polynomials collected
24000 of them are degree N
24000 of them are unique

这些实现的St.Ives算法有点快。

比较算法性能(具有大粒盐)

好的,所以现在’是时候看一个相对算法的性能。那里’s one catch: this is very implementation-dependent, and we are using Python with numpy where some numerical algorithms are very slow and others are very fast, for reasons that are hard to pinpoint. There’s numpy fast mathematics underneath the Python layer; there’S垃圾收集继续,那里’S列表分配和可能是瓶颈的各种各样的东西。如果我真的小心我’D运行一个分析器,看看是否有任何明显的瓶颈。

更好的是,我们应该以java或rust或c ++等快速的语言实现尝试这些,并查看它们的比较。但我们’在Python工作,这样’s what I’我现在要使用。一世’m将达到2000年的原始多项式以进行各种程度,看看每多项式需要多长时间。

import numpy as np

algorithms = [prim_sieve_1,
              prim_construct_1, 
              prim_construct_2,
              prim_construct_3,
              prim_construct_4,
              prim_construct_5,
              prim_construct_6,
              prim_construct_st_ives, 
              prim_construct_gordon,
              prim_construct_shoup,
              prim_construct_falling_coyote]

# cache the first polynomials of each degree;
# we want to exclude time to get these
p1_cache = {}
maxcount = 2000
maxtime = 0.005
data = []
for algorithm in algorithms:
    Nlist = np.arange(8,61)
    print algorithm.__name__
    execution_times = []
    for N in Nlist:
        N = int(N)
        if N in p1_cache:
            p1 = p1_cache[N]
        else:
            p1 = find_first_primitive_polynomial(N)
            p1_cache[N] = p1
        t0 = time.time()
        result = collect_prim_poly(algorithm,N,p1=p1,maxcount=maxcount)
        t1 = time.time()
        time_per_poly = (t1-t0)/len(result)
        execution_times.append(time_per_poly)
        # Give up if it takes too long per polynomial
        if time_per_poly > maxtime:
            break
    npoints = len(execution_times)
    execution_times = np.array(execution_times)
    data.append((algorithm,Nlist[:npoints],execution_times))
prim_sieve_1
prim_construct_1
prim_construct_2
prim_construct_3
prim_construct_4
prim_construct_5
prim_construct_6
prim_construct_st_ives
prim_construct_gordon
prim_construct_shoup
prim_construct_falling_coyote
%matplotlib inline
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1)
for algorithm, Nlist_item, execution_times in data:
    docstring = algorithm.__doc__
    label = algorithm.__name__
    if label.startswith('prim_'):
        label = label[5:]
    try:
        print(label+"\n"+docstring)
    except:
        print label, docstring
    markersize = 6
    if label.startswith('sieve'):
        linestyle = 's-'
        markersize = 3
    elif label.startswith('construct_'):
        if len(label) < 12:
            linestyle = '.-'
        else:
            linestyle = 'x-'
    ax.semilogy(Nlist_item, execution_times*1e3, linestyle, 
                markersize=markersize, label=label)
ax.set_xticks([N for N in Nlist if (N%2)==0])
ax.set_ylabel('execution time per polynomial (msec)')
ax.set_xlabel('primitive polynomial degree')
ax.set_xlim(min(Nlist),max(Nlist))
ax.set_ylim(ax.get_ylim()[0],6.0)
ax.grid('on',which='both')
ax.set_title('Average execution time to find each primitive polynomial'
            +' for the first %d of them' % maxcount, fontsize=11)
ax.legend(labelspacing=0,fontsize=9,loc='best');
sieve_1

    Find up to maxcount primitive polynomials of degree N.
    Approach: For each candidate polynomial p(x) with unity coefficients a0 and aN,
      determine the order of x in the quotient ring GF(2)[x]/p(x); p(x) is primitive
      if and only if the order of x is 2^N-1. Skip polynomials with an even number
      of nonzero coefficients since these are divisible by $x+1$.
      (This is the Alanen-Knuth algorithm.) Primitivity determination is stateless.
    
construct_1
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Compute the characteristic polynomial of
      the jth power of the companion matrix A to the first
      primitive polynomial p1(x) we can find.
      Requires an initial primitive polynomial p1(x).
      Stateful: runs through odd values of j, multiplies by
      A^2 each time, to be faster than calculating A^j.
    
construct_2
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Compute the characteristic polynomial of
      the jth power of the companion matrix to the first
      primitive polynomial p1(x) we can find, but use
      LFSR updates instead of matrix multiplication.
      Stateful: relies on previous matrix content for speedup.
    
construct_3
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the characteristic polynomial of the LFSR,
      using Berlekamp-Massey, that decimates the output 
      of the base LFSR with characteristic polynomial p1(x), 
      the first primitive polynomial we can find.
      Stateless; computes a finite field power
      for each bit in the decimated sequence.
    
construct_4
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the characteristic polynomial of the LFSR,
      using Berlekamp-Massey, that decimates the output 
      of the base LFSR with characteristic polynomial p1(x), 
      the first primitive polynomial we can find.
      Stateless; computes a single finite field power = x^j 
      for each value of j, and a finite field multiply for
      each of 2N bits in the decimated sequence.
    
construct_5
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the minimal polynomial of u=x^j directly
    by computing (x-u)(x-u^2)(x-u^4)...(x-u^(2^(N-1))).
    Stateful: steps through odd values of j, updates
    u=x^j by two left shifts each iteration.
    
construct_6
 
    Find up to maxcount primitive polynomials of degree N.
    Approach: Find the minimal polynomial of x^j using linear algebra.
    Stateful: steps through odd values of j, updates
    u=x^j by two left shifts each iteration.
    
construct_st_ives

    Implement the St. Ives algorithm
    (di porto,guida和montolivo, by computing 1+(2N-1)j bits
    in order to decimate by a small ratio j, varying j when necessary)
    Stateful: depends on previous polynomial each time.
    
construct_gordon

    Use Gordon's method for calculating minimal polynomial.
    Stateful, but only in the updating of u=x^j
    by x^2 each iteration.
    
construct_shoup

    Use Shoup's decimation technique.
    Stateful, but only in the updating of u=x^j
    by x^2 each iteration.
    
construct_falling_coyote

    Implement the Falling Coyote Algorithm.
    Finds N LFSRs from Berlekamp-Massey in order
    to construct a series of bit sequences that,
    when sampled in parallel, yield 2N bits that can
    be used to find the minimal polynomial of u=x^j
    using Berlekamp-Massey.
    Stateful: relies on updating each of the LFSRs
    once per iteration through odd values of j.
    

那里 are some patterns visible in the graph worth noting.

sieve_1 method is just lousy; for each candidate polynomial we have to compute raising to a power, possibly multiple powers depending on how many factors the period has. Values of \( 2^N-1 \) that are Mersenne Primes. (\(n = 2,3,5,7,13,17,19,31,\ ldots \))唐’T有任何其他主要因素,因此它们更快地验证原始性,并具有较低的执行时间。

construct methods other than the St. Ives algorithm all have the same general approach: loop through odd values \( j \) such that \( \gcd(j,2^N-1) = 1 \) and \( j \) is the minimum value in its bitwise rotation. Then use \( j \) to compute the appropriate isomorphism to obtain a new primitive polynomial, whether through LFSR decimation ratios, or minimal polynomials, or characteristic polynomials of powers of a matrix. Most of these algorithms have to do some computations for all odd values of \( j \), not just the values of \( j \) that will yield a primitive polynomial. So the execution time curves tend to have wiggles dependent on the values of \( N \). If \( 2^N-1 \) has lots of factors (like \( N=24, 36, 48 \)) then there is a smaller fraction of primitive polynomials of degree \( N \), and therefore the computation per primitive polynomial is slightly larger.

construct_1construct_2 algorithms use matrices, which have an unfair advantage in that numpy implements them specially in C, so they are faster than a pure Python equivalent. The construct_3 (LFSR decimation) and construct_5 (minimal polynomial calculation, direct) algorithms are very slow. The construct_4 (LFSR decimation, optimized) and construct_6 (minimal polynomial calculation using linear algebra) algorithms are faster. The construct_gordon algorithm is faster but looks to be about the same order of growth as construct_4construct_6 , 和 construct_shoup (Shoup’S算法)甚至更快。但这些都不是STIVE算法或下降的土狼算法。

In addition, you may notice a drop in the execution time after \( N=16 \) for some of the construct algorithms, particularly construct_1, construct_2 , 和 construct_falling_coyote. There are 2048 primitive polynomials of degree 16; as N increases beyond this point the number of primitive polynomials increases rapidly, and because we stop after the first 2000 of them, there is a difference of behavior. Why should this matter? Because one of the computational tasks we do, to emit primitive polynomials without repeats, is to discard any value of \( j \) where a bit rotation yields a smaller number. The values of \( j \) which are the minimum of their bitwise rotations, are denser for lower values of \( j \), and in fact if \( j <2 ^ {\ lfloor n / 2 \ rfloor} \)它们始终是它们按位旋转的最小值。因此,如果我们从\(j \)的低值开始,每次递增2,并且在阈值下戒烟,明显小于原始多项式总数的总数,我们将输出比集的最高多项式的比分更高\(n \)作为整体的多项式。这使得似乎我们需要为每个原始多项式花费更少的计算工作而不是整体的程度 - \(n \)多项式。

圣艾夫斯算法在这里是独一无二的,因为我们不’t have to do 任何 过滤;我们采取的每一步(除了我们需要使用新的乘数来遍历晶格并避免重复)产生新的原始多项式。唯一的捕获是每个多项式的时间与相对素数的最小乘法器\(j \)成比例到\(2 ^ n-1 \)。 \(n \)的奇数值可以使用\(j = 3 \)来远离,而\(n \)的值,\(n \)为12的倍数需要至少\(j = 11 \),显着提高计算时间。

总的来说,速度冠军在圣艾夫斯和落下的土狼算法之间看起来像是一个近似的比赛,至少在Python中;我们将能够在全环的全部范围内更轻松地比较“handicapping”在其位旋转中选择\(j \)到随机条目而不是最小\(j \),使得基元多项式的分数在可能的\(j \)值的范围内更均匀。

sh’S算法可能是随机访问\(j \)最快的算法;许多其他算法依赖于更新\(j \)以确定序列允许基于先前计算的一些增量计算。

如果我稍后有时间,我将尝试在Java中实现一些这些算法(没有矩阵)并重新生锈并报告结果。

2018年9月16日更新: 我已经实施了戈登’s method, Shoup’s algorithm, and下降的土狼算法in Java as part of my jlibgf2 图书馆,并绘制了下面的结果,每年达到前200,000个原始多项式。上面提到的,部分完成是更快的,因为在\(J \)的较低值下发现了更多的原始多项式。但是,有一种方法可以通过使用不同的陪核代表来弥补它:

We’ve一直使用\(j \)的值,使得它们是它们的coset内的最小值,并拒绝具有小于\(j \)的位旋转的任何值。

相反,我们可以允许奇值\(j \),使得(b(j)= 1 \)在其中

$$ b(j)= \ begin {is} 0 &j \,{\ rm偶数},\ quad \ alpha j \ bmod 2 ^ n-1>2 ^ i \ alpha j \ bmod 2 ^ n-1 {\ rm \,for \,some \,} i {\ rm \,这样\,那个\,} 2 ^ i \ cdot j \ bmod 2 ^ n- 1 {\ rm \,是\,奇数} \ cr 1 &{\ rm否则} \结束{案例} $$

对于某个值\(\ alpha \)。基本上我们拒绝任何\(j \)这样的\(\ alpha j’ \bmod 2^N-1 <\ alpha j \ bmod 2 ^ n-1 \)对于一些奇数\(j’\)在与\(J \)相同的陪核中。我使用了第一个奇值\(\ alpha> \frac{2^N-1}{\phi} \) that is relatively prime to \( 2^N-1 \), with \( \phi = \frac{\sqrt{5}+1}{2} \approx 1.618034. \) The reason this works well is that values of \( j \) which are in the same coset \( J_1 \) will all yield values \( \alpha j \bmod 2^N-1 \) that are in the same coset \( J_2 \) for any \( \alpha \), so it guarantees that exactly one value of \( j \) out of its coset \( J_1 \) will have \( b(j)=1 \), and the multiplication tends to produce pseudorandom values of \( j \) as a coset representative, at least for those values of \( j \) that have several 1 bits in their binary representation.

这里’\(n = 18 \)的这个技术的一个例子,显示以绿色突出显示的彩色代表:

N=18
m = (1<<N)-1
phi = (np.sqrt(5)+1)/2
alpha = int(np.floor(m/phi)) | 1
alpha += 10
while gcd(alpha,m) != 1:
    alpha += 2
alpha
162023
i = np.arange(N)
nj = 0
columns = []
columnnames = []
representatives = []
for j0 in xrange(1001,10000,2):
    if gcd(m,j0) != 1:
        continue
    nj += 1
    if nj > 6:
        break
    j = j0
    J1 = []
    J2 = []
    ajmin = m
    reps = []
    for k in xrange(N):
        J1.append(j)
        alphaj = (j*alpha) % m
        if j & 1 and alphaj < ajmin:
            reps = [j,alphaj]
            ajmin = alphaj
        J2.append(alphaj)
        j = (j << 1) % m
    columns += [J1,J2]
    columnnames += ['\\(%d\\)' % j, '\\(%d\\alpha\\)' % j if j > 1 else '\\(\\alpha\\)']
    representatives += reps
columns = np.array(columns).T
df = pd.DataFrame(columns,i,columns=columnnames)
df.index.name = 'i'
representatives = set(representatives)
def highlight(s):
    return ['background-color: #ccffaa' if j in representatives else '' for j in s]
df.style.apply(highlight)
\(1003 \) \(1003 \ alpha \) \(1009 \) \(1009 \ alpha \) \(1013 \) \(1013 \ alpha \) \(1019 \) \(1019 \ alpha \) \(1021 \) \(1021 \ alpha \) \(1025 \) \(1025 \ alpha \)
i
0 1003 242552 1009 166118 1013 27781 1019 213490 1021 13250 1025 137056
1 2006 222961 2018 70093 2026 55562 2038 164837 2042 26500 2050 11969
2 4012 183779 4036 140186 4052 111124 4076 67531 4084 53000 4100 23938
3 8024 105415 8072 18229 8104 222248 8152 135062 8168 106000 8200 47876
4 16048 210830 16144 36458 16208 182353 16304 7981 16336 212000 16400 95752
5 32096 159517 32288 72916 32416 102563 32608 15962 32672 161857 32800 191504
6 64192 56891 64576 145832 64832 205126 65216 31924 65344 61571 65600 120865
7 128384 113782 129152 29521 129664 148109 130432 63848 130688 123142 131200 241730
8 256768 227564 258304 59042 259328 34075 260864 127696 261376 246284 257 221317
9 251393 192985 254465 118084 256513 68150 259585 255392 260609 230425 514 180491
10 240643 123827 246787 236168 250883 136300 257027 248641 259075 198707 1028 98839
11 219143 247654 231431 210193 239623 10457 251911 235139 256007 135271 2056 197678
12 176143 233165 200719 158243 217103 20914 241679 208135 249871 8399 4112 133213
13 90143 204187 139295 54343 172063 41828 221215 154127 237599 16798 8224 4283
14 180286 146231 16447 108686 81983 83656 180287 46111 213055 33596 16448 8566
15 98429 30319 32894 217372 163966 167312 98431 92222 163967 67192 32896 17132
16 196858 60638 65788 172601 65789 72481 196862 184444 65791 134384 65792 34264
17 131573 121276 131576 83059 131578 144962 131581 106745 131582 6625 131584 68528
def gcdv(a,b):
    """ vectorized gcd """
    b = np.atleast_1d(b)
    a = a.copy()
    if b.size == 1:
        b = np.ones_like(a)*b
    while np.any(b):
        ai = a[b>0]
        bi = b[b>0]
        qi,ri = divmod(ai, bi)
        a[b>0] = bi
        b[b>0] = ri
    return a

def coset_representatives(N, alpha=1):
    m = (1<<N)-1
    j = np.arange(1,m,2)
    j = j[gcdv(j,m)==1]
    aj = alpha*j % m
    mask = j == j
    ji = j.copy()
    aji = aj.copy()
    for i in xrange(1,N):
        ji = (2*ji) % m
        aji = (2*aji) % m
        j[(ji&1 != 0) & (aj > aji)] = 0
    return j[j>0]

def coset_histogram(N,alpha,ax=None,figsize=None):
    j = coset_representatives(N,alpha)
    if ax is None:
        fig = plt.figure(figsize=figsize)
        ax=fig.add_subplot(1,1,1)
    m = (1<<N)-1
    ax.hist(j,np.linspace(0,m,64))
    ax.set_xlim(0,m)
    ax.set_xlabel('$j$',fontsize=11)
    ax.set_ylabel('bin count',fontsize=11)
    ax.set_title('Histogram of coset representatives for $N=%d, \\alpha=%d$ (%d values of $j$)'
                 % (N,alpha,len(j)), fontsize=11)
    
fig = plt.figure(figsize=(7,9))
ax = fig.add_subplot(2,1,1)
coset_histogram(18,1,ax=ax)
ax = fig.add_subplot(2,1,2)
coset_histogram(18,alpha,ax=ax)

也许不是最佳的,但\(\ alpha = 162063 \)案例比\(\ alpha = 1 \)更像分布,并且如果在找到所有原始多项式之前,我们会更好地反映每个原始多项式的平均时间,这对于\(n \)的更大值将是典型的。

Anyway here are those timing graphs I mentioned for jlibgf2. The dashed lines are where \( \alpha=1 \); the solid lines are compensated using values of \( \alpha \) described above —除了我划分2的力量,以保持结果\(\ alpha< 2^{31} \). There’奇怪的掉落在\(n = 53 \);不确定为什么会发生这种情况,尽管它可能与我保持的(\ alpha(\ Alpha)有关的(人为)约束< 2^{31} \).

I’当我有机会时,请加入圣IVES算法。

包起来

We’VE看了11(11!)不同的技术,用于查找给定程度的原始多项式\(n \)。这些沸腾到以下一般类别:

  • 穷举算法 取\(O(2 ^ n)\)执行时间以验证基于原始多项式的LFSR的最小时段。这些都很慢。大学教师’t bother.

  • 筛子算法 使用目标测试以确定给定的多项式\是否是原始的。这些最简单的解释是它们确定\(x ^ m \ bmod p(x)= 1,\)其中\(m = 2 ^ n-1 \),\(x ^ {(m / q)所有主要含量的\ bmod p(x)\ ne 1 \)\(q \ mid 2 ^ n-1 \)其中\(q<m \)。如果是这种情况,那么\(m \)是使\(x ^ m \ bmod p(x)= 1,\)和\(p(x)\)是原始多项式的最小的正整数。

  • 合成算法 或者,当已经知道相同程度的一个原始多项式\(P_1(x))时,可用于找到其他原始多项式的构造算法。这些算法至少有两种类型:

    • 在字段\(gf(2)[x] / p_1(x)\)中找到minimal \(x ^ j \)的最小多项式\(p_j(x)\)的算法,其中\(\ gcd( j,2 ^ n-1)= 1. \)所有值\(j \),它是位旋转的相同最小多项式。这种方法等同于找到具有特征多项式\(p_j(x)\)的LFSR,该\(p_j(x)\)从对应的LFSR \(j \)对应于\(p_1(x)\)的因子:更确切地说,它是相当于查找LFSR的特征多项式\(P_J(x)\),其产生与\(b [jk] \)的相同输出序列,其中\(b [k] \)是lfsr \的输出序列( p_1(x)\)。 (Berlekamp-Massey算法是查找输出给定序列的LFSR的特征多项式的通常技术。)它也等同于找到\(p_1的\(p_1)的\(j \)电力的特征多项式(X) \)。这三种方法—最小多项式,抽取和矩阵功率—每个都有它们的优点和缺点。下降的Coyote算法是LFSR抽取技术的特殊优化,其取决于\(j \)定期递增。

    • 通过因子\(j \)通过重复抽取找到一系列多项式的算法—圣艾夫斯算法是唯一一个我’m意识到这可以做到这一点。

我已经测量了在Python中实现的这些算法的示例。 St.Ives算法和下降的Coyote算法似乎是这些算法中最有效的。

参考

桌子

算法

什么’s Next?

下次…好吧,下次会有一个,但就这个系列而言,这就是它。就是那个’s right — you’达到了最后!我计划提供一个简短的后记,有助于导航I - XVIII的各种主题。

谢谢阅读!


©2018年杰森M. Sachs,保留所有权利。


[]
评论 Plinnie. 2019年1月16日

嗨杰森,

我喜欢你对LFSR的深入文章。它是很多信息。也许你应该把它们捆绑成一个pdf /书?我想拥有一份副本:-)

干杯,
文森特

[]
评论 doubleplay2005.2019年9月25日

你好,

我与文森特一致。这些博客帖子非常令人印象深刻,我可以看到他们作为一本好书工作。你在哪里得到了大部分的灵感?


要发布回复评论,请单击连接到每个注释的“回复”按钮。发布新的评论(不是回复评论),请在评论的顶部查看“写评论”选项卡。

注册将允许您参加所有相关网站的论坛,并为您提供所有PDF下载。

注册

我同意 使用条款隐私政策.

尝试我们偶尔但流行的时事通讯。非常容易取消订阅。
或登录