Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement inverseTotient #142

Merged
merged 23 commits into from
Dec 21, 2018
Merged

Implement inverseTotient #142

merged 23 commits into from
Dec 21, 2018

Conversation

Bodigrim
Copy link
Owner

This is still an early draft of a future framework for inversion of multiplicative functions, part of #60, implementing an algorithm along the lines of https://arxiv.org/pdf/1401.6054.pdf

It is fascinating how powerful polymorphism is. Here is how we can compute a list of inverses of the totient function at 120. These are all numbers n such that totient n == 120.

> inverseTotient 120 :: [Integer]
[155,310,183,366,225,450,175,350,231,462,143,286,244,372,396,308,248]

What if there are too many inverses to keep in memory and it is enough only count them? Just switch to another type:

> inverseTotient 120 :: Counter Integer
Counter 17

Interested in minimal or maximal preimage? No worries.

> inverseTotient 120 :: Compose Option Min Integer
Compose (Option (Just (Min 143)))
> inverseTotient 120 :: Compose Option Max Integer
Compose (Option (Just (Max 462)))

Two largest preimages? Easy as a pie.

> inverseTotient 120 :: Max2 Integer
Max2 (Just 450) (Just 462)

@rockbmb
Copy link
Contributor

rockbmb commented Sep 29, 2018

@Bodigrim this looks great, I'm taking a look at the paper as well. I don't think we have σ_k implemented in the library, do we?

@Bodigrim
Copy link
Owner Author

Copy link
Contributor

@rockbmb rockbmb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you profile the function? I don't think you uploaded the Inverse.hs file that your executable used.

@rockbmb
Copy link
Contributor

rockbmb commented Oct 24, 2018

@Bodigrim found what I was looking for, nevermind.

@Bodigrim
Copy link
Owner Author

Bodigrim commented Dec 4, 2018

It is getting tougher than I thought. First, there must be a bug, taking into account failing tests for sigma function. Second, I need more experiments to achieve a decent performance.

@Bodigrim Bodigrim changed the title [WIP] Implement inverseTotient Implement inverseTotient Dec 6, 2018
@Bodigrim
Copy link
Owner Author

Bodigrim commented Dec 6, 2018

OK, it seems to be ready. At least I was able to reproduce all results from the paper, including the inverse sum-of-divisors of 10^1000. @rockbmb @b-mehta and anyone else, please review.

I expect that there are dark corners, where implementation becomes quite convoluted and obscure for an external observer. Please report such places and I'll add clarifying comments. (Since I am deeply in the context, it is difficult for me to identify them myself.)

@rockbmb
Copy link
Contributor

rockbmb commented Dec 7, 2018

@Bodigrim

I was able to reproduce all results from the paper inverse sum-of-divisors of 10^1000.

Excellent work. I'll take a look, although I cannot guarantee any interesting insights, this code seems quite profound.

@rockbmb
Copy link
Contributor

rockbmb commented Dec 7, 2018

@Bodigrim I can't reproduce the 10^1000 result, how long did execution take for you? I presume that you didn't do it in ghci, but not even an executable did it for me.

@Bodigrim
Copy link
Owner Author

Bodigrim commented Dec 7, 2018

It took 4139 sec and 576 Mb or RAM.

@b-mehta
Copy link
Contributor

b-mehta commented Dec 8, 2018

Nice work! I'll take a close look in the next few days.

Copy link
Contributor

