Tylko małe zastrzeżenie wcześniej: nigdy nie studiowałem astronomii ani żadnych nauk ścisłych w tym zakresie (nawet IT), więc staram się wypełnić tę lukę samokształceniem. Astronomia jest jednym z obszarów, który zwrócił moją uwagę, a moja idea samokształcenia opiera się na podejściu stosowanym. Od razu do rzeczy - to model symulacji orbitalnej, nad którym od niechcenia pracuję, kiedy mam czas / nastrój. Moim głównym celem jest stworzenie kompletnego układu słonecznego w ruchu i możliwość planowania startów statków kosmicznych na inne planety.
Wszyscy mogą swobodnie wybrać ten projekt w dowolnym momencie i dobrze się bawić eksperymentując!
aktualizacja!!! (10 listopada)
- prędkość jest teraz właściwa deltaV i dając dodatkowy ruch oblicza teraz sumaryczny wektor prędkości
- możesz umieścić tyle obiektów statycznych, ile chcesz, za każdym razem, gdy obiekt w ruchu sprawdza wektory grawitacyjne ze wszystkich źródeł (i sprawdza kolizję)
- znacznie poprawiła wydajność obliczeń
- poprawka do konta dla interaktywnego modu w matplotlib. Wygląda na to, że jest to domyślna opcja tylko dla ipython. Zwykły python3 wymaga tego wyrażenia wprost.
Zasadniczo można teraz „wystrzelić” statek kosmiczny z powierzchni Ziemi i zaplanować misję na Księżyc, dokonując korekt wektora deltaV za pomocą giveMotion (). Następnie próbuje zaimplementować globalną zmienną czasową, aby umożliwić jednoczesny ruch, np. Księżyc okrąża Ziemię, podczas gdy statek kosmiczny próbuje manewru wspomagania grawitacyjnego.
Komentarze i sugestie dotyczące ulepszeń są zawsze mile widziane!
Wykonano w Python3 z biblioteką 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)
Jak to działa
Wszystko sprowadza się do dwóch rzeczy:
- Tworzenie obiektu jak za
Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)
pomocą parametrów pozycji na siatce (1 jednostka siatki to domyślnie 1000 km, ale można to również zmienić), promień w jednostkach siatki i masa w kg. - Nadanie obiektowi pewnej deltaV, takiej jak
Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)
oczywiście, wymagaCraft = Object(...)
utworzenia w pierwszej kolejności, jak wspomniano w poprzednim punkcie. Parametry tutaj sądeltaV
wm / s (zwróć uwagę, że na razie przyspieszenie jest chwilowe),motionDirection
jest kierunkiem deltaV w stopniach (od aktualnej pozycji wyobraź sobie okrąg o 360 stopni wokół obiektu, więc kierunek jest punktem na tym okręgu), a na koniec parametrtime
określa liczbę sekund po przejściu deltaV obiekt będzie monitorowany. KolejnegiveMotion()
rozpoczęcie od ostatniej pozycji poprzedniejgiveMotion()
.
Pytania:
- Czy to prawidłowy algorytm do obliczania orbit?
- Jakie oczywiste ulepszenia należy wprowadzić?
- Rozważałem zmienną „timeScale”, która zoptymalizuje obliczenia, ponieważ może nie być konieczne ponowne obliczanie wektorów i pozycji na każdą sekundę. Czy są jakieś przemyślenia na temat tego, jak należy go wdrożyć, czy ogólnie jest to dobry pomysł? (utrata dokładności vs poprawiona wydajność)
Zasadniczo moim celem jest rozpoczęcie dyskusji na ten temat i sprawdzenie, dokąd prowadzi. I jeśli to możliwe, naucz się (a jeszcze lepiej - naucz) czegoś nowego i interesującego.
Zapraszam do eksperymentowania!
Spróbuj użyć:
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)
Dzięki dwóm oparzeniom - jednemu postępowi na orbicie Ziemi i jednemu ruchowi wstecz na orbicie Księżyca osiągnąłem stabilną orbitę Księżyca. Czy są one zbliżone do teoretycznie oczekiwanych wartości?
Sugerowane ćwiczenie: Wypróbuj w 3 oparzeniach - stabilna orbita Ziemi od powierzchni Ziemi, spalenie postępowe, aby dotrzeć do Księżyca, spalenie wsteczne, aby ustabilizować orbitę wokół Księżyca. Następnie spróbuj zminimalizować deltaV.
Uwaga: Planuję zaktualizować kod obszernymi komentarzami dla tych, którzy nie znają składni python3.
źródło
Odpowiedzi:
i
Układ równań różniczkowych zwyczajnych (ODE) wraz z położeniami początkowymi i prędkościami stanowi problem wartości początkowej. Zwykle podejście polega na napisaniu tego jako układu 8 równań pierwszego rzędu i zastosowaniu metody Runge-Kutty lub metody wieloetapowej w celu ich rozwiązania.
Jeśli zastosujesz coś prostego, na przykład Euler do przodu lub Euler do tyłu, zobaczysz, że Ziemia spiralnie przechodzi odpowiednio w nieskończoność lub w stronę słońca, ale jest to efekt błędów numerycznych. Jeśli użyjesz dokładniejszej metody, takiej jak klasyczna metoda Runge-Kutta czwartego rzędu, okaże się, że pozostaje ona przez pewien czas blisko prawdziwej orbity, ale ostatecznie ostatecznie przechodzi w nieskończoność. Właściwe podejście polega na zastosowaniu metody symplektycznej, która utrzyma Ziemię na właściwej orbicie - chociaż jej faza nadal będzie wyłączona z powodu błędów numerycznych.
W przypadku problemu dwóch ciał można uzyskać prostszy układ, opierając układ współrzędnych wokół środka masy. Ale myślę, że powyższe sformułowanie jest jaśniejsze, jeśli jest dla ciebie nowe.
źródło