Note
Go to the end to download the full example code or to run this example in your browser via JupyterLite.
Rouché’s theorem: counting zeros by domination#
Eugène Rouché (1862): if \(|g| < |f|\) on a closed contour, then \(f\) and \(f + g\) have the same number of zeros inside it. Picture \(f(\gamma)\) as a person walking around a lamppost at 0 and \(g\) as the leash to a dog at \(f + g\): a leash shorter than the distance to the post means the dog circles it the same number of times. Here \(z^5 + 3z + 1\) has all five zeros in \(|z| < 2\) (dominated by \(z^5\)) and exactly one in \(|z| < 1\) (dominated by \(3z\)).
import matplotlib.pyplot as plt
import numpy as np
from mathematicskit.complex_analysis import argument_principle, circle_contour
def h(z):
return z**5 + 3 * z + 1
roots = np.roots([1, 0, 0, 0, 3, 1])
Two contours, two dominant terms#
fig, axes = plt.subplots(1, 2, figsize=(11, 5))
for ax, (R, f, name) in zip(axes, [(2.0, lambda z: z**5, "z^5"), (1.0, lambda z: 3 * z, "3z")], strict=False):
contour = circle_contour(0.0, R)
z = contour.points(2000)
g = h(z) - f(z)
print(f"|z| = {R}: min |{name}| = {np.min(np.abs(f(z))):.2f} > max |rest| = {np.max(np.abs(g)):.2f}")
print(
f" zeros of {name}: {argument_principle(f, contour)}, zeros of z^5+3z+1: {argument_principle(h, contour)}, "
f"numpy.roots inside: {int(np.sum(np.abs(roots) < R))}"
)
ax.plot(f(z).real, f(z).imag, lw=1, label=f"walker: {name}")
ax.plot(h(z).real, h(z).imag, lw=1, label="dog: z^5 + 3z + 1")
ax.plot(0, 0, "k*", ms=12, label="lamppost 0")
ax.set_aspect("equal")
ax.set_title(f"|z| = {R}")
ax.legend(fontsize=8)
fig.suptitle("Rouché's theorem as dog walking")
fig.tight_layout()
plt.show()

|z| = 2.0: min |z^5| = 32.00 > max |rest| = 7.00
zeros of z^5: 5, zeros of z^5+3z+1: 5, numpy.roots inside: 5
|z| = 1.0: min |3z| = 3.00 > max |rest| = 2.00
zeros of 3z: 1, zeros of z^5+3z+1: 1, numpy.roots inside: 1
Total running time of the script: (0 minutes 0.250 seconds)