유령 정체는 사고가 없어도 밀도가 임계를 넘으면 저절로 생긴다

유령 정체(phantom traffic jam)는 사고도 병목도 없는데 차량 밀도가 임계값을 넘으면 흐름이 저절로 무너져 생기는 정체다. 규칙 네 줄짜리 NaSch 모형으로 재현하면 정체 구간이 차와 반대로 뒤로 흐르고, 30대 중 1대만 부드럽게 몰면 평균 속도를 조금 내주는 대신 멈춰 선 시간이 3분의 1 준다.

브레이크등 물결, 사고 없는 정체

왼쪽은 차량이 고르게 퍼진 자유 흐름 링 도로, 오른쪽은 한 구간에 차량이 뭉친 정체 클러스터와 그 옆에서 뒤쪽을 가리키는 굵은 화살표를 보여주는 비교 그림

앞차의 브레이크등이 켜진다. 빨간 불빛은 한 대씩 뒤로 옮겨붙고, 뒤따르던 차들이 차례로 속도를 줄인다. 한참을 기어가다 어느 순간 흐름이 다시 뻥 뚫린다. 그런데 정체가 풀린 지점에는 아무것도 없다. 사고 현장도, 차선이 줄어드는 구간도, 공사 표지판도 없다.

이런 정체를 유령 정체(phantom traffic jam)라고 부른다. 원인이 될 만한 것이 도로 어디에도 남아 있지 않아서 붙은 이름이다. 브레이크를 먼저 밟은 사람을 찾아 봐야 소용이 없다. 그 사람은 앞차가 조금 느려져서 밟았고, 앞차는 또 그 앞차 때문에 밟았다. 사람이 실제로 몰아도 같은 일이 생긴다. Sugiyama 연구팀이 원형 트랙 위에 차를 여러 대 돌려 2008년에 보고한 실험이 그 근거다.

이 연쇄는 도로가 얼마나 붐비느냐에 달려 있다. 차량 밀도가 어떤 값을 넘어서는 순간, 정체는 아무도 잘못하지 않아도 생긴다. 물이 0도에서 얼음이 되듯 흐름의 성질 자체가 한 지점에서 바뀌는 것이고, 물리학은 이런 변화를 상전이라고 부른다. 이 글에서는 규칙 네 줄짜리 모형으로 그 상전이를 직접 재현한다. 정체 구간이 뒤로 밀려가는 속도도 숫자로 재 본다. 차 한 대를 다르게 몰면 무엇이 달라지는지까지 확인한다.

네 줄짜리 규칙 — NaSch 모형과 기본도

교통 흐름을 다루는 가장 단순한 모형은 도로를 칸으로 자르는 데서 시작한다. 칸 하나에는 차가 한 대만 들어가고, 차마다 속도가 하나씩 붙는다. 이 속도도 정수다. 0부터 최고 속도까지, 값 여섯 개면 충분하다.

여기에 나겔과 슈레켄베르크가 1992년에 제안한 네 가지 규칙을 얹는다. 줄여서 NaSch 모형이라 부른다. 매 틱마다 모든 차가 아래 순서를 그대로 밟는다.

  1. 가속 — 속도를 1 올린다. 단 최고 속도를 넘지 않는다.
  2. 충돌 회피 — 앞차까지 남은 빈 칸 수보다 속도가 크면, 그 빈 칸 수로 속도를 낮춘다.
  3. 확률 제동 — 확률 p로 속도를 1 더 낮춘다. 사람이 운전대를 잡으면 필요 없이도 브레이크를 밟는다는 뜻이다.
  4. 이동 — 지금 속도만큼 앞으로 옮긴다.

여기서 흥미로운 건 세 번째 규칙이다. 나머지 셋은 설명이 필요 없다. 1번과 2번은 누구나 떠올릴 법한 상식이고, 4번은 그냥 이동이다. 3번만 사람의 흔들림을 담는다. 아래는 이 네 규칙이 한 틱 동안 실제로 무엇을 하는지를 스무 칸짜리 링 도로에서 그대로 실행한 것이다. 확률 제동은 이번 틱에 두 번째 차가 걸렸다고 두고 고정했다.

