Skip to content

Instantly share code, notes, and snippets.

@dpiponi
Created November 25, 2017 18:30
Show Gist options
  • Select an option

  • Save dpiponi/fc2817d75cbd41fbc0311c338cd2d591 to your computer and use it in GitHub Desktop.

Select an option

Save dpiponi/fc2817d75cbd41fbc0311c338cd2d591 to your computer and use it in GitHub Desktop.
Primality test based on Chebyshev polynomials
import Data.Bits
import Control.Monad
type Z = Integer
-- Find smallest power of two >= given integer.
-- Sadly it's not convenient using the usual interface to Integer
-- Got exceptions when using Data.Bits.Bitwise
suitablePower :: Z -> Int
suitablePower 0 = -1
suitablePower n = suitablePower' n 0 where
suitablePower' n i = if shiftL 1 i >= n
then i
else suitablePower' n (i+1)
extendedGCD :: Z -> Z -> (Z, Z, Z)
extendedGCD a 0 = (a, 1, 0)
extendedGCD a b = let (q, r) = a `quotRem` b
(g, s, t) = extendedGCD b r
in (abs g, t, s - q * t)
zipWithDefault f [] [] = []
zipWithDefault f [] as = as
zipWithDefault f as [] = as
zipWithDefault f (a : as) (b : bs) = f a b : zipWithDefault f as bs
convolveWith :: Ring a -> [a] -> [a] -> [a]
convolveWith ring [a] bs = map (times ring a) bs
convolveWith ring (a : as) bs =
zipWithDefault (plus ring) (map (times ring a) bs)
(zero ring : convolveWith ring as bs)
-- Rings
data Ring a = R { plus :: a -> a -> a, times :: a -> a -> a, zero :: a, one :: a, num :: Z -> a }
-- From Monoid source
power ring x0 y0 | y0 < 0 = error "Negative exponent"
| y0 == 0 = one ring
| otherwise = f x0 y0
where f x y | even y = f (times ring x x) (y `quot` 2)
| y == 1 = x
| otherwise = g (times ring x x) ((y - 1) `quot` 2) x
g x y z | even y = g (times ring x x) (y `quot` 2) z
| y == 1 = times ring x z
| otherwise = g (times ring x x) ((y - 1) `quot` 2) (times ring x z)
-- The Montgomery representation
-- Based on https://en.wikipedia.org/wiki/Montgomery_modular_multiplication
newtype Montgomery = N Z deriving Show
toMontgomery :: Int -> Z -> Z -> Montgomery
toMontgomery i n a = N $ (shiftL a i) `mod` n
fromMontgomery :: Z -> Z -> Montgomery -> Z
fromMontgomery invs n (N a) = (invs*a) `mod` n
montgomeryReduce :: Int -> Z -> Z -> Z -> Z -> Z
montgomeryReduce i mask n invn t =
let tm = ((t .&. mask)*invn) .&. mask
tt = shiftR (t + tm*n) i
in if tt >= n then tt-n else tt
montgomeryPlus :: Z -> Montgomery -> Montgomery -> Montgomery
montgomeryPlus n (N a) (N b) = let u = a+b in N (if u >= n then u-n else u)
montgomeryMultiply :: Int -> Z -> Z -> Z -> Montgomery -> Montgomery -> Montgomery
montgomeryMultiply i mask n invn (N a) (N b) = N $ montgomeryReduce i mask n invn (a*b)
-- Return pair consisting of
-- 1. The ring structure for montogomery arithmetic
-- 2. The function to map back to the ordinary integers
montgomery :: Z -> (Ring Montgomery, Montgomery -> Z)
montgomery n =
let i = suitablePower n
s = shiftL 1 i
mask = s-1
(_, a, b) = extendedGCD n s
in (R { plus = montgomeryPlus n,
times = montgomeryMultiply i mask n ((-a) .&. mask),
zero = toMontgomery i n 0,
one = toMontgomery i n 1,
num = toMontgomery i n},
fromMontgomery (b `mod` n) n)
-- Polynomial rings
newtype Polynomial a = P [a] deriving Show
reducedPolyMultiply :: Ring a -> a ->
Z -> Polynomial a -> Polynomial a -> Polynomial a
reducedPolyMultiply ring zero r (P a) (P b) = P $ let ps = convolveWith ring a b
in uncurry (zipWithDefault (plus ring)) (splitAt (fromIntegral r) ps)
polyPlus :: (a -> a -> a) -> Polynomial a -> Polynomial a -> Polynomial a
polyPlus (+) (P a) (P b) = P $ zipWithDefault (+) a b
x :: Ring a -> Polynomial a
x ring = P [num ring 0, num ring 1]
makePolynomials :: Ring a -> Z -> Ring (Polynomial a)
makePolynomials ring r = R {
plus = polyPlus (plus ring),
times = reducedPolyMultiply ring (num ring 0) r,
zero = P [],
one = P [num ring 1],
num = \i -> P [num ring i]
}
-- Matrix rings
data Matrix a = M a a a a deriving Show
matrixMultiply (+) (*) (M a b c d) (M e f g h) =
M ((a*e)+(b*g)) ((a*f)+(b*h))
((c*e)+(d*g)) ((c*f)+(d*h))
get :: Ring a -> Ring (Polynomial a) -> Matrix (Polynomial a) -> Polynomial a
get ring polynomials (M a b _ _) = plus polynomials (times polynomials a (x ring)) b
makeMatrices :: Ring a -> Ring (Matrix a)
makeMatrices ring = R {
plus = undefined,
times = matrixMultiply (plus ring) (times ring),
zero = M (zero ring) (zero ring)
(zero ring) (zero ring),
one = M (one ring) (zero ring)
(zero ring) (one ring),
num = \i -> let ii = num ring i
zero = num ring 0
in M ii zero
zero ii
}
-- Compute Chebyshev polynomials using
-- https://mathoverflow.net/questions/286626/is-there-an-explicit-expression-for-chebyshev-polynomials-modulo-xr-1/286639#286639
generator :: Ring a -> Matrix (Polynomial a)
generator ring = M (P [num ring 0, num ring 2]) (P [num ring (-1)])
(P [num ring 1]) (P [num ring 0])
-- Compute mth Chebyshev polynomial modulo (n, x^r-1)
chebyshev :: Ring a -> Ring (Polynomial a) -> Ring (Matrix (Polynomial a)) -> Z -> Polynomial a
chebyshev base polynomials matrices m =
let chebyshevMatrix = power matrices (generator base) (m-1)
in get base polynomials chebyshevMatrix
chebyshevTest :: Z -> Z -> Z -> [Z]
chebyshevTest m r n =
let (base, extract) = montgomery n
polynomials = makePolynomials base r
matrices = makeMatrices polynomials
P as = chebyshev base polynomials matrices m
in map extract as
-- Borrowed from the web somewhere... :-)
primes :: [Z]
primes = sieve [2..]
where sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
-- Check if list is all zeros except for a 1 at given position.
allZeros [] = True
allZeros (0 : as) = allZeros as
allZeros _ = False
allZerosBut 0 (1 : as) = allZeros as
allZerosBut i (0 : as) = allZerosBut (i-1) as
allZerosBut _ _ = False
-- Primality test based on
-- https://mathoverflow.net/questions/286304/chebyshev-polynomials-of-the-first-kind-and-primality-testing
-- Way slower than I'd like.
-- Don't know if this because
-- 1. my implementation is poor
-- 2. the methods that people actually use are faster in practice.
-- But this implementation, if the conjecture is proved, would
-- have a guaranteed deterministic running time polynomial
-- in the size of the queried number.
primeq :: Z -> Bool
primeq n = prime' (tail primes) n where
prime' (r:rs) n | mod n r == 0 = False
| mod (n*n) r /= 1 = allZerosBut (n `mod` r) $ chebyshevTest n r n
| otherwise = prime' rs n
main = do
print "Start"
-- print $ primeq (2^86243-1) -- Made laptop reboot!
-- Find Mersenne primes
forM [2..] $ \i ->
when (primeq (2^i-1)) $ print i
@primus-lab
Copy link
Copy Markdown

Code doesn't work for primes 2,3 and 5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment