Вправа: 2D моделювання орбітальної механіки (пітон)


12

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

Ви всі вільні забрати цей проект у будь-який момент та весело експериментувати!

оновлення !!! (Листопад 10)

  • Швидкість тепер є власною 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. Надаючи об’єкту дельтаV, наприклад, Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)очевидно, його потрібно Craft = Object(...)створити в першу чергу, як було зазначено в попередньому пункті. Параметри тут знаходяться deltaVв м / с (зауважте, що на даний момент прискорення є миттєвим), motionDirectionце напрям deltaV в градусах (з поточного положення уявіть собі 360 градусних кіл навколо об'єкта, тому напрямок є точкою на цьому колі) і, нарешті, параметр time- скільки секунд після того, як траєкторія поштовху об'єкта буде контролюватися. Подальший 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.


Дуже гарна ідея для самоосвіти! Чи можна було б узагальнити формули для тих із нас, хто не знайомий із синтаксисом Python?

Звичайно, я думаю. Я зроблю більш широкі коментарі до коду для тих, хто хоче його підібрати, і підсумую загальну логіку у самому питанні.
statepace

Вгорі голови: подумайте про використання вектора для швидкості, а не про швидкість і напрямок по-різному. Де ви говорите "F = m * v", ви маєте на увазі "F = m * a"? Ви припускаєте, що Земля не рухається, тому що вона набагато важча за астероїд? Поміркуйте, дивлячись на github.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter

Ви можете давати рух будь-якому об’єкту, включаючи Землю. Для тестових цілей я включив у основний цикл лише відношення об'єкт -> Земля. Неважко перетворити, що кожен об’єкт стосується всіх інших створених об'єктів. І кожен об’єкт може мати свій вектор руху. Причина, чому я цього не зробив - дуже повільні обчислення навіть для 1 об’єкта. Я сподіваюся, що масштабування одиниць часу має допомогти багато, але я все ще не впевнений, як це зробити правильно.
державний простір

1
ГАРАЗД. Думка: зробіть моделювання двох реальних об’єктів (наприклад, Земля / Місяць або Земля / Сонце) та порівняйте свої результати з ssd.jpl.nasa.gov/?horizons для точності? Це не буде ідеально через збурення з боку інших джерел, але це дасть вам певне уявлення про точність?
barrycarter

Відповіді:


11

м1,м2

Ж=ма
а

Ж21=Гм1м2|r21|3r21

r21Ж12=-Ж21r12=-r21(х1,у1)(х2,у2)

r21=(х1-х2у1-у2).

і

|r|=(х1-х2)2+(у1-у2)2.
а=Ж/м

х1(т)=Гм2(х2-х1)|r|3у1(т)=Гм2(у2-у1)|r|3х2(т)=Гм1(х1-х2)|r|3у2(т)=Гм1(у1-у2)|r|3.

Разом з початковими положеннями та швидкостями ця система звичайних диференціальних рівнянь (ODE) містить задачу про початкове значення. Звичайний підхід полягає в тому, щоб записати це як систему першого рівня з 8 рівнянь і застосувати метод Ренге-Кутта або багатоступеневий спосіб їх вирішення.

Якщо застосувати щось просте, як Ейлер вперед або назад Ейлера, ви побачите спіраль Землі до нескінченності або до Сонця відповідно, але це є наслідком числових помилок. Якщо ви скористаєтеся більш точним методом, як класичний метод Runge-Kutta четвертого порядку, ви виявите, що він деякий час залишається близьким до справжньої орбіти, але все-таки з часом відходить у нескінченність. Правильний підхід полягає у використанні симплектичного методу, який утримуватиме Землю на правильній орбіті - хоча її фаза все одно вимкнеться через чисельні помилки.

Для проблеми з двома тілами можна вивести більш просту систему, спираючись на систему координат навколо центру маси. Але я думаю, що формулювання вище є зрозумілішим, якщо це для вас нове.


Для перетравлення знадобиться деякий час.
державний простір

Ще перетравлюється. Занадто багато невідомих для мене слів, але я якось відчуваю, що в якийсь момент я туди потраплю. Наразі мій власний алгоритм досить хороший, щоб речі просто працювали. Але коли я підключу синхронний рух - я змушений буде добиратися до літератури та читати про належні алгоритми. З огляду на те, що обмеження сучасного обладнання значно слабкіші, я можу дозволити собі обдурити прості рівняння. Хоча боявся недовго.
державний простір

Дійсно, симплектичні методи на сьогодні є найбільш точними, але я думаю, що комусь, хто не має наукових наук, важко їх застосувати. Натомість ви можете використовувати дуже простий метод Ейлера разом з корекцією Фейнмана. Я не думаю, що вам потрібно щось складніше, ніж це для цілей самоосвіти.
chrispap
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.