#!/usr/bin/env python # coding: utf-8 # ##
Visual study of the winding number of a closed path in the complex plane. The Argument Principle
# This notebook is dedicated to the so called *Argument Principle* for meromorphic functions. More precisely we visualize the geometric version of this principle, which allows # to identify zeros and singularities in a graphical representation of a complex valued function by [domain coloring method](http://nbviewer.ipython.org/github/empet/Math/blob/master/DomainColoring.ipynb). # # The Argument Principle relates the number of zeros and singularities of a function with the so called winding number of a loop encircling these points. # # We present the winding number both theoretically and visually (animated). # ### Winding number of a closed loop with respect to a point # # A closed path or loop in the complex plane is defined by a continuous function $\gamma:[a,b]\to\mathbb{C}$, such that # $\gamma(a)=\gamma(b)$. The range or image of the function $\gamma$ is a curve $\Gamma=im(\gamma)$ in the complex plane. # # The path can be interpreted as defining the motion of a point on the curve $\Gamma$, during the interval of time $[a,b]$. # # For example $\gamma(t)=z_0+re^{2\pi i t}$, $t\in[0,1]$, defines a loop. The moving point describes # a circle centered at $z_0$ and of radius $r$. # # # A loop $\gamma:[a,b]\to\mathbb{C}$ which has no selfintersections, except for $\gamma(a)=\gamma(b)$, is called *simple loop*. # # $\gamma(t)=z_0+re^{2\pi i t}$ defines a simple loop for $t\in[0,1]$, while the loop defining the [figure eight](http://mathworld.wolfram.com/EightCurve.html) by: $\gamma(t)=\cos(t)+i \sin(t)\cos(t)$, $t \in [-\pi/2, 3\pi/2]$ is not simple. # # A simple loop $\gamma$ divides the complex plane into two regions, whose common boundary is the curve # $\Gamma=im(\gamma)$. The bounded region is usually called the *region inside* $\Gamma$. # Let $z_0$ be a point in the complex plane and $\gamma:[a,b]\to\mathbb{C}\setminus\{z_0\}$ a loop not passing through $z_0$. The *winding number* of $\gamma$ with respect to $z_0$ is, intuitively, the number of times the moving point $\gamma(t)$ winds around $z_0$, as $t$ varies in $[a,b]$. # We denote this number by $wind(\gamma, z_0)$. # # # In order to give a formal definition of the winding number we consider first loops that avoid 0, # and are defined (eventually through a reparameterization) on the interval $[0,1]$. # # If $\gamma:[0,1]\to\mathbb{C}\setminus \{0\}$ is a loop, then # there exists a continuous path $\eta:[0,1]\to\mathbb{C}$, such that $$\gamma(t)=e^{\eta(t)}=r(t)e^{i \varphi(t)}, \quad\forall\: t\in [0,1]$$ # # The real functions $r(t)$ and $\varphi(t)$ are obviously continuous, and they are uniquely defined up to multiples of $2\pi$. # # The real function $\varphi(t)$ unwraps the argument of $\gamma(t)$. In Complex Analysis it is called a *continuous branch of the argument along* $\gamma(t)$, whereas # in Topology the function $\varphi/2\pi$ is # *a lift of the path* $t\mapsto \displaystyle\frac{\gamma(t)}{|\gamma(t)|}$ in the unit circle $\mathbb{S}^1=\mathbb{R}/\mathbb{Z}$, # to a path in $\mathbb{R}$. # # Since $\gamma$ is a loop # we have $\gamma(0)=\gamma(1)$ $\Leftrightarrow$ $r(0)e^{i\varphi(0)}=r(1)e^{i\varphi(1)}$ # $\Leftrightarrow$ $r(0)=r(1)$ and $\varphi(1)-\varphi(0)=2 k\pi$, for some integer $k$. # # # By the definition given for example in [P. Henrici, Applied and computational complex analysis, Vol 1,](http://www.amazon.com/Computational-Analysis-Integration-Conformal-Location/dp/0471608416), the integer $k$ is *the winding number of the loop* with respect to $0$, $k=wind(\gamma,0)$. # # It gives the total continuous variation of the angle along the loop. # # If a loop $\gamma$ avoids a point $z_0$, then the loop $\xi$, $\xi(t)=\gamma(t)-z_0$, avoids $0$, and thus one defines the winding number of the loop $\gamma$ with respect to $z_0$ as being $wind(\gamma, z_0)=wind(\xi, 0)$. # # # - The winding number is positive when the path $\gamma$ is positively oriented ($\gamma(t)$ moves anticlockwise on the underlying curve $\Gamma$, as $t$ increases from $a$ to $b$). # - It is negative when $\gamma$ is negatively oriented, and $0$ when $z_0$ is "outside" the curve $\Gamma$. # # In order to get more insight into this notion, # we take a loop and animate the motion of the vector $\overrightarrow{z_0\gamma(t)}$ about $z_0$, as $\gamma(t)$ is running along the curve $\Gamma$, from its starting point $\gamma(0)$, and back. # To activate and display the animation within the IPython Notebook we call the function `matplotlib.animation` and import the package [JSAnimation](https://github.com/jakevdp/JSAnimation) (it can be installed via pip). # In[1]: get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib.pyplot as plt import numpy as np from matplotlib import animation from JSAnimation import IPython_display # In[2]: def moving_vector(gammat, z0, xm, xM, ym, yM): # gammat is an array of N complex numbers, representing the values of gamma(t) at # t=np.linspace(0,1, N) #xm, xM, ym, yM are axes limits nr = gammat.real.shape[0] def init(): v = ax.quiver([], [], [], [], color=(0.45,0.45, 0.45), scale_units='xy', angles='xy', scale=1, linewidth=.1) return v, def animate(i): v.set_UVC(gammat[i].real, gammat[i].imag) return v, fig = plt.figure() ax = fig.add_subplot(111, xlim=(xm, xM), ylim=(xm, xM)) ax.grid() ax.plot(gammat.real, gammat.imag, 'g', lw=2) ax.plot(gammat[0].real, gammat[0].imag, 'ro') #the starting point ax.plot(z0.real, z0.imag, color=(0.45, 0.45, 0.45), marker='o') X = [] Y = [] X.append(z0.real) Y.append(z0.imag) v = ax.quiver( X, Y, [], [], color=(0.45,0.45, 0.45), scale_units='xy', angles='xy', scale=1, linewidth=.1) anim = animation.FuncAnimation(fig, animate, init_func=init, frames=nr, blit=True, interval=30, repeat=False) return anim # To start the animation, one clicks the right black triangle. To restart it, click the left black triangle and wait until the slider reaches the left end, # and then click the right triangle again. # **Example**: we read from a file the real and imaginary parts of the values $\gamma(t)$, where # $t$ is the array `t=np.linspace(0,1, N)`: # In[3]: data = np.loadtxt('Gamma.txt') gamma = data[:, 0]+1j*data[:, 1] anim = moving_vector(gamma, 0.0, -6,6, -4,6) IPython_display.display_animation(anim, default_mode='once') # We can notice that from the starting red point and back, the arrow is running anti-clockwise two complete cycles, i.e. the winding number is 2. # # Now we confirm this observation by using the definition of the winding number. # For, we unwrap the argument $arg(f(\gamma(t))$, and draw the graph of the lift $\phi=\varphi/2\pi$, with $\varphi(0)=arg(f(\gamma(0))$. The winding number is in this case # $wind(\gamma, z_0)=\phi(1)-\phi(0))$. # In[4]: theta = np.angle(gamma) phi = np.unwrap(theta) / (2*np.pi) N = phi.shape[0] t = np.linspace(0, 1, N) plt.plot(t,phi) print(f'The winding number is: {phi[N-1]-phi[0]}') # ### The Argument Principle for meromorphic functions # # A complex function $f$ is meromorphic on a domain $D\subset \mathbb{C}$ if it is analytic on $D$ except at a set of points which are poles of finite order (no one is an essential singularity). # Example of meromorphic function: # # $$f(z)=\displaystyle\frac{(z+2i)e^{ z}}{(z+i)^2(z+5)}$$ # $f$ has a zero, $z_1=-2i$, and three poles: $p_1=p_2=-i$, $p_3=-5$. # # The function $g(z)=z^2e^{1/z}=z^2\sum_{k=0}^\infty z^{-k}/k!$ is not meromorphic on a disk centered at $0$, because $z=0$ is an essential singularity. # The **Argument Priciple** for meromorphic functions states [[Needham](http://www.amazon.com/Visual-Complex-Analysis-Tristan-Needham/dp/0198534469)]: # # *Let $f$ be a meromorphic function on a domain $D\subset\mathbb{C}$, and $\gamma:[a,b]\to D$ a piecewise smooth simple loop in $D$, oriented positively. # If $f$ has $N$ zeros and $P$ poles inside $\Gamma=im(\gamma)$, # and no other zero or pole belongs to $\Gamma$, then the winding number of the path $f\circ \gamma:t\mapsto f(\gamma(t))$, with respect to $0$, is $N-P$:* # # $$wind(f\circ \gamma, 0)=N-P$$ # # In other words, the moving point $f(\gamma(t))$, $t\in[a,b]$, completes $|N-P|$ cycles in its motion about $0$. # When the function $f$ has no poles inside $\Gamma$, it is analytic, and thus the Argument Principle states in this case that the winding number is equal to the number of zeros inside $\Gamma$. # For example, the function $f(z)=(z-2i)(\cos z-1)/(z+1)$ has three zeros: $z_1=2i$ and $z_2=z_3=0$ (because # # $\cos z-1=\displaystyle\sum_{k=0}^\infty (-1)^k \frac{z^{2k}}{(2k)!!}-1=-\frac{z^2}{2}+\frac{z^4}{4!!}+\cdots=z^2g(z)$, with $g(0)\neq 0$), and one pole $p_1=-1$ # # These zeros and the pole belong to the open disk $Disk(0, 2.5)$. Hence we can take $\gamma(t)=2.5e^{2\pi i t}$, $t\in[0,1]$, as a simple loop surrounding them. Its $f$-image is a loop surounding two times (i.e 3-1) the point zero (see the right plot below): # In[5]: plt.rcParams['figure.figsize'] = 10, 6 from matplotlib.patches import ConnectionPatch f = lambda z: (z - 2*1j) * (np.cos(z)-1) / (z+1) gamma = lambda t: 2.5 * np.exp(2*np.pi*1j*t) t = np.linspace(0, 1, 200) fgamma = f(gamma(t)) Z = [0+1j*0, 0+2*1j] pole = -1.0 fig = plt.figure() ax1 = fig.add_subplot(121) ga_t = gamma(t) ax1.plot(ga_t.real, ga_t.imag) for zero in Z: ax1.plot(zero.real, zero.imag, 'ro') ax1.plot(pole.real, pole.imag, 'ks') ax1.plot(ga_t.real, ga_t.imag, 'b', lw=2) ax1.set_title('A simple positive oriented loop encircling all zeros of f') ax1.axis('equal') ax2=fig.add_subplot(122) ax2.plot(0, 0, 'go') ax2.plot(fgamma.real, fgamma.imag, 'g', lw=2) ax2.set_title('f-image of the curve $\Gamma=im(\gamma)$ encircling 0, two times') ax2.patch.set_facecolor('None') ax2.axis('equal') con = ConnectionPatch(xyA=(2.6, 0), xyB=(-4, 0), coordsA='data', coordsB='data', axesA=ax1, axesB=ax2, arrowstyle='->', clip_on=False) ax1.add_artist(con) plt.tight_layout(2) # In the sequel we illustrate the Argument Principle through animations. # We experiment with functions having one or more zeros and/or poles inside a simple loop, $\gamma(t)= a+re^{2\pi it}$, $t\in [0,1]$. # We define the function `winding0` that animates the motion of a point according to $t\mapsto f(\gamma(t))$: # In[6]: def winding(f, gamma, xm, xM, ym, yM, nr=100): #xm, xM, respectively ym, yM, are axes limits def pts_gen():#generates points on f(gamma(t)) t = t0 h = 1/nr while t <= 1.0: tr=gamma( t) yield f(tr) t+=h def animate(Z): X.append(Z.real) Y.append(Z.imag) line.set_data(X, Y) return line, t0 = 0 fig = plt.figure() ax = fig.add_subplot(111, xlim=(xm, xM), ylim=(ym, yM)) line, = ax.plot([], [], color=(0, 0.6, 0), lw=3) ax.grid() ax.plot(0, 0, 'ro')# 0 is marked by a red point X, Y = [], [] # if Z=f(gamma(t)), X=Z.real, Y=Z.imag anim = animation.FuncAnimation(fig, animate, frames=pts_gen, save_count=nr, blit=True, interval=30, repeat=False) return anim # We also define a function that # calculates the winding number $wind(f\circ\gamma, 0)$, and draws the graph of the lift $\varphi/2\pi$, where $\varphi(0)=\arg(f(\gamma(0))$. # In[7]: plt.rcParams['figure.figsize'] = 6, 4 def lift(f, gamma, N=100): t = np.linspace(0,1,N) theta = np.angle(f(gamma(t))) phi = np.unwrap(theta) / (2*np.pi) wind = phi[N-1] - phi[0] print(f'Winding number = {wind}') plt.plot(t, phi) # The function $f(z)=(z-1)(z+i)^2$ has three zeros inside the open disk $D(0, 1.6)$. Hence if $\gamma(t)=1.6e^{2\pi i t}$, $t\in[0,1]$, then the point $f(\gamma(t))$ moves anticlockwise about $0$ (the red point) and describes three complete cycles, as $t$ varies in $0,1]$: # In[8]: f = lambda z: (z-1) * (z+1j)**2 gamma = lambda t: 1.6 * np.exp(2*np.pi*1j*t) anim = winding(f, gamma, -10, 15, -15, 15, nr=150) plt.xlabel('To start the animation, click the right pointing black triangle') IPython_display.display_animation(anim, default_mode='once') # In[9]: lift(f, gamma) # If $N-P<0$ the moving point winds clockwise about $0$, describing $|N-P|$ cycles. # The following example illustrates such a case for a function having one zero and three poles inside a loop: # In[10]: f = lambda z: (z+0.5) / (z**2*(z-1j)) gamma = lambda t: 1.5 * np.exp(2*np.pi*1j*t) anim = winding(f, gamma, -2, 1, -1.5, 1.5, nr=150) IPython_display.display_animation(anim, default_mode='once') # In[11]: lift(f, gamma, N=100) # If the number of zeros is equal to the number of poles lying inside a simple loop, then the winding number is $N-P=0$, i.e. the path $t\mapsto f(\gamma(t))$ does not winds around $0$ ($0$ is outside the loop $im(f\circ \gamma)$). # # For example the function: # $$f(z)=\displaystyle\frac{(z-i)\sin z}{(z-3)^2(z+1)}$$ # has three zeros, $z_1=i$, $z_2=0$, $z_3=\pi$, and three poles: $p_1=p_2=3$ and $p_3=-1$, within the open disk Disk(1, 2.5). The animation below reveals that $0$ is not surrounded by the moving point $f(\gamma(t))$: # # In[12]: f = lambda z: (z-1j) * np.sin(z) / ((z-3)**2 * (z+1)) gamma = lambda t: 1 + 2.5 * np.exp(2*np.pi*1j*t) anim = winding(f, gamma, -1.5, 0.5, -1, 1, nr=175) IPython_display.display_animation(anim) # In[13]: lift(f, gamma) # The relationship between the winding number and the number of # isochromatic lines around a zero or a pole of a meromorphic function, in a representation by [domain coloring method](http://nbviewer.ipython.org/github/empet/Math/blob/master/DomainColoring.ipynb) # of that function, is discussed in [Hans Lundmark's complex analysis pages](http://users.mai.liu.se/hanlu09/complex/domain_coloring.html). # First version of this notebook (Python 2): Nov 18, 2014 # # # This version (Python 3): Aug 10, 2019 # In[14]: from IPython.core.display import HTML def css_styling(): styles = open("./custom.css", "r").read() return HTML(styles) css_styling() # In[ ]: