Двумерный газ

В этом листке-проекты мы создадим и визуализируем модель двумерного газа. Также мы поставим над этим газом некоторые эксперименты: посмотрим на броуновское движение, диффузию и т.п.

pygame

Делать визуальную модель мы будем при помощи библиотеки pygame. Для того, чтобы её установить, нужно выполнить команду pip install pygame --user в anaconda prompt или терминале. В Windows с большой вероятностью команда потерпит неудачу из-за отсутсвия компилятора языка C. Поэтому можно сразу отправиться на страницу собранных для windows пакетов и скачать оттуда подходящий whl-файл (в имени pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl 36 — это версия питона 3.6, win32/win_amd64 — это битность ОС). Дальше для установки пакета нужно будет из anaconda prompt или терминала выполнить команду вида
pip install "Y:\path\to\whl\pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl" --user
и дело в шляпе!

Библиотека pygame достаточно обширная, но нам от неё будет нужно не слишком много. Почти всё, что нужно, это — рисование элементарных фигур. Остальное вы будете делать руками.

Пример программы на pygame с шариками

from random import randint, uniform
import pygame
import sys

SCREEN_SIZE = WIDTH, HEIGHT = (600, 400)

# Готовим 100 шариков в случайных местах случайного радиуса и цвета
N = 100
circs = [
    {
        'xy': [uniform(0, WIDTH), uniform(0, HEIGHT)],
        'r': randint(2, 10),
        'color': [randint(0, 255), randint(0, 255), randint(0, 255)]
    }
    for __ in range(N)]
circs.sort(key=lambda circ: -circ['r'])  # Сортируем, чтобы большие не заслоняли маленьких

# Немного pygame-магии
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
fps = pygame.time.Clock()


# Обновляем коодинаты всех шариков
def update():
    for circ in circs:
        circ['xy'][0] += uniform(-5, 5) / circ['r']
        circ['xy'][1] += uniform(-5, 5) / circ['r']


# Чистим экран и отрисовываем каждый шарик
def render():
    screen.fill((0, 0, 0))  # Заливаем всё чёрным
    for circ in circs:
        pygame.draw.circle(screen, circ['color'], list(map(int, circ['xy'])), circ['r'], 0)
    pygame.display.update()
    fps.tick(60)  # Не обновляем экран чаще, чем 60 раз в секунду


# Главный цикл: пока на нажали крестик обновляем и отрисовываем
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    update()
    render()

A: Прямолинейное движение

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

B: Отскоки от стен

Сделайте так, чтобы шарики из предыдущей задачи отскакивали от стенок:

Упругое столкновение двух шаров на плоскости

Будем рассматривать только одновременное столкновение ровно двух шариков. Чтобы честно всё посчитать, потребуются закон сохранения импульса, закон сохранения энергии и закон сохранения момента импульса.

Пусть масса первого шарика — $m_1$, радиус — $r_1$, его скорость до столкновения — $\vec{v_1} = (v_{1x}, v_{1y})$, скорость после столкновения — $\vec{w_1} = (w_{1x}, w_{1y})$. А ещё обозначим через $\vec{c_1}$ вектор от точки контакта шаров до центра первого шара. Для второго шарика всё то же самое, только с индексом 2. Тогда закон сохранения момента требует, чтобы $$ m_1 \vec{v_1} + m_2 \vec{v_2} = m_1 \vec{w_1} + m_2 \vec{w_2}.$$ Закон сохранения энергии требует, чтобы $$ \frac{m_1 |\vec{v_1}|^2}{2} + \frac{m_2 |\vec{v_2}|^2}{2} = \frac{m_1 |\vec{w_1}|^2}{2} + \frac{m_2 |\vec{w_2}|^2}{2}. $$ Закон сохранения момента импульса более хитрый. Он требует, чтобы $$ [\vec{c_1}, m_1\vec{v_1}] + [\vec{c_2}, m_2\vec{v_2}] = [\vec{c_1}, m_1\vec{w_1}] + [\vec{c_2}, m_2\vec{w_2}]. $$ Всё вместе это даёт систему из 4 уравнений на 4 неизвестных — $x$- и $y$-компоненты новых скоростей шариков.

К счастью, если массы и радиусы шаров равны, то решать эту систему не потребуется. Перейдём в систему отсчёта первого шарика. В ней он покоится, а второй шарик натыкается на него со скоростью $\vec{v_2}-\vec{v_1}$. После столкновения вся часть этой скорости, направленная вдоль линии центров, достанется первому шарику, а ортогональная ей — второму. То есть после столкновения первый шар полетит вдоль линии центров, а второй — в направлении касательной.

