I have to calculate a triple summation (Nested Binomial CDF), the equation is below,

It is a Binomial summation first from k = 0 to C then m = 0 to k and f = 0 to C-k, here s is a function which takes an input between 0 and 1 and gives an output between (0, 1).
I need help with finding an efficient method to execute this in python. For now I am using a triple loop which works fine but isn't efficient for large C's. The triple loop I am currently using is below,
Here 's' is essentially linear but it can take whatever shape needed. Also 'r' is taken as 0.1
import numpy as np
from math import comb
def s(d):
return d * (0.99 - 0.01) + 0.01
S = 0
C = 100
for k in range(C):
for m in range(k):
for f in range(C-k):
S += comb(C, k) * (((0.1)**(k))*((0.9)**(C-k))) * comb(k, m) * comb(C-k, f) * (2**(-C)) * s((f+m)/C)
Is there a more efficient way that I can use?
Edit: s is just a function of the following form, it takes in an input between 0 and 1 and gives output between 0.01 and 0.99 based on its shape. In the example code it is linear, but it can be exponential or whatever.
Edit2: The function 's' can't come out of the first summation, in the equation I provided it could due to a typo. It is now fixed along with inputting chrslg's suggestions.
So that
sis just an affine function. Which allows some optimizationStarting from
Note that there is an error in this code: Σ in math notations use boundaries included (and in this kind of formula, it is quite usual to have
from 0 to n included, which means n+1 iterations. Because when you have n socks in a drawer, and take a random number of them, then you can take 0 sock, 1 sock, 2 socks, ... up to n socks, included :D).So, the code should read
We can get out of the loops everything that is not dependent on the index of the loop, as I mentioned in comments
Here, I just mimick the math equation rather than the code. And some operations are already factorized that way.
Sure, I can't remove directly the inner loop like this, because now you've replace
s(m/C)which is independent fromf, bys((m+f)/C)which is not.But since
sis affine, we can easily replace it bySo, one constant term, one proportional to f term
And since Σcomb(n,k) is 2ᵏ and Σk.comb(n,k) is n×2ᵏ⁻¹, we can replace this by
So here's goes one loop! Now we have 2 loops instead of 3. But it's not over yet!
Combining the
Sf=andSm +=linesNote that, here again, what we are multiplying by
comb(k,m)is made of constant terms (0.01*2**(-k)or0.98/C*(C-k)*2**(-k-1)are constant terms, as far asmis concerned), and proportional tomterms. So, using the sameΣcomb(n,k) = 2ᵏandΣk.comb(n,k) = k.2ᵏ⁻¹formulas, we can rewrite againAnd here's go the second loop! Just one left. Not over yet!
Which can be simplified as
Or even
So, Sm=0.5 obviously
But
Σ comb(n,k) aᵏbⁿ⁻ᵏis the well known development of (a+b)ⁿ. So that is 0.5×(r + (1-r))ᶜ = 0.5×1ᶜ = 0.5Hence the ultimate simplification of your code (sorry, can't do better than that!)
And you can easily check that, once the boundaries problems corrected, your code also returns 0.5 whatever the parameters (C and r) are.
Some numpy trick
Since this is supposed to be a numpy question, not a math simplification question, here is how you can accelerate this kind of computation with numpy.
Suppose you are trying to compute the inner loop
(the more interesting one. The one because of which I wanted to know
s, and particularly if it was vectorizable. My initial plan was to give the kind of answer I am giving now, ifswas vectorizable, that is ifscan work with both scalars and arrays as parameters. But I had to change my plan when I saw thatswas even affine, which is even better. But, well, let's suppose thatsis more complicated, for example containssin,logor even just some higher order polynomials that make my formal computation impossible)If, we could compute, at once, a vector of
C-k+1values, using vectorized functions (again, functions that works on whole arrays, if asked to), then we could skip theforloop, and sum that with numpy'ssummethod. It is still aforloop in reality. But a hidden one, and, more importantly, one that occurs inside numpy's optimized C code, node in our slow pure python.We can start with a vector of values for
ff=np.arange(C-k+1.0). Note the1.0, lazy way to ensure it is floatGood news is your
s(and probably allsyou could think of, even with thesin,logor higher degree polynomials I've mentioned) is vecorized. So we can easily compute a vector ofs((f+m)/C). That is simply by literally doing that: iffis a vector, then so is(f+m)/Cand so iss((f+m)/C)sincesis vectorized.So, the harder part is the comb part.
With numpy you can do it with a
cumprodSo, now we have a vector of
comb(f,C-k)and a vector ofs((f+m)/C). To have a vector of all terms, we just need to multiply bothNote that if you are willing to use scipy, it is even simpler, since scipy contains a vectorized
combfunctionThis times, no math tricks from me. I simplified nothing. This is just coding tricks that made the loop disappear. It is still there (inside scipy's
comb, numpy's+,-,*and.sum()). There are even 5 of them now (non-nested, and way more than 5 times faster than a pure python loop)