Skip to content
This repository was archived by the owner on Jan 30, 2023. It is now read-only.

Commit 0266cef

Browse files
committed
Improve formula for Bell numbers
1 parent 36c150e commit 0266cef

File tree

1 file changed

+99
-63
lines changed

1 file changed

+99
-63
lines changed

src/sage/combinat/combinat.py

+99-63
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,23 @@
164164
def bell_number(n, algorithm='dobinski', **options):
165165
r"""
166166
Return the `n`-th Bell number (the number of ways to partition a set
167-
of n elements into pairwise disjoint nonempty subsets). If `n \leq 0`,
168-
return `1`.
167+
of `n` elements into pairwise disjoint nonempty subsets).
169168
170169
INPUT:
171170
172171
- ``n`` -- a positive integer
173172
174-
- ``algorithm`` -- (Default ``'dobinski'``) Can be any one of the
175-
following:
173+
- ``algorithm`` -- (Default: ``'dobinski'``) any one of the following:
176174
177175
- ``'dobinski'`` -- Use Dobinski's summation formula
178-
(when `n < 200`, this just wraps GAP)
179-
- ``'gap'`` -- Wrap GAP's ``Bell``
176+
177+
- ``'gap'`` -- Wrap libGAP's ``Bell``
178+
180179
- ``'mpmath'`` -- Wrap mpmath's ``bell``
181180
182181
.. WARNING::
183182
184-
When using the mpmath algorithm to compute bell numbers and you specify
183+
When using the mpmath algorithm to compute Bell numbers and you specify
185184
``prec``, it can return incorrect results due to low precision. See
186185
the examples section.
187186
@@ -191,20 +190,23 @@ def bell_number(n, algorithm='dobinski', **options):
191190
192191
B_n = e^{-1} \sum_{k=0}^{\infty} \frac{k^n}{k!}.
193192
194-
To show our implementation of Dobinski's method works, suppose that `n > 8`
195-
and let `k_0` be the smallest integer for that `\frac{k_0^n}{k_0!} < 1`.
193+
To show our implementation of Dobinski's method works, suppose that `n \geq 5`
194+
and let `k_0` be the smallest positive integer such that `\frac{k_0^n}{k_0!} < 1`.
196195
Note that `k_0 > n` and `k_0 \leq 2n` because we can prove that
197196
`\frac{(2n)^n}{(2n)!} < 1` by Stirling.
198197
199-
Next if `k > k_0`, then we have `\frac{k^n}{k!} < \frac{1}{2^{k-k_0}}`, and
200-
the proof is by induction. Let `c_k = \frac{k^n}{k!}`, if `k > n` then
198+
If `k > k_0`, then we have `\frac{k^n}{k!} < \frac{1}{2^{k-k_0}}`.
199+
We show this by induction:
200+
let `c_k = \frac{k^n}{k!}`, if `k > n` then
201201
202202
.. MATH::
203203
204204
\frac{c_{k+1}}{c_k} = \frac{(1+k^{-1})^n}{k+1} < \frac{(1+n^{-1})^n}{n}
205-
< \frac{4}{n} < \frac{1}{2}.
205+
< \frac{1}{2}.
206206
207-
By using this, we can see that `\frac{c_k}{c_{k_0}} < \frac{1}{2^{k-k_0}}`
207+
The last inequality can easily be checked numerically for `n \geq 5`.
208+
209+
Using this, we can see that `\frac{c_k}{c_{k_0}} < \frac{1}{2^{k-k_0}}`
208210
for `k > k_0 > n`. So summing this it gives that `\sum_{k=k_0+1}^{\infty}
209211
\frac{k^n}{k!} < 1`, and hence
210212
@@ -213,61 +215,75 @@ def bell_number(n, algorithm='dobinski', **options):
213215
B_n = e^{-1} \left( \sum_{k=0}^{k_0} \frac{k^n}{k!} + E_1 \right)
214216
= e^{-1} \sum_{k=0}^{k_0} \frac{k^n}{k!} + E_2,
215217
216-
where `0 < E_1 < 1` and `0 < E_2 < e^{-1}`. Next we have:
217-
218-
.. MATH::
219-
220-
\sum_{k=0}^{k_0} \frac{k^n}{k!} = \sum_{k=0}^{k_0} n^{-2} \left\lfloor
221-
\frac{n^2 k^n}{k!} \right\rfloor + \frac{E_3}{n^2}
222-
223-
where `0 \leq E_3 \leq k_0 + 1 \leq 2n + 1 \leq 3n`, so
218+
where `0 < E_1 < 1` and `0 < E_2 < e^{-1}`. Next we have for any `q > 0`
224219
225220
.. MATH::
226221
227-
\sum_{k=0}^{k_0} \frac{k^n}{k!} = \sum_{k=0}{k_0} n^{-2} \left\lfloor
228-
\frac{n^2 k^n}{k!} \right\rfloor + E_4,
222+
\sum_{k=0}^{k_0} \frac{k^n}{k!} = \frac{1}{q} \sum_{k=0}^{k_0} \left\lfloor
223+
\frac{q k^n}{k!} \right\rfloor + \frac{E_3}{q}
229224
230-
where `0 \leq E_4 \leq \frac{3}{n}`. These two bounds gives:
225+
where `0 \leq E_3 \leq k_0 + 1 \leq 2n + 1`. Let `E_4 = \frac{E_3}{q}`
226+
and let `q = 2n + 1`. We find `0 \leq E_4 \leq 1`. These two bounds give:
231227
232228
.. MATH::
233229
234230
\begin{aligned}
235-
B_n & = e^{-1} \sum_{k=0}^{k_0} n^{-2} \left\lfloor
236-
\frac{n^2 k^n}{k!} \right\rfloor + e^{-1} E_4 + E_2 \\
237-
& = e^{-1} \sum_{k=0}^{k_0} n^{-2} \left\lfloor \frac{n^2 k^n}{k!}
231+
B_n & = \frac{e^{-1}}{q} \sum_{k=0}^{k_0} \left\lfloor
232+
\frac{q k^n}{k!} \right\rfloor + e^{-1} E_4 + E_2 \\
233+
& = \frac{e^{-1}}{q} \sum_{k=0}^{k_0} \left\lfloor \frac{q k^n}{k!}
238234
\right\rfloor + E_5
239235
\end{aligned}
240236
241237
where
242238
243239
.. MATH::
244240
245-
0 \leq E_5 < e^{-1} + \frac{3e^{-1}}{n} \leq e^{-1} \left(1 +
246-
\frac{3}{9}\right) < \frac{1}{2}.
241+
0 < E_5 = e^{-1} E_4 + E_2 \leq e^{-1} + e^{-1} < \frac{3}{4}.
247242
248-
Note `E_5` can be close to 0, so to avoid this, we subtract `\frac{1}{4}`
249-
from the sum:
243+
It follows that
250244
251245
.. MATH::
252246
253-
\begin{aligned}
254-
B_n & = e^{-1} \sum_{k=0}^{k_0} n^{-2} \left\lfloor \frac{n^2 k^n}{k!}
255-
\right\rfloor - \frac{1}{4} + E, \\
256-
B_n & = \left\lceil e^{-1} \sum_{k=0}^{k_0} n^{-2} \left\lfloor
257-
\frac{n^2 k^n}{k!} \right\rfloor -1/4 \right\rceil
258-
\end{aligned}
247+
B_n = \left\lceil \frac{e^{-1}}{q} \sum_{k=0}^{k_0} \left\lfloor
248+
\frac{q k^n}{k!} \right\rfloor \right\rceil.
249+
250+
Now define
251+
252+
.. MATH::
259253
260-
where `\frac{1}{4} \leq E < \frac{3}{4}`.
254+
b = \sum_{k=0}^{k_0} \left\lfloor \frac{q k^n}{k!} \right\rfloor.
261255
262-
Lastly, to avoid the costly integer division by `k!`, in one step collect
263-
more terms and do only one division, say collect 3 terms:
256+
This `b` can be computed exactly using integer arithmetic.
257+
To avoid the costly integer division by `k!`, we collect
258+
more terms and do only one division, for example with 3 terms:
264259
265260
.. MATH::
266261
267262
\frac{k^n}{k!} + \frac{(k+1)^n}{(k+1)!} + \frac{(k+2)^n}{(k+2)!}
268263
= \frac{k^n (k+1)(k+2) + (k+1)^n (k+2) + (k+2)^n}{(k+2)!}
269264
270-
using this all above error terms.
265+
In the implementation, we collect `\sqrt{n}/2` terms.
266+
267+
To actually compute `B_n` from `b`,
268+
we let `p = \lfloor \log_2(b) \rfloor + 1` such that `b < 2^p` and
269+
we compute with `p` bits of precision.
270+
This implies that `b` (and `q < b`) can be represented exactly.
271+
272+
We compute `\frac{e^{-1}}{q} b`, rounding up, and we must have an
273+
absolute error of at most `1/4` (given that `E_5 < 3/4`).
274+
This means that we need a relative error of at most
275+
276+
.. MATH::
277+
278+
\frac{e q}{4 b} > \frac{(e q)/4}{2^p} > \frac{7}{2^p}
279+
280+
(assuming `n \geq 5`).
281+
With a precision of `p` bits and rounding up, every rounding
282+
has a relative error of at most `2^{1-p} = 2/2^p`.
283+
Since we do 3 roundings (`b` and `q` do not require rounding),
284+
we get a relative error of at most `6/2^p`
285+
(plus some epsilon for errors on the errors).
286+
All this implies that the precision of `p` bits is sufficient.
271287
272288
EXAMPLES::
273289
@@ -276,7 +292,9 @@ def bell_number(n, algorithm='dobinski', **options):
276292
sage: bell_number(2)
277293
2
278294
sage: bell_number(-10)
279-
1
295+
Traceback (most recent call last):
296+
...
297+
ArithmeticError: Bell numbers not defined for negative indices
280298
sage: bell_number(1)
281299
1
282300
sage: bell_number(1/3)
@@ -287,8 +305,7 @@ def bell_number(n, algorithm='dobinski', **options):
287305
When using the mpmath algorithm, we are required have mpmath's precision
288306
set to at least `\log_2(B_n)` bits. If upon computing the Bell number the
289307
first time, we deem the precision too low, we use our guess to
290-
(temporarily) raise mpmath's precision and the Bell number is recomputed.
291-
The result from GAP's bell number was checked agaist OEIS. ::
308+
(temporarily) raise mpmath's precision and the Bell number is recomputed. ::
292309
293310
sage: k = bell_number(30, 'mpmath'); k
294311
846749014511809332450147
@@ -315,7 +332,7 @@ def bell_number(n, algorithm='dobinski', **options):
315332
316333
TESTS::
317334
318-
sage: all([bell_number(n) == bell_number(n,'gap') for n in range(200, 220)])
335+
sage: all([bell_number(n) == bell_number(n,'gap') for n in range(200)])
319336
True
320337
sage: all([bell_number(n) == bell_number(n,'mpmath', prec=500) for n in range(200, 220)])
321338
True
@@ -324,12 +341,18 @@ def bell_number(n, algorithm='dobinski', **options):
324341
325342
- Robert Gerbicz
326343
344+
- Jeroen Demeyer: improved implementation of Dobinski formula with
345+
more accurate error estimates (:trac:`17157`)
346+
327347
REFERENCES:
328348
329349
- :wikipedia:`Bell_number`
330350
- http://fredrik-j.blogspot.com/2009/03/computing-generalized-bell-numbers.html
331351
- http://mathworld.wolfram.com/DobinskisFormula.html
332352
"""
353+
n = ZZ(n)
354+
if n < 0:
355+
raise ArithmeticError('Bell numbers not defined for negative indices')
333356
if algorithm == 'mpmath':
334357
from sage.libs.mpmath.all import bell, mp, mag
335358
old_prec = mp.dps
@@ -346,24 +369,37 @@ def bell_number(n, algorithm='dobinski', **options):
346369
mp.dps = old_prec
347370
return ret
348371
return ZZ(int(ret_mp))
349-
if n < 200 or algorithm == 'gap':
350-
return ZZ(gap.eval("Bell(%s)"%ZZ(n)))
351-
from sage.functions.log import log
352-
from sage.misc.functional import ceil, N, isqrt, exp as exp2
353-
b, fact, k, n2, si = Integer(0), Integer(1), Integer(1), \
354-
Integer(n)**2, isqrt(Integer(n)) // 2
355-
while True:
356-
mult, v = n2, Integer(0)
357-
for i in range(si - 1, -1, -1):
358-
v += mult * (k + i)**n
359-
mult *= k + i
360-
fact *= mult // n2
361-
v //= fact
362-
b += v
363-
k += si
364-
if v == 0:
365-
break
366-
return ZZ(ceil(N((b - n) / n2 * exp2(Integer(-1)) - 1 / 4, log(b, 2) + 3)))
372+
373+
elif algorithm == 'gap':
374+
from sage.libs.gap.libgap import libgap
375+
return libgap.eval("Bell(%s)" % n).sage()
376+
377+
elif algorithm == 'dobinski':
378+
# Hardcode small cases. We only proved the algorithm below
379+
# for n >= 5, but it turns out that n = 4 also works.
380+
if n < 4:
381+
return Integer( (1, 1, 2, 5)[n] )
382+
b = ZZ.zero()
383+
fact = k = ZZ.one()
384+
q = 2*n + 1
385+
si = Integer(n).sqrtrem()[0] // 2
386+
while True:
387+
partfact = ZZ.one()
388+
v = ZZ.zero()
389+
for i in range(si - 1, -1, -1):
390+
v += partfact * (k + i)**n
391+
partfact *= k + i
392+
fact *= partfact
393+
v = (q * v) // fact
394+
if not v:
395+
break
396+
b += v
397+
k += si
398+
from sage.rings.all import RealField
399+
R = RealField(b.exact_log(2) + 1, rnd='RNDU')
400+
return ( (R(-1).exp() / q) * b).ceil()
401+
402+
raise ValueError("unknown algorithm %r" % algorithm)
367403

368404
def catalan_number(n):
369405
r"""

0 commit comments

Comments
 (0)