# 에너지 보존 오류 디버깅

물리엔진을 구현하면서 종종 에너지가 보존되지 않는 상황에 직면할 수 있다. 이는 물리 엔진의 정확성과 신뢰성에 심각한 문제를 야기할 수 있다. 여기서는 그런 오류를 디버깅하고 해결하는 방법에 대해 자세히 알아보겠다.

#### 에너지 보존 원리

에너지 보존은 물리 엔진의 핵심 원리 중 하나로, 닫힌 시스템 내에서 총 에너지는 시간이 지나도 일정하게 유지되어야 한다는 개념이다. 이는 운동 에너지, 위치 에너지, 그리고 만약 시스템이 갖고 있다면 열 에너지와 같은 모든 형태의 에너지를 합산한 결과이다. 아래는 운동 에너지와 위치 에너지를 표현하는 대표적인 수식이다.

* 운동 에너지 (Kinetic Energy, $E\_k$):

$$
E\_k = \frac{1}{2} m \mathbf{v} \cdot \mathbf{v}
$$

여기서 $m$은 질량, $\mathbf{v}$는 속도 벡터이다.

* 위치 에너지 (Potential Energy, $E\_p$):

$$
E\_p = m g h
$$

여기서 $m$은 질량, $g$는 중력 가속도, $h$는 높이이다.

#### 에너지 보존 오류의 주요 원인

에너지 보존 오류가 발생하는 주요 원인은 다음과 같다:

**시간 통합 방법**

시간 통합(time integration) 방법은 시뮬레이션의 정확성에 큰 영향을 미친다. 보통 사용되는 오일러 방식(Euler method)은 계산이 빠르지만, 에너지가 쉽게 소실되는 문제점이 있다.

* 오일러 방식:

$$
\mathbf{x}(t + \Delta t) = \mathbf{x}(t) + \Delta t \mathbf{v}(t)
$$

$$
\mathbf{v}(t + \Delta t) = \mathbf{v}(t) + \Delta t \mathbf{a}(t)
$$

* 반면, 보다 정확한 방법인 반사각 방법(Symplectic integrator)이나 루킹 방법(Verlet integrator)은 에너지 보존 특성이 뛰어난다.

**충돌 처리**

충돌 처리 시 충격에 의한 에너지 손실이 발생할 수 있다. 이를 방지하기 위해서는 탄성 충돌(elastic collision)과 비탄성 충돌(inelastic collision)을 제대로 모델링해야 한다.

* 탄성 충돌 공식:

$$
\mathbf{v}\_1' = \mathbf{v}\_1 + \frac{2 m\_2}{m\_1 + m\_2} \left( \mathbf{v}\_2 - \mathbf{v}\_1 \right) \cdot \hat{\mathbf{n}} \hat{\mathbf{n}}
$$

$$
\mathbf{v}\_2' = \mathbf{v}\_2 + \frac{2 m\_1}{m\_1 + m\_2} \left( \mathbf{v}\_1 - \mathbf{v}\_2 \right) \cdot \hat{\mathbf{n}} \hat{\mathbf{n}}
$$

여기서 $\mathbf{v}\_1$과 $\mathbf{v}\_2$는 충돌 전 속도 벡터, $\mathbf{v}\_1'$과 $\mathbf{v}\_2'$는 충돌 후 속도 벡터, $\hat{\mathbf{n}}$는 충돌 노멀 벡터, $m\_1$과 $m\_2$는 질량이다.

#### 디버깅 절차

디버깅 할 때는 다음과 같은 절차를 따르는 것이 좋다:

1. **에너지 측정**: 시간에 따라 시스템의 전체 에너지를 측정하고 기록한다.
2. **패턴 분석**: 에너지가 증가하거나 감소하는 패턴을 찾는다.
3. **의심 코드 검토**: 에너지 소실이나 증대를 설명 할 수 있는 코드 부분을 검토한다. 특히 힘 계산, 충돌 처리, 시간 통합 부분을 집중적으로 확인한다.
4. **수정 및 테스트**: 수정한 부분을 다시 테스트하면서 전체 에너지가 보존되는지를 확인한다.

#### 에너지 보존 디버깅 예제

다음은 에너지 보존 오류를 디버깅하기 위한 간단한 코드 예제이다. 이 예제에서는 물체의 위치와 속도를 업데이트하고, 각 단계에서 에너지를 계산하여 출력한다.