import numpy as np

L, v_max = 20, 5
pos = np.array([0, 3, 7, 12, 16])   # 차량 위치 (20칸짜리 링 도로)
vel = np.array([2, 2, 2, 2, 2])     # 현재 속도
brake = np.array([False, True, False, False, False])  # 이번 틱 확률 제동 대상

gap = (np.roll(pos, -1) - pos - 1) % L        # 앞차까지 남은 빈 칸 수
v1 = np.minimum(vel + 1, v_max)               # 1) 가속
v2 = np.minimum(v1, gap)                      # 2) 충돌 회피
v3 = np.where(brake & (v2 > 0), v2 - 1, v2)   # 3) 확률 제동
new_pos = (pos + v3) % L                      # 4) 이동

print("gap        :", gap)
print("1) 가속     :", v1)
print("2) 충돌 회피 :", v2)
print("3) 확률 제동 :", v3)
print("4) 이동     :", new_pos)
gap        : [2 3 4 3 3]
1) 가속     : [3 3 3 3 3]
2) 충돌 회피 : [2 3 3 3 3]
3) 확률 제동 : [2 2 3 3 3]
4) 이동     : [ 2  5 10 15 19]

첫 번째 차는 앞이 두 칸밖에 안 비어서 가속분을 그대로 반납했다. 두 번째 차는 앞이 세 칸 비어 있었는데도 확률 제동에 걸려 속도가 2로 떨어졌다. 이 한 번의 제동이 뒤차의 여유 칸을 줄이고, 그 뒤차가 다시 감속하는 연쇄가 시작된다.

NaSch 모형에는 실수 파라미터가 확률 p 하나뿐이다. 위치도 속도도 정수이고, 계산은 최솟값 비교와 덧셈이 전부다. 미분방정식 하나 없이 이 정도로 실제 도로의 정체 현상을 재현한다는 점이 이 모형이 오래 살아남은 이유다.

규칙을 정했으니 이제 재 볼 차례다. 밀도를 올려 가며 도로가 차를 얼마나 흘려보내는지 보면 된다. 밀도 ρ는 칸 수 대비 차량 수이고, 유량은 밀도에 평균 속도를 곱한 값이다. 칸 1000개짜리 링 도로에서 2000틱을 돌린 뒤 후반 1000여 틱을 집계했고, 시드 0부터 9까지 열 번을 평균했다. 밀도와 유량을 짝지어 그린 이 그림이 기본도다.

밀도에 따른 유량 곡선. 유량이 ρ≈0.10~0.12에서 최댓값을 찍고 그 뒤로 꺾여 내려간다
그림 1. NaSch 링 도로의 기본도. 밀도를 올리면 유량이 ρ≈0.10~0.12 부근에서 최대를 찍고 꺾인다 (L=1000, p=0.3, 시드 0~9 평균).

그림 1의 왼쪽 절반은 직선에 가깝다. 차가 드문 구간에서는 모두가 거의 최고 속도로 달리니 차를 더 넣는 만큼 유량이 그대로 늘어난다. ρ=0.03에서 평균 속도는 4.6925로, 최고 속도 5에서 확률 제동 몫 0.3을 뺀 값과 거의 같다. 그런데 이 직선은 오래가지 않는다. ρ=0.10에서 유량 0.4600, ρ=0.12에서 0.4672로 최댓값을 찍은 뒤 곡선이 꺾여 내려간다.

꺾인 뒤로는 차를 더 넣을수록 도로가 흘려보내는 양이 줄어든다. ρ=0.20에서 유량은 0.4364로 떨어지고, ρ=0.40에서는 0.3475까지 내려간다. 이 그리드에서 유량이 가장 큰 지점은 ρ=0.12다. 다만 ρ=0.10과 차이가 작은 평평한 구간이라, 임계 밀도는 0.10에서 0.12 사이 어딘가라고만 말할 수 있다. 중요한 건 소수점 자리가 아니라 곡선이 꺾인다는 사실이다. 규칙 어디에도 사고나 병목은 없는데 흐름은 스스로 무너진다.

