Analytic Combinatorics Redux
09 May 2025 - Tags: my-talks , sage
Earlier today I gave a talk in the graduate student seminar titled “Counting is Hard. Complex Analysis is Easy.” based in part on my recent blog post about analytic combinatorics and based in part on Varilly’s notes on Dirichlet’s Theorem, showing how to count the number of trees of a certain shape and the number of “primes of a certain shape” by doing complex analysis. While prepping for this talk, I realized there’s a very pretty geometric way to see what’s going on when counting rooted ordered ternary trees! I don’t usually write blog posts about my local seminar talks anymore, but I think this is more than worthy of an exception! For instance, I think this could serve as a great visiual example of branches and mild singularities in complex analysis.
So, let’s remember the problem!
We want to count the number of rooted ordered ternary trees with
- a root node
- a root with one child
- a root with two children
- a root with three children
where each child is recursively a rooted ordered ternary tree.
If we define
That is, if
If we draw (the real locus of)
xxxxxxxxxx
P(z,w) = -w + z + z*w + z*w^2 + z*w^3
implicit_plot(P, (z,-3,3), (w,-3,3), axes=True)
and the implicit function theorem says that we can find functions
xxxxxxxxxx
P(z,w) = -w + z + z*w + z*w^2 + z*w^3
singularity = solve([diff(P,w) == 0, P == 0], [z,w])[0]
singZ, singW = singularity[0].rhs(), singularity[1].rhs()
full = implicit_plot(P, (z,-3,3), (w,-3,3), color='blue')
T = implicit_plot(P, (z,-3,3), (w,-1,singW), color='orange')
pt = point((singZ, singW), rgbcolor='orange', pointsize=30)
name = text(' ({},{})'.format(n(singZ, digits=5), n(singW, digits=5)),
(singZ, singW),horizontal_alignment='left',color='black')
show(full+T+pt+name, xmin=-3, xmax=3, ymin=-3, ymax=3, axes=True)
Note we have to stop at
Next, can we find a good approximation for
Note that
Indeed, we can solve for
xxxxxxxxxx
P(z,w) = -w + z + z*w + z*w^2 + z*w^3
singularity = solve([diff(P,w) == 0, P == 0], [z,w])[0]
singZ, singW = singularity[0].rhs(), singularity[1].rhs()
show((w/(1+w+w^2+w^3)).series(w==singW,5))
Of course, we chose this point because
Here’s a graph zoomed into near the singularity. The actual graph of
xxxxxxxxxx
P(z,w) = -w + z + z*w + z*w^2 + z*w^3
singularity = solve([diff(P,w) == 0, P == 0], [z,w])[0]
singZ, singW = singularity[0].rhs(), singularity[1].rhs()
eps = 0.25
D2 = (w/(1+w+w^2+w^3)).diff(w,2).subs(w=singW)/factorial(2)
S(z,w) = z - singZ - D2 * (w - singW)^2
full = implicit_plot(P, (z,singZ-eps,singZ+eps), (w,singW-eps,singW+eps),
color='blue')
S = implicit_plot(S, (z,singZ-eps,singZ+eps), (w,singW-eps,singW+eps),
color='orange')
pt = point((singZ, singW), rgbcolor='orange', pointsize=30)
name = text(' ({},{})'.format(n(singZ, digits=5), n(singW, digits=5)),
(singZ, singW),horizontal_alignment='left',color='black')
show(full+S+pt+name, xmin=singZ-1.1*eps, xmax=singZ+1.1*eps,
ymin=singW-1.1*eps, ymax=singW+1.1*eps, axes=True)
But of course if
and if we want this to agree with our
xxxxxxxxxx
P(z,w) = -w + z + z*w + z*w^2 + z*w^3
singularity = solve([diff(P,w) == 0, P == 0], [z,w])[0]
singZ, singW = singularity[0].rhs(), singularity[1].rhs()
eps = 0.01
D2 = (w/(1+w+w^2+w^3)).diff(w,2).subs(w=singW)/factorial(2)
S = singW - sqrt(singZ/-D2) * (1 - z/singZ)^(1/2)
T = implicit_plot(P, (z,-0.1,0.3), (w,-0.1,singW+eps), color='blue')
S = plot(S, (z,singZ-eps,singZ), color='orange')
pt = point((singZ, singW), rgbcolor='black', pointsize=30)
show(T+S+pt, xmin=-0.1, xmax=0.3, ymin=-0.1, ymax=singW+eps, axes=True)
But now from here we can run exactly the same argument as in the
previous post! We compute
Again, for more details about exactly what this keyhole contour is and how you estimate this integral along it, see the main post.
Let’s go ahead and call it here! My actual talk was slightly longer than this, since I also sketched a proof of Dirichlet’s Theorem following Varilly’s notes on the subject, but there’s no sense in me writing that up since those notes are already so good! Plus it’s getting late and I want to go to bed, haha.
As always, here’s a copy of my title and abstract. Unfortunately I don’t have any slides or recordings today, but I’m giving another talk at WiSCons in Madison next week and I should have slides for that. I’m also hoping to finish a sister blog post for that talk where I do more example computations in more detail than I could possibly do in a 20 minute talk. Lots of writing to do this week!
Take care all, stay safe, and I can’t wait to talk soon ^_^
Counting is Hard. Complex Analysis is Easy.
Don’t you miss being a kid, when your mom would ask you to count how many of your alphabet fridge magnets were both red and vowels? When you saw the answer was “2”, you felt such accomplishment…. But counting has only gotten harder since then, and now if you want to count how many objects satisfy some properties (say, being both red and vowels) it can be borderline impossible! In this talk we’ll show how you can solve counting problems by doing complex analysis instead. First we’ll count the number of trees of a certain shape, then (given time) we’ll count how many prime numbers are of a (different) certain shape.
-
In the main post we used puiseux series for this, and we had to essentially guess which branch was correct. Now we see how the geometry of the situation tells us that we have to choose the negative branch of the square root! After all, this is the one that locally agrees with the rest of the graph of
! ↩