Copyright Allen Downey 2015, MIT license

This notebook is an example of a process for investigating math topics using Python. In this case, I follow the presentation in the Wikipedia page for complex numbers (http://en.wikipedia.org/wiki/Complex_number), translating the examples into Python.

There are a few ways to write complex numbers in Python. One option is the built-in function `complex`

:

```
In [56]:
```c = complex(1, 2)
c

```
Out[56]:
```

Also, the letter `j`

can be appended to any number type to indicate the imaginary unit:

```
In [57]:
```c = 1 + 2j
c

```
Out[57]:
```

Python uses `j`

to denote the complext unit, but you might also want to define a variable:

```
In [145]:
```i = complex(0, 1)
i

```
Out[145]:
```

```
In [146]:
```def func(x):
return (x+1)**2 == -9

We can confirm that 1 is not a solution:

```
In [148]:
```func(1)

```
Out[148]:
```

And that $-1 + 3i$ is:

```
In [147]:
```x = -1 + 3 * i
func(x)

```
Out[147]:
```

```
In [63]:
```y = -3.5 + 2j

In Python, these parts are accessible as attributes using dot notation:

```
In [64]:
```y.real

```
Out[64]:
```

```
In [65]:
```y.imag

```
Out[65]:
```

```
In [150]:
```z = 3 + 4j

`abs`

, computes the absolute value, or magnitude, of a complex number, which is the distance from the origin of the complext plane:

```
In [151]:
```abs(z)

```
Out[151]:
```

`cmath`

module provides `phase`

and other useful functions for working with complex numbers.

```
In [152]:
```from cmath import *

`phase`

returns the angle of the complex number in radians, relative to the real axis.

```
In [69]:
```phase(z)

```
Out[69]:
```

`polar`

expresses a complex number in polar form, returning a tuple of magnitude and angle:

```
In [70]:
```polar(z)

```
Out[70]:
```

```
In [71]:
```y == z

```
Out[71]:
```

```
In [72]:
```# raise TypeError: no ordering relation is defined for complex numbers
# y > z

```
In [73]:
```z.conjugate()

```
Out[73]:
```

```
In [74]:
```w = complex(12, 13)

And confirm that $\bar{z+w} = \bar{z} + \bar{w}$:

```
In [75]:
```(z + w).conjugate() == z.conjugate() + w.conjugate()

```
Out[75]:
```

Python knows how to compute the reciprocal of a complex number:

```
In [153]:
```1/z

```
Out[153]:
```

But we can also check to confirm that $1/z = \bar{z} / (x^2 + y^2)$:

```
In [77]:
```1 / z == z.conjugate() / (z.real**2 + z.imag**2)

```
Out[77]:
```

```
In [154]:
```i**2

```
Out[154]:
```

Now we can confirm the behavior of complex multiplication:

```
In [155]:
```a, b, c, d = 1, 2, 3, 4
(a + b*i) * (c + d*i) == (a*c - b*d) + (b*c + a*d)*i

```
Out[155]:
```

```
In [184]:
```from numpy import sign
# EXERCISE
z = 3 - 4*i
a, b = z.real, z.imag
real = sqrt(a + abs(z))
imag = sign(b) * sqrt(-a + abs(z))
root_z = complex(real, imag) / sqrt(2)
root_z

```
Out[184]:
```

```
In [185]:
```root_z**2 == z

```
Out[185]:
```

But you can check it by hand:

```
In [186]:
```root_z**2

```
Out[186]:
```

Or check that the difference is small:

```
In [187]:
```abs(root_z**2 - z)

```
Out[187]:
```

```
In [167]:
```phase(z)

```
Out[167]:
```

`phase`

can also be defined in terms of `arctan`

; the best way to compute it is with `math.atan2`

, which correctly figures out which quadrant the number is in:

```
In [168]:
```from math import atan2
atan2(z.imag, z.real)

```
Out[168]:
```

To convert from polar to Cartesian coordinates, you can use trigonometry:

```
In [169]:
```r, phi = polar(z)
r * complex(cos(phi), sin(phi))

```
Out[169]:
```

Or use `rect`

, which converts to rectangular coordinates.

```
In [171]:
```rect(r, phi)

```
Out[171]:
```

```
In [ ]:
```

```
In [188]:
``````
e
```

```
Out[188]:
```

Which we can use to compute complex exponentials (see the Exponentiation section):

```
In [191]:
```x = 2 # angle in radians
e**(i*x)

```
Out[191]:
```

`pi`

, we can test Euler's identity, $e^{i \pi} = -1$, one of the more exquisite results in mathematics:

```
In [194]:
```e**(i*pi)

```
Out[194]:
```

Or more generally, we can check Euler's formula:

```
In [193]:
```e**(i*x) == complex(cos(x), sin(x))

```
Out[193]:
```

```
In [90]:
```log_z = log(r) + phi*i
log_z

```
Out[90]:
```

Or using the `log`

function we imported from `cmath`

:

```
In [174]:
```log(z)

```
Out[174]:
```

```
In [ ]:
```

```
In [ ]:
```

Imaginary numbers turn out to be more useful than we have any reason to expect :) As an example, I'll demonstrate a method for analyzing circuits. Specifically, this circuit:

