Давным-давно я прочитал про Демона Лапласа. Это мысленный эксперимент, предложенный французским математиком Пьером-Симоном Лапласом. Суть этого эксперимента в том, что вымышленное существо в определенный момент времени измеряет положение и скорость каждой частицы во Вселенной и таким образом может рассчитать положение любой частицы в любой момент времени: и в будущем, и в прошлом. То есть такое существо смогло бы узнать все, что когда-либо происходило во Вселенной и будет происходить.
Нетрудно догадаться, что такое существо (или машина) невозможно по нескольким причинам. Во-первых, предсказания существа влияют на будущее (например, на мысли этого существа) и существо в каждом своем предсказании должно было бы учитывать влияние результата предсказания на будущее, потом снова влияние нового предсказания на будущее и т.д. Получилась бы своего рода бесконечная рекурсия. Во-вторых, существует принцип неопределенности, согласно которому при повышении точности измерения одной характеристики (в данном случае скорости или положения) точность измерения другой падает. То есть существо не смогло бы достаточно точно измерить положения и скорости частиц, тем самым сделав ошибку предсказания ощутимой. И в-третьих, каким образом возможно измерить положение и скорость всех частиц во Вселенной?
Но ничто не мешает нам создать "упрощенного" Демона Лапласа. Если мы возьмем систему тел, которую можно считать "почти замкнутой" (например, Солнечная система), и определим положение и скорость не каждой частицы (что мы сделать не сможем), а выделим какие-то группы частиц, которые можно объединить в одно тело (планету) и будем рассчитывать положения исходя из этих тел, то получим приемлемую точность, в зависимости от задачи.
Таким вот образом мы будем моделировать движение небесных тел.
Часть 1. Бэкэнд
Переходим к разработке программы. Принцип работы программы довольно прост: мы разбиваем путь тела на много маленьких отрезков (каждый из которых тело проходит за T секунд), на котором считаем ускорение постоянным. Чем меньше будет величина T, тем меньше будут эти отрезки, и тем точнее получится итоговая траектория. С графикой особо напрягаться не будем и используем pygame.
Из физики нам понадобится только одна формула: . Закон всемирного тяготения.
У нас будет набор параметров, куда положим гравитационную постоянную, единичный промежуток времени и масштаб изображения в метрах на пиксель. Пригодится он нам позже.
params = {
"G" : 6.6743E-11,
"T" : 3600,
"scale" : 6E+8
}
В программе будет 2 класса: класс всей системы и класс отдельного тела. При каждой итерации объект класса системы будет вызывать функцию итерации у каждого тела в этой системе, передавая туда параметры. Переходим к написанию класса тела:
class Body:
def __init__(self, m, x, y, vx, vy, R, color='red'):
self.m = m # масса тела в кг
self.x = x # начальная координата в м
self.y = y # начальная координата в м
self.vx = vx # начальная скорость по x в м/с
self.vy = vy # начальная скорость по y в м/с
self.R = R # радиус круга pygame в пикселях
self.color = color # цвет тела
self.ax = self.ay = 0 # инициализируем ускорения сил, действующих на тело
Теперь пишем функцию итерации в классе. Для этого нам нужно разложить по осям силу тяготения:
Немного поколдовав с Законом всемирного тяготения и Вторым законом Ньютона получаем:
В формуле это координаты тела, для которого мы ищем ускорение. А - координаты тела, которое на него действует.
Функция итерации тела будет принимать параметры нашей системы (T, G) и list из всех объектов, кроме текущего, далее высчитывать суммарные ускорения сил, действующих на тело и раскладывать по осям. Нам понадобится модуль math.
def iterate(self, objects, params):
self.ax = self.ay = 0 #обнуляем ускорение для пересчета
G = params['G']
T = params['T']
# считаем ускорение от всех сил
for obj in objects:
self.ax += G * obj.m * (obj.x - self.x) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.ay += G * obj.m * (obj.y - self.y) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.x += self.vx * T + self.ax * T**2 / 2 # по формуле равноускоренного движения считаем новую координату
self.y += self.vy * T + self.ay * T**2 / 2
self.vx += self.ax * T # считаем новую скорость в конце отрезка
self.vy += self.ay * T
Полный код программы на данный момент:
import pygame
import math
class Body:
def __init__(self, m, x, y, vx, vy, R, color='red'):
self.m = m # масса тела в кг
self.x = x # начальная координата в м
self.y = y # начальная координата в м
self.vx = vx # начальная скорость по x в м/с
self.vy = vy # начальная скорость по y в м/с
self.R = R # радиус круга pygame в пикселях
self.color = color # цвет тела
self.ax = self.ay = 0 # инициализируем ускорения сил, действующих на тело
def iterate(self, objects, params):
self.ax = self.ay = 0 #обнуляем ускорение для пересчета
G = params['G']
T = params['T']
# считаем ускорение от всех сил
for obj in objects:
self.ax += G * obj.m * (obj.x - self.x) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.ay += G * obj.m * (obj.y - self.y) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.x += self.vx * T + self.ax * T**2 / 2 # по формуле равноускоренного движения считаем новую координату
self.y += self.vy * T + self.ay * T**2 / 2
self.vx += self.ax * T # считаем новую скорость в конце отрезка
self.vy += self.ay * T
Теперь переходим к классу системы.
class System:
def __init__(self, objects, params):
self.objects = objects
self.params = params
self.t = 0 # счетчик прошедшего времени
def iterate(self):
for i in range(len(self.objects)):
obj = self.objects[i]
obj.iterate(self.objects[:i] + self.objects[i+1:], self.params) # вызывается функция итерации, и в качестве аргумента передаются все объекты, кроме текущего
self.t += self.params['T']
Вычислительная часть закончена, переходим дальше.
Часть 2. Графика
Сейчас мы настроим pygame, создадим объект класса системы, добавим туда несколько тел и будем в цикле pygame отрисовывать кружок с заданным радиусом, цветом и координатами, после чего вызывать функцию итерации системы.
Только еще нужно немного повозиться с конвертацией координат для отображения на экране. Для этого у нас есть масштаб, а также не забываем, что направление оси y в pygame противоположно (да и начало координат в левом верхнем углу, а нам нужно в центре). Еще pygame автоматически преобразует float в int, поэтому используем округление.
def convert(point, scale, DISPLAY):
return (round(point[0] / scale + DISPLAY[0]/2.0), round(-point[1] / scale + DISPLAY[1] / 2.0))
Теперь переходим к pygame. Для ускорения работы программы мы можем сделать 2 вещи: либо увеличить временной промежуток T, что уменьшит точность, либо отрисовывать не каждый кадр, а только каждый n-ый, что мы и сделаем. И если вы не хотите, чтобы fps зависело напрямую от скорости вычислений (которая колеблется довольно амплитудно), стоит добавить time.sleep(0.0001) в каждый кадр, поэтому импортируем еще time.
def main():
pygame.init()
DISPLAY = [700,700]
screen = pygame.display.set_mode(DISPLAY)
running = True
earth = Body(m=5.97E+24, x=150E+9, y=0, vx=0, vy=30000, R=5, color='blue')
sun = Body(m=1.99E+30, x=0, y=0, vx=0, vy=0, R=10, color='yellow')
params = {
"G" : 6.6743E-11,
"T" : 3600,
"scale" : 6E+8
}
solar_system = System([earth, sun], params)
n = 25
count = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if count % n == 0:
screen.fill((20, 20, 20))
for obj in solar_system.objects:
pygame.draw.circle(screen, obj.color, convert((obj.x, obj.y), params['scale'], DISPLAY), obj.R)
pygame.display.flip()
solar_system.iterate()
count += 1
time.sleep(0.0001)
pygame.quit()
if __name__ == '__main__':
main()
Получилась вот такая орбита Земли!
Позднее напишу про то, что можно сделать с этим простейшим симулятором.
Весь код:
import pygame
import math
import time
class Body:
def __init__(self, m, x, y, vx, vy, R, color='red'):
self.m = m # масса тела в кг
self.x = x # начальная координата в м
self.y = y # начальная координата в м
self.vx = vx # начальная скорость по x в м/с
self.vy = vy # начальная скорость по y в м/с
self.R = R # радиус круга pygame в пикселях
self.color = color # цвет тела
self.ax = self.ay = 0 # инициализируем ускорения сил, действующих на тело
def iterate(self, objects, params):
self.ax = self.ay = 0 #обнуляем ускорение для пересчета
G = params['G']
T = params['T']
# считаем ускорение от всех сил
for obj in objects:
self.ax += G * obj.m * (obj.x - self.x) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.ay += G * obj.m * (obj.y - self.y) / math.sqrt((obj.x - self.x)**2 + (obj.y - self.y)**2)**3
self.x += self.vx * T + self.ax * T**2 / 2 # по формуле равноускоренного движения считаем новую координату
self.y += self.vy * T + self.ay * T**2 / 2
self.vx += self.ax * T # считаем новую скорость в конце отрезка
self.vy += self.ay * T
class System:
def __init__(self, objects, params):
self.objects = objects
self.params = params
self.t = 0 # счетчик прошедшего времени
def iterate(self):
for i in range(len(self.objects)):
obj = self.objects[i]
obj.iterate(self.objects[:i] + self.objects[i+1:], self.params)
self.t += self.params['T']
def convert(point, scale, DISPLAY):
return (round(point[0] / scale + DISPLAY[0]/2.0), round(-point[1] / scale + DISPLAY[1] / 2.0))
def main():
pygame.init()
DISPLAY = [700,700]
screen = pygame.display.set_mode(DISPLAY)
running = True
earth = Body(m=5.97E+24, x=150E+9, y=0, vx=0, vy=30000, R=5, color='blue')
sun = Body(m=1.99E+30, x=0, y=0, vx=0, vy=0, R=10, color='yellow')
params = {
"G" : 6.6743E-11,
"T" : 3600,
"scale" : 6E+8
}
solar_system = System([earth, sun], params)
n = 25
count = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if count % n == 0:
screen.fill((20, 20, 20))
for obj in solar_system.objects:
pygame.draw.circle(screen, obj.color, convert((obj.x, obj.y), params['scale'], DISPLAY), obj.R)
pygame.display.flip()
solar_system.iterate()
count += 1
time.sleep(0.0001)
pygame.quit()
if __name__ == '__main__':
main()
Комментарии (22)
V_Scalar
08.11.2023 11:05-2Опять эта чушь от слепых кротов счетоводов. Зачем вам рассчитывать все эти 10^ 500 графов , имеет значение только одно, у которого самый большой вес. А определяет его геометрия свёрнутого пространства, есть ли спин заряда и спин переносчика сопряжённые, то это и будет то самое единственное значение имеющее смысл. Все остальные идут в балк
V_Scalar
08.11.2023 11:05-1Тут проблема ландшафта не поднималась.
10500 не только проблема ландшафта - это уже мем на все случаи????
myswordishatred
08.11.2023 11:05Во-вторых, существует принцип неопределенности, согласно которому при
повышении точности измерения одной характеристики (в данном случае
скорости или положения) точность измерения другой падает. То есть
существо не смогло бы достаточно точно измерить положения и скорости
частиц, тем самым сделав ошибку предсказания ощутимой. И в-третьих,
каким образом возможно измерить положение и скорость всех частиц во
Вселенной?Какое-то докапывание до заклёпок, честно говоря. Эксперимент на то и мысленный, чтобы предположить последствия существования такой сущности. Вы же, когда теоремы про треугольники читаете не начинаете возражать в духе "В природе не бывает абсолютно прямых углов!"?
ksbes
08.11.2023 11:05Тут есть фундаментальный вопрос: информация не может распространятся со скоростью света. Потому измерить положение всех частиц вроде как нельзя - только в своём световом конусе. И этого нам вроде как достаточно? Или нет? Могут ли частицы вне нашего светового конуса повлиять на точность наши расчётов?
Tyusha
08.11.2023 11:05Да причём здесь это. Автор находится в рамках определённой модели, и решает вполне конкретную задачу — задачу классической небесной механики.
Tyusha
08.11.2023 11:05+1Тут вопрос не в этом. Когда автор поступит в вуз (он ведь школьник, правда?) и будет изучать вычислительную математику, то узнает, что бывают устойчивые разностные схемы, а бывают неустойчивые. Составление правильной (устойчивой) разностной схемы гораздо важнее, чем физические факторы точности начальных условий. Но в данной его работе разностная схема по счастливому стечению обстоятельств устойчивая, хоть и вышло это у автора, скорее всего, не осознанно.
MANAB
08.11.2023 11:05Мне было бы интересно посмотреть чуть более приближенную к реальности имплементацию. Учитывая, что гравитационное воздействие не мгновенное, а действует со скоростью света, как бы вы реализовали данную программу?
a-tk
08.11.2023 11:05Это делается через редукцию к эпохе объекта: в расчётах используется положение на момент, отстоящий в прошлое на световую задержку. Можно считать в 2 итерации, потому что за время световой задержки смещение будет второго порядка малости, можно не учитывать.
MANAB
08.11.2023 11:05Для Солнца-Земли я понимаю, что незначительно в космических рамках. Но мне именно интересна общая имплементация. И вообще, когда что то показывают/рассказывают о том, что заняло больше времени/сил, чем само написание статьи. Хотя хорошиая тема и самому статью написать видимо)
a-tk
08.11.2023 11:05https://www.studmed.ru/abalakin-vk-osnovy-efemeridnoy-astronomii_bf06b94ea4d.html
Вот тут можно почитать :)
По результатам прочтения можете написать статейку.
Tyusha
08.11.2023 11:05Это плохая идея. Задержка гравитационного взаимодействия — это по сути теория относительности. Но если мы начинаем добавлять эффекты ОТО, то помимо задержки взаимодействия придëтся учитывать и искривление пространства-времени. Одно без другого не имеет никакого смысла, т.к. это эффекты одинакового порядка малости для Солнечной системы.
Ну а решение задачи двух тел в ОТО — это уже совершенно другой уровень сложности математики, на два порядка круче, чем представленная работа. Не надо требовать от автора прикручивать к классической модели ненужные костыли. Она хороша сама по себе.
a-tk
Первый вопрос: через какой интервал времени большая полуось изменится, скажем, на 10% вследствие накопления ошибок?
bazilevichla
Запустил программу у себя, добавив проверки. На 1 круге нахожу максимальное R, дальше тупо ищу превышение на >=10%, после чего останавливаю прогу и вывожу count и solar_system.t
Ответ на ваш вопрос следующий:
10% error on count: 146050
system_time: 525780000
UPD: накосячил с поиском большой полуоси сначала, вот реальный ответ:
Max R at first cycle: 150205898566.54425
10% error on count: 146325
system_time: 526770000
Current R: 165244120250.3972
bazilevichla
Мда, кому-то стоит больше спать, ибо я нашёл ещё одну ошибку у себя. Вот теперь уверен в ответе:
Max R at first cycle: 155360521536.66812
10% error on count: 237825
system_time: 856170000
Current R: 170919952686.96307
a-tk
Осталось только понять, почему это так получилось, разобраться как изменяется ошибка при изменении шага интегрирования, потом разобраться какие методы численного интегрирования бывают в принципе и как для них определяется ошибка.
А если хотите поиграться, то задайте начальную скорость больше или меньше и смотрите за результатом. Потом поменяйте направление вектора скорости и тоже последите за результатом.
А потом откройте нормальную книжку по астрономии, где матчасть орбитального движения описана.