Exercício: simulação de mecânica orbital 2D (python)

12

Apenas um pouco de aviso prévio: nunca estudei astronomia ou ciências exatas para esse assunto (nem mesmo TI), por isso estou tentando preencher essa lacuna com a auto-educação. A astronomia é uma das áreas que chamou minha atenção e minha ideia de auto-educação é de abordagem aplicada. Então, direto ao ponto - este é o modelo de simulação orbital em que estou trabalhando casualmente quando tenho tempo / humor. Meu principal objetivo é criar um sistema solar completo em movimento e capacidade de planejar lançamentos de naves espaciais para outros planetas.

Você é livre para escolher este projeto a qualquer momento e se divertir experimentando!

atualizar!!! (Nov10)

  • velocidade agora é deltaV adequada e, dando movimento adicional, agora calcula o vetor soma da velocidade
  • você pode colocar quantos objetos estáticos quiser, em todas as vezes que o objeto da unidade em movimento verificar os vetores de gravidade de todas as fontes (e verificar a colisão)
  • melhorou bastante o desempenho dos cálculos
  • uma correção para considerar o mod interativo no matplotlib. Parece que esta é a opção padrão apenas para ipython. Python3 regular requer essa declaração explicitamente.

Basicamente agora é possível "lançar" uma espaçonave da superfície da Terra e planejar uma missão para a Lua, fazendo correções de vetores deltaV via giveMotion (). O próximo na fila é tentar implementar a variável de tempo global para permitir movimento simultâneo, por exemplo, a Lua orbita a Terra enquanto a sonda tenta uma manobra de auxílio à gravidade.

Comentários e sugestões de melhorias são sempre bem-vindos!

Feito em Python3 com a biblioteca 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)

Como funciona

Tudo se resume a duas coisas:

  1. Criando objetos como Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)com parâmetros de posição na grade (1 unidade da grade é 1000 km por padrão, mas isso também pode ser alterado), raio nas unidades da grade e massa em kg.
  2. Dando ao objeto algum deltaV, como Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)obviamente ele precisa Craft = Object(...)ser criado em primeiro lugar, como mencionado no ponto anterior. Os parâmetros aqui estão deltaVem m / s (observe que, por enquanto, a aceleração é instantânea), motionDirectioné a direção do deltaV em graus (da posição atual, imagine um círculo de 360 ​​graus em torno do objeto, então a direção é um ponto nesse círculo) e finalmente o parâmetro timeé quantos segundos após a trajetória deltaV push do objeto será monitorada. Partida subsequente giveMotion()da última posição da anterior giveMotion().

Questões:

  1. Este é um algoritmo válido para calcular órbitas?
  2. Quais são as melhorias óbvias a serem feitas?
  3. Eu tenho considerado a variável "timeScale" que otimizará os cálculos, pois pode não ser necessário recalcular vetores e posições a cada segundo. Alguma idéia de como deve ser implementada ou geralmente é uma boa ideia? (perda de precisão versus desempenho aprimorado)

Basicamente, meu objetivo é iniciar uma discussão sobre o tópico e ver aonde ele leva. E, se possível, aprenda (ou melhor ainda - ensine) algo novo e interessante.

Sinta-se livre para experimentar!

Tente usar:

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)

Com duas queimaduras - uma prograde na órbita da Terra e uma retrógrada na órbita da Lua, consegui uma órbita lunar estável. Estes valores estão próximos dos valores teóricos esperados?

Exercício sugerido: experimente em 3 queimaduras - órbita terrestre estável da superfície da Terra, prograde a queima para alcançar a Lua, queima retrógrada para estabilizar a órbita em torno da Lua. Então tente minimizar o deltaV.

Nota: Pretendo atualizar o código com comentários extensos para aqueles que não estão familiarizados com a sintaxe python3.