L is the inductance of an inductor in henry, C is the capacitance of a capacitor in farad, and R is the resistance of a resistor in ohms.

```
In [108]:
```R = 1000 # ohm
L = 0.001 # henry
C = 0.00001 # farad

You might already be familiar with Ohm's law, which you can use to compute the current through a resistor as a function of voltage: $I = V/R$.

Ohm's law can be generalized to handle resistors, capacitors, and inductors, by replacing R (which is a real value) with impedance (which is a complex number). For AC signals, impedance depends on the frequency of the signal, $f$, in Hz.

```
In [109]:
```f = 1000 # Hz
Z = R + 2*pi*i*f*L + 1 / (2*pi*i*f*C)

Assuming that the input is a 5V signal with frequency 1000 Hz:

```
In [110]:
```Vin = 5 # Volt

We can use Ohm's law to compute the current:

```
In [111]:
```I = Vin / Z
I

```
Out[111]:
```

And then use Ohm's law again to compute the voltage across the resistor, which is the output signal.

```
In [112]:
```Vout = I * R
Vout

```
Out[112]:
```

```
In [118]:
```abs(Vout)

```
Out[118]:
```

```
In [119]:
```phase(Vout)

```
Out[119]:
```

The phase shift is quite small, so there is only a short delay between the peak of the input signal and the peak of the output signal.

We might be interested to see how this relationship changes for different frequencies. I'll use `numpy`

to make an array of values from $f = 10^0 = 1$ to $f = 10^6$ Hz.

```
In [176]:
```import numpy as np
fs = np.logspace(0, 6, 20, base=10)
fs

```
Out[176]:
```

```
In [132]:
```zs = R + 2*pi*i*fs*L + 1 / (2*pi*i*fs*C)

And we can use Ohm's law again to compute an array of phasors.

```
In [133]:
```v_outs = Vin / zs * R
v_outs

```
Out[133]:
```

Again, the magnitudes encode the amplitude of the output signal (in volts) for each frequency:

```
In [139]:
```magnitudes = abs(v_outs)
magnitudes

```
Out[139]:
```

And the angles contain the phase offsets (in radians):

```
In [140]:
```offsets = np.angle(v_outs)
offsets

```
Out[140]:
```

I'll use matplotlib to plot the results.

```
In [180]:
```import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(fs, magnitudes)
plt.xscale('log')
plt.xlabel('frequency (Hz)')
plt.ylabel('amplitude (V)')
plt.ylim(0, 5.2)

```
Out[180]:
```

Between $10^2 = 100$ Hz and $10^4 = 10$ kHz, the output amplitude is close to 5V. So this circuit "passes" signals in this frequency band (which is roughly the range of audible frequencies). Signals with lower and higher frequencies get attenuated, or "stopped". That's why this circuit is called a "bandpass filter".

The phase offset also depends on the input frequency.

```
In [181]:
```plt.plot(fs, offsets)
plt.xscale('log')
plt.xlabel('frequency (Hz)')
plt.ylabel('phase offset (radian)')
plt.ylim(-pi/2, pi/2)

```
Out[181]:
```

In the pass band, the phase offset is small, so there is not much delay between the peak of the input and the peak of the output. For high frequencies, the offset approaches $\pi/2$, so the output peaks 90 degrees late. For very low frequencies, the output peaks 90 degrees early (or 270 degrees late, if you prefer).

So that's a pretty neat bit of analysis, considering that all we used was complex multiplication and division.

```
In [ ]:
```