The max recursion for the coefficient of a polynomial

Question:

Recently, I came across an interesting counting problem on YouTube posed by 3Blue1Brown-OlympiadLevelCounting. The problem is to find the number of subsets of the set {1, 2, …, 2000} whose elements’ sum is divisible by five. Grant Sanderson presented a beautiful solution, but he also discussed how cumbersome the algorithm could become if implemented on computers to solve the problem efficiently.

However, there is beauty in using integer partitions to solve this problem, thanks to the work of the Euler. The solution is the sum of the distinct partitions of all multiples of 5 less than or equal to 2000 something similar to what I had posted on MathstackExchange years ago, where the comment by use "dxiv" was the key to this code.

Undoubtedly, Grant Sanderson’s solution is elegant as it does not require a computer and can be found without lengthy calculations.

My code is:

import numpy as np

def p(k):
    if k == 1:
        return np.array([1, 1])
    else:
        q = p(k - 1)
    return add(q, np.append(np.zeros(k, int), q))


def add(u, v):
    u = np.append(u, np.zeros(len(v) - len(u), int))
    return np.add(u, v)


def test(n):
    a = 1 << n
    b = 1 << (n//5)
    b *= 4
    return (a+b)//5


n = 1000      
generating_function = sum(p(n)[0:-1:5])+1
grant_sanderson = test(n)
print(generating_function)
print(grant_sanderson)
print(generating_function == grant_sanderson)

My difficulty is when n is a large number. Also, these numbers are too large that I have to use gmpy2 this is a graph that shows that arrays are symmetric about n//2:

a graph

Traceback (most recent call last):
  File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2main.py", line 15, in <module>
    generating_function = sum(p(n)[0:-1:5])+1
                              ^^^^
  File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
    q = p(k - 1)
        ^^^^^^^^
  File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
    q = p(k - 1)
        ^^^^^^^^
  File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
    q = p(k - 1)
        ^^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

This code can show how things quickly go out of hands:

import gmpy2

def q(n):
    Q = [gmpy2.mpz(0)] * (n+1)
    Q[0] = gmpy2.mpz(1)
    for k in range(1, n):
        for i in range(n, k-1, -1):
            Q[i] += Q[i-k]
    return Q[n] + 1
Asked By: Darshan P.

||

Answers:

An alternative method would be to count combinations of sums that reach each of the 5 modulo values (0,1,2,3,4). Hold these counts in a list and increase them progressively. At the end, take the count for the zero modulo to get the number of sets that add up to a multiple of 5:

def sumDiv5Count(numbers):
    modCounts = [0]*5
    for n in numbers:
        modCounts = [c+modCounts[(i-n)%5] for i,c in enumerate(modCounts)]
        modCounts[n%5] += 1        
    return modCounts[0]

Each number’s modulo 5 combines with the 5 possible moduli to add more ways to produce 5 distinct resulting modulo values.

For example 13%5 -> 3,

  • ways to produce 0 are added to ways to produce 0+3 –> slot 3

  • ways to produce 1 are added to ways to produce 1+3 –> slot 4

  • ways to produce 2 are added to ways to produce 2+3 –> slot 0 (2+3)%5 = 0

  • ways to produce 3 are added to ways to produce 3+3 –> slot 1 (3+3)%5 = 1

  • ways to produce 4 are added to ways to produce 4+3 –> slot 2 (4+3)%5 = 2

  • and there is one more way to produce 3 by itself

output:

print(sumDiv5Count(range(1,10)))
# 103

print(sumDiv5Count(range(1,50)))
# 112589990684671

print(sumDiv5Count(range(1,100)))
# 126765060022822940149670739967

print(sumDiv5Count(range(1,500)))
# 327339060789614187001318969682759915221664204604306478948329136809613379640467455488327009232590415715088668412756007101428785894679831065931534041087

print(sumDiv5Count(range(1,1000)))
# 1071508607186267320948425049060001810561404811705533607443750388370351051124936122493198378815695858127594672917553146825187145285692314043598457757469857480393456777482423098542107460506237114187795418215304647498358194126739876755916554395250481509160715757985439053702508024174143636196837700927487

print(sumDiv5Count(range(1,2001)))
# 22962613905485090484656664023553639680446354041773904009552854736515325227847406277133189726330125398368919292779749255468942379217261106628518627123333063707825997829062456000137755829648008974285785398012697248956323092729277672789463405208093270794180999311632479761788925921124662329907232844394066536268833781796891701120475896961582811780186955300085800543341325166104401626447256258352253576663441319799079283625404355971680808431970636650308177886780418384110991556717934409897816293912852988275811422719154702569434391547265221166310540389294622648560061463880851178273858239474974548427800575

One nice thing about this approach is that it could easily be turned into a generator that yields the count progressively as it goes through the numbers. It is also not dependent on the numbers being in a sequence or even distinct. Performance is good (a few milliseconds) given that it’s an O(n) process.

Answered By: Alain T.

This doesn’t throw recursion error!
Changes made to the original code:

  • sys.setrecursionlimit(2500)
  • Don’t know why, but gmpy2 was throwing wrong numbers. so used decimal.Decimal instead
from decimal import Decimal, getcontext
import sys
sys.setrecursionlimit(2500)

def p(k):
    if k == 1:
        return [Decimal(1), Decimal(1)]
    else:
        q = p(k - 1)
        return add(q, [Decimal(0)] * k + q)


def add(u, v):
    u = u + [Decimal(0)] * (len(v) - len(u))
    return [x + y for x, y in zip(u, v)]




def test(n):
    getcontext().prec = n+10  
    a = Decimal(1 << n)
    b = Decimal(1 << (n//5))
    b *= Decimal(4)
    result = (a+b)/Decimal(5)
    return Decimal(result)  

n = 2000
generating_function = sum(p(n)[0:-1:5])+1
print(generating_function)
Answered By: Darshan P.