espaço de estados
fonte
Uma ótima idéia para auto-educar! Seria possível resumir suas fórmulas para aqueles que não estão familiarizados com a sintaxe do Python?
Claro, eu acho. Farei comentários mais extensos no código para aqueles que desejam buscá-lo e resumiremos a lógica geral da própria pergunta.
statespace
Em cima da minha cabeça: considere usar um vetor para velocidade em vez de tratar a velocidade e a direção de maneira diferente. Onde você diz "F = m * v", você quer dizer "F = m * a"? Você está assumindo que a Terra não se move porque é muito mais pesada que o asteróide? Considere olhar para github.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter:
Você pode movimentar qualquer objeto, incluindo a Terra. Para fins de teste, incluí apenas a relação objeto -> Terra no loop principal. É fácil converter que todos os objetos se relacionam com todos os outros objetos criados. E todo objeto pode ter seu próprio vetor de movimento. Razão pela qual não o fiz - cálculos muito lentos, mesmo para um objeto. Espero que as unidades de tempo de escala ajudem bastante, mas ainda não tenho certeza de como fazê-lo corretamente.
statespace
1
ESTÁ BEM. Um pensamento: faça a simulação para dois objetos reais (por exemplo, Terra / Lua ou Terra / Sol) e compare seus resultados com ssd.jpl.nasa.gov/?horizons para obter precisão? Não será perfeito por causa de perturbações de outras fontes, mas lhe dará uma idéia de precisão?
Barrycarter

Respostas:

11

Deixe os dois corpos envolvidos terem massas . Comece com a segunda lei de Newton onde é aceleração. A força gravitacional no corpo 2 do corpo 1 é dada porm1,m2

F=ma
a

F21=Gm1m2|r21|3r21

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

r21=(x1-x2y1-y2).

e

|r|=(x1-x2)2+(y1-y2)2.
uma=F/m

x1(t)=Gm2(x2-x1)|r|3y1(t)=Gm2(y2-y1)|r|3x2(t)=Gm1(x1-x2)|r|3y2(t)=Gm1(y1-y2)|r|3.

Juntamente com as posições e velocidades iniciais, este sistema de equações diferenciais ordinárias (EDOs) compreende um problema de valor inicial. A abordagem usual é escrever isso como um sistema de 8 equações de primeira ordem e aplicar um método Runge-Kutta ou várias etapas para resolvê-las.

Se você aplicar algo simples como Euler dianteiro ou retrógrado, verá a Terra espiralar para o infinito ou em direção ao sol, respectivamente, mas isso é um efeito dos erros numéricos. Se você usar um método mais preciso, como o clássico método Runge-Kutta de 4ª ordem, descobrirá que ele permanece próximo de uma órbita verdadeira por um tempo, mas ainda acaba indo para o infinito. A abordagem correta é usar um método simplético, que manterá a Terra na órbita correta - embora sua fase ainda esteja desativada devido a erros numéricos.

Para o problema de dois corpos, é possível derivar um sistema mais simples baseando seu sistema de coordenadas em torno do centro de massa. Mas acho que a formulação acima é mais clara se isso é novo para você.

David Ketcheson
fonte
Isso levará algum tempo para digerir.
statespace
Ainda digerindo. Muitas palavras desconhecidas para mim, mas de alguma forma sinto que em algum momento chegarei lá. Por enquanto, meu próprio algoritmo é bom o suficiente para que as coisas simplesmente funcionem. Mas quando eu conectar o movimento simultâneo - serei forçado a chegar à literatura e ler sobre algoritmos adequados. Dado que as limitações do hardware moderno são muito mais flexíveis, posso me dar ao luxo de brincar com equações simples. Com medo não por muito tempo embora.
statespace
De fato, os métodos simpléticos são de longe os mais precisos, mas acho que é difícil para alguém sem formação científica implementá-los. Em vez disso, você pode usar o método Euler muito simples, juntamente com a correção de Feynman. Não acho que você precise de algo mais complexo que isso para fins de auto-educação.
chrispap