示例#1
0
def serial(generator, n_bits, m=16, misc=None):
    """Serial Test.

    Test purpose as described in [NIST10, section 2.11]:
    "The focus of this test is the frequency of all possible overlapping m-bit patterns across the
    entire sequence. The purpose of this test is to determine whether the number of occurrences of
    the 2m m-bit overlapping patterns is approximately the same as would be expected for a random
    sequence. Random sequences have uniformity; that is, every m-bit pattern has the same chance of
    appearing as every other m-bit pattern. Note that for m = 1, the Serial test is equivalent to
    the Frequency test of Section 2.1."
    """

    if m < 1:
        raise ValueError("Parameter m must be an integer greater than zero")

    if m >= int(log2(n_bits)) - 2:
        print("Warning: Parameter m should be less than [log2(n)]-2",
              file=stderr)

    sequence_start = [generator.random_bit() for _ in range(m - 1)]
    window = deque(sequence_start, maxlen=m)
    vm0 = [0] * (2**m)
    vm1 = [0] * (2**(m - 1)) if m >= 2 else []
    vm2 = [0] * (2**(m - 2)) if m >= 3 else []
    for i in range(m - 1, n_bits + (m - 1)):
        bit = generator.random_bit() if i < n_bits else sequence_start[i -
                                                                       n_bits]
        window.append(bit)  # when full with m items, left item is discarded

        vm0[concat_chunks(window, bits=1)] += 1
        if m >= 2:
            vm1[concat_chunks(islice(window, 0, m - 1), bits=1)] += 1
        if m >= 3:
            vm2[concat_chunks(islice(window, 0, m - 2), bits=1)] += 1

    psi2m0 = (2**m) / n_bits * sum(map(lambda x: x**2, vm0)) - n_bits
    psi2m1 = (2**(m - 1)) / n_bits * sum(map(lambda x: x**2,
                                             vm1)) - n_bits if vm1 else 0
    psi2m2 = (2**(m - 2)) / n_bits * sum(map(lambda x: x**2,
                                             vm2)) - n_bits if vm2 else 0

    delta_psi2m = psi2m0 - psi2m1
    delta2_psi2m = psi2m0 - 2 * psi2m1 + psi2m2

    p_value1 = igamc(2**(m - 2), delta_psi2m / 2)
    p_value2 = igamc(2**(m - 3), delta2_psi2m / 2)

    if type(misc) == dict:
        misc.update(vm0=vm0,
                    vm1=vm1,
                    vm2=vm2,
                    psi2m0=psi2m0,
                    psi2m1=psi2m1,
                    psi2m2=psi2m2,
                    delta_psi2m=delta_psi2m,
                    delta2_psi2m=delta2_psi2m)

    return p_value1, p_value2
示例#2
0
def block_frequency(generator, n_bits, m=None, misc=None):
    """Frequency Test within a Block.

    Test purpose as described in [NIST10, section 2.2]:
    "The focus of the test is the proportion of ones within M-bit blocks. The purpose of this test
    is to determine whether the frequency of ones in an M-bit block is approximately M/2, as would
    be expected under an assumption of randomness. For block size M=1, this test degenerates to
    test 1, the Frequency (Monobit) test."
    """
    if n_bits < 100:
        print("Warning: Sequence should be at least 100 bits long",
              file=stderr)
    if m:
        if m < 20 or m <= 0.01 * n_bits:
            print("Warning: Parameter m should satisfy m >= 20 and m > .01*n",
                  file=stderr)
    else:
        m = max(20, int(0.01 * n_bits) + 1)

    n_blocks = n_bits // m
    assert (n_bits >= m * n_blocks)
    if n_blocks >= 100:
        print("Warning: Test should have less than 100 blocks", file=stderr)

    pi = []
    for i in range(n_blocks):
        pi.append(sum([generator.random_bit() for _ in range(m)]) / m)

    chi2_obs = 4 * m * sum(map(lambda p: (p - 0.5)**2, pi))
    p_value = igamc(n_blocks / 2, chi2_obs / 2)

    if type(misc) is dict:
        misc.update(m=m, n_blocks=n_blocks, pi=pi, chi2=chi2_obs)

    return p_value
