📖 Документация Qumir

← Вернуться в Playground

← Все примеры

Гравитационная задача N тел

Пример показывает бесконечную 2D-анимацию системы из 64 взаимно притягивающихся тел. Тело с индексом 0 намного массивнее остальных, но оно не закреплено: получает ускорение от других тел и движется вместе со всей системой.

Модель

Для каждого тела хранятся масса, координаты, скорость и ускорение. На каждом шаге сначала обнуляются ускорения, затем один раз обрабатывается каждая пара i, j, где i < j:

dx := x[j] - x[i]
dy := y[j] - y[i]
r2 := dx * dx + dy * dy + SOFTENING2
invR3 := 1.0 / (sqrt(r2) * r2)

ax[i] := ax[i] + G * mass[j] * dx * invR3
ay[i] := ay[i] + G * mass[j] * dy * invR3
ax[j] := ax[j] - G * mass[i] * dx * invR3
ay[j] := ay[j] - G * mass[i] * dy * invR3

Оба тела пары обновляются одновременно равными и противоположными силами. Это полноценное взаимное N-body взаимодействие, а не набор независимых орбит в фиксированном центральном поле.

SOFTENING2 ограничивает ускорение при близком сближении тел. Без softening дискретный интегратор потребовал бы очень малого шага времени.

Интегрирование velocity Verlet

Начальные ускорения вычисляются до входа в цикл. На каждом шаге velocity Verlet сначала обновляет координаты с ускорением в начале шага:

x[i] := x[i] + vx[i] * DT + 0.5 * ax[i] * DT * DT
y[i] := y[i] + vy[i] * DT + 0.5 * ay[i] * DT * DT

После этого ускорения пересчитываются по новым координатам, а скорость корректируется средним ускорением в начале и конце шага:

vx[i] := vx[i] + 0.5 * (ax[i] + nextAx[i]) * DT
vy[i] := vy[i] + 0.5 * (ay[i] + nextAy[i]) * DT

Velocity Verlet существенно лучше сохраняет энергию орбитальной системы, чем явный или semi-implicit Euler при том же шаге времени.

Начальная скорость массивного тела компенсирует суммарный импульс остальных, поэтому центр масс системы не получает искусственный начальный дрейф.

Визуализация

У каждого тела сохраняются последние TAIL положений. Тело 0 рисуется крупнее, остальные получают разные оттенки. Вызов новый лист завершает кадр и подготавливает следующий чёрный лист.

Полная программа

использовать Рисователь

цел W = 900
цел H = 700
цел N = 64
цел TAIL = 20

вещ CX = 450.0
вещ CY = 350.0
вещ G = 1.0
вещ CENTRAL_MASS = 2400.0
вещ DT = 0.18
вещ SOFTENING2 = 36.0
вещ PI = 3.141592653589793

алг
нач
    вещ таб mass[0:N-1]
    вещ таб x[0:N-1], y[0:N-1], vx[0:N-1], vy[0:N-1]
    вещ таб ax[0:N-1], ay[0:N-1], nextAx[0:N-1], nextAy[0:N-1]
    вещ таб tx[0:N-1, 0:TAIL-1], ty[0:N-1, 0:TAIL-1]

    цел frame
    вещ t

    инициализировать(mass, x, y, vx, vy, tx, ty)
    вычислить_ускорения(mass, x, y, ax, ay)
    новый лист(W, H, чёрный)
    frame := 0
    t := 0.0

    нц пока да
        шаг_физики(mass, x, y, vx, vy, ax, ay, nextAx, nextAy, tx, ty)
        нарисовать(frame, t, x, y, tx, ty)
        frame := frame + 1
        t := t + DT
    кц
кон

алг инициализировать(арг рез вещ таб mass[0:N-1], арг рез вещ таб x[0:N-1], арг рез вещ таб y[0:N-1], арг рез вещ таб vx[0:N-1], арг рез вещ таб vy[0:N-1], арг рез вещ таб tx[0:N-1, 0:TAIL-1], арг рез вещ таб ty[0:N-1, 0:TAIL-1])
нач
    цел i, k
    вещ radius, angle, speed, speedScale
    вещ totalPx, totalPy

    mass[0] := CENTRAL_MASS
    x[0] := CX
    y[0] := CY
    vx[0] := 0.0
    vy[0] := 0.0
    totalPx := 0.0
    totalPy := 0.0

    нц для i от 1 до N - 1
        mass[i] := 0.7 + 1.8 * rnd(1.0)
        radius := 65.0 + 215.0 * rnd(1.0)
        angle := 2.0 * PI * rnd(1.0)
        speedScale := 0.96 + 0.08 * rnd(1.0)
        speed := sqrt(G * CENTRAL_MASS / radius) * speedScale

        x[i] := CX + radius * cos(angle)
        y[i] := CY + radius * sin(angle)
        vx[i] := -speed * sin(angle)
        vy[i] := speed * cos(angle)
        totalPx := totalPx + mass[i] * vx[i]
        totalPy := totalPy + mass[i] * vy[i]
    кц

    vx[0] := -totalPx / mass[0]
    vy[0] := -totalPy / mass[0]

    нц для i от 0 до N - 1
        нц для k от 0 до TAIL - 1
            tx[i, k] := x[i]
            ty[i, k] := y[i]
        кц
    кц
