InĀ [1]:
import numpy as np
from scipy.special import gamma
import matplotlib.pyplot as plt

Gamma function¶

A generalization of the factorial¶

Let's remember the factorial. The factorial of an integer $n$ is defined recursively as

$$ n! = n(n-1)! $$

This recursion continues without end (we keep subtracting 1) until we reach 0!. We define $0! \equiv 1$. That is,

$$ n! = n\cdot (n-1) \cdot (n-2) \cdots 1 $$

In Python, we can program this with a recursive function:

InĀ [2]:
def factorial(n):
    if n > 0:
        return n*factorial(n-1)
    if n == 0:
        return 1
    else:
        raise ValueError

Let's test it

InĀ [3]:
print(factorial(4),4*3*2*1)
24 24

Now let's plot the factorials of integer numbers:

InĀ [4]:
n = np.linspace(1,6,6,dtype=int)
factorials = np.array([ factorial(n_i) for n_i in n ])

plt.scatter(n,factorials)
Out[4]:
<matplotlib.collections.PathCollection at 0x112ce4b00>
No description has been provided for this image

Now let's imagine there were a curve that is able to connect the dots. One such curve is called the Gamma function and it is defined recursively in a similar way than the factorial:

$$ \Gamma(x+1) = x \Gamma (x) $$

$\Gamma(x) : \mathbb R \to \mathbb R$.

We can relate this definition to the factorial for integer $n$ as

$$ \Gamma(n) = (n-1)! $$

Please don't forget the -1 when converting from factorial to gamma and vice versa!

Let's take a look at what this function looks like for $x > 0$ and the comparison to the factorial function, using Scipy.

InĀ [5]:
x = np.linspace(0,6,1000)
G = gamma(x+1)

x_posinteger = np.linspace(0,6,6,dtype=int)
n = x_posinteger
factorials = np.array([ factorial(n_i) for n_i in n ])
InĀ [6]:
plt.plot(x,G,label="Gamma(x+1)")
plt.scatter(n,factorials,label="n!")
plt.legend()
Out[6]:
<matplotlib.legend.Legend at 0x112f0dfd0>
No description has been provided for this image

How to define the Gamma function?¶

The Gamma function is defined as

$$ \Gamma(x) = \int_0^\infty t^{x-1} e^{-t} dt $$

$\Gamma(x): \mathbb R \to \mathbb R$, $t\in \mathbb R$.

Important analytical exercise: show the properties of a) recursion b) $\Gamma(1) = 1$ from this definition.

(a) Recursivity: we have $\Gamma(x+1) = \int_0^\infty t^{x} e^{-t} \, dt $. Integrating by parts, we have $$ \Gamma(x+1) = \left[-t^x e^{-t}\right]_0^\infty + \int_0^\infty x \, t^{x-1} e^{-t}\, dt = x \Gamma(x)$$ after seeing that the evaluation at the limits goes to zero and using the definition of Gamma.

(b) $\Gamma(1) = \int_0^\infty t^{1-1} e^{-t}\,dt = \int_0^\infty e^{-t} \, dt = 1.$

Let's see how the Gamma function then looks like for $x \in \mathbb R$.

InĀ [7]:
x = np.linspace(-3,6,5000)
G = gamma(x)

plt.xticks(range(-3,7))

plt.plot(x,G)
plt.ylim(-100,100)
Out[7]:
(-100.0, 100.0)
No description has been provided for this image

We see that Gamma of negative integers [e.g. $\Gamma(-2)$] is not defined, but Gamma of negative non-integers [e.g. $\Gamma(-\pi/2)$] is defined.

Recursivity is part of the definition of $\Gamma(x)$. In this plot, we test that the function is indeed recursive.

InĀ [8]:
x = np.linspace(-2,6,1000)
G = gamma(x+1)
G1 = x*gamma(x)
plt.plot(x,G,ls="-",label="Gamma(x+1)")
plt.plot(x,G1,ls="--",label="x*Gamma(x)")
plt.legend()
plt.ylim(-100,100)
Out[8]:
(-100.0, 100.0)
No description has been provided for this image

What happens when the argument is a large number?¶

In the plot for positive arguments of the Gamma function, one can suspect that there is some sort of nice exponential behavior. First, let's see again what happens to larger and larger factorials:

InĀ [9]:
n = np.linspace(1,8,8,dtype=int)
factorials = np.array([ factorial(n_i) for n_i in n ])

