Skip to content
cfd-lab:~/ko/posts/2026-06-12-rotating-refe…online
NOTE #072DAY FRI CFD기법DATE 2026.06.12READ 5 min readWORDS 2,421#OpenFOAM#MRF#Rotating-Frame#Coriolis#Turbomachinery

임펠러를 멈춰 세우고 푸는 법 — 회전 기준 프레임과 MRF

회전 좌표계의 Coriolis·원심력 항과 MRF 인터페이스 변환을 코드로 검증한다

회전하는 임펠러를 정지시킨 채로 해석한다. 그런데 답은 맞다. 펌프나 팬의 정상 상태 성능을 구할 때 가장 널리 쓰이는 트릭이 바로 이것이다. 이 포스트는 회전 기준 프레임(rotating reference frame)에서 운동량 방정식이 어떻게 바뀌는지, MRF(Multiple Reference Frame)가 회전을 어떻게 "얼려" 정상 해석으로 만드는지를 수식과 Python으로 따라간다. 마지막엔 MRF가 깨지는 자리도 짚는다.

절대 좌표계를 버리면 풍차가 멈춘다#

땅에 박힌 카메라로 보면 임펠러 날개는 빠르게 돈다. 시간에 따라 형상이 계속 변한다. 그래서 비정상(unsteady) 해석이 필요하다.

이제 카메라를 날개 위에 올려보자. 날개는 정지해 있다. 주변 유체만 상대적으로 흐른다. 형상이 안 변하니 정상(steady) 해석이 가능해진다. 회전체 문제를 정지 문제로 바꾸는 이 발상이 회전 기준 프레임의 전부다.

대가는 공짜가 아니다. 좌표계 자체가 가속하기 때문에, 운동량 방정식에 원래 없던 항이 두 개 추가된다.

운동량 방정식에 끼어든 두 가짜 힘#

회전 각속도를 Ω\boldsymbol{\Omega}, 상대 속도를 ur\mathbf{u}_r, 위치를 r\mathbf{r}라 하자. 절대 속도와의 관계는 다음과 같다.

ua=ur+Ω×r\mathbf{u}_a = \mathbf{u}_r + \boldsymbol{\Omega}\times\mathbf{r}

여기서 ua\mathbf{u}_a는 절대(관성) 좌표계 속도, Ω×r\boldsymbol{\Omega}\times\mathbf{r}는 회전에 의한 끌림 속도다.

이 관계를 Navier–Stokes에 대입하면 상대 속도에 대한 정상 운동량 방정식이 나온다.

(ur)ur+2Ω×ur+Ω×(Ω×r)=1ρp+ν2ur(\mathbf{u}_r\cdot\nabla)\mathbf{u}_r + 2\,\boldsymbol{\Omega}\times\mathbf{u}_r + \boldsymbol{\Omega}\times(\boldsymbol{\Omega}\times\mathbf{r}) = -\frac{1}{\rho}\nabla p + \nu\nabla^2\mathbf{u}_r

좌변 둘째 항 2Ω×ur2\,\boldsymbol{\Omega}\times\mathbf{u}_r가 Coriolis 가속도(운동 방향을 꺾는 항), 셋째 항 Ω×(Ω×r)\boldsymbol{\Omega}\times(\boldsymbol{\Omega}\times\mathbf{r})가 원심 가속도(축에서 바깥으로 미는 항)다. 두 항 모두 실제 힘이 아니다. 좌표계가 가속하기 때문에 나타나는 겉보기 항이다.

Coriolis 항은 운동량 방정식의 소스로 들어간다. 그래서 OpenFOAM의 MRFSource는 이 둘을 셀마다 더해주는 일을 한다.

MRF — 회전을 정지로 얼리는 frozen rotor#

MRF의 핵심은 영역을 둘로 나누는 것이다. 회전체를 감싸는 회전 영역(rotating zone)과 그 바깥의 정지 영역(stationary zone).

  • 회전 영역: 위의 상대 속도 방정식을 푼다. Coriolis·원심 소스가 켜진다.
  • 정지 영역: 보통의 절대 좌표계 방정식을 푼다.

날개는 실제로 움직이지 않는다. 메쉬도 고정이다. 단지 회전 영역의 방정식에 회전 효과를 소스로 주입할 뿐이다. 그래서 이 방식을 frozen rotor(얼어붙은 로터)라 부른다. 날개와 주변 유체의 상대 위치가 한 순간에 "동결"된 채 정상 해를 구한다.

