1.10. Numerical Calculation of PGF coefficients#
There are times when we may want to find coefficients of a generating function, but we do not have an analytic function. One scenario where this might occur would be where we know \(\mu(x)\) but we want to find the coefficients of \(\Phi_g(x) = \mu^{(g)}(x)\).
1.10.1. Symbolic calculation#
The natural first attempt is to simply take the series expansion of \(\mu(x)\), plug it into itself repeatedly while tracking the coefficients. Using one of the simplest possible examples, if \(\mu(x) = (1+x^2)/2\), then
Although this approach will work if the \(\mu(x)\) is simple enough, the number of calculations at each level grows quickly. The code below uses Python’s sympy package for symbolic calculation. To check the run time, it uses Python’s timeit package and runs many simulations, and plots the median run time for different \(\Phi_g\).
In the code above, if I run with a larger \(g\), it suddenly slows down (I’ve never waited long enough to see if it will stop). I presume it is running out of memory to hold all the calculations or some similar problem.
1.10.2. Vectorized calculation#
A different method just uses a vector of coefficients to do the calculations. It uses the location in the array as a proxy for \(x^k\), and then it is able to do calculations without the symbolic overhead.
It is implemented below using a numpy array in Python. This can run for larger \(g\). Note that we’ve switched to a log scale.
1.10.3. Cauchy Integration#
Now we’ll try a very different method.
If you’ve taken Complex Analysis, you may recognize the “Cauchy integral” that can extract the coefficient of \(x^k\) from \(\Phi_g(x)\):
(here I’ve used the notation \([x^k]\Phi_g(x)\) to denote the coefficient of \(x^k\) in \(\Phi_g(x)\)) If you haven’t taken Complex Analysis, don’t worry, we’ll rewrite the integral and then derive its properties without requiring the Complex Analysis theorems.
You may recall from Complex Analysis that it’s important that \(\Phi_g(z)\) doesn’t have any singularities inside the unit circle \(|z|=1\). It turns out that this is guaranteed by the fact that \(\Phi_g(1) = \sum_k p_k = 1\). Since \(|p_k z^k| \leq p_k\) for any \(|z|\leq 1\), \(\Phi_g(z)\) converges absolutely everywhere inside and on the unit circle.
We can convert this integral into something that is easier to consider using the substitution \(z = e^{i\theta}\). Then \(dz/d\theta = i e^{i\theta}\) and \(dz = i e^{i\theta} d\theta\) where \(\theta\) goes from \(0\) to \(2\pi\). Substituting this and replacing \(1/z^{k+1}\) with \(e^{-i(k+1)\theta}\) yields:
This last integral is the integral we are going to do.
A few asides - The function \(\Phi_g(e^{i\theta})\) is sometimes called the “Characterstic Function” of the distribution corresponding to \(\Phi_g\). It is also the Fourier Transform of the discrete sequence \(p_{g,0}\), \(p_{g,1}\), \(\ldots\). This last point is useful because there are well-established rapid methods to compute and invert the Fourier Transform of a distribution (the Fast Fourier Transform, (FFT)). We will be able to use the FFT to calculate this integral faster than we can do it by hand.
Now let’s look at this integral from the perspective of someone who hasn’t taken Complex Analysis and learned about residues.
1.10.3.1. Alternate view of Cauchy integral.#
We’d like to understand why this integral finds \(p_{g,k}\) without relying on Complex Analysis.
The fact that \(\Phi_g(x)\) is a PGF guarantees that \(|\Phi_g(z)|\) converges and its magnitude is at most \(\Phi_g(1)=1\) everywhere on the unit circle. This means we can do the following steps with the integral.
So seemingly miraculously, multiplying by \(e^{-ik\theta}\) and integrating allows us to pick out the \(k\)-th coefficient. If you’ve used Fourier Series before, this is the method for calculating the coefficients (for the complex case).
The code in the cell below calculates \(\Phi_g(z)\) at a bunch of points on the unit circle, by starting with \(\Phi_0(z)\) at \(e^{2i\pi m/N}\) for a large value of \(M\). Then it iteratively finds \(\Phi_{g+1}(z) = \mu(\Phi_g(z))\) at those points. Once it’s calculated up to the desired \(g\), it can use a Riemann sum to calculate the integral above to find a desired coefficient. However, since the FFT is designed to work with values like these, it can also use the more efficient FFT methods to calculate all of the coefficients at once. By default it uses the FFT speed-up.
Now let’s compare these methods. We’ll use a different \(\mu(x)\): \(\mu(x) = (1+x+2x^2)/4\). We’ll first look at run-time. The Cauchy method wins out as \(g\) gets larger. We have to stop the symbolic computation at an early \(g\) because it seems to get stuck.
Now we’ll check that the results are consistent.
However, there are round-off errors to consider in the Cauchy integral approximation. In this case, the errors occur for cases whose combined probability is significantly less than \(10^{-15}\).
1.10.4. Additional comments#
Remark 1.8
For the Cauchy integral, if \(\Phi(x)\) has a maximum degree and we perform our discretization giving a finite Riemann sumwith more terms than the maximum degree of \(\Phi_g(x)\) the result is exact up to numerical precision. That is, if the arithmetic is exact and \(\Phi_g(x)\) is a polynomial, then as long as we have enough points in the discrete integral approximation, our coefficients will be exact. If there is no maximum degree or there is arithmetic roundoff error, then using more points helps to reduce accumulated error (but there is a limit to how small the roundoff error can be), and using a smaller radius in the circle can also help.
Remark 1.9
Note that if \(\mu(x) = e^{2(x-1)}\) or something similar rather than a polynomial with only a few terms, then the Cauchy integral method would still work, but the other two methods would require some level of truncation before even beginning. Keeping many terms would also slow those methods down significantly.
Remark 1.10
The method of multiplying by \(e^{-ik\theta}\) and integrating in order to find the \(k\)-th coefficient is a generalization of a concept we encounter in Linear Algebra.
If I have a vector \(\vec{v} \in \mathbb{R}^3\), and I refuse to tell you what it is, but I agree to tell you its dot product with any vectors you ask, the easiest thing to try would be to get the dot products with \((1,0,0)\), \((0,1,0)\) and \((0,0,1)\). By taking their dot product with \(\vec{v}\) you immediately get its component in each of their directions.
If instead you were interested in writing my vector \(\vec{v}\) as a linear combination of \(\vec{w}_1\), \(\vec{w}_2\) and \(\vec{w}_3\). My vector \(\vec{v} = c_1\vec{w}_1 + c_2 \vec{w}_2 + c_3 \vec{w}_3\), even though we don’t know what \(c_1\), \(c_2\), and \(c_3\) are. If you knew a vector \(\vec{u}_1\) that was orthogonal to \(\vec{w}_2\) and \(\vec{w}_3\), but not to \(\vec{w}_1\), then by calculating \(\vec{v} \cdot \vec{u}_1\) you will get \(c_1 \vec{w}_1 \cdot \vec{u_1}\), so
You can similarly extract \(c_2\) and \(c_3\) given vectors with appropriate orthogonality properties.
Now I’ll just make the claim that integrating a product is very similar to taking a dot product, and now the functions \(e^{-ik\theta}\) are acting in a similar way to pick out the coefficient of \(e^{ik\theta}\).
1.10.5. Self-test#
These are just notes to myself about what to create
What happens in integral if we use fewer points than the \(k\) we want?
Give integral with \(z = re^{i\theta}\) for smaller \(r\) and infinite series for \(\mu(x)\). Experimentally observe error.
Design problem showing Riemann sum is exact….