본문 바로가기

카테고리 없음

PTF density 문제

여행은 언제나 예상치 못한 길을 따라 계속된다. 나는 애초에 재미있는 수학퀴즈영상 하나를 만들려 했다. 한 2주정도 소요될거라 예상하고 시작했지만, 그 과정속에서 ‘tetration’이라는 연산에 대한 놀라운 성질들을 알게 되었다. 그렇게 나의 수학math 여행은 2년 넘게 이어지고 있다.

 

tetration에서 가장 흥미로운건 단연 ‘Power Tower Fractal 이하 PTF’ 이다. 나는 무한층의 tetration이 만들어내는 PTF을 확대하는 쇼츠영상을 하나 만들었는데, 당시 coding을 하면서 겪은 한가지 문제점이 있었다. 최근 그 coding을 다시 들여다보며 간단하지만 매우 중요한 지점을 발견했는데, 이번 포스팅에서는 그에 대해 간단히 설명해보려 한다.

 

쇼츠영상에서 쓰인 코드는 대략 다음과 같다[1] :

 

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap


x0 = 0
y0 = 0
eps = 3
eps_y = eps * (9/16)  # 16:9 비율에 맞추기 위해 y축 eps 계산


def compute_tetration_divergence(nx, ny, max_iter, escape_radius):
   x = np.linspace(x0 - eps, x0 + eps, nx)
   y = np.linspace(y0 - eps_y, y0 + eps_y, ny)
   c = x[:, np.newaxis] + 1j * y[np.newaxis, :]


   divergence_map = np.zeros_like(c, dtype=bool)


   for i in range(nx):
       for j in range(ny):
           c_val = c[i, j]
           z = c_val


           for k in range(max_iter):
               z = c_val ** z
           if np.abs(z) > escape_radius:
               divergence_map[i, j] = True


   return divergence_map


n = 1000
nx, ny = int(n*(9/16)), n
max_iter = 500
escape_radius = 1e+10


divergence_map = compute_tetration_divergence(nx, ny, max_iter, escape_radius)


# 커스텀 컬러맵 생성: 발산은 흰색, 수렴은 검은색
cmap = LinearSegmentedColormap.from_list("custom_cmap", ["black", "white"])


plt.imshow(divergence_map.T, extent=[x0 - eps, x0 + eps, y0 - eps_y, y0 + eps_y], origin='lower', cmap=cmap)
plt.axis('off')  # 축 라벨과 타이틀 제거
plt.savefig('tetration_divergence_bw.png', dpi=600, bbox_inches='tight', pad_inches=0)
plt.show()

sample result :

 

설명을 위해 rendering한 위 sample image가 왜 실수축에 대해 대칭적이지 않은지 잘 모르겠는데[2], 그 부분은 그냥 넘어가자. 그건 요점이 아니다. 중요한건 이미지의 ‘밀도’가 너무 낮다는 것이다.

 

실수축에서 \(  ^{\infty} x  \)는 \( x \)가 \( e^{\frac{1}{e}} \) 이상 일때 발산한다[3]. 즉, 위 실수축의 일정값 이상에선 하나의 연속된 선 그려져야 한다는 것이다. 하지만 결과는 그렇지 않고, 이는 코드에 뭔가 이상이 있다는 뜻이다. 이를 통해, 다른 영역에서 흰색의 밀도가 낮아보이는것 또한 그런 문제로 인한 것이란걸 예상 할 수 있다.

 

위 코드의 어떤 문제가 그런 결과로 연결됬는지는, 난 아직 잘 모르겠다. 지금까지 나의 주된 목표는, 적어도 정확히 알 수 있는 양의 실수축에서의 수렴/발산결과를 reproduce하는 PTF image를 얻는것이었다. 그리고 다음과 같은 방법으로 이를 해결했다 :

 

수정전 :
for k in range(max_iter):
    z = c_val ** z
if np.abs(z) > escape_radius:
    divergence_map[i, j] = True
수정후 :
for k in range(max_iter):
    z = c_val ** z
if np.abs(z) > escape_radius:
    divergence_map[i, j] = True
    break

sample result :

 

그러니까, max_iter 만큼의 tetration 연산을 하고 그 복소수의 크기를 escape_radius와 비교하여 수렴/발산을 판단하는 것이 아니라, 그 비교를 for루프가 돌때마다 하면서 복소수 크기가 escape_radius 보다 크면 계산을 중단하게 했고, 그랬더니 density 문제가 해결된 것이다.

 


 

 

하는김에, 내가 이것저것 해보면서 알게된 사실 한두가지를 더 남겨보려 한다. 한가지는, 위와 같은 library — 즉, numpymatplotlib로 짠 코드 하에서는 안정적으로 확대 할 수 있는 최대 scale이 10-13 order 정도라는거다.

 

PTF fractal은 아니지만, \( \sqrt{2} \)에 대한 무한 tetration에 대해서도 비슷한 프랙탈 구조가 관찰된다. 이에 대한 확대영상을 만든적이 있는데, 이 마지막 부분을 보면 이미지가 ‘픽셀화’되고 zoom animation도 불안정해진다. 이는 내가 해본 다른 모든 tetration 관련 프랙탈에 대한 공통된 현상인데, 아마도 numpy가 숫사를 처리하는 방식자체에서 비롯한 문제인걸로 보인다. 10-13 보다 더 확대하려면 보다 더 정밀하게 계산 할 수 있는 library를 이용하는 등 - 방법을 찾아야 할걸로 보인다.

 

PTF의 ‘빽빽함’은 변수 max_iter에 의존하는데, 특히나 확대해 들어갈 수록 그 의존도가 높아진다. 예를들어, 10-13 scale까지 확대했을때 max_iter 대한 dependence를 다음과 같이 확인 해 볼 수 있다 :

 

  • (x0,y0) = (0.43137999995, 0.895038)
  • eps = 10-13

max_iter=500

max_iter=1,000

 

당연히 max_iter가 클수록 무한 tetration에 가깝다. 하지만 그렇게 되면 computing 시간이 그만큼 늘어난다는 문제가 생긴다. 경험상, 10-5 정도의 low scale에서는 max_iter500정도되면, 그 이상의 값과 별 차이가 나지 않았던것 같다. 30초 이상의 긴 확대영상을 만들기 위해선 FHD이상 화질로 500 frame 넘게 rendering 해야 하는데, 이런 경우엔 1~2주 이상 컴퓨터를 돌려야 한다. 이때 만약 max_iter를 두배로 늘린다면 그 기간은 2~4주로 늘어날것이다.


[1] 설명을 위해, 개선된 코드를 기준으로 당시 실제로 돌린 코드에 문제가 된 부분을 거꾸로 집어넣었다.

[2]복소수  \(  ^{\infty} z  \)의 크기는 $ ^{\infty} \left( z^{*} \right)$의 크기와 같다. 이런 정확한 해석적 성질은 PTF은 실수축에 대해 정확히 대칭이어야 함을, 또 위 샘플코드에 만가 문제가 있음을 말해준다.

[3] 양의 실수에 대한 power tower 함수 그래프가 어떻게 그려지는지는 다음 포스팅을 참조 바란다 : infinite power tower 함수의 그래프를 그려보자