아래 시뮬레이션에서 직접 파라미터를 조작해보자. 프레임을 절대에서 상대(MRF)로 바꾸면 회전 영역 안쪽 화살표가 어떻게 변하는지 보라.

Switch to the relative frame: the inner cyan arrows collapse toward zero because the rotating zone becomes steady, while the pink outer field is unchanged. The dashed circle is where the two velocity descriptions must be transformed into each other.

절대 프레임에서는 안쪽 유체가 Ω×r\boldsymbol{\Omega}\times\mathbf{r}만큼 강체 회전한다. 화살표가 인터페이스로 갈수록 커진다. 상대(MRF) 프레임으로 바꾸면 안쪽 화살표가 0 근처로 주저앉는다. 회전 영역의 흐름이 "정지"한 것처럼 보이기 때문이다. 바깥(분홍) 장은 그대로다.

인터페이스에서 속도를 다시 꿰매다#

두 영역은 서로 다른 좌표계로 푼다. 그러면 경계에서 속도가 안 맞는다. 회전 영역은 ur\mathbf{u}_r, 정지 영역은 ua\mathbf{u}_a로 저장하니까.

인터페이스에서는 위의 변환식을 그대로 적용한다. 회전 영역에서 정지 영역으로 넘어갈 때 ua=ur+Ω×r\mathbf{u}_a = \mathbf{u}_r + \boldsymbol{\Omega}\times\mathbf{r}로 더해주고, 반대 방향이면 빼준다. 압력과 난류량 같은 스칼라는 그대로 통과한다. 벡터만 끌림 속도를 보정한다.

이 변환이 빠지면 인터페이스에서 질량과 운동량이 새기 시작한다. 잔차가 떨어지지 않거나, 인터페이스를 가로지르는 비물리적 제트가 생긴다. MRF 설정에서 가장 흔한 실수가 회전 영역 경계를 날개에 너무 바짝 붙이는 것이다. 인터페이스는 유동이 거의 축대칭이 되는 자리에 둬야 변환 오차가 작다.

MRF와 sliding mesh, 무엇을 언제#

MRF는 정상 상태 근사다. 날개와 볼류트(volute) 같은 정지 구조의 상대 위치가 시간에 따라 변하는 효과(rotor–stator 상호작용)를 잡지 못한다. 한 순간의 위치로 동결하기 때문이다.

진짜 비정상 효과가 필요하면 sliding mesh(미끄럼 격자)로 간다. 메쉬가 실제로 회전하고, 매 스텝 인터페이스를 다시 보간한다. 비싸지만 정확하다.

항목MRF (frozen rotor)Sliding mesh
메쉬 모션없음 (고정)실제 회전
시간정상 상태비정상
비용낮음높음
rotor–stator 상호작용근사 못 함잡아냄
용도설계 점검, 성능 곡선소음, 압력 맥동

실무 흐름은 보통 이렇다. MRF로 빠르게 작동점을 잡고, 그 해를 초기장으로 sliding mesh를 돌려 비정상 디테일을 본다.

Python — 회전 좌표계 속 자유 입자의 궤적#

이론이 맞는지 가장 단순한 문제로 검증하자. 아무 힘도 안 받는 자유 입자다. 절대 좌표계에서는 직선으로 날아가야 한다. 회전 좌표계에서 Coriolis·원심 항만으로 적분한 뒤, 다시 절대 좌표계로 되돌렸을 때 직선이 복원되면 소스 항이 옳다는 뜻이다.

import numpy as np
 
OMEGA = np.array([0.0, 0.0, 1.2])   # 회전 각속도 벡터 (rad/s), z축 둘레
 
def rotating_frame_acceleration(r, v_rel):
    """자유 입자(실제 힘 0)가 회전 좌표계에서 받는 겉보기 가속도.
    Coriolis + 원심 항만 남는다."""
    coriolis = -2.0 * np.cross(OMEGA, v_rel)              # -2 Ω × u_r
    centrifugal = -np.cross(OMEGA, np.cross(OMEGA, r))    # -Ω × (Ω × r)
    return coriolis + centrifugal
 