Чтобы доказать это, пригодится система отсчёта общего центра масс, двигающая со скоростью $\dfrac{m_1\vec{v_1}+m_2\vec{v_2}}{m_1+m_2}=\dfrac{\vec{v_1}+\vec{v_2}}{2}$. Центр масс в ней неподвижен. Дальше попробуйте разобраться сами.

C: Столкновения — 1: все шарики одинаковы

Реализуйте столкновение одинаковых шаров. Алгорим, работающий за время $O(n^2)$, где $n$ — число шаров, подойдёт.

D: Столкновения — 2: одинаковые массы

Реализуйте столкновение шаров разного радиуса, но одинаковой массы. Алгорим, работающий за время $O(n^2)$, где $n$ — число шаров, подойдёт.

E: Столкновения — 3: общий случай

Рассчитайте столкновение шаров произвольного радиуса и произвольной массы. Можно либо решить систему уравнений (сложно), либо предварительно дополнительно пошаманить с системами отсчёта (станет проще), либо ещё подумать и сразу написать ответ без решения системы.

На вход даются:

Вычислите скорости первого и второго шара после их упругого столкновения.

1.0 1.0 10.0 0.0 -10.0 0.0 -1.0 0.0 1.0 0.0
-10.0 0.0 10.0 0.0
99.0 1.0 10.0 0.0 -10.0 0.0 -9.0 0.0 1.0 0.0
9.6 0.0 29.6 0.0
100 100 -5.944050560528412 -8.564544641233914 -3.126256984519088 7.602630729293933 0.6381731882506756 9.98254824345279 -0.6381731882506472 -9.982548243452786
-4.903238699022251 7.716231190674263 -4.167068846025249 -8.678145102614243
900 100 6.275207281781148 1.8525669980804769 -2.4707106152583087 12.334611148103816 -15.828382139122077 25.495324853000263 5.276127379707354 -8.49844161766675
4.849142448095183 4.149579154894633 10.36387288791537 -8.338498263223586

F: Столкновения — 4: общий случай

Реализуйте столкновение шаров произвольного радиуса и произвольной массы.

G: Броуновское движение

Сделайте модель броуновского движения.

H: Перегородки

Добавьте возможность расположить в поле произвольное количество прямолинейных перегородок. От них шарики-молекулы тоже должны отскакивать. Границы «сосуда» теперь можно сделать из этих перегородок.

I: Диффузия газов

Сделайте «сосуд», состоящий из двух одинаковых камер. В перегородке между камерами должно быть маленькое отверстие. В левую камеру поместите «горячие» красные молекулы. В правую камеру поместите очень-очень холодные синие молекулы. Проведите несколько экспериментов: с разной разницей «температур» и разным размером отверстия.

J: Сопло ракетного двигателя

В этом эксперименте мы будем изучать сопло ракетного двигателя. Сам двигатель в этой задаче — это просто квадрат с маленьким отверстием, смотрящим направо. Внуть квадрата нужно поместить много-много очень-очень горячих молекул. В этом эксперименте можно отключить взаимодействия молекул друг с другом, оставив только взаимодействия со стенками. После того, как половина молекул вылетет из «двигателя», симуляцию нужно остановить и посчитать среднюю проекцию вылетевших молекул на ось $Ox$ (так как только эта часть момента нужна ракете). Попробуйте приделать к отверстию «сопло», для начала просто рупор. И сравнить результат. Попробуйте объяснить, почему это происходит.

K: Сопло Лаваля

Попробуйте «изобразить» сопло Лаваля и провести эксперимент из предыдущей задачи для него. Скорее всего, ничего интересного не получится, но вдруг? Это было бы очень круто :)

L: Распределение Максвелла

Вернёмся к обычному «ящику» с молекулами. Отрисовывайте на каждом кадре распределение молекул по модулю скоростей. Да-да, график придётся рисовать «руками» по пикселям. Изучите типичные скорости в вашем газе. Разбейте диапазон от 0 до максимальной скорости на, скажем, 100 частей. Посчитайте для каждой, сколько молекул имеют модуль скорости из этой части. И нарисуйте соответствующую гистрограмму. Изучите её вид при разной «температуре» газа.

M: Атмосфера

Добавьте в высокий-высокий ящик гравитацию. Разбейте правую стенку сосуда, скажем, на 100 частей. Для каждой посчитайте «давление». И нарисуйте гистрограмму.