Complex Numbers

We can write a complex number $c = a + ib$ with real part $\mathrm{Re}(c) = a$, imaginary part $\mathrm{Im}(c) = b$ and imaginary unit $i = \sqrt{-1}$. In Python, the symbol j is used to denote the imagniary unit. Furthermore, a coefficient before j is needed.

In [ ]:
c = 2 + 1j
print(c)

Python offers the built-in math package for basic processing complex numbers. As an alternative, we use here the external package numpy, which is used later for various purposes.

In [ ]:
import numpy as np

print(np.real(c))
print(np.imag(c))

A complex number $c = a+ib$ can be plotted as a point $(a,b)$ in the Cartesian coordinate system.

In [ ]:
%matplotlib inline

from matplotlib import pyplot as plt

c = 1.5 + 0.8j

plt.figure(figsize=(6, 6))
plt.plot([np.real(c)], [np.imag(c)], 'ok')
plt.xlim([0, 2])
plt.ylim([0, 1])
plt.grid()
plt.xlabel('$\mathrm{Re}(c)$')
plt.ylabel('$\mathrm{Im}(c)$');

The absolute value (or modulus) of a complex number $a+ib$ is defined by

$$|c| := \sqrt{a^2 + b^2}.$$

The phase (or angle) expressed in radians in the interval $(-\pi,\pi]$ is given by

$$\gamma := \mathrm{atan2}(b, a).$$
In [ ]:
print('Absolute value:', np.abs(c))
print('Angle (in radians):', np.angle(c))
print('Angle (in degree):', np.rad2deg(np.angle(c)))

The polar representation represents a complex number by its absolute value and its phase:

In [ ]:
def plot_polar_vector(c, label=None, color=None, start=0, linestyle='-'):
    # plot line in polar plane
    line = plt.polar([np.angle(start), np.angle(c)], [np.abs(start), np.abs(c)], label=label, color=color, linestyle=linestyle)
    # plot arrow in same color
    this_color = line[0].get_color() if color is None else color
    plt.annotate('', xytext=(np.angle(start), np.abs(start)), xy=(np.angle(c), np.abs(c)),
                 arrowprops=dict(facecolor=this_color, edgecolor="none", width=0))
    

c = 1.5 + 0.8j
plt.figure(figsize=(6, 6))
plot_polar_vector(c, color='k')

A polar representation $(|c|,\gamma)$ of a complex number $c=a+ib$ can be transformed into a Cartesian representation $(a,b)$ as follows:

\begin{eqnarray} a &=& |c| \cdot \cos(\gamma) \\ b &=& |c| \cdot \sin(\gamma) \end{eqnarray}
In [ ]:
this_abs = 1
this_angle = 45  # in degree

this_angle = np.deg2rad(this_angle)  # in rad
a = this_abs * np.cos(this_angle)
b = this_abs * np.sin(this_angle)
c = a + b*1j
print(c)

plt.figure(figsize=(6, 6))
plot_polar_vector(c, color='k')
plt.ylim([0, 1.5]);

$\overline{c} := a - bi$ is called the complex conjugate of $c = a + bi$. This means mirroring the point on the real axis.

In [ ]:
c = 1.5 + 0.8j
c_conj = np.conj(c)
print(c_conj)

plt.figure(figsize=(6, 6))

plot_polar_vector(c, '$c$', 'k')
plot_polar_vector(c_conj, r'$\overline{c}$', 'r')

plt.legend();

You sum two complex numbers by summing their real and imaginary part individually.

$$c_1 + c_2 = (a_1 + ib_1) + (a_2 + ib_2) := (a_1 + a_2) + i(b_1 + b_2).$$
In [ ]:
c1 = 1.5 + 0.8j
c2 = 1.5 + 0j
c = c1 + c2
print(c)

plt.figure(figsize=(6, 6))

plot_polar_vector(c1, '$c_1$', color='k', linestyle=':')
plot_polar_vector(c2, '$c_2$', color='k', linestyle='--')
plot_polar_vector(c, '$c_1 + c_2$', color='r')

plt.legend();

The geometric intuition of addition and substraction can be visualized by a parallelogram:

In [ ]:
c1 = 1 + 0.25j
c2 = -0.2 + 0.45j
c_add = c1 + c2
c_sub = c1 - c2
print(c_add)
print(c_sub)

plt.figure(figsize=(6, 6))

plot_polar_vector(c_add, start=c1, color='lightgray')
plot_polar_vector(c_add, start=c2, color='lightgray')
plot_polar_vector(c_sub+c2, start=c2, color='lightgray')

plot_polar_vector(c1, '$c_1$', color='k', linestyle=':')
plot_polar_vector(c2, '$c_2$', color='k', linestyle='--')
plot_polar_vector(c_add, '$c_1 + c_2$', color='r', linestyle='--')
plot_polar_vector(c_sub, '$c_1 - c_2$', color='r', linestyle=':')

plt.legend();

Multiplying two complex numbers is defined as follows:

$$c = c_1 \cdot c_2 = (a_1 + ib_1) \cdot (a_2 + ib_2) := (a_1a_2 - b_1b_2) + i(a_1b_2 + b_1a_2).$$

Geometrically this means adding their angles and multiplying their absolute values.

In [ ]:
c1 = 1.5 + 0.8j
c2 = 0.8 + 1.2j
c = c1 * c2
print(c)