def test(bits, blocklen=None, template=None):
    n = len(bits)
    M = blocklen if blocklen else n // 8
    if M <= 0.01 * n:
        return (-1, -1, False)
    N = n // M
    blocks = [bits[i * M:(i + 1) * M] for i in range(N)]
    B = template if template else random.choices(templates[8])
    m = len(B)

    W = []
    for block in blocks:
        position = 0
        count = 0
        while position < (M - m):
            if block[position:position + m] == B:
                position += m
                count += 1
            else:
                position += 1
        W.append(count)

    μ = Fraction(M - m + 1, 2**m)
    σ2 = M * (Fraction(1, 2**m) - Fraction(2 * m - 1, 2**(2 * m)))
    χ2_obs = sum([(W[j] - μ)**2 for j in range(N)]) / σ2
    P_value = igamc(N / 2.0, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
示例#4
0
def longest_run_of_ones(generator, n_bits, m=None, misc=None):
    """Test for the Longest Run of Ones in a Block.

    Test purpose as described in [NIST10, section 2.4]:
    "The focus of the test is the longest run of ones within M-bit blocks. The purpose of this test
    is to determine whether the length of the longest run of ones within the tested sequence is
    consistent with the length of the longest run of ones that would be expected in a random
    sequence. Note that an irregularity in the expected length of the longest run of ones implies
    that there is also an irregularity in the expected length of the longest run of zeroes.
    Therefore, only a test for ones is necessary."
    """
    if not m:
        if n_bits >= 750000:
            m = 10000
        elif n_bits >= 6272:
            m = 128
        elif n_bits >= 128:
            m = 8
        else:
            raise ValueError("Sequence must be at least 128 bits long")
    elif m not in (8, 128, 10000):
        raise ValueError("Parameter m must be 8, 128 or 10000")

    if m == 8:
        min_run_length, max_run_length = 1, 4
        pi = (0.2148, 0.3672, 0.2305, 0.1875)
    elif m == 128:
        min_run_length, max_run_length = 4, 9
        pi = (0.1174, 0.2430, 0.2493, 0.1752, 0.1027, 0.1124)
    elif m == 10000:
        min_run_length = 10
        max_run_length = 16
        pi = (0.0882, 0.2092, 0.2483, 0.1933, 0.1208, 0.0675, 0.0727)
    else:
        raise Exception("This shouldn't happen")

    k = max_run_length - min_run_length
    n_blocks = n_bits // m

    v = [0] * (k + 1)
    for _ in range(n_blocks):
        block = [generator.random_bit() for _ in range(m)]
        runs_of_ones = [list(group) for k, group in groupby(block, bool) if k]
        if runs_of_ones:
            longest_run_length = max(map(len, runs_of_ones))
            # if run length is out of bounds, set it to the nearest bound
            # then the run length is the index (minus min_run_length because zero-indexed)
            index = min(max(longest_run_length, min_run_length),
                        max_run_length) - min_run_length
            v[index] += 1

    chi2_obs = sum(
        map(lambda x: (x[0] - n_blocks * x[1])**2 / (n_blocks * x[1]),
            zip(v, pi)))
    p_value = igamc(k / 2, chi2_obs / 2)

    if type(misc) is dict:
        misc.update(m=m, k=k, n=n_blocks, v=v, chi2_obs=chi2_obs)

    return p_value
示例#5
0
def approximate_entropy(generator, n_bits, m=10, misc=None):
    """Approximate Entropy Test.

    Test purpose as described in [NIST10, section 2.12]:
    "As with the Serial test of Section 2.11, the focus of this test is the frequency of all
    possible overlapping m-bit patterns across the entire sequence. The purpose of the test is to
    compare the frequency of overlapping blocks of two consecutive/adjacent lengths (m and m+1)
    against the expected result for a random sequence."
    """

    if m < 1:
        raise ValueError("Parameter m must be an integer greater than zero")

    if m >= int(log2(n_bits)) - 5:
        print("Warning: Parameter m should be less than [log2(n)]-5",
              file=stderr)

    vm0 = [0] * (2**m)
    vm1 = [0] * (2**(m + 1))

    sequence_start = [generator.random_bit() for _ in range(m)]
    window0 = deque(sequence_start, maxlen=m)
    window1 = deque(sequence_start, maxlen=m + 1)

    # take care of the first step for the smaller window
    vm0[concat_chunks(sequence_start, bits=1)] += 1

    all_seq = sequence_start
    for i in range(m, n_bits + m):
        bit = generator.random_bit() if i < n_bits else sequence_start[i -
                                                                       n_bits]
        all_seq.append(bit)
        window0.append(bit)  # when full with m items, left item is discarded
        window1.append(bit)  # when full with m+1 items, left item is discarded

        if i < n_bits + m - 1:  # skip the last step for the smaller window
            vm0[concat_chunks(window0, bits=1)] += 1
        vm1[concat_chunks(window1, bits=1)] += 1

    cm0 = map(lambda x: x / n_bits, vm0)
    cm1 = map(lambda x: x / n_bits, vm1)

    phi_m0 = sum([x * log(x) for x in cm0 if x > 0])
    phi_m1 = sum([x * log(x) for x in cm1 if x > 0])

    chi2_obs = 2 * n_bits * (log(2) - (phi_m0 - phi_m1))
    p_value = igamc(2**(m - 1), chi2_obs / 2)

    if type(misc) == dict:
        misc.update(vm0=vm0,
                    vm1=vm1,
                    phi_m0=phi_m0,
                    phi_m1=phi_m1,
                    chi2_obs=chi2_obs)

    return p_value
示例#6
0
def rank(generator, n_bits, misc=None):
    """Binary Matrix Rank Test.

    Test purpose as described in [NIST10, section 2.5]:
    "The focus of the test is the rank of disjoint sub-matrices of the entire sequence. The purpose
    of this test is to check for linear dependence among fixed length substrings of the original
    sequence. Note that this test also appears in the DIEHARD battery of tests."
    """
    m = q = 32
    n_blocks = n_bits // (m * q)

    if n_blocks < 1:
        raise ValueError(
            "Sequence must be longer, should be at least 38*m*q =" +
            str(38 * m * q) + "bits long")
    elif n_blocks < 38:
        print("Warning: Sequence should be at least 38*m*q =",
              38 * m * q,
              "bits long",
              file=stderr)

    f_m = f_m1 = 0
    for _ in range(n_blocks):
        block = [generator.random_bit() for _ in range(m * q)]
        matrix = [block[i * q:(i + 1) * q] for i in range(m)]

        r = _matrix_rank_bin(matrix, m, q)
        if r == m:
            f_m += 1
        elif r == m - 1:
            f_m1 += 1

    # pre-calculate more precise values for probability values p1, p2, and p3
    # (see section 3.5 in NIST SP 800-22)
    p_ = 0
    p1 = 1
    # for x in range(1, 50): p1 *= 1 - (1.0 / (2 ** x))
    x = 1
    while abs(p1 - p_) > 1e-9:
        p_ = p1
        p1 *= 1 - (1.0 / (2**x))
        x += 1
    p2 = 2 * p1
    p3 = 1 - p1 - p2

    chi2_obs = (f_m - p1 * n_blocks) ** 2 / p1 / n_blocks + \
               (f_m1 - p2 * n_blocks) ** 2 / p2 / n_blocks + \
               (n_blocks - f_m - f_m1 - p3 * n_blocks) ** 2 / p3 / n_blocks
    # p_value = exp(-chi2_obs/2)
    p_value = igamc(1, chi2_obs / 2)

    if type(misc) is dict:
        misc.update(n=n_blocks, f_m=f_m, f_m1=f_m1, chi2_obs=chi2_obs)

    return p_value
示例#7
0
def test(bits, blocklen=10):
    n = len(bits)
    M = blocklen
    N = n // M
    π = [0] * N
    for i in range(N):
        π[i] = Fraction(sum([bits[i * M + j] for j in range(M)]), M)

    χ2_obs = 4 * M * sum([(π[i] - Fraction(1, 2))**2 for i in range(N)])
    P_value = igamc(N / 2.0, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
示例#8
0
def linear_complexity(generator,
                      n_bits,
                      m=500,
                      misc=None,
                      use_old_nist_pi=False):
    """Linear Complexity Test.

    Test purpose as described in [NIST10, section 2.10]:
    "The focus of this test is the length of a linear feedback shift register (LFSR). The purpose of
    this test is to determine whether or not the sequence is complex enough to be considered random.
    Random sequences are characterized by longer LFSRs. An LFSR that is too short implies
    non-randomness."
    """

    n_blocks = n_bits // m

    if n_bits < 10**6:
        print("Warning: Sequence should be at least 10^6 bits long",
              file=stderr)
    if not (500 <= m < 5000):
        print("Warning: Parameter m should be in range [500, 5000]",
              file=stderr)
    if n_blocks < 200:
        print("Warning: The sequence should be split to at least 200 blocks",
              file=stderr)

    exp_mean = m / 2 + (9 + (-1)**(m + 1)) / 36 - (m / 3 + 2 / 9) / (2**m)

    v = [0] * 7
    for i in range(n_blocks):
        block = [generator.random_bit() for _ in range(m)]
        l = _berlekamp_massey(block)
        t = ((-1)**m) * (l - exp_mean) + 2 / 9
        index = int(min(max(ceil(t + 2.5), 0), 6))
        v[index] += 1

    if use_old_nist_pi:
        # old NIST values - these pass the test samples but the first value is incorrect
        pi = (0.01047, 0.03125, 0.125, 0.5, 0.25, 0.0625, 0.020833)
    else:
        # correct pi values
        pi = (1 / 96, 0.03125, 0.125, 0.5, 0.25, 0.0625, 1 / 48)

    chi2_obs = sum(
        map(lambda v_i, pi_i: (v_i - n_blocks * pi_i)**2 / (n_blocks * pi_i),
            v, pi))
    p_value = igamc(6 / 2., chi2_obs / 2.)

    if type(misc) == dict:
        misc.update(exp_mean=exp_mean, v=v, chi2_obs=chi2_obs)

    return p_value
示例#9
0
def overlapping_template_matching(generator,
                                  n_bits,
                                  b=(1, 1, 1, 1, 1, 1, 1, 1, 1),
                                  misc=None,
                                  use_old_nist_pi=False):
    """Overlapping Template Matching Test.

    Test purpose as described in [NIST10, section 2.8]:
    "The focus of the Overlapping Template Matching test is the number of occurrences of
    pre-specified target strings. Both this test and the Non-overlapping Template Matching test of
    Section 2.7 use an m-bit window to search for a specific m-bit pattern. As with the test in
    Section 2.7, if the pattern is not found, the window slides one bit position. The difference
    between this test and the test in Section 2.7 is that when the pattern is found, the window
    slides only one bit before resuming the search."
    """

    if type(b) == tuple:
        b = list(b)
    b_len = len(b)
    # if b_len != 9:
    #     raise ValueError("Length of pattern b must be exactly 9 bits")

    m = 1032  # hardcoded to match the pre-computed pi values that depend on m and length of b
    n_blocks = n_bits // m

    lambda_ = (m - b_len + 1) / (2**b_len)
    eta = lambda_ / 2

    v = [0] * 6
    for _ in range(n_blocks):
        block = [generator.random_bit() for _ in range(m)]
        w = 0
        for i in range(m - b_len + 1):
            if block[i:i + b_len] == b:
                w += 1
        v[min(w, 5)] += 1

    if use_old_nist_pi:
        pi = (0.367879, 0.183940, 0.137955, 0.099634, 0.069935, 0.140657)
    else:  # use improved pi values given by Hamano and Kaneko
        pi = (0.364091, 0.185659, 0.139381, 0.100571, 0.0704323, 0.139865)

    chi2_obs = sum(
        map(lambda x: (x[0] - n_blocks * x[1])**2 / (n_blocks * x[1]),
            zip(v, pi)))

    p_value = igamc(5 / 2, chi2_obs / 2)

    if type(misc) == dict:
        misc.update(v=v, lambda_=lambda_, eta=eta, chi2_obs=chi2_obs)

    return p_value
示例#10
0
def non_overlapping_template_matching(generator,
                                      n_bits,
                                      b=None,
                                      n_blocks=None,
                                      misc=None):
    """Non-overlapping Template Matching Test.

    Test purpose as described in [NIST10, section 2.7]:
    "The focus of this test is the number of occurrences of pre-specified target strings. The
    purpose of this test is to detect generators that produce too many occurrences of a given
    non-periodic (aperiodic) pattern. For this test and for the Overlapping Template Matching test
    of Section 2.8, an m-bit window is used to search for a specific m-bit pattern. If the pattern
    is not found, the window slides one bit position. If the pattern is found, the window is reset
    to the bit after the found pattern, and the search resumes."
    """
    if not b:
        b = [0, 0, 0, 0, 0, 0, 0, 0, 1]
    elif type(b) == tuple:
        b = list(b)
    b_len = len(b)

    if not n_blocks:
        n_blocks = 8
    m = n_bits // n_blocks

    exp_mean = (m - b_len + 1) / (2**b_len)
    exp_var = m * (1 / (2**b_len) - (2 * b_len - 1) / (2**(2 * b_len)))

    n_blocks = n_bits // m
    chi2_obs = 0
    for _ in range(n_blocks):
        block = [generator.random_bit() for _ in range(m)]
        i, w = 0, 0
        while i < m - b_len + 1:
            if block[i:i + b_len] == b:
                w += 1
                i += b_len
            else:
                i += 1
        chi2_obs += ((w - exp_mean)**2) / exp_var

    p_value = igamc(n_blocks / 2, chi2_obs / 2)

    if type(misc) == dict:
        misc.update(exp_mean=exp_mean,
                    exp_var=exp_var,
                    n=n_blocks,
                    chi2_obs=chi2_obs)

    return p_value
示例#11
0
def test(bits, blocklen=2):
    n = len(bits)
    m = blocklen
    N = n // m
    bits += bits[0:m - 1]

    ν_m = [[0 for j in range(2**(m - i))] for i in range(3)]
    for i in range(3):
        for position in range(n):
            idx = block2int(bits[position:position + (m - i)])
            ν_m[i][idx] += 1

    ψ2_m = [0] * 3
    for i in range(3):
        a = Fraction(2**(m - i), n)
        ψ2_m[i] = a * sum([ν_m[i][j]**2 for j in range(2**(m - i))]) - n

    nabla_ψ2_m = ψ2_m[0] - ψ2_m[1]
    nabla2_ψ2_m = ψ2_m[0] - 2 * ψ2_m[1] + ψ2_m[2]
    P_value1 = igamc(2**(m - 2), nabla_ψ2_m / 2.0)
    P_value2 = igamc(2**(m - 3), nabla2_ψ2_m / 2.0)
    level = 0.01
    ok = (P_value1 >= level and P_value2 >= level)
    return ((P_value1, P_value2), level, ok)
def test(bits, blocklen=1000):
    n = len(bits)
    M = blocklen
    N = n // M
    L = []
    for i in range(N):
        x = bits[i * M:(i + 1) * M]
        L.append(berelekamp_massey(x)[0])

    μ = Fraction(M, 2) + Fraction(9 + (-1)**(M+1), 36) \
        - Fraction(Fraction(M, 3) + Fraction(2, 9), 2**M)
    T = []
    for i in range(N):
        T.append(((-1.0)**M) * (L[i] - μ) + (2 / 9.0))

    ν = [0] * 7
    for t in T:
        if t <= -2.5: ν[0] += 1
        elif t <= -1.5: ν[1] += 1
        elif t <= -0.5: ν[2] += 1
        elif t <= 0.5: ν[3] += 1
        elif t <= 1.5: ν[4] += 1
        elif t <= 2.5: ν[5] += 1
        else: ν[6] += 1

    K = 6
    π = [
        Fraction(0.010417),
        Fraction(0.03125),
        Fraction(0.125),
        Fraction(0.5),
        Fraction(0.25),
        Fraction(0.0625),
        Fraction(0.020833)
    ]
    χ2_obs = sum([(ν[i] - N * π[i])**2 / (N * π[i]) for i in range(K)])
    P_value = igamc(K / 2.0, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
def test(bits, rowlen=32, collen=32):
    n = len(bits)
    M = rowlen
    Q = collen
    N = n // (M * Q)

    blocks = []
    for j in range(N):
        blocks.append(
            [bits[(j * Q + i) * M:(j * Q + i + 1) * M] for i in range(Q)])
    R = [matrix_rank(block) for block in blocks]

    F_M = 0
    F_M_1 = 0
    for i in range(N):
        if R[i] == M: F_M += 1
        elif R[i] == M - 1: F_M_1 += 1

    F = [F_M, F_M_1, N - F_M - F_M_1]
    π = [Fraction(0.2888), Fraction(0.5776), Fraction(0.1336)]
    χ2_obs = sum([(F[i] - N * π[i])**2 / (N * π[i]) for i in range(3)])
    P_value = igamc(1, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
def test(bits, blocklen=None, template=None):
    n = len(bits)
    M = blocklen if blocklen else n // 8
    if M <= 0.01 * n:
        return (-1, -1, False)

    N = n // M
    blocks = [bits[i * M:(i + 1) * M] for i in range(N)]
    B = template if template else random.choices(templates[8])
    m = len(B)

    ν = [0] * 6
    for block in blocks:
        count = 0
        for position in range(M - m + 1):
            if block[position:position + m] == B:
                count += 1

        if count >= 5:
            ν[5] += 1
        else:
            ν[count] += 1

    # λ = (M - m + 1) / 2**m  ~~ 2
    π = [
        Fraction(0.364091),
        Fraction(0.185659),
        Fraction(0.139381),
        Fraction(0.100571),
        Fraction(0.070432),
        Fraction(0.139865)
    ]
    χ2_obs = sum([(ν[i] - N * π[i])**2 / (N * π[i]) for i in range(6)])
    P_value = igamc(5 / 2.0, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
def test(bits):
    n = len(bits)
    if n < 128:
        return (-1, -1, False)  # Not enough bits length
    elif n < 6272:
        M = 8
    elif n < 750000:
        M = 128
    else:
        M = 10000

    N = n // M
    blocks = [bits[i * M:(i + 1) * M] for i in range(N)]
    ν = [0] * 7
    for i in range(N):
        # get longest run of ones
        run = 0
        longest = 0
        for bit in blocks[i]:
            if bit == 1:
                run += 1
                if run > longest:
                    longest = run
            else:
                run = 0

        if M == 8:
            if longest <= 1: ν[0] += 1
            elif longest == 2: ν[1] += 1
            elif longest <= 3: ν[2] += 1
            else: ν[3] += 1
        elif M == 128:
            if longest <= 4: ν[0] += 1
            elif longest == 5: ν[1] += 1
            elif longest == 6: ν[2] += 1
            elif longest == 7: ν[3] += 1
            elif longest == 8: ν[4] += 1
            else: ν[5] += 1
        else:
            if longest <= 10: ν[0] += 1
            elif longest == 11: ν[1] += 1
            elif longest == 12: ν[2] += 1
            elif longest == 13: ν[3] += 1
            elif longest == 14: ν[4] += 1
            elif longest == 15: ν[5] += 1
            else: ν[6] += 1

    if M == 8:
        K = 3
        N = 16
    elif M == 128:
        K = 5
        N = 49
    else:
        K = 6
        N = 75

    π = prob_table(M)
    χ2_obs = sum([(ν[i] - N * π[i])**2 / (N * π[i]) for i in range(K + 1)])
    P_value = igamc(K / 2.0, χ2_obs / 2.0)
    level = 0.01
    return (P_value, level, P_value >= level)
示例#16
0
def random_excursions(generator, n_bits, misc=None):
    """Random Excursions Test.

    Test purpose as described in [NIST10, section 2.14]:
    "The focus of this test is the number of cycles having exactly K visits in a cumulative sum
    random walk. The cumulative sum random walk is derived from partial sums after the (0,1)
    sequence is transferred to the appropriate (-1, +1) sequence. A cycle of a random walk consists
    of a sequence of steps of unit length taken at random that begin at and return to the origin.
    The purpose of this test is to determine if the number of visits to a particular state within a
    cycle deviates from what one would expect for a random sequence. This test is actually a series
    of eight tests (and conclusions), one test and conclusion for each of the states: -4, -3, -2, -1
    and +1, +2, +3, +4."
    """

    if n_bits < 10**6:
        print("Warning: Sequence should be at least 10^6 bits long",
              file=stderr)

    s = [0] * n_bits
    for i in range(n_bits):
        x = 1 if generator.random_bit() else -1
        s[i] = s[i - 1] + x
    s.append(0)  # leading zero not needed for our implementation

    v = [[0] * 6 for _ in range(8)]
    f = [0] * 8
    j = 0
    for x in s:
        if x == 0:
            j += 1
            for i, y in enumerate(f):
                v[i][min(y, 5)] += 1
            f = [0] * 8
        elif -4 <= x <= 4:
            f[x + 4 - (x > 0)] += 1

    if j < 500:
        print(
            "Warning: The number of cycles (zero crossings) should be at least 500 (is %d)"
            % j,
            file=stderr)

    def pi(k, x):
        a = 1 - 1 / 2 / abs(x)
        if k == 0:
            return a
        elif k < 5:
            return 1 / 4 / (x**2) * (a**(k - 1))
        else:  # k >= 5
            return 1 / 2 / abs(x) * (a**4)

    chi2_obs = []
    for i, x in enumerate(list(range(-4, 0)) + list(range(1, 5))):
        chi2_obs.append(
            sum(
                map(lambda v_k_x, pi_k_x: (v_k_x - j * pi_k_x)**2 / j / pi_k_x,
                    v[i], [pi(k, x) for k in range(0, 6)])))

    p_value = list(map(lambda chi: igamc(5 / 2, chi / 2), chi2_obs))

    if type(misc) == dict:
        misc.update(cumsum=s, j=j, v=v, chi2_obs=chi2_obs)

    return p_value
def test(bits, mode=0):
    n = len(bits)
    X = [2 * x - 1 for x in bits]
    total = 0
    S = []
    for i in range(n):
        total += X[i]
        S.append(total)

    S_dash = [0] + S + [0]
    zero_positions = [i for i in range(1, len(S_dash)) if S_dash[i] == 0]
    J = len(zero_positions)
    Cycles = []
    pre = 0
    for pos in zero_positions:
        Cycles.append(S_dash[pre:pos + 1])
        pre = pos

    states = []
    for cycle in Cycles:
        state = {-4: 0, -3: 0, -2: 0, -1: 0, 1: 0, 2: 0, 3: 0, 4: 0}
        for x in cycle:
            if x == -4: state[x] += 1
            elif x == -3: state[x] += 1
            elif x == -2: state[x] += 1
            elif x == -1: state[x] += 1
            elif x == +1: state[x] += 1
            elif x == +2: state[x] += 1
            elif x == +3: state[x] += 1
            elif x == +4: state[x] += 1
            else: pass
        states.append(state)

    table = {
        -4: [0] * 6,
        -3: [0] * 6,
        -2: [0] * 6,
        -1: [0] * 6,
        +1: [0] * 6,
        +2: [0] * 6,
        +3: [0] * 6,
        +4: [0] * 6
    }

    for x in (-4, -3, -2, -1, 1, 2, 3, 4):  # state x
        for n in range(6):  # number of cycles (0..5)
            count = len([True for state in states if state[x] == n])
            table[x][n] = count

    πxk = {
        1: [0.5, 0.25, 0.125, 0.0625, 0.0312, 0.0312],
        2: [0.75, 0.0625, 0.0469, 0.0352, 0.0264, 0.0791],
        3: [0.8333, 0.0278, 0.0231, 0.0193, 0.0161, 0.0804],
        4: [0.875, 0.0156, 0.0137, 0.012, 0.0105, 0.0733],
        5: [0.9, 0.01, 0.009, 0.0081, 0.0073, 0.0656],
        6: [0.9167, 0.0069, 0.0064, 0.0058, 0.0053, 0.0588],
        7: [0.9286, 0.0051, 0.0047, 0.0044, 0.0041, 0.0531]
    }

    χ2_obs = {-4: 0, -3: 0, -2: 0, -1: 0, 1: 0, 2: 0, 3: 0, 4: 0}
    f = lambda obs, exp: (obs - exp)**2 / exp
    for x in (-4, -3, -2, -1, 1, 2, 3, 4):
        χ2_obs[x] = sum(
            [f(table[x][k], Fraction(J * πxk[abs(x)][k])) for k in range(6)])

    P_value = {-4: 0, -3: 0, -2: 0, -1: 0, 1: 0, 2: 0, 3: 0, 4: 0}
    for x in (-4, -3, -2, -1, 1, 2, 3, 4):
        P_value[x] = igamc(5 / 2.0, χ2_obs[x] / 2.0)

    level = 0.01
    ok = all(P_value[x] >= level for x in (-4, -3, -2, -1, 1, 2, 3, 4))
    return (P_value, level, ok)