코딩 더 매트릭스 - 2장 필드 (1)

복소수 연산을 이용한 복소 평면과 오일러 공식의 적용

2018년 11월 17일

필립 클라인의 저서 코딩 더 매트릭스 2장 필드 중 복소수


  1. 복소수와 복소 평면을 알아봅니다.
  2. 복소수의 연산이 복소 평면에서 어떻게 표현되는지 알아봅니다.
  3. 오일러의 공식을 이용해 복소수를 편각으로 표현하고 복소 평면에 표현해 봅니다.





복소수

모든 실수에 대해 x2=1x^2 = -1인 경우는 없습니다. 수학자들은 이 문제를 해결하기 위해 -1의 제곱근 ii를 도입합니다. 그리고 어떤 실수에 ii를 곱한 수를 허수(imaginary number)라고 부릅니다. 복소수는 허수와 실수의 합, 예를 들면 1+3i1 + 3i 같은 수입니다. 고등학교에서 배운 수학을 조금 더 떠올려 보면 다음과 같은 식도 있었습니다. (1+3i)(13i)=10(1 + 3i)(1 - 3i) = 10 1+3i1 + 3i13i1 - 3i는 서로 켤레 복소수입니다. Python에서 허수 ii1j와 같이 씁니다. 예를 들어 -9의 제곱근 3i3i3j라 씁니다. 아주 간단한 연산을 예를 들면

>>> (1 + 3j) + (10 + 20j)
(11 + 23j)
>>> x = 1 + 3j
>>> (x - 1) ** 2
(-9+0j)
>>> y = 1 - 3j
>>> x * y
(10+0j)

복소수의 덧셈, 뺄셈은 실수는 실수끼리 허수는 허수끼리 연산을 수행합니다. 곱셈은 인수분해된 두 일차식을 곱해 2차식을 만들듯 각항의 곱들을 더한 값입니다. e.g.(1+3i)(2i)=2i+6i3i2=5+5ie.g. (1 + 3i)(2 - i) = 2 - i + 6i - 3i^2 = 5 + 5i 복소수의 나눗셈은 제곱근을 포함하는 유리수 분모의 실수화와 비슷합니다. 112\frac{1}{1- \sqrt{2}}를 계산하기 위해 분모, 분자에 1+21 + \sqrt{2}를 곱하는 것처럼 복소수의 경우 아래와 같이 e.g.1+2i3+4i=(1+2i)(34i)(3+4i)(34i)=11+2i25e.g. \frac{1 + 2i}{3 + 4i} = \frac{(1 + 2i)(3 - 4i)}{(3 + 4i)(3 - 4i)} = \frac{11 + 2i}{25} 분모를 실수화해 계산합니다.





복소수 필드 C\Bbb{C}

복소수 필드 또는 복소 평면은 복소수가 실수부와 허수부 두 개의 독립된 변수로 이루어 진다는 점을 이용해 만들어집니다. 복소 평면의 xx축은 실수부, yy축은 허수부입니다.

Python에서는 변수 z가 복소수일때 z.real z.imag로 실수부와 허수부를 분리합니다. 실제 좌표계를 띄워 눈으로 보고 이해하는게 가장 좋을 것 같습니다. 코딩 더 매트릭스에서는 소스파일을 제공해 독자들의 실습을 돕고 있습니다. 좌표를 직접 띄울 수 있도록 plotting.py모듈 파일을 제공하니 유용하게 쓸 수 있을 것 같습니다. 다음 코드는 plotting.py의 코드 중 일부입니다.

for pt in L:
	if isinstance(pt, Number):
		x,y = pt.real, pt.imag
	else:
		if isinstance(pt, tuple) or isinstance(pt, list):
			x,y = pt
		else:
			raise ValueError
	h.writelines([
		'<circle cx="%d" cy="%d" r="%d" fill="red"/>\n'
		% (origin[0]+scalar*x,origin[1]-scalar*y,dot_size)
	])
h.writelines(['</svg>\n</body>\n</html>'])

