This is an automated email from the git hooks/post-receive script.

ppm-guest pushed a commit to annotated tag v0.13
in repository libmath-prime-util-perl.

commit e440b5da107e790f0c9876b26257368378d687aa
Author: Dana Jacobsen <d...@acm.org>
Date:   Sat Nov 17 21:53:56 2012 -0800

    Add AKS primality test
---
 .gitignore                |   2 +
 Changes                   |   6 +-
 MANIFEST                  |   2 +
 Makefile.PL               |   1 +
 TODO                      |   6 +-
 XS.xs                     |   4 +
 aks.c                     | 210 ++++++++++++++++++++++++++++++++++++++++++++++
 aks.h                     |   8 ++
 lib/Math/Prime/Util.pm    |  43 ++++++++--
 lib/Math/Prime/Util/PP.pm | 156 ++++++++++++++++++++++++++++++++++
 sieve.c                   |   2 +-
 t/10-isprime.t            |  11 ++-
 12 files changed, 437 insertions(+), 14 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4ed058c..04fde6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,10 +3,12 @@ Makefile
 Makefile.old
 MYMETA.*
 Util.bs
+pm_to_blib
 XS.[co]
 cache.o
 factor.o
 sieve.o
 util.o
 lehmer.o
+aks.o
 blib*
diff --git a/Changes b/Changes
index 64480bb..3a29363 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,6 @@
 Revision history for Perl extension Math::Prime::Util.
 
-0.12  12 November 2012
+0.12  17 November 2012
 
     - Add bin/primes.pl and bin/factor.pl
 
@@ -10,6 +10,7 @@ Revision history for Perl extension Math::Prime::Util.
            prime_set_config        set config options
            RiemannZeta             export and make accurate for small reals
            is_provable_prime       prove primes after BPSW
+           is_aks_prime            prove prime via AKS
 
     - Add 'assume_rh' configuration option (default: false) which can be set
       to allow functions to assume the Riemann Hypothesis.
@@ -30,7 +31,8 @@ Revision history for Perl extension Math::Prime::Util.
 
     - bug fix for prime_count on edge of cache.
 
-    - Add Lehmer prime counting algorithm.  This is much faster than sieving.
+    - prime_count will use Lehmer prime counting algorithm for largish
+      sizes (above 4 million).  This is MUCH faster than sieving.
 
 0.11  23 July 2012
     - Turn off threading tests on Cygwin, as threads on some Cygwin platforms
diff --git a/MANIFEST b/MANIFEST
index 608078e..bcc1329 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,8 @@ README
 TODO
 XS.xs
 ptypes.h
+aks.h
+aks.c
 cache.h
 cache.c
 factor.h
