Упражнение: 2D моделирование орбитальной механики (питон)

12

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

Вы все можете выбрать этот проект в любое время и весело провести время!

Обновить!!! (Nov10)

  • скорость теперь правильная deltaV и при дополнительном движении теперь вычисляется суммарный вектор скорости
  • Вы можете разместить столько статических объектов, сколько захотите, на каждый объект времени, находящийся в движении, проверяющий векторы гравитации из всех источников (и проверяющий на столкновение)
  • значительно улучшена производительность расчетов
  • исправление для учета интерактивного мода в matplotlib. Похоже, что это опция по умолчанию только для ipython. Обычный python3 требует этого утверждения явно.

По сути, теперь можно «запускать» космический корабль с поверхности Земли и планировать полет на Луну, внося поправки на вектор deltaV с помощью метода giveMotion (). Далее на очереди попытка реализовать глобальную переменную времени, чтобы обеспечить одновременное движение, например, Луна вращается вокруг Земли, в то время как космический корабль испытывает маневр гравитации.

Комментарии и предложения по улучшению всегда приветствуются!

Сделано в Python3 с библиотекой matplotlib

import matplotlib.pyplot as plt
import math
plt.ion()

G = 6.673e-11  # gravity constant
gridArea = [0, 200, 0, 200]  # margins of the coordinate grid
gridScale = 1000000  # 1 unit of grid equals 1000000m or 1000km

plt.clf()  # clear plot area
plt.axis(gridArea)  # create new coordinate grid
plt.grid(b="on")  # place grid

class Object:
    _instances = []
    def __init__(self, name, position, radius, mass):
        self.name = name
        self.position = position
        self.radius = radius  # in grid values
        self.mass = mass
        self.placeObject()
        self.velocity = 0
        Object._instances.append(self)

    def placeObject(self):
        drawObject = plt.Circle(self.position, radius=self.radius, fill=False, color="black")
        plt.gca().add_patch(drawObject)
        plt.show()

    def giveMotion(self, deltaV, motionDirection, time):
        if self.velocity != 0:
            x_comp = math.sin(math.radians(self.motionDirection))*self.velocity
            y_comp = math.cos(math.radians(self.motionDirection))*self.velocity
            x_comp += math.sin(math.radians(motionDirection))*deltaV
            y_comp += math.cos(math.radians(motionDirection))*deltaV
            self.velocity = math.sqrt((x_comp**2)+(y_comp**2))

            if x_comp > 0 and y_comp > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity))  # update motion direction
            elif x_comp > 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 90
            elif x_comp < 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 270

        else:
            self.velocity = self.velocity + deltaV  # in m/s
            self.motionDirection = motionDirection  # degrees
        self.time = time  # in seconds
        self.vectorUpdate()

    def vectorUpdate(self):
        self.placeObject()
        data = []

        for t in range(self.time):
            motionForce = self.mass * self.velocity  # F = m * v
            x_net = 0
            y_net = 0
            for x in [y for y in Object._instances if y is not self]:
                distance = math.sqrt(((self.position[0]-x.position[0])**2) +
                             (self.position[1]-x.position[1])**2)
                gravityForce = G*(self.mass * x.mass)/((distance*gridScale)**2)

                x_pos = self.position[0] - x.position[0]
                y_pos = self.position[1] - x.position[1]

                if x_pos <= 0 and y_pos > 0:  # calculate degrees depending on the coordinate quadrant
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+90

                elif x_pos > 0 and y_pos >= 0:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))+180

                elif x_pos >= 0 and y_pos < 0:
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+270

                else:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))

                x_gF = gravityForce * math.sin(math.radians(gravityDirection))  # x component of vector
                y_gF = gravityForce * math.cos(math.radians(gravityDirection))  # y component of vector

                x_net += x_gF
                y_net += y_gF

            x_mF = motionForce * math.sin(math.radians(self.motionDirection))
            y_mF = motionForce * math.cos(math.radians(self.motionDirection))
            x_net += x_mF
            y_net += y_mF
            netForce = math.sqrt((x_net**2)+(y_net**2))

            if x_net > 0 and y_net > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce))  # update motion direction
            elif x_net > 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 90
            elif x_net < 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 270

            self.velocity = netForce/self.mass  # update velocity
            traveled = self.velocity/gridScale  # grid distance traveled per 1 sec
            self.position = (self.position[0] + math.sin(math.radians(self.motionDirection))*traveled,
                             self.position[1] + math.cos(math.radians(self.motionDirection))*traveled)  # update pos
            data.append([self.position[0], self.position[1]])

            collision = 0
            for x in [y for y in Object._instances if y is not self]:
                if (self.position[0] - x.position[0])**2 + (self.position[1] - x.position[1])**2 <= x.radius**2:
                    collision = 1
                    break
            if collision != 0:
                print("Collision!")
                break

        plt.plot([x[0] for x in data], [x[1] for x in data])

Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(100.0, 100.0), radius=1.737, mass = 7.347e22)  # position not to real scale
Craft = Object(name="SpaceCraft", position=(49.0, 40.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)
Craft.giveMotion(deltaV=2000.0, motionDirection=90, time=60000)
plt.show(block=True)

Как это устроено

Все сводится к двум вещам:

  1. Создание объекта Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)с параметрами положения на сетке (1 единица сетки по умолчанию составляет 1000 км, но это также можно изменить), радиус в единицах сетки и масса в кг.
  2. Придание объекту некоторого deltaV, такого как, Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)очевидно, его необходимо Craft = Object(...)создать в первую очередь, как упоминалось в предыдущем пункте. Параметры здесь указаны deltaVв м / с (обратите внимание, что на данный момент ускорение является мгновенным), motionDirectionэто направление дельта в градусах (из текущей позиции представьте круг на 360 градусов вокруг объекта, так что направление - это точка на этом круге), и, наконец, параметр time- это сколько секунд после deltaV push будет отслеживаться траектория движения объекта. Последующий giveMotion()старт с последней позиции предыдущего giveMotion().