def integrate_rotating_particle(r0, v0, dt, steps):
    """velocity-Verlet 변형으로 상대 좌표계 궤적을 적분."""
    r = np.array(r0, float); v = np.array(v0, float)
    traj = [r.copy()]
    a = rotating_frame_acceleration(r, v)
    for _ in range(steps):
        r = r + v * dt + 0.5 * a * dt * dt
        a_half = rotating_frame_acceleration(r, v)        # 속도 의존 -> 1회 보정
        v = v + 0.5 * (a + a_half) * dt
        a = rotating_frame_acceleration(r, v)
        traj.append(r.copy())
    return np.array(traj)
 
def relative_to_absolute(traj_rel, dt):
    """상대 좌표계 궤적을 절대 좌표계로 되돌린다: 각 시각 Ωt 만큼 역회전."""
    w = OMEGA[2]
    out = []
    for k, r in enumerate(traj_rel):
        t = k * dt
        c, s = np.cos(w * t), np.sin(w * t)
        out.append([c * r[0] - s * r[1], s * r[0] + c * r[1], r[2]])
    return np.array(out)
 
# 절대 좌표계에서 직선 운동하도록 초기조건을 잡는다
V = 0.8
r0 = np.array([1.0, 0.0, 0.0])
v_abs0 = np.array([0.0, V, 0.0])                # 절대 속도: +y 방향 직선
v_rel0 = v_abs0 - np.cross(OMEGA, r0)           # u_r = u_a - Ω × r
 
dt = 0.002
traj_rel = integrate_rotating_particle(r0, v_rel0, dt, 1500)
traj_abs = relative_to_absolute(traj_rel, dt)
 
x_dev = np.abs(traj_abs[:, 0] - 1.0).max()      # 직선이면 x ≈ 1.0 유지
print(f"절대 좌표계 x 최대 이탈: {x_dev:.4e}")
print(f"절대 좌표계 끝점: {traj_abs[-1].round(3)}")

핵심은 v_rel0 한 줄이다. 절대 좌표계 직선 운동을 회전 좌표계 초기 속도로 번역할 때 끌림 속도 Ω×r\boldsymbol{\Omega}\times\mathbf{r}를 빼준다. 적분 후 되돌리면 xx 좌표가 1.0 근방에서 거의 흔들리지 않는다. Coriolis와 원심 항의 부호가 맞다는 직접 증거다. 부호를 하나라도 뒤집으면 입자가 바깥으로 튕겨나가며 xx 이탈이 급증한다.

이번엔 같은 운동을 눈으로 보자. 아래 시뮬레이션에서 직접 조작해보자.

At omega = 0 both paths coincide. Increase omega and the cyan trail curls into a spiral — the Coriolis deflection that an observer riding the turntable mistakes for a force.

회색은 절대 좌표계의 진짜 직선 경로다. 시안은 같은 운동을 회전 좌표계에서 본 궤적이다. Ω=0\Omega = 0이면 둘이 포개진다. Ω\Omega를 올리면 시안이 나선으로 감긴다. 회전판 위 관측자가 "힘"으로 착각하는 Coriolis 휘어짐이다.

코드로 옮기기 전 짚을 함정#

세 가지만 기억하면 MRF 설정의 8할은 막을 수 있다.

첫째, 회전축과 Ω\boldsymbol{\Omega} 방향을 도면과 한 번 더 대조하라. 부호가 반대면 추력이 정반대로 나온다. 발산하지 않고 "그럴듯하게 틀린" 답이 나오기 때문에 잡기 어렵다.

둘째, 인터페이스를 유동이 부드러운 곳에 둬라. 날개 끝 와류 한가운데를 가르면 변환 오차가 커진다. 회전 영역은 날개보다 충분히 크게 잡는다.

셋째, MRF 해를 절대 진실로 믿지 마라. 정상 상태 근사다. rotor–stator 맥동이 중요한 문제면 sliding mesh로 한 번 더 검증한다.

다시 읽지 않을 사람을 위한 요약#

  • 회전 기준 프레임은 회전체를 정지시켜 정상 해석을 가능케 하고, 대가로 Coriolis·원심 소스 항을 운동량 방정식에 더한다.
  • MRF는 회전 영역만 상대 좌표계로 풀고 인터페이스에서 ua=ur+Ω×r\mathbf{u}_a=\mathbf{u}_r+\boldsymbol{\Omega}\times\mathbf{r}로 속도를 변환한다.
  • 자유 입자 검증에서 절대 궤적이 직선으로 복원되면 소스 항 부호가 옳다는 신호다.

도움이 됐다면 공유해주세요.