Latihan: Simulasi mekanika orbital 2D (python)

12

Hanya sedikit penafian sebelumnya: Saya belum pernah belajar astronomi atau ilmu pasti untuk masalah itu (bahkan TI), jadi saya mencoba mengisi celah ini dengan belajar sendiri. Astronomi adalah salah satu bidang yang telah menarik perhatian saya dan ide saya tentang pendidikan mandiri adalah pendekatan langsung. Jadi, langsung ke intinya - ini adalah model simulasi orbital yang saya kerjakan dengan santai ketika saya punya waktu / suasana hati. Tujuan utama saya adalah menciptakan tata surya lengkap dalam gerakan dan kemampuan untuk merencanakan peluncuran pesawat ruang angkasa ke planet lain.

Anda semua bebas untuk mengambil proyek ini kapan saja dan bersenang-senang bereksperimen!

memperbarui!!! (Nov 10)

  • kecepatan sekarang deltaV yang tepat dan memberikan gerakan tambahan sekarang menghitung jumlah vektor kecepatan
  • Anda dapat menempatkan objek statis sebanyak yang Anda suka, pada setiap objek unit waktu bergerak memeriksa vektor gravitasi dari semua sumber (dan memeriksa tabrakan)
  • sangat meningkatkan kinerja perhitungan
  • perbaikan untuk akun mod interaktif di matplotlib. Sepertinya ini hanya opsi default untuk ipython. Python3 reguler membutuhkan pernyataan itu secara eksplisit.

Pada dasarnya sekarang mungkin untuk "meluncurkan" pesawat ruang angkasa dari permukaan bumi dan merencanakan misi ke Bulan dengan membuat koreksi vektor deltaV melalui giveMotion (). Selanjutnya sejalan mencoba menerapkan variabel waktu global untuk memungkinkan gerakan simultan misalnya Bulan mengorbit Bumi sementara pesawat ruang angkasa mencoba manuver bantuan gravitasi.

Komentar dan saran untuk perbaikan selalu diterima!

Dilakukan di Python3 dengan pustaka 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)

Bagaimana itu bekerja

Semuanya bermuara pada dua hal:

  1. Membuat objek seperti Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)dengan parameter posisi pada grid (1 unit grid adalah 1000km secara default tetapi ini dapat diubah juga), radius dalam unit grid dan massa dalam kg.
  2. Memberikan objek beberapa deltaV seperti Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)itu jelas perlu Craft = Object(...)dibuat di tempat pertama seperti yang disebutkan pada poin sebelumnya. Parameter di sini adalah deltaVdalam m / s (perhatikan bahwa untuk saat ini akselerasi adalah instan), motionDirectionadalah arah deltaV dalam derajat (dari posisi saat ini bayangkan 360 derajat lingkaran di sekitar objek, jadi arah adalah titik pada lingkaran itu) dan akhirnya parameter timeadalah berapa detik setelah lintasan dorong deltaV objek akan dipantau. Mulai selanjutnya giveMotion()dari posisi terakhir sebelumnya giveMotion().

Pertanyaan:

  1. Apakah ini algoritma yang valid untuk menghitung orbit?
  2. Apa saja perbaikan nyata yang harus dilakukan?
  3. Saya telah mempertimbangkan variabel "timeScale" yang akan mengoptimalkan perhitungan, karena mungkin tidak perlu menghitung ulang vektor dan posisi untuk setiap detik. Adakah pemikiran tentang bagaimana itu harus diimplementasikan atau itu umumnya merupakan ide yang baik? (kehilangan akurasi vs peningkatan kinerja)

Pada dasarnya tujuan saya adalah memulai diskusi tentang topik dan melihat ke mana arahnya. Dan, jika mungkin, pelajari (atau bahkan lebih baik - ajarkan) sesuatu yang baru dan menarik.

Silakan bereksperimen!

Coba gunakan:

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)

Dengan dua luka bakar - satu prograde di orbit Bumi dan satu retrograde di orbit Bulan I mencapai orbit Bulan yang stabil. Apakah ini mendekati nilai yang diharapkan secara teoritis?

Latihan yang disarankan: Cobalah dalam 3 luka bakar - orbit Bumi yang stabil dari permukaan Bumi, pembakaran tingkat tinggi untuk mencapai Bulan, luka bakar retrograde untuk menstabilkan orbit di sekitar Bulan. Kemudian cobalah untuk meminimalkan deltaV.

Catatan: Saya berencana untuk memperbarui kode dengan komentar luas untuk mereka yang tidak terbiasa dengan sintaks python3.

