-
Notifications
You must be signed in to change notification settings - Fork 40
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
Add a 'partition' recurrence #115
Conversation
Benchmarks for
|
Math/NumberTheory/Recurrencies.hs
Outdated
-- | ||
-- Note: @tail@ is applied to @pents@ because otherwise the calculation of | ||
-- @p(n)@ would involve a duplicated @p(n-1)@ term (see the above example). | ||
partition :: forall a . Integral a => [a] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is valuable do not impose Integral a
constraint here. Can we do with Num a
only?
The reason is that since partition
grows very fast, we are often interested not in the value of partition
itself, but rather, for instance, in partition(x) mod 100
. It is very handy to compute it as partition !! x :: Mod 100
. But Mod is not Integral
, it has only Num
interface.
Math/NumberTheory/Recurrencies.hs
Outdated
{-# LANGUAGE BangPatterns #-} | ||
{-# LANGUAGE RankNTypes #-} | ||
|
||
module Math.NumberTheory.Recurrencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us move this stuff to a new Math.NumberTheory.Recurrencies.Pentagonal
and use Math.NumberTheory.Recurrencies
to re-export Linear
, Bilinear
and Pentagonal
.
Math/NumberTheory/Recurrencies.hs
Outdated
-- >>> take 10 pents | ||
-- [0, 1, 2, 5, 7, 12 ,15, 22, 26, 35] | ||
pents :: Integral a => [a] | ||
pents = map pent pentIndexes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need only consecutive applications of pent
, then we can speed up computations using identities
map pent [0..] = scanl (\acc n -> acc + 3 * n - 2) 0 [1..]
map pent [0,-1..] = scanl (\acc n -> acc + 3 * n - 1) 0 [1..]
This implementation avoids both multiplication and division and requires Num a
only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first identity holds, but the second one does not seem to:
[0,2,7,15,26,40,57,77,100,126]
where the first few generalized pentagonal numbers are [0, 1, 2, 5, 7, 12, 15, 22, 26, 35, 40, 51, 57, 70, 77, 92, 100, 117, 126]
. I would bet only those with positive indexes are being calculated here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
> take 10 $ scanl (\acc n -> acc + 3 * n - 1) (-1) [-1, -2..]
[-1,-5,-12,-22,-35,-51,-70,-92,-117,-145]
This means that
map pents [0..] = interleave (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
(map abs $ scanl (\acc n -> acc + 3 * n - 1) (-1) [-1, -2..])
where interleave :: [a] -> [a] -> [a]
weaves two lists together, element by element, starting with the left argument list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first identity holds, but the second one does not seem to
The second identity looks good to me:
> take 10 (map pent [0,-1..])
[0,2,7,15,26,40,57,77,100,126]
> take 10 (scanl (\acc n -> acc + 3 * n - 1) 0 [1..])
[0,2,7,15,26,40,57,77,100,126]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Bodigrim My apologies, I misread the left hand side of the second identity...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Bodigrim I just noticed, won't a Enum a
be necessary for pents
in any case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right about Enum a
, my fault.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, although I made another mistake in thinking it a problem. (Enum a, Num a, Ord a)
is necessary for Integral a
, but not sufficient. I distractedly assumed it was, and that the former implied the latter.
In fact, Mod m
has instances for (Enum a, Num a, Ord a)
, but none for Integral a
, which means having those 3 constraints on partition
is just fine. Will push a fix.
'pents' has been refactored per @Bodigrim's remarks.
Math/NumberTheory/Recurrencies.hs
Outdated
-- [1, 2, -3, -4, 5, 6] | ||
pentagonalSigns :: Num a => [a] -> [a] | ||
pentagonalSigns = helper True | ||
where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we express this manual recursion via some list combinators? Looks like zipWith
and cycle
may be applicable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
Math/NumberTheory/Recurrencies.hs
Outdated
interleave _ _ = [] | ||
|
||
-- | When calculating the @n@-th partition number @p(n)@ using the sum | ||
-- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(11) + ...@, the signs of each |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo, should be p(n-11)
.
Math/NumberTheory/Recurrencies.hs
Outdated
helper _ _ = [] | ||
|
||
-- @partition !! n@ calculates the @n@-th partition number: | ||
-- @p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(11) + ...@, where @p(0) = 1@ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo, should be p(n-11)
.
Math/NumberTheory/Recurrencies.hs
Outdated
-- | ||
-- Note: @tail@ is applied to @pents@ because otherwise the calculation of | ||
-- @p(n)@ would involve a duplicated @p(n-1)@ term (see the above example). | ||
partition :: forall a . (Enum a, Num a, Ord a) => [a] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please write a test checking that, for example, partition :: [Mod 10]
coincides with partition :: [Integer]
modulo 10.
'partition' and 'pents' are now found in 'Math.NumberTheory.Recurrencies.Pentagonal', which is reexported by the reintroduced 'Math.NumberTheory.Recurrencies' module.
Also fixes the name of a test in 'Math.NumberTheory.Recurrencies.PentagonalTests'.
e81a619
to
e2f61c6
Compare
@Bodigrim can you try doing It calculates the head of the list, and that's as far as it goes. Consumed 12GB of RAM before I killed the session. |
p = case someNatVal n of | ||
SomeNat (_ :: Proxy t) -> t | ||
driver :: KnownNat p => Bool | ||
driver = take m' (partition :: KnownNat n => [Mod p]) == |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Bodigrim I haven't made this work yet, but the idea is present in this and the next lines. I'm not sure how I'll reconcile the [SomeMod]
produced by helper with the [Mod n]
that partition
return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can either apply fromInteger :: Integer -> Mod p
to partition :: [Integer]
and compare with partition :: [Mod p]
, or on contrary extract integers from partition :: [Mod p]
by getVal
and compare with map (flip mod p) partition :: [Integer]
.
Or wrap everything up to SomeMod
, employing its Eq
instance, applying SomeMod
constructor to Mod p
and flip modulo p
to Integer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or on contrary extract integers from partition :: [Mod p] by getVal and compare with map (flip mod p) partition :: [Integer]
I'll need to somehow reflect the Natural
passed to the test as an argument into a Nat
. someNatVal
does :: Natural -> SomeNat
, but I'm not sure how to do SomeNat -> Nat
, but at the type level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually you cannot pass any term of type Nat
to partitionProperty2
, because Nat
is an uninhabited type (but "inhabited" kind). So we should go other way around: pass Natural
to partitionProperty
and reflect it on type level as Nat
, e. g., following pattern from modulo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it just before you posted this, now I'm fixing the fact that the test will fail because smallcheck: spec: internal error: Unable to commit 1048576 bytes of memory
. This is related to below's comments about using Map Int a
in partition
. I'll commit the corrected test for now.
09858ad
to
44d690a
Compare
let n' = (sum . | ||
pentagonalSigns . | ||
map (\m -> dict M.! (n - m)) . | ||
takeWhile (\m -> n - m >= 0) . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Bodigrim can you try doing take 10 (Math.NumberTheory.Recurrencies.partition) :: [Math.NumberTheory.Moduli.Mod 10] in a cabal new-repl session?
It calculates the head of the list, and that's as far as it goes. Consumed 12GB of RAM before I killed the session.
I can reproduce the issue. AFAIU it happens because the condition n - m >= 0 :: [Mod m]
is always true in modular arithmetic. Can we actually employ Map Int a
or even IntMap a
for caching here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we actually employ Map Int a or even IntMap a for caching here?
Are you asking if it is possible, or if we should it at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My impression is that it is possible and desirable. maxBound :: Int
is big enough to store indices, so no worries about possible overflow here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even after using Map Int a
, doing partition :: [Mod 10]
won't calculate more than 7 terms. I think it's because of the following line, tail) pents
. By asking for partition
with type [Mod 10]
, what happens is that pents
also has that type, and this spoils the remainder of the calculation. The following takeWhile
won't stop where it's supposed to, and so on.
I'll try giving pents
type [Int]
and see what happens.
3bfa707
to
44d690a
Compare
7047f84
to
8d619e5
Compare
@Bodigrim Travis CI's Haddock builds tell me:
The module header part is the one that has the |
8d619e5
to
2d89c42
Compare
Benchmark with
Benchmark with
|
@Bodigrim Inside
or
? |
e4fbc32
to
db8555d
Compare
db8555d
to
c7dadbd
Compare
-- | ||
-- Note: @tail@ is applied to @pents@ because otherwise the calculation of | ||
-- @p(n)@ would involve a duplicated @p(n-1)@ term (see the above example). | ||
partition :: forall a . (Enum a, Num a, Ord a) => [a] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum
and Ord
constraints seem to be redundant.
partition :: forall a . (Enum a, Num a, Ord a) => [a] | ||
partition = 1 : go (IM.singleton 0 1) [1..] | ||
where | ||
go :: forall a . (Enum a, Num a, Ord a) => IM.IntMap a -> [Int] -> [a] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum
and Ord
constraints seem to be redundant.
where | ||
go :: forall a . (Enum a, Num a, Ord a) => IM.IntMap a -> [Int] -> [a] | ||
go dict (n : ns) = | ||
let intN = fromEnum n |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIU both n
and intN
have type Int
, so this cast is redundant.
pentagonalSigns . | ||
map (\m -> partition' (n - m)) . | ||
takeWhile (\m -> n - m >= 0) . | ||
tail $ pents) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If your intention was to compare partition
against the reference implementation, it is worth to copy-paste reference definitions of pents
and pentagonalSigns
to tests as well. Otherwise, if one breaks, for instance, pents
, this test would not be able to catch it.
It is also worth to check that first, like, 20 values of partition
match the baseline. Brutal, but practical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Bodigrim it is probably not necessary to copy-paste pents
as pentagonalNumbersProperty1
in this module checks that if the sequence is correct.
I'd prefer the first snippet. |
bbbd45c
to
38d8ed5
Compare
Redundant 'Enum, Ord' constraints have been removed from 'partition'.
2a041f4
to
988acfb
Compare
A note about the maximum effective index of `partition`'s index has also been added.
988acfb
to
4cfdfef
Compare
@Bodigrim The PR should be complete now. |
Merged. Thanks for your efforts and contribution! |
Closes #107.