개미 맴돌이(ant mill)란 개미 무리가 스스로 만든 페로몬 고리에 갇혀 계속 원을 그리며 도는 현상이다. 이 갇힘은 개체의 판단 오류가 아니라 국소규칙이 만드는 양의 피드백이다.
개미 맴돌이, 실측된 현상이다

1921년에 나온 남미 정글 관찰기 속 개미 떼는 둘레 366미터짜리 원을 그리며 하루가 넘도록 돌았다. 길을 잃어서가 아니다 — 개미 한 마리 한 마리가 "가장 신선한 냄새 자국을 따라간다"는 규칙을 한 치의 오차 없이 지켰기 때문이다.
기록을 남긴 사람은 박물학자 윌리엄 비비다. 그는 아침 수영을 하러 나섰다가 실험실 계단 아래를 지나가는 군대개미 행렬을 봤고, 그 줄을 거꾸로 되짚어 갔다. 대나무 숲과 속이 빈 통나무와 강가 모래밭을 지나 다시 출발점으로 돌아왔다. 나가는 줄과 들어오는 줄이 같은 줄이었다.
"the ends of the circle actually came together! It was the most astonishing thing, and I had to verify it again and again before I could believe the evidence of my eyes." 원의 양 끝이 실제로 맞붙어 있었다. 눈으로 본 것을 믿기까지 몇 번이고 다시 확인해야 했을 만큼 놀라운 광경이었다.
출처: Beebe, *Edge of the Jungle* (1921)비비는 그 원을 재기까지 했다. 둘레는 1,200피트, 미터법으로 약 366미터였다. 짐을 진 개미들의 속도는 초당 2~2.75인치, 초당 5.1~7.0센티미터쯤이었다. 그리고 그는 개미 한 마리가 한 바퀴를 도는 데 약 두 시간 반이 걸린다고 적었다.
앞의 두 숫자로 직접 나눠 보면 세 번째 숫자가 나오지 않는다. 둘레를 속도로 나누면 한 바퀴는 1.45~2.0시간이다. 원문의 "약 두 시간 반"과 어긋나고, 원문은 그 차이를 설명하지 않는다. 어느 쪽이 맞는지 기록만으로는 정할 수 없으니 두 값을 모두 남겨 둔다.
고리가 얼마나 오래 돌았는지도 기록에 있다. 비비가 원을 발견한 것은 아침 여섯 시였고, 정오에 내린 비로 행렬이 한 시간쯤 흐트러졌다가 해가 나자 다시 이어졌다. 오후 내내 원은 돌았고 자정에도 개미들은 여전히 움직이고 있었다. 이튿날 아침이 되어서야 상당수가 짐을 놓쳤고, 걸음이 눈에 띄게 느려졌다.
대중 서사는 여기서 "개미들이 하루가 넘도록 돌다가 전부 탈진해 죽었다"로 끝난다. 탈진까지는 1차 기록에 있다 — 비비는 완전한 탈진에서 온 절박함이, 필요로도 기회로도 평소의 생활로도 되지 않던 일을 해냈다고 적었다. 어긋나는 것은 그다음이다. 반쯤 죽은 개미 한 마리가 고리를 벗어나 강가로 빠져나가자, 남은 행렬이 그 냄새를 따라 정글 쪽으로 흘러나갔다. 비비는 그들이 어떻게 됐을지 모르겠다는 물음으로 장을 닫는다. 오래 돌았다는 부분도 탈진했다는 부분도 기록에 있고, 기록에 없는 것은 그래서 전멸했다는 결말 하나다.
같은 고리가 비비 한 사람의 눈에만 보인 것도 아니다. 1944년에는 학술 기록도 나왔다. 동물행동학자 T. C. 슈나이얼라는 본대에서 떨어져 나온 군대개미(Labidus praedator, 당시 표기는 Eciton praedator)가 스스로 유지되는 고리를 만드는 과정을 미국자연사박물관 학술보고(AMNH Novitates 1253)에 적었다. 우연히 한 번 찍힌 장면이 아니라 조건이 갖춰지면 생기는 상태라는 뜻이다. 그렇다면 들여다볼 것은 개체가 아니라 그 조건이다.
왜 규칙을 지키는데 고리에 갇히는가
개미가 따르는 규칙은 하나다. 앞서 지나간 개체가 땅에 남긴 화학 자국을 감지하고, 자국이 진한 쪽으로 간다. 자국은 시간이 지나면 옅어진다. 그래서 개미가 자주 지나가는 길일수록 옅어지기 전에 다시 덧칠되고, 진해진 길이 더 많은 개미를 불러들인다.
평소에 이 규칙은 대단히 잘 듣는다. 먹이까지 가는 길이 두 갈래일 때, 짧은 쪽은 왕복이 빨라 같은 시간에 더 여러 번 덧칠된다. 시간이 조금 지나면 짧은 길의 자국이 긴 길을 앞지르고 무리 전체가 짧은 길로 몰린다. 어느 개체도 두 길의 길이를 재지 않았는데 무리는 짧은 길을 고른다.
이 결론은 고스 연구팀이 1989년에 한 이중다리 실험에서 실제로 나왔다. 쓰인 종은 군대개미가 아니라 아르헨티나개미(Linepithema humile)였다. 종이 달라도 자국을 따라간다는 규칙은 같다. 개미 군집 최적화(ant colony optimization)라는 알고리즘 계열이 이 규칙을 거의 그대로 옮겨 쓴다.
문제는 자국이 어쩌다 스스로 닫혔을 때다. 비비가 본 상황이 정확히 그랬다. 선두가 나가던 길을 놓치고 뒤따라오던 행렬의 자국에 다시 올라타면, 나가는 줄과 들어오는 줄이 한 줄이 된다. 이 순간 무리가 감지할 수 있는 가장 진한 자국은 자기들이 방금 지나온 그 고리다.
여기서부터 규칙은 고리를 스스로 강화한다. 한 바퀴 돌 때마다 자국이 다시 덧칠되고, 자국이 진해질수록 벗어날 확률이 낮아지고, 벗어나는 개체가 줄수록 고리는 더 진해진다. 이런 되먹임을 양의 피드백이라고 부른다. 고리를 깨려면 어떤 개체가 규칙을 어겨야 하는데, 규칙을 어길 이유가 개체에게는 없다.
비비도 같은 대목을 적었다. 그는 하루가 넘도록 지켜보는 동안 제 발로 고리에서 한 발짝이라도 옆으로 벗어나는 개미를 한 마리도 보지 못했다. 갇힘은 개체가 실수해서 생긴 것이 아니라, 규칙이 정확히 작동해서 생긴 것이다. 그렇다면 이 갇힘은 냄새를 쓰는 생물에게만 일어나는 일일까. 페로몬을 통째로 빼고 처음부터 다시 만들어 보면 알 수 있다.
페로몬을 빼도 무리는 제자리를 돈다 — 자기추진입자로 갈아타기
여기서 재료를 바꾼다. 페로몬도 화학 감각도 기억도 없는 입자 100개를 평면에 흩뿌린다. 각 입자는 일정한 속도로 앞으로 가고, 자기 주변에 있는 다른 입자의 위치와 진행 방향만 본다. 이런 모형을 자기추진입자(self-propelled particle, SPP)라고 부른다.
한 입자가 보는 범위는 자기를 중심으로 한 동심원 세 겹으로 나뉜다. 가장 안쪽은 반발 구역이다. 여기에 다른 입자가 들어오면 그 반대쪽으로 돌아선다. 그 바깥은 정렬 구역이고, 이 안의 이웃과 같은 방향을 보려 한다. 가장 바깥은 유인 구역이며, 이 안의 이웃 쪽으로 다가가려 한다.
세 규칙이 동시에 쓰이지는 않는다. 반발 구역에 이웃이 하나라도 있으면 정렬과 유인은 무시되고, 부딪히지 않는 쪽으로만 방향을 정한다. 반발 구역이 비어 있을 때만 정렬과 유인이 더해진다. 우선순위가 이렇게 정해져 있다는 것이 이 모형의 전부다. 이웃 세 마리를 놓고 한 번 계산해 보면 이 우선순위가 진행 방향을 어떻게 가르는지 바로 보인다.
import numpy as np
rr, ro, ra = 1.0, 4.0, 16.0 # 반발 반경 / 정렬 구역 바깥 / 유인 구역 바깥
def desired(rel_pos, nb_head):
d = np.linalg.norm(rel_pos, axis=1)
unit = rel_pos / d[:, None]
near = d < rr
if near.any(): # 규칙 1: 반발이 언제나 우선한다
want = -unit[near].sum(axis=0)
else: # 규칙 2 + 3: 정렬과 유인을 더한다
want = (nb_head[(d >= rr) & (d < ro)].sum(axis=0)
+ unit[(d >= ro) & (d < ra)].sum(axis=0))
return np.round(want / np.linalg.norm(want), 3)
rel = np.array([[0.6, 0.2], [2.0, 1.5], [-9.0, 3.0]]) # 이웃 3마리의 상대 위치
head = np.array([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0]]) # 이웃 3마리의 진행 방향
print("반발 구역에 이웃이 있을 때:", desired(rel, head))
print("반발 구역이 비었을 때 :", desired(rel[1:], head[1:]))반발 구역에 이웃이 있을 때: [-0.949 -0.316]
반발 구역이 비었을 때 : [-0.585 0.811]같은 이웃 배치인데 가까운 한 마리를 빼자 원하는 방향이 70도 넘게 꺾인다. 두 방향 모두 x 성분은 음수로 남지만, y 성분은 -0.316에서 +0.811로 부호가 뒤집힌다. 충돌을 피하는 쪽과 무리를 따라가는 쪽이 이만큼 어긋난다는 뜻이다. 이 한 줄짜리 우선순위가 뒤에 나올 모든 차이를 만든다.
이 글에서 돌린 재현 모형의 값은 이렇다. 반발 반경은 1.0, 유인 구역의 폭은 12.0으로 고정했다. 입자 수는 100, 속도는 3.0, 한 단위시간에 꺾을 수 있는 최대 각도는 40도, 시야는 진행 방향을 기준으로 270도다. 방향에는 표준편차 0.05의 각도 잡음을 넣었고, 시간 간격 0.1로 적분했다.
고정하지 않은 값은 하나뿐이다. 정렬 구역의 폭이고, 앞으로 이 값을 Δro라고 부른다. 반발 반경 바깥에서 정렬 구역이 어디까지 뻗는가 — 그 폭 하나가 이 글의 제어변수다.
Δro만 바꿔 두 번 돌린 결과가 그림 1다. 규칙도 같고 입자 수도 같고 속도도 같은데 결과는 둘로 갈린다. 오른쪽은 화살표가 모두 나란한 조밀한 띠이고, 무리가 통째로 그 방향으로 옮겨 간다. 왼쪽은 훨씬 느슨한 구름이라 화살표만 봐서는 방향이 잡히지 않는다. 그런데도 전체로는 무리 중심을 시계 방향으로 감고 돌고, 그림 왼쪽 위 배지의 방향은 눈대중이 아니라 계산해서 얻은 값이다. 입자는 쉬지 않고 나아가는데 무리 전체는 거의 그 자리에 남는다 — 페로몬을 한 방울도 쓰지 않고 나온 제자리 회전이다.
무리의 상태를 재는 눈금 — 차수변수 Op와 Or
그림 1의 오른쪽 띠는 눈으로도 바로 알아본다. 왼쪽은 그렇지 않다. 흩어진 구름에 화살표가 제각각이라, 무리가 돌고 있다는 것부터가 그림만으로는 잡히지 않는다. 눈으로 아는 것으로는 부족하다는 뜻이다. Δro를 조금씩 바꿔 가며 어디서 상태가 갈리는지 보려면, 그림 대신 상태마다 하나씩 붙는 숫자가 필요하다.
첫 번째 숫자는 무리가 얼마나 한 방향으로 가는지를 잰다. 입자마다 진행 방향을 길이 1짜리 화살표로 적고, 그 화살표를 전부 더한 다음 개수로 나눈다. 남은 화살표의 길이가 극성(polarization)이고, 기호로는 $O_p$라고 쓴다. 모두 같은 방향을 보면 화살표가 그대로 쌓여 길이가 1이 된다. 방향이 고르게 흩어지면 서로 지워져 0에 가까워진다.
식 1에서 $\hat{v}_i$는 i번째 입자의 진행 방향 단위벡터이고, N은 입자 수다. 앞 문단에서 말로 적은 절차를 기호로 옮겨 놓은 것뿐이다. 이 값 하나로 무리가 한 방향으로 가는 정도를 0과 1 사이 숫자로 말할 수 있게 된다.
두 번째 숫자는 무리가 중심을 두고 도는지를 잰다. 먼저 모든 입자의 평균 위치, 즉 무리의 중심을 잡는다. 그 중심에서 각 입자로 향하는 단위벡터와 그 입자의 진행 방향을 외적한다. 두 방향이 직각이면 결과가 ±1이 되고, 나란하면 0이 된다. 입자가 중심을 얼마나 옆으로 끼고 도는지를 나타내는 값이다. 이것을 전부 평균 내고 절댓값을 취한 것이 회전(rotation), 기호로는 $O_r$이다.
식 2의 $\hat{r}_{ic}$는 무리 중심에서 i번째 입자로 향하는 단위벡터다. 평면 위의 외적이라 결과는 방향 없는 숫자 하나다. 모두 같은 방향으로 돌면 1이 되고, 절반씩 반대로 돌면 서로 지워져 0이 된다.
두 눈금은 서로 독립이다. 극성이 0이라고 해서 회전이 0인 것도 아니고, 그 반대도 아니다. 극단적인 배치 두 개를 만들어 직접 재 보면 확인된다.
import numpy as np
def order_params(pos, head):
Op = np.linalg.norm(head.mean(axis=0)) # 극성
r = pos - pos.mean(axis=0) # 무리 중심 기준 위치
r = r / np.linalg.norm(r, axis=1)[:, None]
cross = r[:, 0] * head[:, 1] - r[:, 1] * head[:, 0]
return Op, abs(cross.mean()) # 극성, 회전
N = 100
t = np.linspace(0, 2 * np.pi, N, endpoint=False)
ring_pos, ring_head = np.c_[np.cos(t), np.sin(t)], np.c_[-np.sin(t), np.cos(t)]
rng = np.random.default_rng(0)
pol_pos, pol_head = rng.normal(size=(N, 2)), np.tile([1.0, 0.0], (N, 1))
for name, pos, head in [("고리", ring_pos, ring_head), ("정렬", pol_pos, pol_head)]:
Op, Or = order_params(pos, head)
print(f"{name}: Op={Op:.3f} Or={Or:.3f}")고리: Op=0.000 Or=1.000
정렬: Op=1.000 Or=0.012원 위에 놓고 접선 방향으로만 가게 한 무리는 극성이 0, 회전이 1이다. 한 방향을 보게 한 무리는 정확히 반대다. 두 숫자를 함께 봐야 어느 쪽인지 말할 수 있다는 뜻이다. 정렬한 무리의 회전이 딱 0이 아니라 0.012인 것은 입자 위치를 무작위로 흩뿌린 탓에 완전히 지워지지 않고 남은 잔여값이다. 재는 자가 두 개 다 준비됐으니, 남은 일은 Δro를 돌려 가며 값을 읽는 것이다.
숫자로 갈리는 세 상태
읽은 값에 이름을 붙이려면 기준이 있어야 한다. 여기서는 툰스트룀 연구팀이 2013년에 물고기 무리의 상태를 분류하며 쓴 경계를 그대로 가져다 쓴다 — 극성이 0.35보다 작고 회전이 0.65보다 크면 맴돌이로 본다. 상태 이름은 셋이다. 방향도 회전도 없이 그저 뭉쳐 있으면 뭉침(swarm), 한 방향으로 나란히 가면 정렬(polarized)이다. 극성이 낮은데 회전만 높은 상태, 그러니까 무리가 통째로 옮겨 가지 않고 제 중심을 감고 도는 상태가 맴돌이(milling)다. 맴돌이를 가르는 것은 눈에 보이는 모양이 아니라 이 두 숫자다.
Δro는 1.0부터 12.0까지 바꿔 가며 값마다 새로 돌렸다. 값이 바뀔 때마다 독립된 초기조건에서 다시 시작했으니, 앞선 값에서 무리가 어떤 모양이었는지는 다음 값에 전혀 남지 않는다. 각 조건마다 2,000스텝을 버리고 이어지는 1,000스텝을 평균했고, 표에 적은 것은 시드 세 개의 평균값이다.
| Δro | 극성 | 회전 | 상태 |
|---|---|---|---|
| 1.0 | 0.187 | 0.208 | 뭉침 |
| 3.0 | 0.224 | 0.567 | 뭉침 |
| 4.0 | 0.135 | 0.658 | 맴돌이 |
| 6.0 | 0.247 | 0.690 | 맴돌이 |
| 8.0 | 0.294 | 0.681 | 맴돌이 |
| 9.0 | 0.828 | 0.195 | 정렬 |
| 12.0 | 0.999 | 0.014 | 정렬 |
표를 위에서 아래로 읽으면 순서가 보인다. 정렬 구역이 좁을 때는 뭉침이고, 넓히면 맴돌이로 넘어가고, 더 넓히면 정렬로 넘어간다. Δro가 3.0일 때 회전은 이미 0.567까지 올라와 있다. 그래도 판정 경계인 0.65에는 아직 못 미친다. 4.0에서 0.658로 경계를 넘어서고, 그 상태가 8.0까지 이어진다. 9.0에서는 극성이 0.828로 뛰고 회전이 0.195로 떨어진다.
그림 2는 같은 값들을 극성-회전 평면에 찍은 것이다. 맴돌이 세 점은 판정 상자 안에 들어가 있는데, 그것도 아래쪽 경계에 바짝 붙어 있다. 정렬 두 점은 둘 다 오른쪽 아래에 있다. 뭉침 두 점은 한자리에 모이지 않는다 — Δro가 1.0인 점은 왼쪽 아래에 있고, 3.0인 점은 같은 왼쪽이되 상자 바로 아래까지 올라와 있다. 가운데는 거의 비어 있고, 이 빈 공간이 상태 사이의 전이가 급하게 일어난다는 신호다.
뭉침에서 맴돌이를 거쳐 정렬로 가는 이 순서는 쿠진 연구팀이 2002년에 보고한 순서와 같다. 다만 전이가 일어나는 Δro 값 자체는 논문의 값이 아니다. 앞에 적어 둔 우리 파라미터에서 나온 값이다. 무엇보다 쿠진 연구팀의 모형은 3차원이고 이 재현은 평면 위에서 돌렸다. 같은 3존 규칙이라도 차원이나 속도, 선회 한계, 시야를 바꾸면 경계는 다른 자리로 옮겨 간다.
같은 규칙, 다른 결과 — 양안정과 이력
앞의 스윕에는 조건이 하나 붙어 있었다. Δro 값마다 매번 새 초기조건에서 시작해, 직전 값에서 무리가 어떤 상태였는지를 전혀 물려받지 않았다는 것이다. 이번에는 그 조건을 뗀다. 한 번은 좁은 Δro에서 출발해 값을 조금씩 올리면서 직전 상태를 그대로 이어받아 다음 값을 돌리고, 다른 한 번은 넓은 Δro에서 출발해 값을 내리면서 같은 식으로 이어 간다. 규칙도 같고 파라미터도 같다. 다른 것은 무리가 그 값에 닿기까지 지나온 길뿐이다.
| Δro | 올리며 이어감 (극성 / 회전) | 내리며 이어감 (극성 / 회전) |
|---|---|---|
| 7.0 | 0.288 / 0.673 — 맴돌이 | 0.996 / 0.024 — 정렬 |
| 8.0 | 0.306 / 0.645 — 맴돌이 근방 | 0.998 / 0.014 — 정렬 |
| 9.0 | 0.564 / 0.508 — 중간 | 0.998 / 0.010 — 정렬 |
같은 Δro 칸을 왼쪽과 오른쪽으로 나란히 읽어 보면 차이가 분명하다. 위로 이어 온 무리는 7.0과 8.0에서 여전히 돌고 있고, 9.0에서야 흔들리기 시작한다. 아래로 이어 온 무리는 7.0에서도 8.0에서도 극성 0.99대다.
8.0의 상행 회전값 0.645는 툰스트룀 기준의 경계 0.65에 아주 조금 못 미쳐, 엄밀히는 맴돌이로 분류되지 않는다. 그래도 하행이 같은 값에서 기록한 0.014와는 자릿수부터 다르다. 경계를 확실히 넘는 칸은 7.0이다. 같은 7.0이 위로 온 무리에게는 맴돌이고, 아래로 온 무리에게는 정렬이다.
두 갈래를 한 그림에 겹친 것이 그림 3다. 두 선이 한 번만 갈렸다가 붙는 것은 아니다. Δro가 2에서 3인 구간에서도 두 선은 한 차례 벌어졌다가 4에서 6 사이에서 다시 겹친다. 이쪽은 값을 옮겨 갈 때 생기는 일시적인 차이다. 내리며 이어 온 쪽이 정렬에서 물려받은 배치를 아직 털어내는 중이고, 두 선 모두 회전값이 원래 오르내리는 뭉침 구간 안에 있기 때문이다.
7에서 9 사이는 성격이 다르다. 여기서는 올리며 이어 온 선이 맴돌이 값에, 내리며 이어 온 선이 정렬 값에 각각 자리를 잡고 구간 내내 그대로 머문다. 앞의 벌어짐이 값을 옮기는 동안 잠깐 난 차이라면, 이쪽은 무리가 놓인 상태 자체가 갈린 것이다. 양안정 구간이라고 부를 수 있는 것은 7에서 9 사이뿐이다. 같은 규칙과 같은 파라미터 아래 안정한 상태가 둘 있고, 무리는 그중 자기가 이미 들어와 있던 쪽에 머문다.
제어변수를 한 방향으로 훑을 때와 반대 방향으로 훑을 때 결과가 갈리는 이 성질을 이력(hysteresis)이라고 부른다. 쿠진 연구팀은 2002년 논문에서 이 이력 의존성을 집단 기억(collective memory)이라고 불렀다. 기억이라는 말이 붙었지만 개체가 무언가를 저장하고 있는 것은 아니다. 무리가 지금 어떤 배치에 있는지가 다음 순간의 규칙 계산에 그대로 입력으로 들어가고, 그 입력이 다음 배치를 정한다.
무엇이 상태를 정하는가
개미 맴돌이를 두고 흔히 묻는 것은 "왜 갇혔나"다. 지금까지의 재현이 답한 것은 그 물음이 아니다. 페로몬을 걷어낸 입자 모형에는 냄새도 기억도 지능도 없었다. 그런데 정렬 구역의 폭 하나를 넓히자 무리는 뭉침에서 맴돌이로, 다시 정렬로 넘어갔다. 같은 폭에서도 어느 쪽에서 왔느냐에 따라 남는 상태가 갈렸다. 모형 안에서 무리의 상태를 정한 것은 개체의 판단이 아니라 규칙의 폭과 지나온 이력, 이 두 가지다.
그러면 모형 안에서 "갇혔다"는 말이 무엇을 가리키는지도 분명해진다. 맴돌이는 이 규칙계가 허용하는 두 안정 상태 가운데 하나이고, 무리는 그중 한쪽에 들어가 있다. 비비가 본 개미 고리를 여기에 그대로 포갤 수는 없다. 그쪽은 재료부터 페로몬이고, 고리가 하루를 넘긴 것도 결국 풀린 것도 앞에서 본 자국의 되먹임과 한 마리의 탈진이 설명한다. 재료가 달라도 같이 나오는 결론은 하나다. 냄새를 쓰는 무리에서도 쓰지 않는 무리에서도, 도는 상태에 들어가고 벗어나는 일을 가른 것은 국소규칙과 그때의 조건이다.
이 재현에서 Δro는 우리가 정해 넣은 값이지, 무리를 들여다봐서 읽어내는 양이 아니다. 야외의 개미 떼 앞에서 정렬 구역의 폭을 재는 방법은 이 글에 없다. 극성과 회전은 다르다. 궤적만 찍혀 있으면 개미든 물고기든 입자든 같은 식으로 계산해 볼 수 있다. 그러니 무리가 지금 어느 상태에 있는지는 두 숫자로 말할 수 있다. 무엇이 그 상태를 정했는지까지 말하려면, 규칙의 폭과 지나온 이력을 아는 모형이 있어야 한다.