@rockbmb rockbmb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a careful look, but may have let some things slip. Will double-check later today.

Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
{-# SPECIALISE inverseTotient :: Semiring b => (Integer -> b) -> Integer -> b #-}
{-# SPECIALISE inverseTotient :: Semiring b => (Natural -> b) -> Natural -> b #-}

-- | The inverse 'sigma' 1 function such that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent comments.

zero = MinWord maxBound
one = MinWord 1
plus (MinWord a) (MinWord b) = MinWord (a `min` b)
times (MinWord a) (MinWord b) = MinWord (a * b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL this how we define one valid semiring for the min operator, I wasn't quite sure (*) would work.

@Bodigrim
Copy link
Owner Author

Bodigrim commented Dec 8, 2018

@rockbmb thanks for valuable suggestions.

@rockbmb
Copy link
Contributor

rockbmb commented Dec 8, 2018

Took another look, couldn't find anything significant.

LGTM, if you don't have anything else to add or fix I suggest you merge it now since making this PR larger will make it more difficult to review, and also should this be rolled back for some reason, larger PRs that touch many things are more difficult to handle.

newtype MinNatural = MinNatural { unMinNatural :: Maybe Natural }
deriving (Show)
data MinNatural
= MinNatural { unMinNatural :: !Natural }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I didn't give much importance to this before, but why was this Maybe before, and why change it now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that switching to a newly defined sum type allows the non-infinite case to be more strict, since the indirection provided by Just is removed

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operations on DirichletSeries never force values of the underlying Map: all multiplications and filtering is based solely on keys. So values of the Map inevitably accumulate huge thunks, unless a) Data.Map.Strict is used, which triggers weak head normal form (WHNF) evaluation of values, b) datatype for values is strict, meaning that WHNF conicides with a normal form. This is crucial for a decent performance.

Maybe a type is lazy: forcing WHNF will force only evaluation of Just constructor, but not the value of a. On contrary MinNatural is strict, thanks to bang.

Copy link
Contributor

@b-mehta b-mehta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work! Apologies for the delay.

Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
benchmark/Math/NumberTheory/InverseBench.hs Outdated Show resolved Hide resolved
Math/NumberTheory/ArithmeticFunctions/Inverse.hs Outdated Show resolved Hide resolved
-- > x `elem` inverseTotient (totient x)
--
-- The return value is parametrized by a semiring, which allows
-- various applications. E. g., list all preimages:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to include examples of calculating the sum or sum of squares of preimages:

>>> getSum $ inverseTotient Sum 120
4904
>>> getSum $ inverseTotient (Sum . (^2)) 120
1573974

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually Sum is redundant: inverseTotient id 120 works as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! In that case, it might be nice to add a note saying that the semiring on Word is the one given by addition and multiplication, to explain why that works.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do even something like inverseTotient (sigma 1) 120 %)

@b-mehta
Copy link
Contributor

b-mehta commented Dec 21, 2018

For interest, we can also compute the product of preimages:

data Prod a = Prod { count :: Word
                   , value :: a
                   }
                   deriving Show

instance Monoid a => Semiring (Prod a) where
  zero = Prod 0 mempty
  one = Prod 1 mempty
  (Prod n a) `plus` (Prod m b) = Prod (n + m) (a <> b)
  (Prod n a) `times` (Prod m b) = Prod (n * m) (stimes m a <> stimes n b)

then

>>> inverseTotient (Prod 1 . Product) 120             
Prod {count = 17, value = Product {getProduct = 239173504760939546437745014990080000000000}}

matches the expected value, and counts the number of preimages as a bonus. (It is not hard to check this forms a commutative semiring, and that the map from P_fin(Z) is a weak homomorphism: note that disjointness is required here!).

Another semiring can be given by wrapping Set to ensure it contains no more than a fixed number of elements, to get the first n preimages:

newtype FirstThree a = Three (S.Set a)
  deriving Show

instance (Ord a, Monoid a) => Semiring (FirstThree a) where
  zero = Three zero
  one = Three one
  (Three a) `plus` (Three b) = Three (S.take 3 (a `plus` b))
  (Three a) `times` (Three b) = Three (S.take 3 (a `times` b))

then

>>> inverseSigma (Three . S.singleton . Product) 120
Three (fromList [Product {getProduct = 54},Product {getProduct = 56},Product {getProduct = 87}])

@Bodigrim Bodigrim merged commit 34af34c into Bodigrim:master Dec 21, 2018
@Bodigrim Bodigrim deleted the inverse-totient branch December 21, 2018 22:32
@Bodigrim
Copy link
Owner Author

@rockbmb @b-mehta thanks for reviewing!

@Bodigrim
Copy link
Owner Author

@b-mehta Prod and FirstThree are very cunning, cool stuff :)

@Bodigrim Bodigrim mentioned this pull request Dec 22, 2018
7 tasks
@rockbmb
Copy link
Contributor

rockbmb commented Dec 22, 2018

@b-mehta using your Data.Ord.Down tip with the FirstThree datatype means we can also find the largest N preimages, by doing newtype LastThree a = Three (S.Set (Down a)). This is really quite cunning.

@b-mehta
Copy link
Contributor

b-mehta commented Dec 22, 2018

Absolutely! I was tempted to use the trick from the Chudnovsky tests to generalise to First n also...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants