나비효과와 기상예보 | 작은 변화가 어떻게 큰 파장을 만드는가?

2023. 3. 31. 15:22Ray 수학

이미지를 클릭하면 영상으로 이동합니다

브라질에 있는 나비의 날개짓이 텍사스에서 폭풍우를 발생시킬 수 있을까요? 나비효과는 미세한 변화나 사소한 사건이 예상하지 못한 큰 결과나 파장으로 이어지는 현상을 말합니다. 그런데 나비의 날갯짓은 에너지가 적은 반면 허리케인의 에너지는 매우 크므로 에너지 보존법칙에 따르면 일어날 수 없다고 생각이 들 수 있습니다. 과연 그럴까요?

 

나비효과란 무엇인가?

대기권의 대류를 단순한 모델로 만들어 보려던 기상학자 에드워드 로렌츠는 컴퓨터를 이용해 시뮬레이션을 돌리고 있었습니다. 같은 작업을 반복하던 그는 어느날 중간에 컴퓨터가 멈췄을 때 오류를 없애기 위해 수치를 일일히 수작업으로 입력해 다시 시뮬레이션을 돌렸습니다. 그런데 얼마 지나지 않아 기존의 시뮬레이션과는 결괏값이 크게 차이가 나는 것을 발견했습니다.

 

그 이유를 분석한 로렌츠는 특별한 사실을 발견합니다. 컴퓨터는 소수점 아래 여섯자리까지 계산을 하는 반면 소수점 아래 세자리에서 반올림한 값을 화면에 출력했는데 중간에 소수점 세자리에서 반올림 된 값을 재입력하고 시뮬레이션을 돌리니 오차가 생겼던 거죠. 예를들어 $\sqrt2 \times {{1}\over{\sqrt2}}=1$이지만, 이를 소수점 아래 셋째자리에서 반올림해서 계산하면 $\sqrt2=1.41$이고, ${{1}\over{\sqrt2}}=0.71$이므로 $1.41 \times 0.71 =1.0011$인 것처럼 말이죠. 소수점 세자리까지 계산하면 오차가 없는것 같지만 사실 오차가 조금씩 쌓이게되죠. 로렌츠 시스템은 단순한 곱셈이 아닌 세개의 비선형 상미분 방정식으로 이루어져 있어 이 오차는 더욱 복잡하게 증폭되었습니다.

 

기상예보는 왜 틀릴 수 밖에 없는가?

앞서 본 결과는 사람들을 당황스럽게 했습니다. 사람들은 기상을 예측하는 모델이 너무 단순해서 기상을 정확히 예측하지 못한다고 생각했는데, 사실은 단순한 모델조차도 예측할 수 없는 움직임을 보일 수 있다는 것을 알게 되었기 때문이죠.

 

기상 예보는 현재 날씨에 대한 정보를 토대로 만들 수 있습니다. 온도, 습도, 기압, 풍속 심지어 해류나 지형도 기상예보에 영향을 끼칠 수 있죠. 그런데 현재 날씨를 정확히 측정할 수 있을까요? 현재의 온도나 습도, 기압을 아무리 정확하게 측정한다 하더라도 반드시 오차는 존재합니다. 그리고 그러한 오차값은 구성 요소 간에 무수한 상호 작용이 있는 카오스 시스템 의해 점점 증폭되어 큰 날씨의 변화를 일으킬 수 있습니다. 따라서 먼 날짜의 예보의 경우 더더욱 예측하기 어려워지죠.

 

나아가 대기의 소규모 변화는 전 세계적으로 지속적으로 발생하고 있습니다. 이러한 변화를 하나의 초기값에 대해 하나의 결괏값을 내놓는 결정론적인 물리공식으로 설명하기 위해서는 예측 정확도를 개선하기 위해 많은 데이터가 필요하므로 컴퓨터의 발전과 기상예보 모델의 개선이 필요합니다.

 

왜 비는 확률적으로 오는가?

기상예보 모델은 하나의 결괏값을 갖는 결정론적 모델인데 반해 왜 기상예보는 확률을 사용할까요? 예를 들어 기상 예보에서는 ‘오늘 오후에 40%의 확률로 비가 올 것으로 보인다.’ 라는 표현을 사용합니다. 비는 오늘 오후에 오거나 오지 않거나 둘 중 하나입니다. 슈레딩거 고양이마냥 비가 오거나 오지않는 게 중첩되는 상황을 볼 수는 없죠. 미래에는 반드시 어떤 한 사건이 일어날텐데 기상예보에서는 왜 확률을 사용하는 것일까요?

 

앞서 말했듯 기상예보를 할 때 사용하는 방정식은 결정론적이기 때문에 반드시 하나의 결괏값만을 내놓습니다. 하지만 현재의 정보를 토대로 미래를 예측하는 방법은 불확실성이 높죠. 그래서 기상 예보에서는 이러한 오차를 오히려 이용하는 색다른 방법을 제안합니다. 바로 ‘앙상블 예측’이죠. 앙상블은 물리학에서 유사한 시스템 집합을 나타내는 용어입니다. 기상예보의 경우 초기값에 오차가 있음을 인정하고 약간 다른 초기 조건 또는 다른 모델을 사용하여 여러개의 예보 집합을 생성합니다.

 

예를 들어보죠. 오늘 아침에 온도가 20도로 측정되어 오후 날씨에 대한 시뮬레이션을 돌리면 날씨가 맑다는 결과가 나온다고 하겠습니다. 그런데 20도라는 온도는 정확한 온도가 아닐 수 있습니다. 만약 오차가 1도 정도 있다면 현재 온도는 사실 19도 19.5도, 20도, 20.5도, 21도 모두 후보가 될 수 있는거죠. 그래서 이 모든 경우에 대하여 시뮬레이션을 돌려 결괏값을 구하면 하나의 날씨 집합이 만들어 집니다. 날씨 집합의 원소 5개 중 3개는 맑음, 2개는 비이므로 ‘40%의 확률로 비가 올 것으로 보인다.’라는 예보를 하는 거죠.

 

현실에서는 현재 대기 상태를 반복적으로 관찰하는 대신 일련의 관찰을 수행하고 예측 시뮬레이션을 실행합니다. 그런 다음 데이터를 임의로 약간 변경하고 시뮬레이션을 반복하죠. 컴퓨터의 발달로 이러한 작업은 한, 두시간정도면 충분히 많은 예측 데이터들을 만들 수 있습니다. 결과적으로 날씨집합의 원소의 개수는 증가하고 더 정확한 확률을 추정할 수 있게 되는거죠.

 

마무리하며

나비의 날개짓이 주변의 공기 분자에게 일부 운동 에너지를 전달한다고 해보겠습니다. 이로 인해 공기압과 온도에 작은 교란이 생기고, 이 교란은 파동으로서 대기 중에 전파됩니다. 파동은 다른 파동과 상호작용하며, 그 효과를 증폭시키거나 감소시킬 수 있습니다. 시간이 지남에 따라, 이러한 상호작용은 날씨 패턴과 기후에 중요한 변화를 초래할 수 있죠. 결과적으로 나비 효과는 에너지 보존 법칙을 위반하지 않을 수 있습니다. 오히려 대기가 작은 교란에 얼마나 민감한지와 그 동역학이 얼마나 비선형적이고 혼돈적인지를 보여준다고 말할 수 있는거죠. 기상예보는 그 혼돈 속에서 최적의 결과를 제공하는 방향으로 발전해가고 있습니다. 만약 이러한 혼돈과 오차마저 수학이 해결할 수 있는 날이 온다면 그때는 날씨를 정확하게 맞출 수 있지 않을까요?

 

Lorenz system 애니메이션 python 코드

import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation

# Define Lorenz system parameters
sigma = 10
rho = 28
beta = 8/3

# Define Lorenz system
def lorenz(t, y):
    y1, y2, y3 = y
    return [sigma*(y2-y1), y1*(rho-y3)-y2, y1*y2-beta*y3]

# Define initial conditions
y0 = [0, 1, 0]

# Define time span and time step
t_span = [0, 100]
t_step = 0.003

# Solve Lorenz system using solve_ivp
sol = solve_ivp(lorenz, t_span, y0, t_eval=np.arange(t_span[0], t_span[1], t_step))

# Create figure and axis for animation
fig = plt.figure(figsize=(12,9))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(-25, 25)
ax.set_ylim(-25, 25)
ax.set_zlim(0, 50)
ax.set_facecolor((0.1, 0.9, 0.2))  # set background color to green

# Define function to update animation
def update(i):
    ax.clear()
    ax.set_xlim(-25, 25)
    ax.set_ylim(-25, 25)
    ax.set_zlim(0, 50)
    ax.set_title("Lorenz system simulation")
    ax.set_facecolor((0.1, 0.9, 0.2))  # set background color to green
    ax.plot(sol.y[0, :i*20], sol.y[1, :i*20], sol.y[2, :i*20], color="orange")
    return ax

# Create animation
animation = FuncAnimation(fig, update, frames=1000, interval=10, blit=False)

# Show animation
plt.show()

 

 

Rossler attractor 애니메이션 python 코드

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D
from scipy.integrate import solve_ivp

def rossler_system(t, xyz, a, b, c):
    x, y, z = xyz
    return [
        -y - z,
        x + a * y,
        b + z * (x - c)
    ]

def update_plot(num, data, line):
    line.set_data(data[:2, :num])
    line.set_3d_properties(data[2, :num])
    return line,

def animate_rossler_attractor(a, b, c, initial_conditions, t_span, num_points):
    t_eval = np.linspace(t_span[0], t_span[1], num_points)
    solution = solve_ivp(rossler_system, t_span, initial_conditions, args=(a, b, c), t_eval=t_eval)
    data = solution.y

    fig = plt.figure(figsize=(16, 9))
    ax = fig.add_subplot(111, projection='3d')
    ax.set_facecolor('lightgreen')

    # Calculate axis limits and set them
    x_min, x_max = np.min(data[0]), np.max(data[0])
    y_min, y_max = np.min(data[1]), np.max(data[1])
    z_min, z_max = np.min(data[2]), np.max(data[2])
    x_range = 5
    y_range = 5
    z_range = 10
    ax.set_xlim(x_min - x_range, x_max + x_range)
    ax.set_ylim(y_min - y_range, y_max + y_range)
    ax.set_zlim(z_min - z_range, z_max + z_range)

    line, = ax.plot(data[0, 0:1], data[1, 0:1], data[2, 0:1], lw=1, color='navy')

    ax.set_title("Rossler Attractor")

    ani = FuncAnimation(fig, update_plot, num_points, fargs=(data, line), interval=1, blit=True)
    plt.show()

if __name__ == "__main__":
    a = 0.2
    b = 0.2
    c = 5.7
    initial_conditions = [0, 1, 0]
    t_span = (0, 100)
    num_points = 10000

    animate_rossler_attractor(a, b, c, initial_conditions, t_span, num_points)