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.
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.
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.
%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).$$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:
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}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.
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).$$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:
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.
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}.$$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}.$$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}$.
def exp(c, N):
pass # your implementation here!
print(exp(1, 10))
print(np.exp(1))
# your code here
# your code here
By assigning a color to $c$, we can nicely visualize the correspondence, beween the exponential, sine and cosine functions.
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}$.
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();