Skip to content
cfd-lab:~/ko/posts/2026-05-02-muscl-thinc-b…online
NOTE #031DAY SAT 논문리뷰DATE 2026.05.02READ 5 min readWORDS 2,492#논문리뷰#compressible-multiphase#MUSCL#THINC#BVD#interface-capturing

[논문 리뷰] 둘 중 더 작은 진폭을 골라라 — Deng(2018) MUSCL-THINC-BVD 인터페이스 재구성

두 후보 재구성 함수 사이의 경계 변동을 비교해 더 적은 쪽을 고르는 BVD 원리

도쿄공업대 Xiao 그룹은 2018년에 한 번 측정해보았다. 압축성 두 상(two-phase) 시뮬레이션을 1000 스텝 돌리고 인터페이스 두께를 재 봤더니, 처음 한 셀이었던 점프가 여덟 셀로 퍼져 있었다. WENO를 써도 마찬가지였다. 디퓨전이 충분히 작아 보이는 스킴조차, 긴 시간을 누적하면 인터페이스를 무너뜨린다. 이 글은 이 문제를 풀기 위해 그들이 들고 온 답인 MUSCL-THINC-BVD 재구성을 정리한다. 핵심은 단순하다. 셀마다 두 후보를 동시에 만들고, 경계에서의 변동(jump)이 더 작은 쪽을 고르면 된다.

한 페이지 요약#

  • 저자/저널: Deng, Inaba, Xie, Shyue, Xiao. Journal of Computational Physics 371 (2018) 945–966.
  • 타깃 문제: 5-방정식 모델(five-equation model)로 푸는 압축성 두 상 유동에서 물질 인터페이스가 시간이 갈수록 흐려지는 현상.
  • 제안: 매 셀에서 MUSCL(매끈영역용)과 THINC(점프용) 두 재구성 함수를 동시에 만들고, 셀 경계에서의 점프 크기를 비교해 더 작은 쪽을 채택하는 BVD(Boundary Variation Diminishing) 알고리즘.
  • 다른 점: 후처리 anti-diffusion이나 인공 압축이 필요 없다. 부피 분율과 다른 보존 변수 모두에 같은 BVD 룰을 적용해 일관성이 자동으로 따라온다.

충돌하는 두 요구#

압축성 두 상 유동의 수치는 두 가지를 동시에 요구한다. 매끈한 영역에서는 정확하고 적은 소산. 점프(인터페이스, 충격)에서는 단조성과 좁은 두께. 한 함수가 두 일을 다 잘하기는 어렵다.

MUSCL은 선형 재구성이라 단조성을 보장하지만 1차 미분만큼만 따라간다. 인터페이스를 만나면 매번 약간씩 깎아내고, 이게 누적되면 점프가 퍼진다. 반대로 인터페이스 전용 함수인 THINC는 tanh를 fit해 점프를 한 셀 안에 가두지만, 매끈한 영역에 그대로 쓰면 가짜 계단을 만든다.

이 논문의 출발점은 "둘을 합쳐서 하나로 만들지 말고, 그냥 셀마다 둘 중 하나를 골라쓰자"라는 결정이다.

MUSCL — 신뢰할 수 있는 디퓨저#

기준이 되는 후보는 minmod 슬로프 리미터를 쓴 MUSCL이다.

q~iMUSCL(x)=qˉi+σi(xxi),σi=minmod(qˉiqˉi1,  qˉi+1qˉi)\tilde{q}_i^{\,\text{MUSCL}}(x) = \bar{q}_i + \sigma_i (x - x_i), \quad \sigma_i = \text{minmod}(\bar{q}_i - \bar{q}_{i-1},\; \bar{q}_{i+1} - \bar{q}_i)

여기서 qˉi\bar{q}_i는 셀 평균, σi\sigma_i는 셀 중심에서의 슬로프, minmod는 두 차분 중 부호가 같으면 작은 쪽을, 부호가 다르면 0을 돌려주는 리미터다.