plt.figure(figsize=(6, 6))

plot_polar_vector(c1, '$c_1$', color='k', linestyle=':')
plot_polar_vector(c2, '$c_2$', color='k', linestyle='--')
plot_polar_vector(c, '$c_1 \cdot c_2$', color='r')

plt.legend();

Each complex number $c = a + bi$ has an inverse $c^{-1}$. This means $c c^{-1} = 1$.

$$c^{-1} := \frac{a}{a^2 + b^2} + i \frac{-b}{a^2 + b^2}.$$
In [ ]:
c = 1.5 + 0.8j
c_inverse = 1 / c
c_times_inverse = c * c_inverse

plt.figure(figsize=(6, 6))

plot_polar_vector(c, '$c$', color='k', linestyle=':')
plot_polar_vector(c_inverse, '$c^{-1}$', color='r')
plot_polar_vector(c_times_inverse, '$c c^{-1}$', color='k', linestyle='--')

plt.legend();

With the inverse, division can be defined:

$$\frac{c_1}{c_2} = c_1 c_2^{-1} = \frac{a_1 + ib_1}{a_2 + ib_2} := \frac{a_1a_2 + b_1b_2}{a_2^2 + b_2^2} + i\frac{b_1a_2 - a_1b_2}{a_2^2 + b_2^2}.$$
In [ ]:
c1 = 1.5 + 0.8j
c2 = 0.8 + 1.2j
c = c1 / c2

plt.figure(figsize=(6, 6))

plot_polar_vector(c1, '$c_1$', color='k', linestyle=':')
plot_polar_vector(c2, '$c_2$', color='k', linestyle='--')
plot_polar_vector(c, '$c_1 / c_2$', color='r')

plt.legend();

An important function in this context is the (complex) exponential function $\exp:\mathbb{C}\to \mathbb{C}$ defined by the series:

$$\mathrm{exp}(c) = e^c := \sum_{n=0}^{\infty} \frac{c^n}{n!} = 1 + c + \frac{c^2}{1 \cdot 2} + \frac{c^3}{1 \cdot 2 \cdot 3} + \dots$$

for $c\in\mathbb{C}$.

Exercise 1
Implement a function that yields an approximation by considering a finite sum for $n \in \{0,\ldots,N-1\}$.
In [ ]:
def exp(c, N):
    pass  # your implementation here!

print(exp(1, 10))
print(np.exp(1))
Exercise 2
Check that $$\mathrm{exp(c_1 + c_2)} = \mathrm{exp(c_1)} \cdot \mathrm{exp(c_2)}$$ holds for some examples. Rather not use your approximation, but the function np.exp.
In [ ]:
# your code here
Exercise 3
Euler's formula gives an important relation between exponential, sine and cosine function: $$\mathrm{exp}(ic) = \cos(c) + i\sin(c).$$ Also check this for some examples.
In [ ]:
# your code here

By assigning a color to $c$, we can nicely visualize the correspondence, beween the exponential, sine and cosine functions.

In [ ]:
import matplotlib

cmap = matplotlib.cm.get_cmap('hsv') # hsv is nice because it is a circular color map

N = 90

fig = plt.figure(figsize=(6 * 3, 6))
ax1 = fig.add_subplot(1, 3, 1, projection='polar')
ax2 = fig.add_subplot(1, 3, 2)
ax3 = fig.add_subplot(1, 3, 3)

for i in range(N):
    arg = 2 * i / N
    c = np.exp(1j * np.pi * arg)
    color = cmap(i / N)
    ax1.plot([0, np.angle(c)], [0, np.abs(c)], color=color)
    ax1.plot(np.angle(c), np.abs(c), 'o', color=color)
    ax2.plot(arg, np.real(c), 'o', color=color)
    ax3.plot(arg, np.imag(c), 'o', color=color)
    
ax2.grid()
ax2.set_xlabel('$c$')
ax2.set_ylabel('$\mathrm{Re}(\exp(i c))$')
ax2.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("$%s\pi$")) 

ax3.grid()
ax3.set_xlabel('$c$')
ax3.set_ylabel('$\mathrm{Im}(\exp(i c))$')
ax3.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter("$%s\pi$")) 
plt.tight_layout()

Given a number $N \in \mathbb{N}^{>0}$, a complex number $\Omega \in \mathbb{C}$ is called a $N^\mathrm{th}$ root of unity if $\Omega^N = 1$. Furthermore, it is called a primitive $N^\mathrm{th}$ root of unity if $\Omega^n \neq 1$ for all $n\in [1:N-1]$. One can show that $\Omega_N:=\exp(-2 \pi i / N)$ (and also $\overline{\Omega_N}=\exp(2 \pi i / N))$ is a $N^\mathrm{th}$ primitive root of unity for a given $N \in \mathbb{N}^{>0}$.

In [ ]:
N = 8
omega = np.exp(-2j * np.pi / N)
omegapower = omega

plt.figure(figsize=(6, 6))
plot_polar_vector(omega, r'$\Omega_{%d} = \exp(-2 i \pi / %d)$' % (N,N), 'red')

for n in range(2, N+1):
    omegapower *= omega
    if np.isclose(omegapower, 1):
        plot_polar_vector(omegapower, r'$\Omega_{%d}^{%d} = 1$' % (N,n), 'black')
    else:
        plot_polar_vector(omegapower, color='lightgray')

plt.legend();