Statespace
sumber
Ide yang sangat bagus untuk belajar mandiri! Apakah mungkin untuk meringkas rumus Anda untuk kita yang tidak terbiasa dengan sintaksis Python?
Sepertinya, iya. Saya akan membuat komentar yang lebih luas dalam kode untuk mereka yang ingin mengambilnya dan merangkum logika umum dalam pertanyaan itu sendiri.
Statespace
Dari atas kepala saya: pertimbangkan untuk menggunakan vektor untuk kecepatan alih-alih memperlakukan kecepatan dan arah secara berbeda. Di mana Anda mengatakan "F = m * v" maksud Anda "F = m * a"? Anda berasumsi Bumi tidak bergerak karena jauh lebih berat daripada asteroid? Pertimbangkan untuk melihat github.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter
Anda dapat memberi gerakan ke objek apa pun, termasuk Bumi. Untuk tujuan pengujian saya hanya menyertakan objek -> Hubungan bumi dalam loop utama. Dapat dengan mudah dikonversi bahwa setiap objek berhubungan dengan semua objek lain yang dibuat. Dan setiap objek dapat memiliki vektor geraknya sendiri. Alasan mengapa saya tidak melakukannya - perhitungan sangat lambat bahkan untuk 1 objek. Saya berharap bahwa penskalaan satuan waktu harus membantu banyak tetapi saya masih tidak yakin bagaimana melakukannya dengan benar.
Statespace
1
BAIK. Sebuah pemikiran: apakah simulasi untuk dua objek nyata (mis. Bumi / Bulan atau Bumi / Matahari) dan membandingkan hasil Anda dengan ssd.jpl.nasa.gov/?horizons untuk akurasi? Itu tidak akan sempurna karena gangguan oleh sumber lain, tetapi itu akan memberi Anda beberapa gagasan tentang keakuratan?
barrycarter

Jawaban:

11

Biarkan dua badan yang terlibat memiliki massa . Mulai dengan hukum kedua Newton mana adalah percepatan. Gaya gravitasi pada tubuh 2 dari tubuh 1 diberikan olehm1,m2

F=ma
a

F21=Gm1m2|r21|3r21

di mana adalah vektor posisi relatif untuk dua tubuh yang dimaksud. Gaya pada tubuh 1 dari tubuh dua tentu saja sejak . Jika kami menunjukkan posisi dengan dan , makar21F12=F21r12=r21(x1,y1)(x2,y2)

r21=(x1x2y1y2).

dan

a=F/m

|r|=(x1x2)2+(y1y2)2.
Kemudian hukum Newton menjadia=F/m

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

Bersama dengan posisi awal dan kecepatan, sistem persamaan diferensial biasa (ODE) ini terdiri dari masalah nilai awal. Pendekatan yang biasa adalah menulis ini sebagai sistem orde pertama dari 8 persamaan dan menerapkan metode Runge-Kutta atau multistep untuk menyelesaikannya.

Jika Anda menerapkan sesuatu yang sederhana seperti maju Euler atau mundur Euler, Anda akan melihat Bumi berputar hingga tak terbatas atau ke arah matahari, tetapi itu adalah efek dari kesalahan numerik. Jika Anda menggunakan metode yang lebih akurat, seperti metode Runge-Kutta orde 4 klasik, Anda akan menemukan bahwa ia tetap dekat dengan orbit yang sebenarnya untuk sementara waktu tetapi pada akhirnya akan tetap tak terhingga. Pendekatan yang tepat adalah dengan menggunakan metode symplectic, yang akan menjaga Bumi dalam orbit yang benar - meskipun fase itu masih akan mati karena kesalahan numerik.

Untuk masalah 2-tubuh, dimungkinkan untuk mendapatkan sistem yang lebih sederhana dengan mendasarkan sistem koordinat Anda di sekitar pusat massa. Tapi saya pikir formulasi di atas lebih jelas jika ini baru bagi Anda.

David Ketcheson
sumber
Ini akan membutuhkan waktu untuk dicerna.
Statespace
Masih mencerna. Terlalu banyak kata yang tidak diketahui untuk saya tetapi entah bagaimana saya merasa bahwa pada suatu saat saya akan sampai di sana. Untuk saat ini algoritma saya sendiri cukup baik untuk hal-hal yang hanya berfungsi. Tetapi ketika saya akan memasukkan gerakan simultan - saya akan dipaksa untuk pergi ke literatur dan membaca tentang algoritma yang tepat. Mengingat bahwa keterbatasan perangkat keras modern jauh lebih longgar, saya dapat bermain-main dengan persamaan sederhana. Takut tidak lama.
Statespace
Memang metode symplectic sejauh ini adalah yang paling akurat tetapi saya pikir sulit bagi seseorang tanpa latar belakang sains untuk mengimplementasikannya. Alih-alih, Anda dapat menggunakan metode Euler yang sangat sederhana bersama dengan koreksi Feynman. Saya tidak berpikir bahwa Anda memerlukan sesuatu yang lebih kompleks daripada itu untuk tujuan pendidikan mandiri.
chrispap