Skip to content

Commit

Permalink
[#148] Generalize 'inverseSigma' and 'inverseTotient'
Browse files Browse the repository at this point in the history
  • Loading branch information
rockbmb committed Mar 1, 2020
1 parent 56cd483 commit 14339d7
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 25 deletions.
96 changes: 79 additions & 17 deletions Math/NumberTheory/ArithmeticFunctions/Inverse.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

module Math.NumberTheory.ArithmeticFunctions.Inverse
( inverseTotient
, inverseJordan
, inverseSigma
, inverseSigmaK
, -- * Wrappers
MinWord(..)
, MaxWord(..)
Expand Down Expand Up @@ -44,7 +46,7 @@ import Numeric.Natural

import Math.NumberTheory.ArithmeticFunctions
import Math.NumberTheory.Logarithms
import Math.NumberTheory.Roots (integerRoot)
import Math.NumberTheory.Roots (exactRoot, integerRoot)
import Math.NumberTheory.Primes
import Math.NumberTheory.Utils.DirichletSeries (DirichletSeries)
import qualified Math.NumberTheory.Utils.DirichletSeries as DS
Expand All @@ -71,32 +73,36 @@ atomicSeries point (ArithmeticFunction f g) (PrimePowers p ks) =

-- | See section 5.1 of the paper.
invTotient
:: forall a. (UniqueFactorisation a, Eq a)
=> [(Prime a, Word)]
:: forall a. (Integral a, UniqueFactorisation a, Eq a)
=> Word
-- ^ -- ^ Value of 'k' in 'jordan_k'
-> [(Prime a, Word)]
-- ^ Factorisation of a value of the totient function
-> [PrimePowers a]
-- ^ Possible prime factors of an argument of the totient function
invTotient fs = map (\p -> PrimePowers p (doPrime p)) ps
invTotient k fs = map (\p -> PrimePowers p (doPrime p)) ps
where
divs :: [a]
divs = runFunctionOnFactors divisorsListA fs

ps :: [Prime a]
ps = mapMaybe (isPrime . (+ 1)) divs
ps = mapMaybe (\d -> exactRoot k (d + 1) >>= isPrime) divs

doPrime :: Prime a -> [Word]
doPrime p = case lookup p fs of
Nothing -> [1]
Just k -> [1 .. k+1]
Just k' -> [1 .. k'+1]

-- | See section 5.2 of the paper.
invSigma
:: forall a. (Euclidean a, Integral a, UniqueFactorisation a, Enum (Prime a), Bits a)
=> [(Prime a, Word)]
=> Word
-- ^ Value of 'k' in 'sigma_k'
-> [(Prime a, Word)]
-- ^ Factorisation of a value of the sum-of-divisors function
-> [PrimePowers a]
-- ^ Possible prime factors of an argument of the sum-of-divisors function
invSigma fs
invSigma k fs
= map (\(x, ys) -> PrimePowers x (S.toList ys))
$ M.assocs
$ M.unionWith (<>) pksSmall pksLarge
Expand All @@ -108,7 +114,7 @@ invSigma fs
divs = runFunctionOnFactors divisorsListA fs

n :: a
n = product $ map (\(p, k) -> unPrime p ^ k) fs
n = product $ map (\(p, k') -> unPrime p ^ k') fs

-- There are two possible strategies to find possible prime factors
-- of an argument of the sum-of-divisors function.
Expand Down Expand Up @@ -141,17 +147,17 @@ invSigma fs
doPrime :: Prime a -> Set Word
doPrime p' = let p = unPrime p' in S.fromDistinctAscList
[ e
| e <- [1 .. intToWord (integerLogBase (toInteger p) (toInteger n))]
, n `rem` ((p ^ (e + 1) - 1) `quot` (p - 1)) == 0
| e <- [1 .. intToWord (integerLogBase (toInteger (p ^ k)) (toInteger n))]
, n `rem` ((p ^ (k * (e + 1)) - 1) `quot` (p ^ k - 1)) == 0
]

pksLarge :: Map (Prime a) (Set Word)
pksLarge = M.unionsWith (<>)
[ maybe mempty (flip M.singleton (S.singleton e)) (isPrime p)
| d <- divs
, e <- [1 .. intToWord (integerLogBase (toInteger lim) (toInteger d))]
, let p = integerRoot e (d - 1)
, p ^ (e + 1) - 1 == d * (p - 1)
, e <- [1 .. intToWord (quot (integerLogBase (toInteger lim) (toInteger d)) (wordToInt k)) ]
, let p = integerRoot (e * k) (d - 1)
, p ^ (k * (e + 1)) - 1 == d * (p ^ k - 1)
]

-- | Instead of multiplying all atomic series and filtering out everything,
Expand Down Expand Up @@ -264,16 +270,44 @@ invertFunction point f invF n
-- >>> unMaxWord (inverseTotient MaxWord 120)
-- 462
inverseTotient
:: (Semiring b, Euclidean a, UniqueFactorisation a, Ord a)
:: (Semiring b, Integral a, Euclidean a, UniqueFactorisation a, Ord a)
=> (a -> b)
-> a
-> b
inverseTotient point = invertFunction point totientA invTotient
inverseTotient = inverseJordan 1
{-# SPECIALISE inverseTotient :: Semiring b => (Int -> b) -> Int -> b #-}
{-# SPECIALISE inverseTotient :: Semiring b => (Word -> b) -> Word -> b #-}
{-# SPECIALISE inverseTotient :: Semiring b => (Integer -> b) -> Integer -> b #-}
{-# SPECIALISE inverseTotient :: Semiring b => (Natural -> b) -> Natural -> b #-}

-- | The inverse for 'jordan_k' function, where 'k' is a nonnegative integer.
--
-- Generalizes the 'inverseTotient' function, which is 'inverseJordan' when 'k'
-- is '1'.
--
-- The return value is parameterized by a 'Semiring', which allows
-- various applications by providing different (multiplicative) embeddings.
-- E. g., list all preimages (see a helper 'asSetOfPreimages'):
--
-- >>> import qualified Data.Set as S
-- >>> import Data.Semigroup
-- >>> S.mapMonotonic getProduct (inverseJordan (S.singleton . Product) 192)
-- fromList [15,16]
--
-- Similarly to 'inverseTotient', it is possible to count and sum preimages, or
-- get the maximum/minimum preimage.
inverseJordan
:: (Semiring b, Integral a, Euclidean a, UniqueFactorisation a, Ord a)
=> Word
-> (a -> b)
-> a
-> b
inverseJordan k point = invertFunction point (jordanA k) (invTotient k)
{-# SPECIALISE inverseJordan :: Semiring b => Word -> (Int -> b) -> Int -> b #-}
{-# SPECIALISE inverseJordan :: Semiring b => Word -> (Word -> b) -> Word -> b #-}
{-# SPECIALISE inverseJordan :: Semiring b => Word -> (Integer -> b) -> Integer -> b #-}
{-# SPECIALISE inverseJordan :: Semiring b => Word -> (Natural -> b) -> Natural -> b #-}

-- | The inverse for 'sigma' 1 function.
--
-- The return value is parameterized by a 'Semiring', which allows
Expand Down Expand Up @@ -306,12 +340,40 @@ inverseSigma
=> (a -> b)
-> a
-> b
inverseSigma point = invertFunction point (sigmaA 1) invSigma
inverseSigma = inverseSigmaK 1
{-# SPECIALISE inverseSigma :: Semiring b => (Int -> b) -> Int -> b #-}
{-# SPECIALISE inverseSigma :: Semiring b => (Word -> b) -> Word -> b #-}
{-# SPECIALISE inverseSigma :: Semiring b => (Integer -> b) -> Integer -> b #-}
{-# SPECIALISE inverseSigma :: Semiring b => (Natural -> b) -> Natural -> b #-}

-- | The inverse for 'sigma_k' function, where 'k' is a nonnegative integer.
--
-- Generalizes the 'inverseSigma' function, which is 'inverseSigmaK' when 'k'
-- is '1'.
--
-- The return value is parameterized by a 'Semiring', which allows
-- various applications by providing different (multiplicative) embeddings.
-- E. g., list all preimages (see a helper 'asSetOfPreimages'):
--
-- >>> import qualified Data.Set as S
-- >>> import Data.Semigroup
-- >>> S.mapMonotonic getProduct (inverseSigmaK 4 (S.singleton . Product) 6651267)
-- fromList [50]
--
-- Similarly to 'inverseSigma', it is possible to count and sum preimages, or
-- get the maximum/minimum preimage.
inverseSigmaK
:: (Semiring b, Euclidean a, UniqueFactorisation a, Integral a, Enum (Prime a), Bits a)
=> Word
-> (a -> b)
-> a
-> b
inverseSigmaK k point = invertFunction point (sigmaA k) (invSigma k)
{-# SPECIALISE inverseSigmaK :: Semiring b => Word -> (Int -> b) -> Int -> b #-}
{-# SPECIALISE inverseSigmaK :: Semiring b => Word -> (Word -> b) -> Word -> b #-}
{-# SPECIALISE inverseSigmaK :: Semiring b => Word -> (Integer -> b) -> Integer -> b #-}
{-# SPECIALISE inverseSigmaK :: Semiring b => Word -> (Natural -> b) -> Natural -> b #-}

--------------------------------------------------------------------------------
-- Wrappers

Expand Down
3 changes: 1 addition & 2 deletions Math/NumberTheory/Quadratic/EisensteinIntegers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,7 @@ divideByPrime p p' np k = go k 0
where
(d1, z') = go1 c 0 z
d2 = c - d1
z'' = head $ drop (wordToInt d2)
$ iterate (\g -> fromMaybe err $ (g * unPrime p) `quotEvenI` np) z'
z'' = iterate (\g -> fromMaybe err $ (g * unPrime p) `quotEvenI` np) z' !! max 0 (wordToInt d2)

go1 :: Word -> Word -> EisensteinInteger -> (Word, EisensteinInteger)
go1 0 d z = (d, z)
Expand Down
7 changes: 3 additions & 4 deletions Math/NumberTheory/Zeta/Hurwitz.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ zetaHurwitz eps a = zipWith3 (\s i t -> s + i + t) ss is ts
(\powOfA int -> powOfA * fromInteger int)
powsOfAPlusN
[-1, 0..]
in zipWith (/) (repeat aPlusN) denoms
in map ((/) aPlusN) denoms

-- [ 1 | ]
-- [ ----------- | s <- [0 ..] ]
Expand Down Expand Up @@ -109,9 +109,8 @@ zetaHurwitz eps a = zipWith3 (\s i t -> s + i + t) ss is ts
(skipEvens powsOfAPlusN)

fracs :: [a]
fracs = zipWith
(\sec pochh -> sum $ zipWith (\s p -> s * fromInteger p) sec pochh)
(repeat second)
fracs = map
(\pochh -> sum $ zipWith (\s p -> s * fromInteger p) second pochh)
pochhammers

-- Infinite list of @T@ values in 4.8.5 formula, for every @s@ in
Expand Down
2 changes: 1 addition & 1 deletion benchmark/Math/NumberTheory/InverseBench.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fact = product [1..13]
tens :: Num a => a
tens = 10 ^ 18

countInverseTotient :: (Ord a, Euclidean a, UniqueFactorisation a) => a -> Word
countInverseTotient :: (Ord a, Integral a, Euclidean a, UniqueFactorisation a) => a -> Word
countInverseTotient = inverseTotient (const 1)

countInverseSigma :: (Integral a, Euclidean a, UniqueFactorisation a, Enum (Prime a), Bits a) => a -> Word
Expand Down
90 changes: 90 additions & 0 deletions test-suite/Math/NumberTheory/ArithmeticFunctions/InverseTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,28 @@ import Math.NumberTheory.TestUtils
totientProperty1 :: forall a. (Euclidean a, Integral a, UniqueFactorisation a) => Positive a -> Bool
totientProperty1 (Positive x) = x `S.member` asSetOfPreimages inverseTotient (totient x)

jordanProperty1
:: (Euclidean a, Integral a, UniqueFactorisation a)
=> Power Word
-> Positive a
-> Bool
jordanProperty1 (Power k') (Positive x) =
-- 'k' shouldn't be large to avoid slow tests.
let k = succ $ k' `Prelude.div` 50
in x `S.member` asSetOfPreimages (inverseJordan k) (jordan k x)

totientProperty2 :: (Euclidean a, Integral a, UniqueFactorisation a) => Positive a -> Bool
totientProperty2 (Positive x) = all (== x) (S.map totient (asSetOfPreimages inverseTotient x))

jordanProperty2
:: (Euclidean a, Integral a, UniqueFactorisation a, Ord a)
=> Power Word
-> Positive a
-> Bool
jordanProperty2 (Power k') (Positive x) =
let k = succ $ k' `Prelude.div` 50
in all (== x) (S.map (jordan k) (asSetOfPreimages (inverseJordan k) x))

-- | http://oeis.org/A055506
totientCountFactorial :: [Word]
totientCountFactorial =
Expand Down Expand Up @@ -132,15 +151,74 @@ totientSpecialCases3 = zipWith mkAssert (tail factorial) totientMaxFactorial
totientMax :: Word -> Word
totientMax = unMaxWord . inverseTotient MaxWord

jordans5 :: [Word]
jordans5 =
[ 1
, 31
, 242
, 992
, 3124
, 7502
, 16806
, 31744
, 58806
, 96844
, 161050
, 240064
, 371292
, 520986
, 756008
, 1015808
, 1419856
, 1822986
, 2476098
, 3099008
, 4067052
, 4992550
, 6436342
, 7682048
, 9762500
, 11510052
, 14289858
, 16671552
, 20511148
]

jordanSpecialCase1 :: [Assertion]
jordanSpecialCase1 = zipWith mkAssert ixs jordans5
where
mkAssert a b = assertEqual "should be equal" (S.singleton a) (asSetOfPreimages (inverseJordan 5) b)
ixs = [1 .. 29]

-------------------------------------------------------------------------------
-- Sigma

sigmaProperty1 :: forall a. (Euclidean a, UniqueFactorisation a, Integral a, Enum (Prime a), Bits a) => Positive a -> Bool
sigmaProperty1 (Positive x) = x `S.member` asSetOfPreimages inverseSigma (sigma 1 x)

sigmaKProperty1
:: forall a
. (Euclidean a, UniqueFactorisation a, Integral a, Enum (Prime a), Bits a)
=> Power Word
-> Positive a
-> Bool
sigmaKProperty1 (Power k') (Positive x) =
-- 'k' shouldn't be large to avoid slow tests.
let k = 2 + k' `Prelude.mod` 50
in x `S.member` asSetOfPreimages (inverseSigmaK k) (sigma k x)

sigmaProperty2 :: (Euclidean a, UniqueFactorisation a, Integral a, Enum (Prime a), Bits a) => Positive a -> Bool
sigmaProperty2 (Positive x) = all (== x) (S.map (sigma 1) (asSetOfPreimages inverseSigma x))

sigmaKProperty2
:: (Euclidean a, UniqueFactorisation a, Integral a, Enum (Prime a), Bits a)
=> Power Word
-> Positive a
-> Bool
sigmaKProperty2 (Power k') (Positive x) =
let k = 2 + k' `Prelude.mod` 50
in all (== x) (S.map (sigma k) (asSetOfPreimages (inverseSigmaK k) x))

-- | http://oeis.org/A055486
sigmaCountFactorial :: [Word]
sigmaCountFactorial =
Expand Down Expand Up @@ -260,4 +338,16 @@ testSuite = testGroup "Inverse"
, testGroup "max"
(zipWith (\i a -> testCase ("factorial " ++ show i) a) [1..] sigmaSpecialCases3)
]

, testGroup "Jordan"
[ testIntegralPropertyNoLarge2 "forward" jordanProperty1
, testIntegralPropertyNoLarge2 "backward" jordanProperty2
, testGroup "inverseJordan"
(zipWith (\i test -> testCase ("inverseJordan 5" ++ show i) test) jordans5 jordanSpecialCase1)
]

, testGroup "SigmaK"
[ testIntegralPropertyNoLarge2 "forward" sigmaKProperty1
, testIntegralPropertyNoLarge2 "backward" sigmaKProperty2
]
]
12 changes: 11 additions & 1 deletion test-suite/Math/NumberTheory/TestUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
Expand All @@ -34,6 +33,7 @@ module Math.NumberTheory.TestUtils
, NonZero(..)
, testIntegralProperty
, testIntegralPropertyNoLarge
, testIntegralPropertyNoLarge2
, testSameIntegralProperty
, testSameIntegralProperty3
, testIntegral2Property
Expand Down Expand Up @@ -153,6 +153,16 @@ testIntegralPropertyNoLarge name f = testGroup name
, QC.testProperty "quickcheck Natural" (f :: wrapper Natural -> bool)
]

testIntegralPropertyNoLarge2
:: forall wrapper1 wrapper2 bool. (TestableIntegral wrapper1, TestableIntegral wrapper2, SC.Testable IO bool, QC.Testable bool)
=> String -> (forall a. (Euclidean a, Semiring a, Integral a, Bits a, UniqueFactorisation a, Show a, Enum (Prime a)) => wrapper1 Word -> wrapper2 a -> bool) -> TestTree
testIntegralPropertyNoLarge2 name f = testGroup name
[ SC.testProperty "smallcheck Integer" (f :: wrapper1 Word -> wrapper2 Integer -> bool)
, SC.testProperty "smallcheck Natural" (f :: wrapper1 Word -> wrapper2 Natural -> bool)
, QC.testProperty "quickcheck Integer" (f :: wrapper1 Word -> wrapper2 Integer -> bool)
, QC.testProperty "quickcheck Natural" (f :: wrapper1 Word -> wrapper2 Natural -> bool)
]

testSameIntegralProperty
:: forall wrapper1 wrapper2 bool. (TestableIntegral wrapper1, TestableIntegral wrapper2, SC.Testable IO bool, QC.Testable bool)
=> String -> (forall a. (GcdDomain a, Euclidean a, Integral a, Bits a, UniqueFactorisation a, Show a) => wrapper1 a -> wrapper2 a -> bool) -> TestTree
Expand Down

0 comments on commit 14339d7

Please sign in to comment.