같은 밀도, 다른 결과 — 방아쇠는 확률 요동이다

밀도만 높으면 정체가 생기는 것일까? 확인하는 방법은 간단하다. 밀도를 그대로 두고 확률 제동만 껐다 켜 보면 된다.

ρ=0.12는 임계 밀도 근처다. 그런데 확률 제동을 뺀 채로 계산하면 아직 여유가 있는 밀도다. 최고 속도가 5인 도로에서 모든 차가 5칸씩 나아가려면 차 한 대당 여섯 칸이 필요하다. 자기 칸 하나에 앞의 빈 칸 다섯 개다. 그래서 확률 제동이 없는 세계의 자유 흐름 한계 밀도는 1/6, 약 0.167이다. ρ=0.12는 그보다 낮으니 원칙적으로는 모두가 최고 속도로 달릴 수 있다.

실제로 그랬다. p=0으로 두고 열 개 시드를 돌리자 후반 1000여 틱의 평균 속도가 전부 정확히 5.0000이었고, 시드 간 표준편차는 0.0000이었다. 차가 멈춰 서 있는 시간은 0.000%다. 같은 밀도에 확률 제동만 p=0.3으로 켜자 평균 속도가 3.8934로 떨어졌다. 최고 속도 5를 기준으로 약 22% 하락이고, 차량이 완전히 멈춰 서 있는 시간이 전체의 7.31%로 나타났다.

차량 수는 한 대도 바뀌지 않았다. 바뀐 것은 운전자가 가끔 이유 없이 브레이크를 밟는다는 조건 하나뿐이다. 임계 밀도 근처에서 도로는 이 작은 흔들림을 흡수하지 못하고 증폭한다. 자발 정체의 방아쇠는 밀도가 아니라 요동이고, 밀도는 그 요동이 커질지 잦아들지를 결정한다.

차는 앞으로, 정체는 뒤로 — 시공간도로 본 후방 전파

정체가 생겼다는 것까지는 확인했다. 다음 질문은 그 정체 구간이 어디로 가느냐이고, 이걸 보려면 시공간도가 편하다. 가로축에 도로 위 위치를, 세로축에 시간을 놓고 각 시점의 정체 구간을 한 줄씩 쌓아 올린 그림이다.

NaSch 링 도로의 시공간도. 정체 구간이 좌상향 사선 줄무늬로 나타나 시간이 흐를수록 위치가 뒤로 밀린다
그림 2. 시공간도(가로=도로 위치, 세로=시간). 차량은 오른쪽으로 가지만 정체 줄무늬는 왼쪽 위로 기운다 — 파동이 뒤로 흐른다는 뜻이다.

그림 2에서 눈에 먼저 들어오는 것은 기울어진 줄무늬다. 개별 차량은 오른쪽으로, 즉 진행 방향으로 간다. 그런데 정체 덩어리를 이루는 줄무늬는 위로 갈수록 왼쪽으로 기운다. 시간이 흐를수록 정체 구간의 위치가 뒤로 물러난다는 뜻이다. 정체에 붙잡혔던 차는 앞으로 빠져나가고, 그 자리로 뒤에 있던 차가 새로 들어오면서 덩어리 자체는 상류로 이동한다.

기울기를 눈대중하지 않고 재려면 상관을 쓰면 된다. 어느 시점에 멈춰 있는 차들의 위치를 0과 1의 배열로 적고, 20틱 뒤의 같은 배열과 순환 상호상관을 구한다. 상관이 최대가 되는 시프트가 그 20틱 동안 정체 패턴이 통째로 얼마나 움직였는지다.

import numpy as np

L, N, P, V_MAX, T, DT = 1000, 200, 0.3, 5, 2000, 20
rng = np.random.default_rng(0)
pos = np.sort(rng.choice(L, size=N, replace=False))
vel = np.zeros(N, dtype=int)