리스트로 제공될 복소수들을 HTML의 svg 태그로 매핑하는 반복문입니다. 함수를 호출하면 브라우저 화면에 좌표축을 그리고 전달한 복소수의 리스트를 점으로 표시하게 됩니다. 예를 들어 다음과 같은 복소수 리스트 S={2+2i,1.5+1i,2+1i,2.5+1i}S = \{2 + 2i, 1.5 + 1i, 2 + 1i, 2.5 + 1i\}는 좌표상에 아래와 같이 그려집니다.

L = { 2 + 2j, 1.5 + 1j, 2 + 1j, 2.5 + 1j }
plot(L)





복소수의 절대값

복소수의 절대값이란 복소 평면의 원점에서의 거리를 말합니다. 2차원 벡터의 거리처럼 피타고라스의 정리로 구할 수 있습니다. 표기는 z\mid z\mid, Python에서는 abs(z)로 표현합니다. 아래 그림에서는 파란 화살표의 길이가 절대값입니다. z2=(z.real)2+(z.imag)2\mid z\mid^2 = (z.real)^2 + (z.imag)^2

켤레복소수를 알면 제곱을 쓰지않고 복소수의 절대값을 구할 수 있습니다. 허수 i2i^2이 -1이라는 성질을 이용하면 됩니다. z=a+biz = a + bi일때 다음 식이 성립합니다. z2=a2+b2=(a+bi)(abi)\mid z\mid ^2 = a^2 + b^2 = (a + bi)(a - bi)





복소수 연산과 필드

S={2+2i,1.5+1i,2+1i,2.5+1i}S = \{2 + 2i, 1.5 + 1i, 2 + 1i, 2.5 + 1i\} 각각의 요소에 1+2i1 + 2i를 더하면 복소 평면에서 어떻게 그려질까요? 복소수의 덧셈은 좌표에서 평행이동 변환을 나타냅니다.

L = { 2 + 2j, 1.5 + 1j, 2 + 1j, 2.5 + 1j }
plot({ x + (1 + 2j) for x in L })

원본 SS와 평행이동 S+(1+2i)S + (1 + 2i)



벡터에 스칼라곱을 하듯 복소수에 실수를 곱하면 크기 변환을 할 수 있습니다. 복소수 z=a+biz = a + bi에 2를 곱하면 각각의 점들은 원점과 서로에게 두배 멀어지고 12\frac{1}{2}를 곱하면 두배 가까워집니다. 만약 곱하는 실수가 음수라면 180도 회전 후 스케일링한 결과를 보입니다.

plot({ x * 0.5 for x in L })
plot({ x * -0.5 for x in L })

원본 SS와 0.5 스케일링, -0.5 스케일링



평행이동과 스케일링은 벡터와 동일한 연산으로 나타낼 수 있지만 복소수에 한해 허수 ii를 곱함으로서 90도 회전을 나타낼 수 있습니다. 이 성질은 90도 회전이 (x,y)(y,x)(x, y) \to (-y, x) 라는 점을 이용한 것입니다. x+yix + yiii를 곱하면 y+xi-y + xi가 됩니다. 이는 복소 평면에서 x,yx, y가 서로 바뀐 후 xx1-1을 곱한 것과 같습니다. 90도 회전 변환을 나타내기 위해 예시로 이미지를 사용해 보겠습니다. 아래 코드는 코딩 더 매트릭스 소스파일에서 제공하고 있는 파일을 흑백 명암처리하는 프로시저입니다.

def get_pts():
    data = file2image('img01.png')
    pts = {
        (x + y*1j) * 0.02                       # 이미지의 크기를 plot 함수의 좌표 범위에 조정 (* 0.02)
        for y, row in enumerate(reversed(data)) # 상하 반대
        for x, rgb in enumerate(row)
        if rgb[0] < 256 * 0.5                   # 128 미만은 검은색, 이상은 흰색
    }
    return pts

S = get_pts()
plot(S)

90도 회전 변환을 위해 원래 수에 ii곱 매핑하는 컴프리헨션을 이용합니다.

plot({x * 1j for x in S})
90도 회전된 이미지