кон

алг вычислить_ускорения(вещ таб mass[0:N-1], вещ таб x[0:N-1], вещ таб y[0:N-1], арг рез вещ таб ax[0:N-1], арг рез вещ таб ay[0:N-1])
нач
    цел i, j
    вещ dx, dy, r2, invR, invR3, factor

    нц для i от 0 до N - 1
        ax[i] := 0.0
        ay[i] := 0.0
    кц

    нц для i от 0 до N - 2
        нц для j от i + 1 до N - 1
            dx := x[j] - x[i]
            dy := y[j] - y[i]
            r2 := dx * dx + dy * dy + SOFTENING2
            invR := 1.0 / sqrt(r2)
            invR3 := invR * invR * invR
            factor := G * invR3

            ax[i] := ax[i] + factor * mass[j] * dx
            ay[i] := ay[i] + factor * mass[j] * dy
            ax[j] := ax[j] - factor * mass[i] * dx
            ay[j] := ay[j] - factor * mass[i] * dy
        кц
    кц
кон

алг шаг_физики(вещ таб mass[0:N-1], арг рез вещ таб x[0:N-1], арг рез вещ таб y[0:N-1], арг рез вещ таб vx[0:N-1], арг рез вещ таб vy[0:N-1], арг рез вещ таб ax[0:N-1], арг рез вещ таб ay[0:N-1], арг рез вещ таб nextAx[0:N-1], арг рез вещ таб nextAy[0:N-1], арг рез вещ таб tx[0:N-1, 0:TAIL-1], арг рез вещ таб ty[0:N-1, 0:TAIL-1])
нач
    цел i, k

    нц для i от 0 до N - 1
        x[i] := x[i] + vx[i] * DT + 0.5 * ax[i] * DT * DT
        y[i] := y[i] + vy[i] * DT + 0.5 * ay[i] * DT * DT
    кц

    вычислить_ускорения(mass, x, y, nextAx, nextAy)

    нц для i от 0 до N - 1
        vx[i] := vx[i] + 0.5 * (ax[i] + nextAx[i]) * DT
        vy[i] := vy[i] + 0.5 * (ay[i] + nextAy[i]) * DT
        ax[i] := nextAx[i]
        ay[i] := nextAy[i]

        нц для k от TAIL - 1 до 1 шаг -1
            tx[i, k] := tx[i, k - 1]
            ty[i, k] := ty[i, k - 1]
        кц
        tx[i, 0] := x[i]
        ty[i, 0] := y[i]
    кц
кон

алг нарисовать(цел frame, вещ t, вещ таб x[0:N-1], вещ таб y[0:N-1], вещ таб tx[0:N-1, 0:TAIL-1], вещ таб ty[0:N-1, 0:TAIL-1])
нач
    цел i, k
    цел hue, alpha, radius

    нц для i от 0 до N - 1
        если i = 0 то
            hue := 45
            radius := 8
        иначе
            hue := mod(div(i * 360, N) + div(frame, 4), 360)
            radius := 2
        все

        нц для k от TAIL - 1 до 1 шаг -1
            alpha := 16 + (TAIL - k) * 8
            перо(1, HSLA(hue, 85, 60, alpha))
            кисть(HSLA(hue, 85, 60, alpha))
            окружность(int(tx[i, k]), int(ty[i, k]), 1)
        кц

        перо(1, HSL(hue, 90, 65))
        кисть(HSL(hue, 90, 65))
        окружность(int(x[i]), int(y[i]), radius)
    кц

    шрифт("Arial", 13, нет, нет)
    перо(1, RGB(160, 160, 160))
    надпись(8, 18, "N-body, t = " + вещ_в_лит(t))

    новый лист(W, H, чёрный)
кон

Полный исходный код находится в examples/painter/orbits.kum.

▶ Запустить пример