Вопросов:

  1. Это правильный алгоритм для расчета орбит?
  2. Какие очевидные улучшения должны быть сделаны?
  3. Я рассматривал переменную "timeScale", которая оптимизирует вычисления, так как может не потребоваться пересчитывать векторы и позиции для каждой секунды. Любые мысли о том, как это должно быть реализовано или это вообще хорошая идея? (потеря точности против улучшенной производительности)

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

Не стесняйтесь экспериментировать!

Попробуйте использовать:

Earth = Object(name="Earth", position=(50.0, 100.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(434.0, 100.0), radius=1.737, mass = 7.347e22)
Craft = Object(name="SpaceCraft", position=(43.0, 100.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=10575.0, motionDirection=180, time=322000)
Craft.giveMotion(deltaV=400.0, motionDirection=180, time=50000)

С двумя ожогами - одним градусом на околоземной орбите и ретроградным на орбите Луны, я достиг стабильной орбиты Луны. Они близки к теоретически ожидаемым значениям?

Предлагаемое упражнение: Испытайте его в 3 ожогах - стабильная околоземная орбита с поверхности Земли, прогрессивное горение для достижения Луны, ретроградное горение для стабилизации орбиты вокруг Луны Затем попытайтесь минимизировать deltaV.

Примечание: я планирую обновить код обширными комментариями для тех, кто не знаком с синтаксисом python3.

statespace
источник
Очень хорошая идея для самообразования! Можно ли обобщить ваши формулы для тех из нас, кто не знаком с синтаксисом Python?
Конечно, я думаю. Я сделаю более подробные комментарии в коде для тех, кто хочет его поднять, и суммирую общую логику в самом вопросе.
Statespace
От макушки головы: подумайте об использовании вектора скорости вместо того, чтобы по-разному трактовать скорость и направление. Где вы говорите «F = m * v», вы имеете в виду «F = m * a»? Вы предполагаете, что Земля не движется, потому что она намного тяжелее астероида? Подумайте о том, чтобы посмотреть на github.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter
Вы можете дать движение любому объекту, включая Землю. Для тестирования я включил только объект -> отношение Земли в основной цикл. Можно легко преобразовать, что каждый объект относится ко всем другим объектам, которые созданы. И каждый объект может иметь свой собственный вектор движения. Причина, по которой я этого не делал - очень медленные расчеты даже для 1 объекта. Я надеюсь, что масштабирование единиц времени должно помочь, но я все еще не уверен, как это сделать правильно.
Statespace
1
OK. Мысль: проведите симуляцию для двух реальных объектов (например, Земли / Луны или Земли / Солнца) и сравните свои результаты с ssd.jpl.nasa.gov/?horizons для точности? Он не будет идеальным из-за возмущений от других источников, но даст вам представление о точности?
Баррикартер

Ответы:

11

m1,m2

F=ma
a

F21=Gm1m2|r21|3r21

r21F12=F21r12=r21(x1,y1)(x2,y2)

r21=(x1x2y1y2).

и

|r|=(x1x2)2+(y1y2)2.
a=F/m

x1(t)=Gm2(x2x1)|r|3y1(t)=Gm2(y2y1)|r|3x2(t)=Gm1(x1x2)|r|3y2(t)=Gm1(y1y2)|r|3.

Вместе с начальными положениями и скоростями эта система обыкновенных дифференциальных уравнений (ОДУ) содержит начальную задачу. Обычный подход состоит в том, чтобы написать это как систему первого порядка из 8 уравнений и применить метод Рунге-Кутты или многошаговый метод для их решения.

Если вы примените что-то простое, например, прямой Эйлер или обратный Эйлер, вы увидите, что Земля движется по спирали к бесконечности или к Солнцу соответственно, но это является результатом численных ошибок. Если вы используете более точный метод, такой как классический метод Рунге-Кутты 4-го порядка, вы обнаружите, что он некоторое время остается близко к истинной орбите, но все же в конечном итоге уходит в бесконечность. Правильный подход заключается в использовании симплектического метода, который будет удерживать Землю на правильной орбите - хотя ее фаза все еще будет отключена из-за числовых ошибок.

Для задачи с двумя телами можно вывести более простую систему, базируя вашу систему координат вокруг центра масс. Но я думаю, что приведенная выше формулировка понятнее, если она для вас нова.

Дэвид Кетчесон
источник
Это займет некоторое время, чтобы переварить.
Statespace
Все еще переваривает. Слишком много неизвестных мне слов, но почему-то я чувствую, что в какой-то момент я доберусь до них. На данный момент мой собственный алгоритм достаточно хорош, чтобы вещи просто работали. Но когда я включу одновременное движение - я буду вынужден добраться до литературы и прочитать о правильных алгоритмах. Учитывая, что ограничения современного оборудования намного слабее, я могу позволить себе дурачиться с простыми уравнениями. Боюсь недолго, хотя.
Statespace
Действительно, симплектические методы на сегодняшний день являются наиболее точными, но я думаю, что для человека, не имеющего опыта в науке, трудно их реализовать. Вместо этого вы можете использовать очень простой метод Эйлера вместе с поправкой Фейнмана. Я не думаю, что вам нужно что-то более сложное, чем это для целей самообразования.
chrispap