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

Нетрудно догадаться, что такое существо (или машина) невозможно по нескольким причинам. Во-первых, предсказания существа влияют на будущее (например, на мысли этого существа) и существо в каждом своем предсказании должно было бы учитывать влияние результата предсказания на будущее, потом снова влияние нового предсказания на будущее и т.д. Получилась бы своего рода бесконечная рекурсия. Во-вторых, существует принцип неопределенности, согласно которому при повышении точности измерения одной характеристики (в данном случае скорости или положения) точность измерения другой падает. То есть существо не смогло бы достаточно точно измерить положения и скорости частиц, тем самым сделав ошибку предсказания ощутимой. И в-третьих, каким образом возможно измерить положение и скорость всех частиц во Вселенной?

Но ничто не мешает нам создать "упрощенного" Демона Лапласа. Если мы возьмем систему тел, которую можно считать "почти замкнутой" (например, Солнечная система), и определим положение и скорость не каждой частицы (что мы сделать не сможем), а выделим какие-то группы частиц, которые можно объединить в одно тело (планету) и будем рассчитывать положения исходя из этих тел, то получим приемлемую точность, в зависимости от задачи.

Таким вот образом мы будем моделировать движение небесных тел.

Часть 1. Бэкэнд

Переходим к разработке программы. Принцип работы программы довольно прост: мы разбиваем путь тела на много маленьких отрезков (каждый из которых тело проходит за T секунд), на котором считаем ускорение постоянным. Чем меньше будет величина T, тем меньше будут эти отрезки, и тем точнее получится итоговая траектория. С графикой особо напрягаться не будем и используем pygame.

Из физики нам понадобится только одна формула: F = G \cdot \frac{m_{1} \cdot m_{2}}{r^2}{} . Закон всемирного тяготения.

У нас будет набор параметров, куда положим гравитационную постоянную, единичный промежуток времени и масштаб изображения в метрах на пиксель. Пригодится он нам позже.

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 # инициализируем ускорения сил, действующих на тело

Теперь пишем функцию итерации в классе. Для этого нам нужно разложить по осям силу тяготения:

Немного поколдовав с Законом всемирного тяготения и Вторым законом Ньютона получаем:

В формуле x_o, y_o это координаты тела, для которого мы ищем ускорение. А x, y - координаты тела, которое на него действует.