plt.scatter(n,factorials)
Out[9]:
<matplotlib.collections.PathCollection at 0x113066d20>
No description has been provided for this image

The order of magnitude of the factorial increases with each new number in the horizontal axis. Let's try to visualize this by taking the logarithm base 10 of the Gamma function:

InĀ [10]:
# Logarithm base 10 of the Gamma function
x = np.linspace(0,20,1000)
plt.plot(x,np.log10(gamma(x)))
# easy way to visualize n! of a very large number
Out[10]:
[<matplotlib.lines.Line2D at 0x1131f17c0>]
No description has been provided for this image

Taking the logarithm has made the input of large numbers manageable. The natural logarithm also works:

InĀ [11]:
# Natural logarithm of the Gamma function
x = np.linspace(0,20,1000)
plt.plot(x,np.log(gamma(x)))
Out[11]:
[<matplotlib.lines.Line2D at 0x1132f5a30>]
No description has been provided for this image

Important analytical exercise: because of this very nice behavior for large numbers, one can approximate the factorial of large numbers (or equivalently, the Gamma function of large numbers) as

$$ \ln n! \approx n \ln n - n $$

This is called the Stirling approximation. We can visualize it in the following plot.

InĀ [12]:
n = np.linspace(2,50,1000)
plt.plot(n+1,np.log(gamma(n+1)), label=r"$\ln(n!)$")
plt.plot(n,n*np.log(n)-n, label=r"Stirling's formula")
plt.legend()
Out[12]:
<matplotlib.legend.Legend at 0x1131f20f0>
No description has been provided for this image

The Gamma function and the Gaussian distribution¶

Important analytical exercise: In Physics, we frequently encounter the Gaussian distribution, $\sim e^{-x^2}$. Evaluate $\Gamma(1/2)$ by using the Gaussian distribution.

Consider the integral $I_0 = \int_{-\infty}^{\infty} e^{-a x^2} dx$. We can integrate by considering first the integral $I_0^2 = \int_{-\infty}^\infty e^{a(x^2 + y^2)} dx dy$ and changing to polar coordinates, $I_0^2 = \int_0^{\infty} \int_0^{2\pi} e^{-ar^2} rdrd\phi = \pi/a $ $\implies I_0 = \sqrt{\pi/a}$.

For $a = 1$, we have $I_0 = \sqrt{\pi}$. Now think of the integral

$$ \Gamma(1/2) = \int_0^{\infty} \frac{1}{\sqrt{t}}e^{-t} dt $$

With the change of variable $t = y^2$, we find $ \Gamma(1/2) = 2\int_0^\infty e^{-y^2} dy = \sqrt{\pi} $

InĀ [13]:
# Verification
print(gamma(1/2),np.sqrt(np.pi))
1.7724538509055159 1.7724538509055159

By the recurrence relation, we can also find

InĀ [14]:
print(gamma(3/2), (1/2)*gamma(1/2), (1/2)*np.sqrt(np.pi))
0.8862269254527579 0.8862269254527579 0.8862269254527579

What happens for complex numbers?¶

We can define $\Gamma(z): \mathbb C \to \mathbb C$, $t\in \mathbb R$, as

$$ \Gamma(z) = \int_0^\infty t^{z-1} e^{-t} dt $$

in a similar way than the real $\Gamma(x)$, through the integral definition.

InĀ [15]:
x = np.linspace(-10,10,1000)
y = np.linspace(-10,10,1000)
x, y = np.meshgrid(x,y,indexing="ij")
z = x + 1j*y
gamma_eval = gamma(z)

Let's plot the real part of $\Gamma(z)$:

InĀ [16]:
plt.pcolormesh(np.real(z),np.imag(z),np.real(gamma_eval),vmin=-10,vmax=10,cmap="seismic_r")
plt.colorbar()
plt.grid()
No description has been provided for this image

Mental check: in the horizontal axis, the function grows strongly just as we have plotted before (following the factorial function). For negative numbers, we see that the function becomes undefined for negative integers.

Now, let's plot the imaginary part:

InĀ [17]:
plt.pcolormesh(np.real(z),np.imag(z),np.imag(gamma_eval),vmin=-10,vmax=10,cmap="seismic_r")
plt.colorbar()
plt.grid()
No description has been provided for this image

Mental check: along the horizontal axis, the imaginary part is everywhere zero, meaning that $\Gamma(x)$ is indeed real for real $x$.