Загрузка данных


simulation.py:
from body import *
import numpy as np
# from itertools import combinations

RES = WIDTH, HEIGHT = 1400, 750
FPS = 60

running = True
paused = False

pygame.init()
screen = pygame.display.set_mode(RES)
clock = pygame.time.Clock()

scale = 300
camera_x, camera_y = 0, 0
camera_up = 10

AU = 1.495978707e11
DAY = 86400
SUN_MASS = 1.9885e30
G = 6.67430e-11 * SUN_MASS * DAY ** 2 / (AU ** 3)
r_aph = 1.0167
a = 1
v_aph = np.sqrt(G * (2.0 / r_aph - 1.0 / a))

dt = 0.01
visual_speed = 10
speed_per_FPS = visual_speed / FPS
steps_per_frame = int(speed_per_FPS / dt)

# ПАРАМЕТРЫ ЗЕМЛИ
earth_a = 1.0  # большая полуось в AU
earth_e = 0.0167  # эксцентриситет
earth_r_aph = earth_a * (1 + earth_e)  # 1.0167 AU
earth_mass = 3.0027e-6  # масса Земли в массах Солнца (точное значение)
earth_v_aph = np.sqrt(G * (2.0 / earth_r_aph - 1.0 / earth_a))

# ПАРАМЕТРЫ ЛУНЫ
moon_a = 0.00257  # большая полуось орбиты Луны в AU (384,400 км)
moon_period_real = 27.321661  # реальный сидерический период в днях

# Рассчитаем необходимую скорость для этого периода
# Из 3-го закона Кеплера: T = 2π√(a³/μ), где μ = G * (M_earth + M_moon)
moon_mass = earth_mass / 81.3
mu_moon = G * (earth_mass + moon_mass)

# Орбитальная скорость для круговой орбиты с периодом moon_period_real
moon_v_circ = 2 * np.pi * moon_a / moon_period_real

# Альтернативно: из формулы круговой орбиты v = √(μ/a)
moon_v_circ2 = np.sqrt(mu_moon / moon_a)

# ИНИЦИАЛИЗАЦИЯ ТЕЛ
Sun = Body(1, [0, 0, 0], [0, 0, 0], 0.004649,  10, (255, 255, 150))

# Меркурий
Mercury = Body(earth_mass*0.055274,
             [solar(semi_major_axis=0.38709927, eccentricity=0.20563593, g=G)[0], 0, 0],
             [0, solar(semi_major_axis=0.38709927, eccentricity=0.20563593, g=G)[1], 0],
              1.63e-5, 3, (80, 60, 50))

# Венера
Venus = Body(earth_mass*0.815,
             [solar(semi_major_axis=0.723332, eccentricity=0.0068, g=G)[0], 0, 0],
             [0, solar(semi_major_axis=0.723332, eccentricity=0.0068, g=G)[1], 0],
              4.05e-5, 5, (200, 220, 0))

# Земля
Earth = Body(earth_mass,
             [solar(semi_major_axis=1, eccentricity=0.01671123, g=G)[0], 0, 0],
             [0.0, solar(semi_major_axis=1, eccentricity=0.01671123, g=G)[1], 0],
              4.26e-5, 5, (0, 0, 255))

# Луна
Moon = Body(moon_mass,
            [earth_r_aph + moon_a, 0.0, 0.0],  # апогей
            [0.0, earth_v_aph + moon_v_circ, 0.0],  # скорость Земли + орбитальная
             1.16e-5, 2, (255, 255, 255))

# Марс
Mars = Body(earth_mass*0.107,
             [solar(semi_major_axis=1.523662, eccentricity=0.0933941, g=G)[0], 0, 0],
             [0.0, solar(semi_major_axis=1.523662, eccentricity=0.0933941, g=G)[1], 0],
             2.27e-5, 4, (200, 70, 40))

# Юпитер
Jupiter = Body(earth_mass*317.8,
             [solar(semi_major_axis=5.204267, eccentricity=0.048775, g=G)[0], 0, 0],
             [0.0, solar(semi_major_axis=5.204267, eccentricity=0.048775, g=G)[1], 0],
             0.000478, 7, (120, 80, 70))


bodies = []

Sun.orbital.append(Mercury)
Sun.orbital.append(Venus)
Sun.orbital.append(Earth)
Sun.orbital.append(Mars)
Sun.orbital.append(Jupiter)

Earth.orbital.append(Moon)

bodies.append(Sun)
bodies.append(Mercury)
bodies.append(Venus)
bodies.append(Earth)
bodies.append(Moon)
bodies.append(Mars)
bodies.append(Jupiter)


for body in bodies:
    body.acc = np.zeros(3)
    for other in bodies:
        if other != body:
            r_vec = other.pos - body.pos
            r = np.linalg.norm(r_vec)
            if r > 0:
                body.acc += G * other.mass * r_vec / (r ** 3)
    body.acc_prev = body.acc.copy()


