factory-0.2.0.0: src/Factory/Math/Implementations/Primes/SieveOfAtkin.hs
{-# LANGUAGE CPP #-}
{-
Copyright (C) 2011 Dr. Alistair Ward
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@]
* Generates the constant /bounded/ list of /prime-numbers/, using the /Sieve of Atkin/; <http://en.wikipedia.org/wiki/Sieve_of_Atkin>.
* <cr.yp.to/papers/primesieves-19990826.pdf>.
* The implementation;
has been optimised using a /wheel/ of static, but parameterised, size;
has been parallelized;
is polymorphic, but with a specialisation for type 'Int'.
[@CAVEAT@] The 'Int'-specialisation is implemented by a /rewrite-rule/, which is /very/ fragile.
-}
module Factory.Math.Implementations.Primes.SieveOfAtkin(
-- * Types
-- ** Data-types
-- PolynomialType,
-- * Constants
-- atkinsModulus,
-- inherentPrimes,
-- nInherentPrimes,
-- squares,
-- * Functions
-- polynomialTypeLookupPeriod,
-- polynomialTypeLookup,
-- findPolynomialSolutions,
-- filterOddRepetitions,
-- generateMultiplesOfSquareTo,
-- getPrefactoredPrimes,
sieveOfAtkin,
-- sieveOfAtkinInt
) where
import qualified Control.DeepSeq
import qualified Data.Array
import qualified Data.Array.IArray
import Data.Array.IArray((!))
--import qualified Data.Array.Unboxed
import qualified Data.IntSet
import qualified Data.List
import qualified Data.Set
import qualified Factory.Data.PrimeWheel as Data.PrimeWheel
import qualified Factory.Math.Power as Math.Power
import qualified ToolShed.ListPlus as ListPlus
#if MIN_VERSION_parallel(3,0,0)
import qualified Control.Parallel.Strategies
#endif
-- | Defines the types of /quadratic/, available to test the potential primality of a candidate integer.
data PolynomialType
= ModFour -- ^ Suitable for primality-testing numbers meeting @(n `mod` 4 == 1)@.
| ModSix -- ^ Suitable for primality-testing numbers meeting @(n `mod` 6 == 1)@.
| ModTwelve -- ^ Suitable for primality-testing numbers meeting @(n `mod` 12 == 11)@.
| None -- ^ There's no polynomial which can assess primality, because the candidate is composite.
deriving Eq
-- | The constant modulus used to select the appropriate quadratic for a prime candidate.
atkinsModulus :: Integral i => i
atkinsModulus = foldr1 lcm [4, 6, 12] --Sure, this is always '12', but this is the reason why.
-- | The constant list of primes factored-out by the unoptimised algorithm.
inherentPrimes :: Integral i => [i]
inherentPrimes = [2, 3]
-- | The constant number of primes factored-out by the unoptimised algorithm.
nInherentPrimes :: Int
nInherentPrimes = length (inherentPrimes :: [Int])
-- | Typically the set of primes which have been built into the specified /wheel/, but never fewer than 'inherentPrimes'.
getPrefactoredPrimes :: Integral i => Data.PrimeWheel.PrimeWheel i -> [i]
getPrefactoredPrimes = max inherentPrimes . Data.PrimeWheel.getPrimeComponents
-- | The period over which the data returned by 'polynomialTypeLookup' repeats.
polynomialTypeLookupPeriod :: Integral i => Data.PrimeWheel.PrimeWheel i -> i
polynomialTypeLookupPeriod = lcm atkinsModulus . Data.PrimeWheel.getCircumference
{- |
* Defines which, if any, of the three /quadratics/ is appropriate for the primality-test for each candidate.
* Since this algorithm uses /modular arithmetic/, the /range/ of results repeat after a short /domain/ related to the /modulus/.
Thus one need calculate at most one period of this cycle, but fewer if the maximum prime required falls within the first cycle of results.
* Because the results are /bounded/, they're returned in a zero-indexed /array/, to provide efficient random access;
the first few elements should never be required, but it makes query clearer.
* <http://en.wikipedia.org/wiki/Sieve_of_Atkin>.
-}
polynomialTypeLookup :: (Data.Array.IArray.Ix i, Integral i)
=> Data.PrimeWheel.PrimeWheel i
-> i -- ^ The maximum prime required.
-- -> Data.Array.Unboxed.Array i PolynomialType --Changes neither execution-time nor space ?!
-> Data.Array.Array i PolynomialType
polynomialTypeLookup primeWheel maxPrime = Data.Array.IArray.listArray (0, pred (polynomialTypeLookupPeriod primeWheel) `min` maxPrime) $ map select [0 ..] where
-- select :: Integral i => i -> PolynomialType
select n
| any (
(== 0) . (n `mod`) --Though this is merely /Trial Division/, it's only performed over a short bounded interval of numerators.
) primeComponents = None
| r `elem` [1, 5] = ModFour --We actually require @(n `mod` 4 == 1)@, but this is the equivalent modulo 12, with @(r == 9)@ removed because they're all divisible by /3/.
| r == 7 = ModSix --We actually require @(n `mod` 6 == 1)@, but this is the equivalent modulo 12, where @(r == 1)@ has been accounted for above.
| r == 11 = ModTwelve --We require @(n `mod` 12 == 11)@.
| otherwise = None
where
r = n `mod` atkinsModulus
primeComponents = drop nInherentPrimes $ Data.PrimeWheel.getPrimeComponents primeWheel
-- | The constant, infinite list of the /squares/, of integers increasing from /1/.
squares :: Integral i => [i]
squares = map snd $ Math.Power.squaresFrom 1
{- |
* Returns the /ordered/ list of those values with an /odd/ number of occurrences in the specified /unordered/ list.
* CAVEAT: this is expensive in both execution-time and space.
The typical imperative-style implementation accumulates polynomial-solutions in a /mutable array/ indexed by the candidate integer.
This doesn't translate seamlessly to the /pure functional/ domain where /arrays/ naturally immutable,
so we /sort/ a /list/ of polynomial-solutions, then measure the length of the solution-spans, corresponding to viable candidates.
Regrettably, 'Data.List.sort' (implemented in /GHC/ by /mergesort/) has a time-complexity /O(n*log n)/
which is greater than the theoretical /O(n)/ of the whole /Sieve of Atkin/;
/GHC/'s old /qsort/-implementation is even slower :(
-}
filterOddRepetitions :: Ord a => [a] -> [a]
--filterOddRepetitions = map head . filter (foldr (const not) False) . Data.List.group . Data.List.sort --Too slow.
filterOddRepetitions = slave True . Data.List.sort where
slave isOdd (one : remainder@(two : _))
| one == two = slave (not isOdd) remainder
| isOdd = one : beginSpan
| otherwise = beginSpan
where
beginSpan = slave True remainder
slave True [singleton] = [singleton]
slave _ _ = []
{- |
* Returns the ordered list of solutions aggregated from each of three /bivariate quadratics/; @z = f(x, y)@.
* For a candidate integer to be prime, it is necessary but insufficient, that there are an /odd/ number of solutions of value /candidate/.
* At most one of these three polynomials is suitable for the validation of any specific candidate /z/, depending on 'lookupPolynomialType'.
so the three sets of solutions are mutually exclusive.
One coordinate @(x, y)@, can have solutions in more than one of the three polynomials.
* This algorithm exhaustively traverses the domain @(x, y)@, for resulting /z/ of the required modulus.
Whilst it tightly constrains the bounds of the search-space, it searches the domain methodically rather than intelligently.
-}
findPolynomialSolutions :: (Control.DeepSeq.NFData i, Data.Array.IArray.Ix i, Integral i)
=> Data.PrimeWheel.PrimeWheel i
-> i -- ^ The maximum prime-number required.
-> [i]
findPolynomialSolutions primeWheel maxPrime = foldr1 ListPlus.merge --The lists were previously sorted, as a side-effect, by 'filterOddRepetitions'.
#if MIN_VERSION_parallel(3,0,0)
$ Control.Parallel.Strategies.withStrategy (Control.Parallel.Strategies.parList Control.Parallel.Strategies.rdeepseq)
#endif
[
{-# SCC "4x^2+y^2" #-} filterOddRepetitions [
z |
x' <- takeWhile (<= pred maxPrime) $ map (* 4) squares,
z <- takeWhile (<= maxPrime) $ map (+ x') oddSquares,
lookupPolynomialType z == ModFour
], --Twice the length of the other two lists.
{-# SCC "3x^2+y^2" #-} filterOddRepetitions [
z |
x' <- takeWhile (<= pred maxPrime) $ map (* 3) squares,
z <- takeWhile (<= maxPrime) . map (+ x') $ if even x' then oddSelection else evenSelection,
lookupPolynomialType z == ModSix
],
{-# SCC "3x^2-y^2" #-} filterOddRepetitions [
z |
x2 <- takeWhile (<= maxPrime `div` 2) squares,
z <- dropWhile (> maxPrime) . map (3 * x2 -) . takeWhile (< x2) $ if even x2 then oddSelection else evenSelection,
lookupPolynomialType z == ModTwelve
]
] where
(evenSquares, oddSquares) = Data.List.partition even squares
-- evenSelection, oddSelection :: Integral i => [i]
evenSelection = selection110 evenSquares where
selection110 (x0 : x1 : _ : xs) = x0 : x1 : selection110 xs --Effectively, those for meeting ((== 4) . (`mod` 6)).
selection110 xs = xs
oddSelection = selection101 oddSquares where
selection101 (x0 : _ : x2 : xs) = x0 : x2 : selection101 xs --Effectively, those for meeting ((== 1) . (`mod` 6)).
selection101 xs = xs
-- lookupPolynomialType :: (Data.Array.IArray.Ix i, Integral i) => i -> PolynomialType
lookupPolynomialType = (polynomialTypeLookup primeWheel maxPrime !) . (`mod` polynomialTypeLookupPeriod primeWheel)
-- | Generates the /bounded/ list of multiples, of the /square/ of the specified prime, skipping those which aren't required.
generateMultiplesOfSquareTo :: Integral i
=> Data.PrimeWheel.PrimeWheel i -- ^ Used to generate the gaps between prime multiples of the square.
-> i -- ^ The /prime/.
-> i -- ^ The maximum bound.
-> [i]
generateMultiplesOfSquareTo primeWheel prime max' = takeWhile (<= max') . scanl (\accumulator -> (+ accumulator) . (* prime2)) prime2 . cycle $ Data.PrimeWheel.getSpokeGaps primeWheel where
prime2 = Math.Power.square prime
{- |
* Generates the constant /bounded/ list of /prime-numbers/.
* <http://cr.yp.to/papers/primesieves-19990826.pdf>
-}
sieveOfAtkin :: (Control.DeepSeq.NFData i, Data.Array.IArray.Ix i, Integral i)
=> Data.PrimeWheel.NPrimes -- ^ Other implementations effectively use a hard-coded value either /2/ or /3/, but /6/ seems better.
-> i -- ^ The maximum prime required.
-> [i] -- ^ The /bounded/ list of primes.
sieveOfAtkin wheelSize maxPrime = (prefactoredPrimes ++) . filterSquareFree Data.Set.empty . dropWhile (<= maximum prefactoredPrimes) $ findPolynomialSolutions primeWheel maxPrime where
primeWheel = Data.PrimeWheel.mkPrimeWheel wheelSize
prefactoredPrimes = getPrefactoredPrimes primeWheel
-- filterSquareFree :: Integral i => Data.Set.Set i -> [i] -> [i]
filterSquareFree _ [] = []
filterSquareFree primeMultiples (candidate : candidates)
| Data.Set.member candidate primeMultiples = {-# SCC "delete" #-} filterSquareFree (Data.Set.delete candidate primeMultiples) candidates --Tail-recurse.
| otherwise = {-# SCC "insert" #-} candidate : filterSquareFree (Data.Set.union primeMultiples . Data.Set.fromDistinctAscList $ generateMultiplesOfSquareTo primeWheel candidate maxPrime) candidates
{-# NOINLINE sieveOfAtkin #-}
{-# RULES "sieveOfAtkin/Int" sieveOfAtkin = sieveOfAtkinInt #-} --CAVEAT: doesn't fire when built with profiling enabled.
-- | A specialisation of 'sieveOfAtkin', which reduces both the execution-time and the space required.
sieveOfAtkinInt :: Data.PrimeWheel.NPrimes -> Int -> [Int]
sieveOfAtkinInt wheelSize maxPrime = (prefactoredPrimes ++) . filterSquareFree Data.IntSet.empty . dropWhile (<= maximum prefactoredPrimes) $ findPolynomialSolutions primeWheel maxPrime where
primeWheel = Data.PrimeWheel.mkPrimeWheel wheelSize
prefactoredPrimes = getPrefactoredPrimes primeWheel
filterSquareFree :: Data.IntSet.IntSet -> [Int] -> [Int]
filterSquareFree _ [] = []
filterSquareFree primeMultiples (candidate : candidates)
| Data.IntSet.member candidate primeMultiples = filterSquareFree (Data.IntSet.delete candidate primeMultiples) candidates
| otherwise = candidate : filterSquareFree (Data.IntSet.union primeMultiples . Data.IntSet.fromDistinctAscList $ generateMultiplesOfSquareTo primeWheel candidate maxPrime) candidates