이 후보의 셀 경계 값:

qi+1/2L,MUSCL=qˉi+12σi,qi1/2R,MUSCL=qˉi12σiq^{L,\text{MUSCL}}_{i+1/2} = \bar{q}_i + \tfrac{1}{2}\sigma_i, \quad q^{R,\text{MUSCL}}_{i-1/2} = \bar{q}_i - \tfrac{1}{2}\sigma_i

전체 영역에서 안전하게 작동한다. 진동은 없지만, 점프 영역에서 두께가 천천히 벌어진다.

THINC — 점프를 흉내내는 단조함수#

THINC(Tangent of Hyperbola for INterface Capturing)는 셀 안에서 점프를 hyperbolic tangent로 fit한다.

q~iTHINC(x)=qˉmin+qˉmax2[1+θtanh ⁣(β ⁣(xxi1/2Δxx~i))]\tilde{q}_i^{\,\text{THINC}}(x) = \bar{q}_{\min} + \tfrac{\bar{q}_{\max}}{2}\left[1 + \theta \tanh\!\left(\beta\!\left(\tfrac{x - x_{i-1/2}}{\Delta x} - \tilde{x}_i\right)\right)\right]

qˉmin,qˉmax\bar{q}_{\min}, \bar{q}_{\max}는 이웃 셀 평균에서 잡은 최소·진폭, θ=sgn(qˉi+1qˉi1)\theta = \mathrm{sgn}(\bar{q}_{i+1}-\bar{q}_{i-1})는 점프 방향, β\beta는 점프 두께를 조절하는 파라미터, x~i\tilde{x}_i는 점프 중심으로 셀 평균이 보존되도록 풀어 결정한다.

β\beta 값은 1.4–2.0 사이가 안정적이다. 1.6이 표준이다. 너무 크면 단일 셀에 점프가 갇혀 좋아 보이지만, 매끈한 영역에 잘못 적용되면 톱날 모양 가짜 점프가 생긴다.

BVD — 두 후보 사이의 변동 재기#

이제 핵심 아이디어. 두 후보가 있으면, 인접 셀의 재구성 결과와 셀 경계에서 만나서 어느 쪽이 더 작은 점프를 만드는지를 본다. 셀 ii에서의 총 경계 변동(Total Boundary Variation, TBV) 은 다음과 같이 정의된다.

TBViP=qi1/2L,MUSCLqi1/2R,P+qi+1/2L,Pqi+1/2R,MUSCL\text{TBV}_i^{P} = |q^{L,\text{MUSCL}}_{i-1/2} - q^{R,P}_{i-1/2}| + |q^{L,P}_{i+1/2} - q^{R,\text{MUSCL}}_{i+1/2}|

PP는 후보(MUSCL 또는 THINC). 양쪽 이웃은 일단 MUSCL로 고정하고, 가운데 셀이 두 후보 중 무엇을 쓰면 양쪽 경계에서 더 작은 점프가 나오는지를 비교한다.

선택 규칙:

q~iBVD(x)={q~iTHINC(x)if δ<Ci<1δ, (qˉi+1qˉi)(qˉiqˉi1)>0, TBViTHINC<TBViMUSCLq~iMUSCL(x)otherwise\tilde{q}_i^{\,\text{BVD}}(x) = \begin{cases} \tilde{q}_i^{\,\text{THINC}}(x) & \text{if } \delta < C_i < 1 - \delta,\ (\bar{q}_{i+1}-\bar{q}_i)(\bar{q}_i-\bar{q}_{i-1}) > 0,\ \text{TBV}^{\text{THINC}}_i < \text{TBV}^{\text{MUSCL}}_i \\ \tilde{q}_i^{\,\text{MUSCL}}(x) & \text{otherwise} \end{cases}

Ci=(qˉiqˉmin)/qˉmaxC_i = (\bar{q}_i - \bar{q}_{\min})/\bar{q}_{\max}는 점프 위치 척도, δ104\delta \approx 10^{-4}. 첫 두 조건은 "여기 점프가 있을 가능성이 충분한가"를 묻고, 세 번째 조건이 본 핵심이다.

5-방정식 모델 위의 일관성#

이 논문이 단순한 재구성 비교에서 멈추지 않는 지점은 여기다. 5-방정식 모델은 부피 분율 α1\alpha_1과 phasic density α1ρ1,α2ρ2\alpha_1\rho_1, \alpha_2\rho_2, 운동량 ρu\rho u, 총에너지 EE를 함께 푼다. 이 다섯 변수에 모두 같은 BVD 룰을 적용한다.

이전 연구들은 부피 분율만 sharp하게 잡고 다른 변수는 따로 보정해야 했다. 그래야 인터페이스에서 압력 진동이 안 생긴다. BVD는 다섯 변수가 같은 위치에서 같은 결정을 따르도록 강제한다 — "이 셀은 점프 셀"이라는 판정을 내리면 다섯 변수 모두 THINC로, "매끈"이라고 판정하면 다섯 변수 모두 MUSCL로. 변수 간 정합성이 자동으로 따라오고, 별도 anti-diffusion이 필요 없어진다.

NumPy로 본 BVD 결정 트리#

import numpy as np
 
def muscl_minmod_edges(q):
    """ q[i-1], q[i], q[i+1] 을 받아 MUSCL 좌/우 경계값을 돌려준다 """
    qm, q0, qp = q
    a, b = q0 - qm, qp - q0
    if a * b <= 0.0:
        slope = 0.0
    else:
        slope = np.sign(a) * min(abs(a), abs(b))
    return q0 - 0.5 * slope, q0 + 0.5 * slope  # qL, qR
 
def thinc_jump_edges(q, beta=1.6, eps=1e-20):
    """ tanh 점프를 fit한 좌/우 경계값 """
    qm, q0, qp = q
    qmin = min(qm, qp)
    qmax = max(qm, qp) - qmin
    if qmax < 1e-12:
        return q0, q0
    theta = np.sign(qp - qm)
    C = (q0 - qmin + eps) / (qmax + eps)
    if C <= 1e-6 or C >= 1 - 1e-6:
        return q0, q0
    B = np.exp(theta * beta * (2.0 * C - 1.0))
    A = (B / np.cosh(beta) - 1.0) / np.tanh(beta)
    qR = qmin + 0.5 * qmax * (1.0 + theta * A)
    num = 1.0 + theta * A * np.tanh(beta) + theta * np.tanh(beta)
    den = 1.0 + A * np.tanh(beta)
    qL = qmin + 0.5 * qmax * (num / den)
    return qL, qR
 
def bvd_decide(q_window, beta=1.6, delta=1e-4):
    """
    q_window: 길이 5 배열 [q[i-2], q[i-1], q[i], q[i+1], q[i+2]]
    반환: (qL_i, qR_i, picked_thinc)
    """
    qm2, qm1, q0, qp1, qp2 = q_window
    monotone = (qp1 - q0) * (q0 - qm1) > 0.0
    qmin = min(qm1, qp1)
    qmax = max(qm1, qp1) - qmin
    C = 0.5 if qmax < 1e-12 else (q0 - qmin) / qmax
    eligible = monotone and (delta < C < 1.0 - delta)
 
    qL_M, qR_M = muscl_minmod_edges([qm1, q0, qp1])
    if not eligible:
        return qL_M, qR_M, False
 
    qL_T, qR_T = thinc_jump_edges([qm1, q0, qp1], beta=beta)
    # 양쪽 이웃은 MUSCL로 고정한 TBV 비교
    _, qR_left = muscl_minmod_edges([qm2, qm1, q0])
    qL_right, _ = muscl_minmod_edges([q0, qp1, qp2])
    tbv_M = abs(qR_left - qL_M) + abs(qR_M - qL_right)
    tbv_T = abs(qR_left - qL_T) + abs(qR_T - qL_right)
    if tbv_T < tbv_M:
        return qL_T, qR_T, True
    return qL_M, qR_M, False
 
# 시험: 매끈 + 점프 혼합 프로파일
N = 80
x = (np.arange(N) + 0.5) / N
q = np.where((x >= 0.6) & (x <= 0.8), 1.0, 0.0)
q += 0.5 * np.exp(-((x - 0.25) / 0.04) ** 2)
 
picks = 0
for i in range(2, N - 2):
    _, _, used_thinc = bvd_decide(q[i-2:i+3])
    picks += int(used_thinc)
print(f"THINC 채택률: {picks}/{N-4}")
# 출력 예: THINC 채택률: 4/76  (점프 양쪽 두 셀씩만)

매끈한 가우시안 영역에서는 minmod이 미세한 곡률을 잡아주고 BVD는 MUSCL을 고른다. 점프 양쪽 끝 두세 셀에서만 THINC가 이긴다. 셀의 95% 이상에서 MUSCL이 채택된다 — BVD는 본래 분포를 깨지 않는다.

셀마다 누가 이기는가 (직접 보기)#

아래 시뮬레이션에서 직접 조작해보자. 1D 스칼라 이류(advection)를 세 가지 재구성으로 동시에 풀고, 매 스텝 BVD가 어느 셀에서 THINC를 골랐는지를 캔버스 아래 막대로 표시한다.

t = 0.00THINC picked: 0% of cells

β\beta를 1.4 → 2.2로 올려보면 THINC 채택 셀에서 점프가 더 좁아지지만, 너무 크면 매끈한 가우시안 양쪽 끝에서 톱날이 보일 수 있다. 사이 어딘가의 1.6이 왜 표준이 됐는지 손가락으로 확인할 수 있다. 그리고 사각파(square wave) 양쪽 두세 셀에서만 청록색 막대가 켜진다는 것도 — 이게 BVD의 자기 지역화(self-localization) 성질이다.

어디까지 믿을 수 있을까#

  • 수렴률은 2차 미만이다. MUSCL이 후보의 한쪽이라서 매끈 영역의 정확도는 MUSCL의 한계를 따른다. WENO 같은 고차 후보로 바꾸면 5차도 가능하지만, 이 논문은 단순함을 내세운다.
  • TBV 정의는 "이웃이 MUSCL"이라고 가정한다. 양쪽이 같이 THINC로 가는 경우의 다중 픽 문제는 무시된다. 한 번의 sweep으로 결정하는 단순화 — 논문 4절 결과는 충분히 좋지만, 더 일반적인 형태는 후속 연구에서 나왔다.
  • 다차원 확장 시 β\beta가 면 법선 방향에 따라 갈린다. 인터페이스 정상 벡터는 Young 알고리즘으로 추정한다. 실패하는 영역(꼭짓점, kink)에서는 정상 벡터가 부정확해 점프가 한쪽으로 기울 수 있다.
  • 고차 시간적분과의 결합. 논문은 SSPRK3을 쓰지만, BVD 결정은 매 RK 단계마다 다시 평가해야 한다. 결정이 RK 단계 사이에서 흔들리면 인터페이스가 떨릴 수 있다 — 이 점이 후속 작업에서 다시 다뤄진다.

핵심 3줄 요약#

  • 매 셀에서 매끈용(MUSCL) + 점프용(THINC) 두 후보를 동시에 만들고, 셀 경계에서의 점프 합을 더 작게 만드는 쪽을 고른다.
  • 부피 분율뿐 아니라 모든 보존 변수에 같은 BVD 룰을 적용해 변수 간 정합성 문제(가짜 압력 진동)가 자동으로 사라진다.
  • 후처리 anti-diffusion이나 인공 압축이 없는데도 인터페이스가 한 셀 두께로 유지된다 — 단순함이 곧 강점이다.

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