Python
  14. Counting Partitions   


Counting Partitions: See partition function from Wikipedia for a definition of partition. Here is a recurrence equation for the nth partition (discovered by the Swiss mathematician Euler):

,
where p(0) = 1 and p(n) = 0 for all n < 0.

(Initially I didn't realize that the above formula generates a huge number of negative calls to p, and these caused debugging problems. All such calls should return 0.)

Counting Partitions
Program "Memoized" (in red)
# part.py: recursive relation
import sys

LIM = 40

def p(n):
    if n < 0:
        return 0
    if n == 0: # or n == 1 speeds up
        return 1
    # print "Call p with n = %i" % n
    sign = 1
    res = 0
    for k in range(1, n+1):
        term = p(n - k*(3*k - 1)/2)+\
          p(n - k*(3*k + 1)/2)
        res = res + sign*term
        sign = -sign
    return res

n = 0
while n <= LIM:
    res = p(n)
    print n, res
    n = n+1

print "\n"


# part.py: memoized version
import sys

LIM = 10000
pmem = [0.0]*(LIM+1)

def insertp(t, loc):
   pmem[loc] = t
   
def p(n):
    if n < 0:
        return 0
    if n == 0:
        return 1
    if pmem[n] != 0:
        return pmem[n]
    sign = 1
    res = 0
    for k in range(1, n+1):
        p1 = n - k*(3*k - 1)/2
        if p1 >= 0:
            term1 = p(p1)
            insertp(term1, p1)
        else:
            term1 = 0
        p2 = n - k*(3*k + 1)/2
        if p2 >= 0:
            term2 = p(p2)
            insertp(term2, p2)
        else:
            term2 = 0
        term = term1 + term2
        res = res + sign*term
        sign = -sign
    return res

print "LIM:", LIM
n = 0
while n <= LIM:
    res = p(n)
    if n%100 == 0:
        print n, res
    n = n+1
print "\n"

The Python program on the left works but is impossibly slow. It slows a program to a crawl well before n = 30, while a C version slows before 40. The Python memoized version computed p(1000) in a few seconds. The memoizing features are shown in red. The answer agreed with the cited correct value. See Decorators for the use of decorators in memoization.

p(1000) = 24061467864032622473692149727991


Python Recursion Limit: Suppose one replaces the last 7 lines above on the right with just:

    print p(LIM)

Then the calculation works well and quickly for

    LIM < 998

but it fails with the message "RuntimeError: maximum recursion depth exceeded in cmp" for

    LIM > 999

Online, during a simple discussion of using tail recursion, one contributor wrote:

[The default recursion limit] is a guard against a stack overflow, [and] yes. Python (or rather, the CPython implementation) doesn't optimize tail recursion, and unbridled recursion causes stack overflows. You can change the recursion limit with sys.setrecursionlimit(), but doing so is dangerous -- the standard limit is a little conservative, but Python stackframes can be quite big.

Python isn't a functional language and tail recursion is not a particularly efficient technique. Rewriting the algorithm iteratively, if possible, is generally a better idea.

In the case above, eliminating recursion would be annoying (at least for me), but the memoization works fine. It's interesting that even with memoization, it's necessary to compute the numbers explicitly in increasing order.

If I use

    LIM = 1500

in the program, and include

    sys.setrecursionlimit(1502)
    print sys.getrecursionlimit()

the program completes (on my very slow PC) in about 3 seconds with the correct answer. Using

    LIM = sys.setrecursionlimit(1501)

it fails as above.


Output for LIM = 10000: The program on the right above prints every 100th value of n.

Counting Partions
LIM: 10000
   0 1
 100 190569292
 200 3972999029388
 300 9253082936723602
 400 6727090051741041926
 500 2300165032574323995027
 600 458004788008144308553622
 700 60378285202834474611028659
 800 5733052172321422504456911979
 900 415873681190459054784114365430
1000 24061467864032622473692149727991
1100 1147240591519695580043346988281283
1200 46240102378152881298913555099661657
1300 1607818855017534550841511230454411672
1400 49032194652550394774839040691532998261
1500 1329461690763193888825263136701886891117
1600 32417690376154241824102577250721959572183
1700 717802041964941442478681516751205185010007
1800 14552716211005418005132948684850541312590849
1900 272089289788583262011466359201428623427767364
2000 4720819175619413888601432406799959512200344166
2100 76425873151741373195807749021080021459080291165
2200 1160062988372259455129906418328374912794875140516
2300 16580882366033921211442301450921091904365926280416
2400 224019709133932919957689061390552862746031758458304
2500 2870875510641352469269629800993561138276373608937244
2600 35005956793582340304825402566403235647028948536389790
2700 407278345428047701089944225634803285388037631071892526
2800 4532872099182923619049859845142060284200333044402212796
2900 48372862414248702774338895349417770286909805269514122108
3000 496025142797537184410324879054927095334462742231683423624
3100 4897004232436187154426335670559788093746430251226858051409
3200 46630044321532856043343915329456863532851102570239322864540
3300 428976086384455648551897229907054981237744500383167911256648
3400 3818577179261075662543355322819183380028708524034054784736986
3500 32937611415266420716588951556626875161392556976192849379343514
3600 275665068861284810290044101570761861748430175616309003852396779
3700 2241341402634321055788966550515665143369526716939294744638765998
3800 17724444041459622360329965524696897122905314326530819162102408111
3900 136472351510363591782653499754288099391061996305932097073128381323
4000 1024150064776551375119256307915896842122498030313150910234889093895
4100 7497935328548449535702187865816110603416105023151668502544153457027
4200 53600201889731554250773472718455478280482250637568866044486232973779
4300 374457460414215868448133766669360170060817264014869518676465118717910
4400 2558548242369110117905768192203953673806562824713508030257053783385410
4500 17110560044768282210476612699360518321086100801683778015778885821060726
4600 112078147859115816999126321522967417358090737706137418780480473571955939
4700 719538293145254204424531219341177120471186283684609087012107593827438580
4800 4530415154482543489372276252457466551826962429694310684753928087354851999
4900 27992000249818987975876632570541354100311092214008840815304047895504442376
5000 169820168825442121851975101689306431361757683049829233322203824652329144349
5100 1012137045583577272567032375878786696258153248761665275335459087530372728641
5200 5929362362070600776535409434791952596191418853312676517778328220594156828647
5300 34159266038468003998790470174296225311524103498629802955160187851545132086559
5400 193617330770706811340412693335130549655797388143520763019778499059764674660785
5500 1080212160360281625523653320982093320016274120648743425880152804993035174398265
5600 5934556620278205627645931020617053617157862218336868021888564410567794993567938
5700 32118772122480245290469538088355471413877697511245354385278507253915966434148070
5800 171313031255778577473562365739765678693330188762706687939782609401958729691523896
5900 900834089399327418907067049568656782336766478375144787446088993379923459446019215
6000 4671727531970209092971024643973690643364629153270037033856605528925072405349246129
6100 23902173858568500797439820262355164819927956742777651650770775747985506596289009040
6200 120688912462669110648994658098021478525464221986804415971950581029388369481288872217
6300 601595516593233948630278112914792450057999816694636793725622864764078737334555742762
6400 2961289220573735487865399779740561874081799956999864821635708504984607831165182799000
6500 14398682328744966062302816386640063608668783058502316874419748926902008784956228204378
6600 69175484198939453735543424035821376436080510297190116417316287045785335347598133467370
6700 328463008949927362286384320031440588164857794923744709520914182709890483574859608793442
6800 1541836985128299385584634624829042495151798419179228089036359716875408807872398723479588
6900 7156768992821471670382796701972564751598093672706728809202714414800393262031080669955397
7000 32856930803440615786280925635924166861950151574532240659699032157432236394374450791229199
7100 149234636458163941912807193282622360734138899647617800889808437499620076682646168821118636
7200 670722033669076256394019640056342293385257623914626309372318433869787569361844908555237138
7300 2983596661408058320217504346115256173031354405038980303586035797268556834128554232470705564
7400 13138728587959149229661034774925356370522624024391726697552691549786141958588589370027581873
7500 57288875747737932492033748208752243349142287525488103263257939589868769968806720175560763797
7600 247386568242444643087094590874896598972940729527039312103267753049369798923771874068858437983
7700 1058164387992734442898200268698176841886131201501197195822901846423355698767587635442464188537
7800 4484159727822371942424402303953121817628329350465834714024276310522476371288602348799464746065
7900 18829425836890633524262419780136430910746176315113230876755756124896832173248125286904416162840
8000 78360264351568349490593145013364599719010769352985864331118600209417827764524450990388402844164
8100 323243605622034803775593457647549417335140404163485596116549381937454704164935621306345066509985
8200 1321932964057743030478245292116741052330576146334259591506013684339769787713248646695222090914684
8300 5360464732486834988159408342043381397773889426782372211774321404146419026020488562244802280226022
8400 21556339755050784891571613706614823131326374380589249012821772954347063288481816491423701411979152
8500 85978775934264643475951691907951624840913375430640446017748247621905399148093137569912610655120228
8600 340183605633788169655636610401223122745343438676784755591904298941662396614099486093654579109312679
8700 1335370611535728393424433285798885851794483592509821255818681109957161832005940825936745446680885372
8800 5201340250918823643734251983087617727462615380875643203549785578063796810264489130001885319973599575
8900 20105310256370107587752390698585718619164664776012331850377023244535403137500725846754146976502484846
9000 77133638117808884907320791427403134961639798322072034262647713694605367979684296948790335590435626459
9100 293743524998398056437596976973075841540795086520798506451345241962250422934735721709803018093944326439
9200 1110546364683147768260184758389891194377591780478557548706805655917900818750691728474145159723181517311
9300 4168694170849982986413700913673771488525129049418037019206213411465152987257907843658376378209899880711
9400 15538460980913579174525284637846631616335231540882309266171034771303164179121768198157679968890710363515
9500 57518686646518700964555319342916112045502828499627459319418662980578386032748782846488875653216540152024
9600 211470608972561580577336030122188577403780464039093719083950937240331857173366546539114852372449615181162
9700 772284023462944975579861992003236371643055389784813117257384988985686138098799378986837028282918895639671
9800 2801784797155189921384084589514651798297994988798785090303531746242005236946060555611550908993354389431793
9900 10098730077806228973681782632940775116594317099744833094046821998220906480848884335297149040911020531658901
10000 36167251325636293988820471890953695495016030339315650422081868605887952568754066420592310556052906916435144

  The value of P(10000) agrees with the value given in: 10001 terms of OEIS sequence A000041.

In the program, if I used

    LIM = 10000
    ...
    # last 7 lines replaced with
    sys.setrecursionlimit(10003)
    print sys.getrecursionlimit()
    print p(LIM)

the program completed (again on my very slow PC) in about 4 minutess with the correct answer.

See Computing the H-R-R function for a very sophisticated discussion of efficiently computing the Partition function p(n) for very large n. The author of this article developed the mpmath library. It turns out that the memoized program on the right above is an efficient way to calculate p(i) for all i up to some n. Just calculating p(n) alone can be done very much more efficiently for a single value of n, even if n is extremely large (up to 1019).


Calls to the function p(n): The formula for p(n) is amazing, since it involves a tremendous number of recursive calls. The data below was calculated using C. (part.c.)

Number of Recursive calls p(n) (non-convergent digits in red)
n p(n) n < 0 n == 0 n > 0 total calls
10 42 562 138 189 889
63.21 15.52 21.25 100.0 %
20 672 136461 33091 45337 214889
63.503017 15.399113 21.097869 100.0 %
30 5604 32755642 7942870 10881865 51580377
63.5040763 15.3990150 21.0969086 100.0 %
40 37338 7862170263 1906487020 2611917946 12380575229
63.50407891051 15.39901809678 21.09690299269 100.0 %
45 89134 121806527466 29536699936 40465754877 191808982279
63.50407891160 15.39901811951 21.09690296888 100.0 %
50 204226 1887116359576 457604291102 626925252825 2971645903503
63.5040789130176 15.3990181186315 21.0969029683508 100.0 %

Only those calls with n >= 0 make any difference, so 63.5% of the calls contribute nothing. Note that the three percentages of different types of recursive calls are converging.

The calculation for 50, with nearly 3 trillion recursive calls, took about a day and a half on my (slow) PC at home.


Approximation Formula for p(n): Donald Knuth gives two approximation formulas for p(n). The first one is only "rough", but the second is much better:

The first formula above is not very accurate at all, giving 199280893 for p(100). Using the second formula above, and the Python high-precision floating point package for the last 4 entries:

Comparison: approximation with true
(incorrect digits in red)
nValues
1041.6277858456
42
20625.75767545
672
305600.27955774
5604
4037330.6091926
37338
100190568944.783
190569292
5002300165032573762997671.
2300165032574323995027
100024061467864032622434996664766901.
24061467864032622473692149727991
50001.6982016882544212185902117792781737453 . . .
1.6982016882544212185197510168930643136 . . .
100003.6167251325636293989885023865630871e+106
3.6167251325636293988820471890953695e+106
  
Program for table at left
# part_mp.py: mp version
import sys
from mpmath import *

mp.dps = 80
print "Precision: ", mp.dps

mp500 = mpf(500.0)
mp1000 = mpf(1000.0)
mp5000 = mpf(5000.0)
mp10000 = mpf(10000.0)

# int/mp and int*mp works
def p1(n):
    return (mp.exp(mp.pi * 
      mp.sqrt(2.0*n/3.0)) / 
      (4.0 * n * mp.sqrt(3.0)))

def p2(n):
    np = n - (1.0/24.0)
    return (p1(np) * 
      (1.0 - (1.0/mp.pi) * 
      mp.sqrt(3.0/(2.0*np))))

print p2(mp500)
print p2(mp1000)
print p2(mp5000)
print p2(mp10000)

Note that the program above uses extra parens to continue a statement across several lines, rather than the continuation character \. This is a style issue, a less error-prone method.

(Revision date: 2015-06-08. Please use ISO 8601, the International Standard.)