a = 0
while running:
    a += 1
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            world_x = (mouse_x - WIDTH/2 - camera_x) / scale
            world_y = (HEIGHT - mouse_y - HEIGHT/2 - camera_y) / scale

            new = Body(1 / 333030, [world_x, world_y, 0], [0, 0, 0], 4.26e-5, 7, (255, 0, 255))
            bodies.append(new)


        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                paused = not paused

            if event.key == pygame.K_s:
                camera_y += camera_up*scale/300

            if event.key == pygame.K_w:
                camera_y -= camera_up*scale/300

            if event.key == pygame.K_d:
                camera_x -= camera_up*scale/300

            if event.key == pygame.K_a:
                camera_x += camera_up*scale/300

            if event.key == pygame.K_q:
                center_world_x = (-camera_x) / scale
                center_world_y = (-camera_y) / scale

                scale /= 1.5

                camera_x = -center_world_x * scale
                camera_y = -center_world_y * scale

            if event.key == pygame.K_e:
                center_world_x = (-camera_x) / scale
                center_world_y = (-camera_y) / scale

                scale *= 1.5

                camera_x = -center_world_x * scale
                camera_y = -center_world_y * scale

            if event.key == pygame.K_z:
                camera_up /= 2

            if event.key == pygame.K_x:
                camera_up *= 2


    if not paused:
        for i in range(steps_per_frame):
            # Способ 1: Использование отдельных методов leapfrog
            # Шаг 1: Обновление скорости на половину шага
            for body in bodies:
                body.update_velocity_half_step(dt)

            # Шаг 2: Обновление позиций
            for body in bodies:
                body.update_position(dt)

            # Шаг 3: Расчет новых ускорений
            for body in bodies:
                body.update_acceleration(bodies, G)

            # Шаг 4: Обновление скорости на вторую половину шага
            for body in bodies:
                body.update_velocity_full_step(dt)


    screen.fill('black')

    for body in bodies:
        for point in body.trajectory[::1]:
            pygame.draw.circle(screen, body.color,
                                   (int(point[0] * scale + WIDTH/2 + camera_x),
                                    HEIGHT - (point[1] * scale + HEIGHT/2 + camera_y)), 1)


        if scale < 100:
            pygame.draw.circle(screen, body.color,
                               (int(body.pos[0] * scale + WIDTH/2 + camera_x),
                                HEIGHT - int(body.pos[1] * scale + HEIGHT/2 + camera_y)), 3)

        elif scale > 500 and body.radius * scale > 1:
            pygame.draw.circle(screen, body.color,
                               (int(body.pos[0] * scale + WIDTH/2 + camera_x),
                                HEIGHT - int(body.pos[1] * scale + HEIGHT/2 + camera_y)), body.radius * scale)

        else:
            pygame.draw.circle(screen, body.color,
                               (int(body.pos[0] * scale + WIDTH/2 + camera_x),
                                HEIGHT - int(body.pos[1] * scale + HEIGHT/2 + camera_y)), body.display_radius)

    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()

body.py:
import pygame
import numpy as np


class Body:
    def __init__(self, mass, pos, vel, radius, display_radius, color):
        self.mass = mass
        self.pos = np.array(pos, dtype=float)
        self.vel = np.array(vel, dtype=float)
        self.acc = np.zeros(3)  # Текущее ускорение
        self.acc_prev = np.zeros(3)  # Предыдущее ускорение (для leapfrog)
        self.radius = radius
        self.display_radius = display_radius
        self.color = color
        self.trajectory = []
        self.orbital = []

    def update_position(self, dt):
        """Обновление позиции методом leapfrog"""
        self.pos += self.vel * dt + 0.5 * self.acc_prev * dt ** 2
        self.trajectory.append([self.pos[0], self.pos[1], self.pos[2]])

    def update_velocity_half_step(self, dt):
        """Половина шага для скорости (первая половина leapfrog)"""
        self.vel += 0.5 * self.acc_prev * dt

    def update_acceleration(self, bodies, G):
        """Расчет нового ускорения"""
        self.acc_prev = self.acc.copy()  # Сохраняем предыдущее ускорение
        self.acc = np.zeros(3)

        for body in bodies:
            if body != self:
                r_vec = body.pos - self.pos
                r = np.linalg.norm(r_vec)
                if r > 0:
                    self.acc += G * body.mass * r_vec / (r ** 3)

    def update_velocity_full_step(self, dt):
        """Вторая половина шага для скорости"""
        self.vel += 0.5 * self.acc * dt

    # Альтернативный вариант: один метод для leapfrog целиком
    def leapfrog_update(self, bodies, dt, G):
        """Полный шаг leapfrog"""
        # Сохраняем текущее ускорение
        acc_old = self.acc.copy()

        # Обновляем позицию
        self.pos += self.vel * dt + 0.5 * acc_old * dt ** 2
        self.trajectory.append([self.pos[0], self.pos[1], self.pos[2]])

        # Рассчитываем новое ускорение
        self.acc = np.zeros(3)
        for body in bodies:
            if body != self:
                r_vec = body.pos - self.pos
                r = np.linalg.norm(r_vec)
                if r > 0:
                    self.acc += G * body.mass * r_vec / (r ** 3)

        # Обновляем скорость
        self.vel += 0.5 * (acc_old + self.acc) * dt


def solar(semi_major_axis, eccentricity, g):
    r_aph = semi_major_axis * (1 + eccentricity)
    v_aph = np.sqrt(g * (2.0 / r_aph - 1.0 / semi_major_axis))

    return r_aph, v_aph