```python
import numpy as np

m = 1.0  # 질량
g = 9.81  # 중력 가속도
h_initial = 10.0  # 초기 높이

position = np.array([0.0, h_initial, 0.0], dtype=float)
velocity = np.array([0.0, 0.0, 0.0], dtype=float)

dt = 0.01  # 시간 간격
total_time = 2.0  # 전체 시뮬레이션 시간
num_steps = int(total_time / dt)

def kinetic_energy(m, v):
    return 0.5 * m * np.dot(v, v)

def potential_energy(m, g, h):
    return m * g * h

for step in range(num_steps):
    # 위치 및 속도 업데이트 (오일러 방식)
    position += dt * velocity
    velocity += dt * np.array([0.0, -g, 0.0], dtype=float)  # 중력 가속도 적용
    
    # 에너지 계산
    current_height = position[1]
    E_k = kinetic_energy(m, velocity)
    E_p = potential_energy(m, g, current_height)
    E_total = E_k + E_p

    # 에너지 출력
    print(f"Step {step}: Kinetic Energy = {E_k:.3f}, Potential Energy = {E_p:.3f}, Total Energy = {E_total:.3f}")
```

이 코드는 매우 단순화된 형태이지만, 기본적인 에너지 보존 원리를 디버깅하는데 유용할 수 있다. 각 단계별로 운동 에너지, 위치 에너지, 그리고 총 에너지를 계산하여 출력함으로써 에너지 보존 여부를 직접 확인할 수 있다.

다음으로는 위 예제 코드에서 더 복잡한 상황으로 확장할 수 있는 요소들을 소개하겠다.

#### 복잡한 물리 상황 디버깅

**다자유도 시스템**

다자유도 시스템에서는 각 물체가 독립적으로 움직일 수 있으며, 서로 상호작용하는 힘을 가질 수 있다. 이러한 시스템의 경우, 각 물체에 대해 개별적으로 에너지를 계산하고, 전체 시스템의 총 에너지를 합산하여 보존 여부를 검사한다.

**비탄성 충돌**

비탄성 충돌에서는 충돌 후 일부 에너지가 열이나 변형 에너지로 변환될 수 있다. 이 경우에도 총 에너지가 사라지지 않도록 주의해야 한다.

```python
import numpy as np

m1 = 1.0  # 질량
m2 = 2.0  # 질량
g = 9.81  # 중력 가속도
e = 0.8   # 충돌 계수

position1 = np.array([0.0, 10.0, 0.0], dtype=float)
velocity1 = np.array([0.0, 0.0, 0.0], dtype=float)
position2 = np.array([0.0, 5.0, 0.0], dtype=float)
velocity2 = np.array([0.0, 0.0, 0.0], dtype=float)

dt = 0.01  # 시간 간격
total_time = 2.0  # 전체 시간
num_steps = int(total_time / dt)

def kinetic_energy(m, v):
    return 0.5 * m * np.dot(v, v)

def potential_energy(m, g, h):
    return m * g * h

for step in range(num_steps):
    # 위치 및 속도 업데이트 (오일러 방식)
    position1 += dt * velocity1
    velocity1 += dt * np.array([0.0, -g, 0.0], dtype=float)
    position2 += dt * velocity2
    velocity2 += dt * np.array([0.0, -g, 0.0], dtype=float)
    
    # 충돌 판정 및 처리
    if np.linalg.norm(position1 - position2) < 1e-5:
        relative_velocity = velocity1 - velocity2
        impulse = -(1 + e) * np.dot(relative_velocity, position1 - position2) / np.dot(position1 - position2, position1 - position2)
        velocity1 += (impulse / m1) * (position1 - position2)
        velocity2 -= (impulse / m2) * (position1 - position2)
    
    # 에너지 계산
    current_height1 = position1[1]
    current_height2 = position2[1]
    E_k1 = kinetic_energy(m1, velocity1)
    E_p1 = potential_energy(m1, g, current_height1)
    E_k2 = kinetic_energy(m2, velocity2)
    E_p2 = potential_energy(m2, g, current_height2)
    E_total = E_k1 + E_p1 + E_k2 + E_p2

    # 에너지 출력
    print(f"Step {step}: Total Energy = {E_total:.3f}")
```

#### 기타 에너지 손실 요인

디버깅 과정에서 고려할 다른 에너지 손실 요인은 다음과 같다:

1. **마찰**: 접촉 표면에서 발생하는 마찰력에 의한 에너지 손실.
2. **저항**: 유체나 공기 저항에 의한 에너지 손실.
3. **구조적 손실**: 변형과 같은 구조적 변화로 인해 발생하는 에너지 손실.

***

에너지 보존 오류를 디버깅하는 것은 물리엔진 개발에서 매우 중요한 단계이다. 시간 통합 방법 선택, 충돌 처리, 외부 힘과 저항 요소까지 모두 고려하여 에너지가 보존될 수 있도록 주의해야 한다. 이를 위해 단계별 에너지 측정과 패턴 분석을 통한 문제 해결이 필요하다.

이와 같은 디버깅 과정을 통해 보다 정확하고 신뢰성 있는 물리 엔진을 구축할 수 있다.
