is_extra_bfsw_pseudoprime.sf 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/ruby
  2. # Author: Daniel "Trizen" Șuteu
  3. # Date: 31 October 2023
  4. # https://github.com/trizen
  5. # A new primality test, using only the Lucas V sequence.
  6. # This test is a simplification of the strengthen BPSW test:
  7. # https://arxiv.org/abs/2006.14425
  8. define USE_METHOD_A_STAR = false # true to use the A* method in finding (P,Q)
  9. func partial_lucasVmod_pow2(P, Q, two_val, m, V1=2, V2=P, Q1=1, Q2=1) {
  10. Q1 = mulmod(Q1, Q2, m)
  11. Q2 = mulmod(Q1, Q, m)
  12. V1 = mulsubmulmod(V1, V2, P, Q1, m)
  13. Q2 = mulmod(Q2, Q1, m)
  14. two_val.times {
  15. V1 = mulsubmulmod(V1, V1, 2, Q2, m)
  16. Q2 = mulmod(Q2, Q2, m)
  17. }
  18. return (V1, Q2)
  19. }
  20. func partial_lucasVmod(P, Q, bits, m, V1=2, V2=P, Q1=1, Q2=1) {
  21. for bit in bits {
  22. Q1 = mulmod(Q1, Q2, m)
  23. if (bit) {
  24. Q2 = mulmod(Q1, Q, m)
  25. V1 = mulsubmulmod(V2, V1, P, Q1, m)
  26. V2 = mulsubmulmod(V2, V2, 2, Q2, m)
  27. }
  28. else {
  29. Q2 = Q1
  30. V2 = mulsubmulmod(V2, V1, P, Q1, m)
  31. V1 = mulsubmulmod(V1, V1, 2, Q2, m)
  32. }
  33. }
  34. return (V1,V2,Q1,Q2)
  35. }
  36. func check_lucasV(P,Q,n,m) {
  37. var b1 = n.bits
  38. var b2 = n.inc.bits
  39. var k = 0
  40. if (b1.len == b2.len) {
  41. k = b1.range.first{|i| b1[i] != b2[i] }
  42. }
  43. var(V1,V2,Q1,Q2) = partial_lucasVmod(P, Q, b1.first(k), m)
  44. var(V1_a,Q2_a) = partial_lucasVmod_pow2(P, Q, b2.end - k, m, V1, V2, Q1, Q2)
  45. V1_a.is_congruent(2*Q, m) || return false
  46. Q2_a.is_congruent(Q*Q, m) || return false
  47. var(V1_b,_,_,Q2_b) = partial_lucasVmod(P, Q, b1.slice(k), m, V1, V2, Q1, Q2)
  48. V1_b.is_congruent(P,m) || return false
  49. Q2_b.is_congruent(Q*kronecker(Q, m), m) || return false
  50. return true
  51. }
  52. func findQ(N) {
  53. for k in (2 .. Inf) {
  54. var D = ((-1)**k * (2*k + 1))
  55. var K = kronecker(D, N)
  56. if (K.is_zero && gcd(D, N).is_between(2, N-1)) {
  57. return nil
  58. }
  59. elsif ((k == 20) && N.is_square) {
  60. return nil
  61. }
  62. return ((1-D)/4) if (K == -1)
  63. }
  64. }
  65. func findP (N, Q) {
  66. for P in (2..Inf) {
  67. var D = (P*P - 4*Q)
  68. var K = kronecker(D, N)
  69. if (K == -1) {
  70. return P
  71. }
  72. elsif (K.is_zero && gcd(D, N).is_between(2, N-1)) {
  73. return nil
  74. }
  75. elsif ((P == 20) && N.is_square) {
  76. return nil
  77. }
  78. }
  79. }
  80. func is_extra_bfsw_psp(n) {
  81. n <= 1 && return false
  82. n == 2 && return true
  83. n.is_even && return false
  84. var(P,Q)
  85. if (USE_METHOD_A_STAR) {
  86. P = 1
  87. Q = findQ(n) \\ return false
  88. if (Q == -1) {
  89. P = 5
  90. Q = 5
  91. }
  92. }
  93. else { # this is faster
  94. Q = -2
  95. P = findP(n, Q) \\ return false
  96. }
  97. check_lucasV(P,Q,n,n)
  98. }
  99. say 25.by(is_extra_bfsw_psp)
  100. assert([913, 150267335403, 430558874533, 14760229232131, 936916995253453].none(is_extra_bfsw_psp))
  101. for n in (1..1e3) {
  102. if (is_extra_bfsw_psp(n)) {
  103. if (!n.is_prime) {
  104. say "Counter-example: #{n}"
  105. }
  106. }
  107. elsif (n.is_prime) {
  108. say "Missed-prime: #{n}"
  109. }
  110. }
  111. __END__
  112. Inspired by the paper "Strengthening the Baillie-PSW primality test", I propose a simplified test based on Lucas V-pseudoprimes, that requires computing only the Lucas V sequence, making it faster than the full BPSW test, while being about as strong.
  113. The first observation was that none of the 5 vpsp terms < 10^15 satisfy:
  114. Q^(n+1) == Q^2 (mod n)
  115. This gives us a simple test:
  116. V_{n+1}(P,Q) == 2*Q (mod n)
  117. Q^(n+1) == Q^2 (mod n)
  118. where (P,Q) are selected using Method A*.
  119. At very little additional computational cost (on average), we can make the test even stronger, by also checking:
  120. V_n(P,Q) == P (mod n)
  121. Notice that also none of the 5 vpsp terms < 10^15 satisfy the above congruence.
  122. The trick for computing V_n with very little additional computational cost (on average), is to compute the partial value of the Lucas V sequence, using the most significant overlapping bits of n and n+1.
  123. First we compute:
  124. V_d(P,Q) mod n
  125. where d is the "most significant overlapping binary part" of n and n+1.
  126. For example, if n = 43, we have:
  127. n = 101011_2
  128. n+1 = 101100_2
  129. The most significant overlapping bits of n and n+1 are: "101", therefore d = 101_2 = 5.
  130. From V_d(P,Q) mod n, we compute V_{n+1}(P,Q) mod n, using the remaining bits of n+1: "100".
  131. Notice that the remaining bits of n+1 always form a power of two, allowing us to optimize the computation of V_{n+1}(P,Q) mod n.
  132. At this stage, we check the necessary congruences trying to return early:
  133. V_{n+1}(P,Q) == 2*Q (mod n)
  134. Q^(n+1) == Q^2 (mod n)
  135. If the number passed the above congruences, we compute V_n(P,Q) mod n from V_d(P,Q) mod n, using the remaining bits of n: "011", then we check:
  136. V_n(P,Q) == P (mod n)
  137. Q^((n+1)/2) == Q*(Q|n) (mod n)
  138. Finally, we return true if the number satisfied all the congruences, indicating that it is probably prime.
  139. There are no known counter-examples to the presented test.
  140. Remarks:
  141. - For numbers of the form n = 4*x + 1, only the last last two bits differ from n and n+1, therefore only two extra steps in the "partial_lucasVmod()" function are needed to also compute V_n(P,Q) mod n, which is very cheap.
  142. - On the other hand, for numbers of the form n = 2^k - 1, all the bits of n and n+1 are different, which makes the computation of V_n(P,Q) quite expensive. But we can use the Lucas-Lehmer test for such numbers.
  143. - Numbers of the form x*2^k - 1, with x < 2^k, also take longer to check, but we can use the Lucas-Lehmer-Riesel (LLR) test for those.
  144. Optimization ideas:
  145. - To ensure that the test is always fast, we can skip the computation of V_n(P,Q) if the length of the remaining bits of n is too large (e.g. larger than the number of bits of d). This bounds the running time of the test to: 1.5 * (the cost of computing V_n(P,Q) mod n), while still having no known counter-examples.
  146. - In the selection of parameters (P,Q), we can start with Q = -2 and finding the first P >= 2 that satisfies jacobi(P^2 - 4*Q, n) = -1. The reason being that it is faster for computers to multiply by powers of two, and thus it makes the computation of the Lucas V sequence a bit faster, since |Q| is a power of two and, most of the time, P is also a power of 2.
  147. - In a general-purpose "is_prime(n)" function, for performance reasons, we should also do a little bit of trial-division (or gcd with primorials) and then a strong pseudoprime test to base 2, trying to return early if possible.