[libmath-prime-util-perl] 44/50: Add AKS primality test
Partha P. Mukherjee
ppm-guest at moszumanska.debian.org
Thu May 21 18:45:40 UTC 2015
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 <dana at 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
More information about the Pkg-perl-cvs-commits
mailing list