diff --git a/Makefile.PL b/Makefile.PL
index ca13682..ad34e57 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -9,6 +9,7 @@ WriteMakefile1(
 
     OBJECT       => 'cache.o '  .
                     'factor.o ' .
+                    'aks.o '    .
                     'lehmer.o ' .
                     'sieve.o '  .
                     'util.o '   .
diff --git a/TODO b/TODO
index d20aa19..7da582a 100644
--- a/TODO
+++ b/TODO
@@ -23,8 +23,6 @@
 
 - tests for primorial
 
-- document prime_set_config
-
 - Test all routines for numbers on word-size boundary, or ranges that cross.
 
 - Test all functions return either native or bigints.  Functions that return
@@ -32,4 +30,6 @@
 
 - Make proper pminus1 in PP code, like factor.c.
 
-- Add Lehmer prime count function for large values.
+- Add Lehmer in PP (I have a version, just needs some polishing).
+
+- Move AKS tests into their own test, and add some provable prime tests.
diff --git a/XS.xs b/XS.xs
index ba7d0b3..d61f012 100644
--- a/XS.xs
+++ b/XS.xs
@@ -9,6 +9,7 @@
 #include "util.h"
 #include "factor.h"
 #include "lehmer.h"
+#include "aks.h"
 
 MODULE = Math::Prime::Util     PACKAGE = Math::Prime::Util
 
@@ -49,6 +50,9 @@ _XS_nth_prime(IN UV n)
 int
 _XS_is_prime(IN UV n)
 
+int
+_XS_is_aks_prime(IN UV n)
+
 UV
 _XS_next_prime(IN UV n)
 
diff --git a/aks.c b/aks.c
new file mode 100644
index 0000000..2b43943
--- /dev/null
+++ b/aks.c
@@ -0,0 +1,210 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+/*
+ * The AKS v6 algorithm, for native integers, where n < 2^(wordbits/2)-1.
+ * Hence on 64-bit machines this works for n < 4294967295, because we do
+ *   r = (r + a * b) % n
+ * where r, a, and b are mod n.  This could be extended to a full word by
+ * using a mulmod function (like factor.c has), but it's easier to go to
+ * GMP at that point, which also lets one do r or 2r modulos instead of r*r.
+ *
+ * Copyright 2012, Dana Jacobsen.
+ */
+
+#define SQRTN_SHORTCUT 1
+#define VERBOSE 0
+
+#include "ptypes.h"
+#include "util.h"
+#include "sieve.h"
+#include "factor.h"
+#include "cache.h"
+
+#define addmod(n,a,m) ((((m)-(n)) > (a))  ?  ((n)+(a))  :  ((n)+(a)-(m)))
+
+static UV log2floor(UV n) {
+  UV log2n = 0;
+  while (n >>= 1)
+    log2n++;
+  return log2n;
+}
+
+/* See Bach and Sorenson (1993) for much better algorithm */
+static int is_perfect_power(UV x) {
+  UV b, last;
+  if ((x & (x-1)) == 0)  return 1;          /* powers of 2    */
+  b = sqrt(x); if (b*b == x)  return 1;     /* perfect square */
+  b = cbrt(x); if (b*b*b == x)  return 1;   /* perfect cube   */
+  last = log2floor(x) + 1;
+  for (b = 5; b < last; b = _XS_next_prime(b)) {
+    UV root = pow(x, 1.0 / (double)b);
+    if (pow(root, b) == x)  return 1;
+  }
+  return 0;
+}
+
+static UV order(UV r, UV n, UV limit) {
+  UV j;
+  UV t = 1;
+  for (j = 1; j <= limit; j++) {
+    t = (t * n) % r;
+    if (t == 1)
+      break;
+  }
+  return j;
+}
+
+static void poly_print(UV* poly, UV r)
+{
+  int i;
+  for (i = r-1; i >= 1; i--) {
+    if (poly[i] != 0)
+      printf("%lux^%d + ", poly[i], i);
+  }
+  if (poly[0] != 0) printf("%lu", poly[0]);
+  printf("\n");
+}
+
+static void poly_mod_mul(UV* px, UV* py, UV* res, UV r, UV mod)
+{
+  int i, j;
+  UV pxi, pyj, rindex;
+
+  memset(res, 0, r * sizeof(UV));
+  for (i = 0; i < r; i++) {
+    pxi = px[i];
+    if (pxi == 0)  continue;
+    for (j = 0; j < r; j++) {
+      pyj = py[j];
+      if (pyj == 0)  continue;
+      rindex = (i+j) < r ? i+j : i+j-r; /* (i+j) % r */
+      res[rindex] = (res[rindex] + (pxi*pyj) ) % mod;
+    }
+  }
+  memcpy(px, res, r * sizeof(UV)); /* put result in px */
+}
+static void poly_mod_sqr(UV* px, UV* res, UV r, UV mod)
+{
+  int d, s;
+  UV sum, rindex;
+  UV degree = r-1;
+
+  /* we sum a max of r*mod*mod between modulos */
+  if (mod > sqrt(UV_MAX/r))
+    return poly_mod_mul(px, px, res, r, mod);
+
+  memset(res, 0, r * sizeof(UV)); /* zero out sums */
+  for (d = 0; d <= 2*degree; d++) {
+    sum = 0;
+    for (s = (d <= degree) ? 0 : d-degree; s <= (d/2); s++) {
+      UV c = px[s];
+      sum += (s*2 == d) ? c*c : 2*c * px[d-s];
+    }
+    rindex = (d < r) ? d : d-r;  /* d % r */
+    res[rindex] = (res[rindex] + sum) % mod;
+  }
+  memcpy(px, res, r * sizeof(UV)); /* put result in px */
+}
+
+static UV* poly_mod_pow(UV* pn, UV power, UV r, UV mod)
+{
+  UV* res;
+  UV* temp;
+
+  Newz(0, res, r, UV);
+  New(0, temp, r, UV);
+  if ( (res == 0) || (temp == 0) )
+    croak("Couldn't allocate space for polynomial of degree %lu\n", r);
+
+  res[0] = 1;
+
+  while (power) {
+    if (power & 1)  poly_mod_mul(res, pn, temp, r, mod);
+    power >>= 1;
+    if (power)      poly_mod_sqr(pn, temp, r, mod);
+  }
+  Safefree(temp);
+  return res;
+}
+
+static int test_anr(UV a, UV n, UV r)
+{
+  UV* pn;
+  UV* res;
+  int i;
+  int retval = 1;
+
+  Newz(0, pn, r, UV);
+  if (pn == 0)
+    croak("Couldn't allocate space for polynomial of degree %lu\n", r);
+  a %= r;
+  pn[0] = a;
+  pn[1] = 1;
+  res = poly_mod_pow(pn, n, r, n);
+  res[n % r] = addmod(res[n % r], n - 1, n);
+  res[0]     = addmod(res[0],     n - a, n);
+
+  for (i = 0; i < r; i++)
+    if (res[i] != 0)
+      retval = 0;
+  Safefree(res);
+  Safefree(pn);
+  return retval;
+}
+
+int _XS_is_aks_prime(UV n)
+{
+  UV sqrtn, limit, r, rlimit, a;
+  double log2n;
+
+  /* Check for overflow */
+#if BITS_PER_WORD == 32
+  if (n >= UVCONST(65535))
+#else
+  if (n >= UVCONST(4294967295))
+#endif
+    croak("aks(%"UVuf") overflow", n);
+
+  if (n < 2)
+    return 0;
+  if (n == 2)
+    return 1;
+
+  if (is_perfect_power(n))
+    return 0;
+
+  sqrtn = sqrt(n);
+  log2n = log(n) / log(2);   /* C99 has a log2() function */
+  limit = (UV) floor(log2n * log2n);
+
+  if (VERBOSE) { printf("limit is %lu\n", limit); }
+
+  for (r = 2; r < n; r++) {
+    if ((n % r) == 0)
+      return 0;
+#if SQRTN_SHORTCUT
+    if (r > sqrtn)
+      return 1;
+#endif
+    if (order(r, n, limit) > limit)
+      break;
+  }
+
+  if (r >= n)
+    return 1;
+
+  rlimit = (UV) floor(sqrt(r-1) * log2n);
+
+  if (VERBOSE) { printf("r = %lu  rlimit = %lu\n", r, rlimit); }
+
+  for (a = 1; a <= rlimit; a++) {
+    if (! test_anr(a, n, r) )
+      return 0;
+    if (VERBOSE) { printf("."); fflush(stdout); }
+  }
+  if (VERBOSE) { printf("\n"); }
+  return 1;
+}
diff --git a/aks.h b/aks.h
new file mode 100644
index 0000000..e8c7452
--- /dev/null
+++ b/aks.h
@@ -0,0 +1,8 @@
+#ifndef MPU_AKS_H
+#define MPU_AKS_H
+
+#include "ptypes.h"
+
+extern int _XS_is_aks_prime(UV n);
+
+#endif
diff --git a/lib/Math/Prime/Util.pm b/lib/Math/Prime/Util.pm
index f94cbef..eafa8cc 100644
--- a/lib/Math/Prime/Util.pm
+++ b/lib/Math/Prime/Util.pm
@@ -16,6 +16,7 @@ our @EXPORT_OK = qw(
                      prime_precalc prime_memfree
                      is_prime is_prob_prime is_provable_prime
                      is_strong_pseudoprime is_strong_lucas_pseudoprime
+                     is_aks_prime
                      miller_rabin
                      primes
                      next_prime  prev_prime
@@ -819,6 +820,19 @@ sub is_prime {
   return is_prob_prime($n);
 }
 
+sub is_aks_prime {
+  my($n) = @_;
+  return 0 if $n <= 0;
+  _validate_positive_integer($n);
+
+  my $max_xs = ($_Config{'maxparam'} > 4294967295) ? 4294967294 : 65534;
+  return _XS_is_aks_prime($n) if $n <= $_XS_MAXVAL && $n <= $max_xs;
+  return Math::Prime::Util::GMP::is_aks_prime($n) if $_HAVE_GMP
+                       && defined &Math::Prime::Util::GMP::is_aks_prime;
+  return Math::Prime::Util::PP::is_aks_prime($n);
+}
+
+
 sub next_prime {
   my($n) = @_;
   _validate_positive_integer($n);
@@ -1017,7 +1031,7 @@ sub is_provable_prime {
   # prove it.  We should do ECPP or APR-CL now, or failing that, do the
   # Brillhart-Lehmer-Selfridge test, or Pocklington-Lehmer.  Until those
   # are written here, we'll do a Lucas test, which is super simple but may
-  # be very slow.  We could do AKS, but that's even slower.
+  # be very slow.  We have AKS code, but it's insanely slow.
   # See http://primes.utm.edu/prove/merged.html or other sources.
 
   # It shouldn't be possible to get here without BigInt already loaded.
@@ -1662,10 +1676,10 @@ base under C<10^14>.
 
 Lehmer's method has complexity approximately C<O(b^0.7) + O(a^0.7)>.  It
 does use more memory however.  A calculation of C<Pi(10^14)> completes in
-under 1 minute, C<Pi(10^15)> in approximately 5 minutes, and C<Pi(10^16)>
-in about 30 minutes, however using nearly 800MB of peak memory for the last.
-In contrast, even primesieve using multiple cores would take over a week
-on this same computer to determine C<Pi(10^16)>.
+under 1 minute, C<Pi(10^15)> in undef 5 minutes, and C<Pi(10^16)> in under
+30 minutes, however using nearly 1400MB of peak memory for the last.
+In contrast, even primesieve using 12 cores would take over a week on this
+same computer to determine C<Pi(10^16)>.
 
 Also see the function L</"prime_count_approx"> which gives a very good
 approximation to the prime count, and L</"prime_count_lower"> and
@@ -1844,6 +1858,21 @@ usually take less time, but can still fail.  Hence you 
should always test that
 the result is C<2> to ensure the prime is proven.
 
 
+=head2 is_aks_prime
+
+  say "$n is definitely prime" if is_aks_prime($n);
+
+Takes a positive number as input, and returns 1 if the input passes the
+Agrawal-Kayal-Saxena (AKS) primality test.  This is a deterministic
+unconditional primality test which runs in polynomial time for general input.
+
+This function is only included for completeness and as an example.  While the
+implementation is fast compared to the only other Perl implementation available
+(in L<Math::Primality>), it is slow compared to others.  However, even
+optimized AKS implementations are far slower than ECPP or other modern
+primality tests.
+
+
 =head2 moebius
 
   say "$n is square free" if moebius($n) != 0;
@@ -2293,7 +2322,9 @@ Perl threads.
 =head1 PERFORMANCE
 
 Counting the primes to C<10^10> (10 billion), with time in seconds.
-Pi(10^10) = 455,052,511.
+Pi(10^10) = 455,052,511.  Note that the Lehmer prime counting method in
+Math::Prime::Util version 0.12 takes 0.064 seconds to do this -- the numbers
+below are all for doing it the long way (sieving).
 
    External C programs in C / C++:
 
diff --git a/lib/Math/Prime/Util/PP.pm b/lib/Math/Prime/Util/PP.pm
index 181f316..0a67888 100644
--- a/lib/Math/Prime/Util/PP.pm
+++ b/lib/Math/Prime/Util/PP.pm
@@ -533,6 +533,36 @@ sub _gcd_ui {
   $x;
 }
 
+sub _is_perfect_power {
+  my $n = shift;
+  my $log2n = _log2($n);
+  $n = Math::BigInt->new("$n") unless ref($n) eq 'Math::BigInt';
+  for my $e (@{primes($log2n)}) {
+    return 1 if $n->copy()->broot($e)->bpow($e) == $n;
+  }
+  0;
+}
+
+sub _order {
+  my($r, $n, $lim) = @_;
+  $lim = $r unless defined $lim;
+
+  return 1 if ($n % $r) == 1;
+  for (my $j = 2; $j <= $lim; $j++) {
+    return $j if _powmod($n, $j, $r) == 1;
+  }
+  return $lim+1;
+}
+
+# same result as:  int($n->blog(2)->floor->bstr)
+sub _log2 {
+  my $n = shift;
+  my $log2n = 0;
+  $log2n++ while ($n >>= 1);
+  $log2n;
+}
+
+
 
 sub miller_rabin {
   my($n, @bases) = @_;
@@ -783,6 +813,126 @@ sub is_strong_lucas_pseudoprime {
 }
 
 
+my $_poly_bignum;
+sub _poly_new {
+  my @poly;
+  if ($_poly_bignum) {
+    foreach my $c (@_) {
+      push @poly, (ref $c eq 'Math::BigInt') ? $c->copy : 
Math::BigInt->new("$c");
+    }
+  } else {
+    push @poly, $_ for (@_);
+    push @poly, 0 unless scalar @poly;
+  }
+  return \@poly;
+}
+
+sub _poly_print {
+  my($poly) = @_;
+  warn "poly has null top degree" if $#$poly > 0 && !$poly->[-1];
+  foreach my $d (reverse 1 .. $#$poly) {
+    my $coef = $poly->[$d];
+    print "", ($coef != 1) ? $coef : "", ($d > 1) ? "x^$d" : "x", " + "
+      if $coef;
+  }
+  my $p0 = $poly->[0] || 0;
+  print "$p0\n";
+}
+
+sub _poly_mod_mul {
+  my($px, $py, $r, $n) = @_;
+
+  my $px_degree = $#$px;
+  my $py_degree = $#$py;
+  my @res;
+
+  # convolve(px, py) mod (X^r-1,n)
+  my @indices_y = grep { $py->[$_] } (0 .. $py_degree);
+  for (my $ix = 0; $ix <= $px_degree; $ix++) {
+    my $px_at_ix = $px->[$ix];
+    next unless $px_at_ix;
+    foreach my $iy (@indices_y) {
+      my $py_at_iy = $py->[$iy];
+      my $rindex = ($ix + $iy) % $r;  # reduce mod X^r-1
+      if (!defined $res[$rindex]) {
+        $res[$rindex] = $_poly_bignum ? Math::BigInt->bzero : 0
+      }
+      $res[$rindex] = ($res[$rindex] + ($py_at_iy * $px_at_ix)) % $n;
+    }
+  }
+  # In case we had upper terms go to zero after modulo, reduce the degree.
+  pop @res while !$res[-1];
+  return \@res;
+}
+
+sub _poly_mod_pow {
+  my($pn, $power, $r, $mod) = @_;
+  my $res = _poly_new(1);
+  my $p = $power;
+
+  while ($p) {
+    $res = _poly_mod_mul($res, $pn, $r, $mod) if ($p & 1);
+    $p >>= 1;
+    $pn  = _poly_mod_mul($pn,  $pn, $r, $mod) if $p;
+  }
+  return $res;
+}
+
+sub _test_anr {
+  my($a, $n, $r) = @_;
+  my $pp = _poly_mod_pow(_poly_new($a, 1), $n, $r, $n);
+  $pp->[$n % $r] = (($pp->[$n % $r] || 0) -  1) % $n;  # subtract X^(n%r)
+  $pp->[      0] = (($pp->[      0] || 0) - $a) % $n;  # subtract a
+  return 0 if scalar grep { $_ } @$pp;
+  1;
+}
+
+sub is_aks_prime {
+  my $n = shift;
+  $n = Math::BigInt->new("$n") unless ref($n) eq 'Math::BigInt';
+
+  return 0 if $n < 2;
+  return 0 if _is_perfect_power($n);
+
+  # limit = floor( log2(n) * log2(n) ).  o_r(n) must be larger than this
+  my $sqrtn = int(Math::BigFloat->new($n)->bsqrt->bfloor->bstr);
+  my $log2n = Math::BigFloat->new($n)->blog(2);
+  my $log2_squared_n = $log2n * $log2n;
+  my $limit = int($log2_squared_n->bfloor->bstr);
+
+  my $r = next_prime($limit);
+  foreach my $f (@{primes(0,$r-1)}) {
+    return 1 if $f == $n;
+    return 0 if !($n % $f);
+  }
+
+  while ($r < $n) {
+    return 0 if !($n % $r);
+    #return 1 if $r >= $sqrtn;
+    last if _order($r, $n, $limit) > $limit;
+    $r = next_prime($r);
+  }
+
+  return 1 if $r >= $n;
+
+  # Since r is a prime, phi(r) = r-1
+  my $rlimit = int( Math::BigFloat->new($r)->bsub(1)
+                    ->bsqrt->bmul($log2n)->bfloor->bstr);
+
+  $_poly_bignum = 1;
+  if ( $n < ( (~0 == 4294967295) ? 65535 : 4294967295 ) ) {
+    $_poly_bignum = 0;
+    $n = int($n->bstr) if ref($n) eq 'Math::BigInt';
+  }
+
+  for (my $a = 1; $a <= $rlimit; $a++) {
+    return 0 unless _test_anr($a, $n, $r);
+  }
+
+  return 1;
+}
+
+
 sub _basic_factor {
   # MODIFIES INPUT SCALAR
   return ($_[0]) if $_[0] < 4;
@@ -1539,6 +1689,12 @@ sources call this a strong Lucas-Selfridge pseudoprime). 
 This is one half
 of the BPSW primality test (the Miller-Rabin strong pseudoprime test with
 base 2 being the other half).
 
+=head2 is_aks_prime
+
+Takes a positive number as input, and returns 1 if the input can be proven
+prime using the AKS primality test.  This code is included for completeness
+and as an example, but is impractically slow.
+
 
 =head1 UTILITY FUNCTIONS
 
diff --git a/sieve.c b/sieve.c
index 138ca24..eb38b82 100644
--- a/sieve.c
+++ b/sieve.c
@@ -186,7 +186,7 @@ int sieve_segment(unsigned char* mem, UV startd, UV endd)
 
   MPUassert( (mem != 0) && (endd >= startd) && (endp >= startp),
              "sieve_segment bad arguments");
-  
+
   /* Fill buffer with marked 7, 11, and 13 */
   sieve_prefill(mem, startd, endd);
 
diff --git a/t/10-isprime.t b/t/10-isprime.t
index a1df315..cdddc9c 100644
--- a/t/10-isprime.t
+++ b/t/10-isprime.t
@@ -3,13 +3,13 @@ use strict;
 use warnings;
 
 use Test::More;
-use Math::Prime::Util qw/is_prime/;
+use Math::Prime::Util qw/is_prime is_aks_prime/;
 
 my $use64 = Math::Prime::Util::prime_get_config->{'maxbits'} > 32;
 my $extra = defined $ENV{RELEASE_TESTING} && $ENV{RELEASE_TESTING};
 my $broken64 = (18446744073709550592 == ~0);
 
-plan tests => 6 + 19 + 3573 + (5 + 29 + 22 + 23 + 16) + 15 + 27 + 1
+plan tests => 6 + 19 + 3573 + (5 + 29 + 22 + 23 + 16) + 15 + 27 + 1 + 3
               + ($use64 ? 5+1 : 0)
               + ($extra ? 6 : 0)
               + (($extra && $use64) ? 19 : 0);
@@ -118,3 +118,10 @@ SKIP: {
   eval { is_prime( $use64 ? "18446744073709551629" : "4294967306" ); };
   like($@, qr/range/i, "is_prime on ~0 + delta without bigint should croak");
 }
+
+# Simple AKS
+is( is_aks_prime(877), 1, "is_aks_prime(877) is true" );
+# The first number that makes it past the sqrt test to actually run.
+is( is_aks_prime(69197), 1, "is_aks_prime(69197) is true" );
+# A composite
+is( is_aks_prime(1262952907), 0, "is_aks_prime(1262952907) is false" );

-- 
Alioth's /usr/local/bin/git-commit-notice on 
/srv/git.debian.org/git/pkg-perl/packages/libmath-prime-util-perl.git

_______________________________________________
Pkg-perl-cvs-commits mailing list
Pkg-perl-cvs-commits@lists.alioth.debian.org
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-perl-cvs-commits

Reply via email to