Функция итерации тела будет принимать параметры нашей системы (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()

by Real Universe

Комментарии (22)


  1. a-tk
    08.11.2023 11:05
    +2

    Получилась вот такая орбита Земли!

    Первый вопрос: через какой интервал времени большая полуось изменится, скажем, на 10% вследствие накопления ошибок?


    1. bazilevichla
      08.11.2023 11:05

      Запустил программу у себя, добавив проверки. На 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


      1. bazilevichla
        08.11.2023 11:05

        Мда, кому-то стоит больше спать, ибо я нашёл ещё одну ошибку у себя. Вот теперь уверен в ответе:
        Max R at first cycle: 155360521536.66812
        10% error on count: 237825
        system_time: 856170000
        Current R: 170919952686.96307


        1. a-tk
          08.11.2023 11:05

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

          А если хотите поиграться, то задайте начальную скорость больше или меньше и смотрите за результатом. Потом поменяйте направление вектора скорости и тоже последите за результатом.

          А потом откройте нормальную книжку по астрономии, где матчасть орбитального движения описана.


  1. Tyusha
    08.11.2023 11:05
    +5

    Для 10 класса пойдëт. Зачем минусов накидали, это же школьник наверное.


  1. V_Scalar
    08.11.2023 11:05
    -2

    Опять эта чушь от слепых кротов счетоводов. Зачем вам рассчитывать все эти 10^ 500 графов , имеет значение только одно, у которого самый большой вес. А определяет его геометрия свёрнутого пространства, есть ли спин заряда и спин переносчика сопряжённые, то это и будет то самое единственное значение имеющее смысл. Все остальные идут в балк


    1. Tyusha
      08.11.2023 11:05

      Очень интересно, но видимо, не там. Тут проблема ландшафта не поднималась.


  1. V_Scalar
    08.11.2023 11:05
    -1

     Тут проблема ландшафта не поднималась.

    10500 не только проблема ландшафта - это уже мем на все случаи????


  1. TitovVN1974
    08.11.2023 11:05

    Автору: Изучите численные методы для выбора/построения корректной схемы.


  1. Maxh
    08.11.2023 11:05

    Я один кто после таких постов вспоминает "Задача трех тел"?


    1. a-tk
      08.11.2023 11:05

      Несколько сотен тысяч тел неплохо себя чувствуют после распила зон гравитационного влияния.


      1. konst90
        08.11.2023 11:05

        Какой распил зон? Каждое тело взаимодействует с каждым.

        Или вы про объединение в планету?


        1. a-tk
          08.11.2023 11:05

          Я про Солнечную систему.

          Планеты заняли свои устойчивые орбиты, планеты имеют спутники.

          В целом на масштабах последних нескольких миллиардов лет система устойчива.


  1. myswordishatred
    08.11.2023 11:05

    Во-вторых, существует принцип неопределенности, согласно которому при
    повышении точности измерения одной характеристики (в данном случае
    скорости или положения) точность измерения другой падает. То есть
    существо не смогло бы достаточно точно измерить положения и скорости
    частиц, тем самым сделав ошибку предсказания ощутимой. И в-третьих,
    каким образом возможно измерить положение и скорость всех частиц во
    Вселенной?

    Какое-то докапывание до заклёпок, честно говоря. Эксперимент на то и мысленный, чтобы предположить последствия существования такой сущности. Вы же, когда теоремы про треугольники читаете не начинаете возражать в духе "В природе не бывает абсолютно прямых углов!"?


    1. ksbes
      08.11.2023 11:05

      Тут есть фундаментальный вопрос: информация не может распространятся со скоростью света. Потому измерить положение всех частиц вроде как нельзя - только в своём световом конусе. И этого нам вроде как достаточно? Или нет? Могут ли частицы вне нашего светового конуса повлиять на точность наши расчётов?


      1. Tyusha
        08.11.2023 11:05

        Да причём здесь это. Автор находится в рамках определённой модели, и решает вполне конкретную задачу — задачу классической небесной механики.


    1. Tyusha
      08.11.2023 11:05
      +1

      Тут вопрос не в этом. Когда автор поступит в вуз (он ведь школьник, правда?) и будет изучать вычислительную математику, то узнает, что бывают устойчивые разностные схемы, а бывают неустойчивые. Составление правильной (устойчивой) разностной схемы гораздо важнее, чем физические факторы точности начальных условий. Но в данной его работе разностная схема по счастливому стечению обстоятельств устойчивая, хоть и вышло это у автора, скорее всего, не осознанно.


  1. MANAB
    08.11.2023 11:05

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


    1. a-tk
      08.11.2023 11:05

      Это делается через редукцию к эпохе объекта: в расчётах используется положение на момент, отстоящий в прошлое на световую задержку. Можно считать в 2 итерации, потому что за время световой задержки смещение будет второго порядка малости, можно не учитывать.


      1. MANAB
        08.11.2023 11:05

        Для Солнца-Земли я понимаю, что незначительно в космических рамках. Но мне именно интересна общая имплементация. И вообще, когда что то показывают/рассказывают о том, что заняло больше времени/сил, чем само написание статьи. Хотя хорошиая тема и самому статью написать видимо)


        1. a-tk
          08.11.2023 11:05

          https://www.studmed.ru/abalakin-vk-osnovy-efemeridnoy-astronomii_bf06b94ea4d.html

          Вот тут можно почитать :)

          По результатам прочтения можете написать статейку.


    1. Tyusha
      08.11.2023 11:05

      Это плохая идея. Задержка гравитационного взаимодействия — это по сути теория относительности. Но если мы начинаем добавлять эффекты ОТО, то помимо задержки взаимодействия придëтся учитывать и искривление пространства-времени. Одно без другого не имеет никакого смысла, т.к. это эффекты одинакового порядка малости для Солнечной системы.

      Ну а решение задачи двух тел в ОТО — это уже совершенно другой уровень сложности математики, на два порядка круче, чем представленная работа. Не надо требовать от автора прикручивать к классической модели ненужные костыли. Она хороша сама по себе.