stopped = np.zeros((T + 1, L))            # 정지 차량이 있는 셀 = 1
stopped[0, pos[vel == 0]] = 1.0
for t in range(1, T + 1):
    gap = (np.roll(pos, -1) - pos - 1) % L
    vel = np.minimum(vel + 1, V_MAX)
    vel = np.minimum(vel, gap)
    vel = np.where((rng.random(N) < P) & (vel > 0), vel - 1, vel)
    pos = (pos + vel) % L
    order = np.argsort(pos)
    pos, vel = pos[order], vel[order]
    stopped[t, pos[vel == 0]] = 1.0

shifts = []
for t in range(1000, T - DT, DT):         # 후반부만, 20틱 간격
    a, b = stopped[t], stopped[t + DT]
    if a.sum() < 3 or b.sum() < 3:
        continue
    fa, fb = np.fft.rfft(a - a.mean()), np.fft.rfft(b - b.mean())
    xcorr = np.fft.irfft(np.conj(fa) * fb, n=L)
    s = int(np.argmax(xcorr))
    shifts.append((s - L if s > L // 2 else s) / DT)

drift = float(np.median(shifts))
print("정체파 드리프트 중앙값:", drift, "cell/tick")
print("표준 보정 환산:", round(drift * 27.0, 2), "km/h")
정체파 드리프트 중앙값: -0.55 cell/tick
표준 보정 환산: -14.85 km/h

값이 음수라는 것이 핵심이다. 차량은 양의 방향으로 가는데 패턴은 음의 방향으로 간다. 눈으로 본 사선의 기울기가 숫자로도 확인된 셈이다.

셀과 틱은 그 자체로는 단위가 없는 숫자다. 실제 도로 감각으로 옮기려면 1 cell = 7.5 m, 1 tick = 1 s라는 표준 보정을 쓴다. 차 한 대 길이에 최소 차간을 더한 값이 7.5 m라는 관례에서 나온 값이고, 이 보정을 따르면 1 cell/tick이 27 km/h가 된다. 우리가 이 도로에 맞춰 직접 캘리브레이션한 값이 아니라 NaSch 계열 문헌이 공유하는 관례라는 점은 짚어 둔다. 위 −14.85 km/h도 그 관례 위에서 읽어야 하는 숫자다.

이 드리프트 값은 밀도를 바꿔도 거의 그대로였다. ρ=0.15, 0.20, 0.25, 0.30 네 조건에서 시드 열 개씩을 돌렸다. 시드 간 중앙값은 네 밀도 모두 −0.55 cell/tick으로 같았다. 시드별로 봐도 −0.60에서 −0.50 사이에 전부 들어온다. 도로가 얼마나 붐비든 정체파는 같은 속도로 뒤로 흐른다.

이 값이 실제 도로와 얼마나 맞는지는 밖에서 잰 숫자와 대 봐야 안다. Sugiyama 연구팀은 2008년 보고에서 정체 덩어리가 시속 약 20 km로 뒤로 이동한다고 적었다. 실제 고속도로에서 재는 값이 그 정도라는 것이다. 표준 보정을 얹은 −14.85 km/h는 부호도 자릿수도 그 값과 같다. 그런데 이 현상을 격자 규칙이 아니라 연속체 방정식으로 설명하려 한 계보가 따로 있다.

왜 1차 모델로는 안 되는가 — 반응 지연에서 진행파까지

세포 격자에 확률 요동을 주는 방식은 하나의 답이지 유일한 답은 아니다. 그쪽 계보는 도로를 아예 연속된 흐름으로 놓고 방정식을 세운다. 거기서도 자발 정체는 오래 풀리지 않던 문제였다. 어디서 막혔고 어떻게 뚫었는지를 따라가면, 자발 정체에 필요한 조건이 더 또렷해진다.

가장 오래된 접근은 라이트힐–위덤–리처즈(LWR) 모형이다. 도로 위 차량을 물처럼 연속된 밀도로 보고, 밀도가 정해지면 유량도 정해진다고 가정한다. 앞 절에서 그린 기본도가 바로 그 대응 관계다.

이 모형은 정체의 앞뒤 경계가 충격파처럼 이동하는 것까지는 잘 설명한다. 그러나 균일한 흐름에 작은 흔들림을 주면 그 흔들림은 커지지 않고 그대로 흘러갈 뿐이다. 정체가 저절로 태어나려면 균일 흐름 자체가 불안정해야 한다. 밀도만으로 유량이 정해지는 1차 모형에는 그 불안정을 만들 여지가 없다.

여기서 빠진 것이 시간이다. 실제 운전자는 앞차와의 간격을 보고 즉시 그에 맞는 속도가 되지 않는다. 인지하고, 발을 옮기고, 차가 반응하기까지 시간이 걸린다. 최적 속도 모형(Optimal Velocity Model, OVM)은 이 지연을 정면으로 넣는다. 운전자에게는 간격에 따른 목표 속도가 있고, 현재 속도를 그 목표를 향해 서서히 끌어당긴다고 본다.

앞차와의 간격을 보고 반응하기까지 시간차가 있어, 뒤로 갈수록 제동이 점점 세지는 차량 행렬
그림 3. 반응 지연이 있으면 앞차의 작은 감속이 뒤차로 갈수록 커진다.

그림 3가 그 결과다. 앞차가 살짝 속도를 줄이면 뒤차는 늦게 반응하는 만큼 더 세게 밟아야 간격을 지킨다. 그 뒤차는 다시 더 세게 밟는다. 지연이 충분히 크고 차간이 충분히 좁으면 이 증폭이 사그라들지 않고 뒤로 갈수록 커지는데, 제어 쪽 용어로는 대열이 불안정하다고 말한다. 흔들림을 증폭하는 메커니즘이 바로 이 지점에서 생긴다.

증폭이 무한정 계속되지는 않는다. 속도가 0 아래로 내려갈 수 없고 앞차를 추월할 수도 없으니, 어느 선에서 성장이 멈추고 모양이 굳는다. 이렇게 굳은 정체 덩어리에는 이름이 있다. 속도에 자체 동역학을 준 2차 모형에서 이것을 자미톤(jamiton)이라고 부른다. Flynn, Kasimov, Nave, Rosales, Seibold가 2009년 Physical Review E에 보고하며 붙인 이름이다.

이들이 유도한 자미톤 방정식은 폭발파(detonation wave)를 기술하는 방정식과 닮은꼴이다. 폭발파도 자기 뒤에 남긴 상태에 기대어 모양을 지키며 나아간다. 그 점에서 두 파동의 구조가 서로 닮았다. 자미톤은 한번 자리를 잡으면 모양을 유지한 채로 흐름을 거슬러 뒤로 이동한다. 앞 절에서 잰 −0.55 cell/tick짜리 줄무늬와 같은 그림이다.

자발 정체를 설명하는 길은 하나가 아니다. 세포 격자에 확률 요동을 주는 쪽, 연속체에 반응 지연을 넣는 쪽, 2차 방정식에서 진행파를 찾는 쪽이 각각 다른 언어로 같은 결론에 닿는다. 어떤 길을 택하든 필요한 재료는 둘이다. 흔들림을 만들 원천, 그리고 그 흔들림을 키울 만큼 빽빽한 밀도.

정체는 제어 문제다 — 자율주행차 1대의 효과

정체가 밀도와 요동의 문제라면 대책도 그 둘 중 하나를 건드려야 한다. 밀도를 낮추는 쪽은 도로를 넓히거나 차를 줄이는 일이라 비싸다. 요동을 줄이는 쪽은 훨씬 싸게 시도해 볼 수 있다. 흔들림을 만들지 않고 일정한 속도를 유지하는 차 한 대를 흐름 속에 섞어 보면 된다.

칸 200개짜리 링 도로에 차 30대를 넣고(ρ=0.15) 그중 한 대만 자율주행차(autonomous vehicle)로 바꿨다. 전체의 3.3%다. 이 차는 확률 제동을 하지 않고, 목표 순항 속도를 정해 틱당 1씩만 부드럽게 가감속한다. 앞이 막히면 당연히 그에 맞춰 줄이되, 앞차를 바짝 쫓지는 않는다. 시드 0부터 19까지 스무 번을 돌려 후반 1000여 틱을 집계한 결과가 아래다.

조건 평균 속도 (cell/tick) 정지 시간 비율 속도 요동 (표준편차)
자율주행차 없음 3.0610 16.44% 1.9035
1대, 목표 속도 3 2.9164 10.69% 1.6395
1대, 목표 속도 4 3.0888 14.48% 1.8267

목표 속도를 3으로 둔 조건에서 차량이 멈춰 서 있는 시간이 16.44%에서 10.69%로 줄었다. 상대적으로 35.0% 감소다. 속도 요동의 표준편차도 1.9035에서 1.6395로 13.9% 줄었다. 서른 대 중 한 대만 다르게 몰았을 뿐인데 나머지 스물아홉 대가 덜 멈춘다.

자율주행차가 없을 때와 1대 있을 때의 속도 분포 비교. 속도 0 근처의 밀집이 얇아지고 그만큼 중간 속도대가 두꺼워진다
그림 4. 차량×틱 속도 분포 비교(ρ=0.15, 시드 0~19). 자율주행차 1대를 넣으면 속도 0 근처의 밀집이 얇아진다.

그림 4를 보면 무엇이 줄었는지가 분명해진다. 속도 0 근처에 몰려 있던 봉우리가 낮아지고, 그만큼이 중간 속도대로 옮겨 갔다. 완전히 멈추는 사건 자체가 줄었다는 뜻이다.

공짜는 아니다. 같은 조건에서 평균 속도는 3.0610에서 2.9164로 4.7% 떨어졌다. 목표 속도를 4로 느슨하게 잡으면 평균 속도는 3.0888로 오히려 유지된다. 대신 정지 시간 감소폭이 상대 12.0%로 절반 이하다. 세게 누르면 정체가 확실히 줄고 평균 속도를 조금 내주며, 살살 누르면 속도는 지키되 효과가 얕아진다.

여기서 쓴 자율주행 전략은 Stern 연구팀이 2018년에 실차로 검증한 아이디어(더 읽기의 Stern et al. 2018)를 격자 모형에 맞게 옮긴 우리 구현이다. 규칙도 단위도 다르므로 위 수치를 그 실험이 보고한 연료 소비나 속도 개선치와 나란히 놓고 비교하면 안 된다. 이 표가 말할 수 있는 것은 "같은 원리가 이 모형에서도 작동한다"까지다.

정체를 제어 문제로 보는 시각은 자율주행차만의 것이 아니다. 도로를 넓히지 않고 밀도와 요동만 손대는 개입은 이미 실제 도로에서 쓰인다. 램프 미터링은 진입로에서 차를 한 대씩 끊어 보내 본선 밀도가 임계를 넘지 않게 막는다. 밀도 쪽 개입이다. 가변 속도 제한은 정체 상류의 속도를 미리 낮춰 흔들림이 증폭될 여지를 줄인다. 이쪽은 요동을 겨눈다.

이 개입들이 기대는 원리는 앞에서 돌린 모형에도 그대로 있었다. 규칙은 네 줄뿐이었다. 그 모형이 실제 도로에서 보고되는 정체파의 후진 속도와 같은 자릿수의 값을 냈고, 30대 중 1대만 다르게 몰면 정지 시간이 3분의 1 줄었다. 유령 정체는 운전자의 잘못이 아니라 밀도의 상전이이고, 상전이라면 밀도와 요동 둘 중 하나만 건드려도 늦출 수 있다.

더 읽기

  • Nagel, K. & Schreckenberg, M., "A cellular automaton model for freeway traffic", Journal de Physique I 2 (1992) — https://doi.org/10.1051/jp1:1992277
  • Sugiyama, Y. et al., "Traffic jams without bottlenecks — experimental evidence for the physical mechanism of the formation of a jam", New Journal of Physics 10 (2008) — https://doi.org/10.1088/1367-2630/10/3/033001
  • Stern, R. E. et al., "Dissipation of stop-and-go waves via control of autonomous vehicles: Field experiments" (2018) — https://arxiv.org/abs/1705.01693