복소 평면에서의 편각

복소 평면에서 단위원에 대한 호도를 편각이라 합니다. 실수부축 절편 1과 -1에서 각각 0, π\pi, 허수부축 절편 1, -1에서 각각 12π\frac{1}{2}\pi, 32π\frac{3}{2}\pi입니다. 오일러는 복소수 zz를 편각으로 표현하는 공식을 만들었습니다. z=eθiz = e^{\theta \cdot i}1+0i-1 + 0i의 편각은 π\pi입니다. π\pi를 오일러의 공식에 대입하면 eπi=1e^{\pi \cdot i} = -1을 얻습니다. 아래 코드는 eπie^{\pi \cdot i}를 Python으로 계산한 결과입니다.

>>> from math import pi, e
>>> e**(pi * 1j)
(-1+1.2246467991473532e-16j)

매우 작은 허수부를 고려할때 결과가 -1임을 보여줍니다. 다음 코드는 θ\theta값을 0에서 14\frac{1}{4}씩 더해가며 복소수 집합을 만들고 이를 좌표에 나타냅니다.

from math import pi, e

plot({ e ** (1j * pi * (1 / 4) * r) for r in range(0, 8)})

오일러의 공식을 이용해 단위원 뿐만 아니라 복소 평면 위의 모든 점을 편각으로 표현할 수 있습니다. 복소 평면 위의 임의의 점 zz가 있을때 원점에서 zz까지의 선분이 단위원과 만나는 지점, zz'가 있습니다. 원점에서 zz까지의 거리가 rr일때 zzzz'의 원점으로부터 거리의 비율은 r:1r : 1입니다. 따라서 z=rzz = r z'이라 쓸 수 있습니다. 한편 zz'은 단위원 위의 점이므로 오일러 공식에 의해 z=eθiz' = e^{\theta i}입니다. 정리하면 다음과 같은 간단한 식을 쓸 수 있습니다. z=reθiz = re^{\theta i} 여기서 rrθ\thetazz의 극좌표라 합니다.

복소 평면 위의 임의의 점 z=reθiz = re^{\theta i}가 있을때 이 점을 τ\tau만큼 회전시킨다면 다음과 같이 쓸 수 있습니다.

re(θ+τ)i=reθieτi=zeτi\begin{aligned} re^{(\theta + \tau) i} & = re^{\theta i}e^{\tau i} \\ & = ze^{\tau i} \end{aligned}

위의 식을 이용해 앞서 복소 평면에 그려봤던 이미지를 π4\frac{\pi}{4}만큼 회전시켜 봅시다.

def rotate(pt, radian):
    return pt * e **(radian * 1j)

S = get_pts()
plot({rotate(x, pi / 4) for x in S})

만약 이미지를 π4\frac{\pi}{4} 만큼 회전시킨 후 12\frac{1}{2}배 스케일링, 평행 이동해 이미지의 중심이 원점으로 가게 하려면 어떻게 해야 할까요? 각각의 목적이 독립적이므로 각각을 함수로 작성하면 좋을 것 같습니다.

from math import e

def rotate(pt, radian):
    return pt * e **(radian * 1j)

def scale(pt, num):
	return pt * num

def move(pt, c_num):
	return pt + c_num

이미지의 중심이 원점으로 가야한다면 현재 이미지의 중심을 알아야 합니다. 중심은 각각의 축에 대해 최대값과 최소갑을 더한 후 2로 나누면 구할 수 있습니다. Python의 내장함수 maxmin을 이용하면 좋습니다.

middle_of_real = (max(reals) - min(reals)) / 2
middle_of_imag = (max(imags) - min(imags)) / 2

이미지의 중심 좌표를 각각의 점에서 빼면 원점으로 이동합니다. 각각의 함수를 합성해 복소수의 집합을 새롭게 구성합니다.

from math import pi

S = get_pts()		# origin
S_result = { rotate(scale(move(x,  -1 * (middle_of_real + middle_of_imag * 1j)), 1/2), pi / 4) for x in S }
plot(S_result)