| import pandas as pd |
| import pygame as pg |
| import math |
|
|
| import os |
|
|
| import torch |
|
|
| from collections import deque |
|
|
| from Const import* |
| from Class_Def import Celestial_Core, Planet, SpaceShip |
| device = DEVICE |
|
|
| def update_position(planets,rockets): |
| |
| total_fx = [0] * len(planets+rockets) |
| total_fy = [0] * len(planets+rockets) |
|
|
| for n,i in enumerate(planets+rockets): |
| for j in planets+rockets: |
| if i != j: |
| fx, fy = i.attraction(j) |
| total_fx[n-1] += fx |
| total_fy[n-1] += fy |
| if (n>=len(planets)): |
| |
| if i.reactor1 : |
| total_fx[n-1] += - i.accelerator() |
| if i.reactor2 : |
| total_fx[n-1] += - i.accelerator() * math.cos(2.*math.pi/3.) |
| total_fy[n-1] += - i.accelerator() * math.sin(2.*math.pi/3.) |
| if i.reactor3 : |
| total_fx[n-1] += - i.accelerator() * math.cos(- 2.*math.pi/3.) |
| total_fy[n-1] += - i.accelerator() * math.sin(- 2.*math.pi/3.) |
| |
| for n,i in enumerate(planets+rockets): |
| i.vx += total_fx[n-1] / i.mass * TIMESTEP |
| i.vy += total_fy[n-1] / i.mass * TIMESTEP |
|
|
| i.x += i.vx * TIMESTEP |
| i.y += i.vy * TIMESTEP |
| i.orbit.append((i.x, i.y)) |
| while(len(i.orbit)>600): |
| i.orbit.pop(0) |
| |
| for i in rockets: |
| for j in planets: |
| if (i.x - j.x)**2 + (i.y - j.y)**2 < (i.radius + j.radius)**2: |
| del i |
| |
| return planets,rockets |
|
|
| def Solar_System(): |
| sun = Planet(0, 0, 1.98892 * 10**30, 695_700e3, 30, YELLOW) |
| |
| earth = Planet(-1 * AU , 0, 5.9742 * 10**24, 6_371e3, 16, BLUE) |
| earth.vy = 29.783 * 1000 |
|
|
| moon = Planet((-1 - 0.00256954)*AU , 0, 7.347 * 10**22, 1_737e3, 10, WHITE) |
| moon.vy = (29.783+1.022) * 1000 |
| |
| mars = Planet(-1.524 * AU, 0, 6.39* 10**23 , 3_390e3, 12, RED) |
| mars.vy = 24.077 * 1000 |
|
|
| mercury = Planet(0.387 * AU, 0, 3.30 * 10**23, 2_440e3, 8, DARK_GREY) |
| mercury.vy = -47.4 * 1000 |
|
|
| venus = Planet(0.723 * AU, 0, 4.8685 * 10**24, 6_052e3, 14, WHITE) |
| venus.vy = -35.02 * 1000 |
| |
| jupiter = Planet(5.19 * AU, 0, 1.898* 10**27, 69_911e3, 25, JUP) |
| jupiter.vy = -13.06 * 1000 |
| |
| saturn = Planet(9.536 * AU, 0, 5.684 * 10**26, 58_232e3, 20, GREEN) |
| saturn.vy = -9.64 * 1000 |
|
|
| neptune = Planet(30.07 * AU, 0, 1.024 * 10**26, 24_622e3, 18, BLUE) |
| neptune.vy = -5.43 * 1000 |
|
|
| return [sun,mercury,venus,earth,moon,mars,jupiter,saturn,neptune] |
|
|
| def Launch(planet): |
| |
| rocket = SpaceShip(planet.x , planet.y + 0.00256954*AU, 30_000, 12, RED) |
| rocket.vx = planet.vx -1.022*1000 |
| rocket.vy = planet.vy |
| |
| return rocket |
|
|
|
|
|
|
| def get_distance(core1, core2): |
| return math.sqrt( (core1.x-core2.x)**2 + (core1.y-core2.y)**2) |
|
|
| def draw_dist(win, shift_x , shift_y, core1, core2, Scale): |
|
|
| x1 = (core1.x+shift_x) * Scale + WIDTH / 2 |
| y1 = (core1.y+shift_y) * Scale + HEIGHT / 2 |
|
|
| x2 = (core2.x+shift_x) * Scale + WIDTH / 2 |
| y2 = (core2.y+shift_y) * Scale + HEIGHT / 2 |
| |
| pg.draw.line(win, WHITE, [x2, y2],[x1, y1], 3) |
|
|
| |
|
|
| def is_orbiting(core1, core2, start=0): |
| hist_dist = [] |
| |
| |
| for i in range(start,len(core1.orbit)): |
| hist_dist.append(math.sqrt( (core1.orbit[i][0] - core2.orbit[i][0])**2 + (core1.orbit[i][1]-core2.orbit[i][1])**2)) |
|
|
| if (max(hist_dist) - min(hist_dist)) < 0.01 * min(hist_dist): |
| return True |
| else: |
| return False |
|
|
| def magnitude(vx, vy): |
| return math.sqrt(vx**2 + vy**2) |
|
|
|
|
|
|
| |
| def get_state(rocket, target_planet): |
| """Calculates the state vector for the agent.""" |
| if rocket is None or target_planet is None: |
| |
| return torch.zeros((1, STATE_SIZE), dtype=torch.float32, device=device) |
| |
| rel_x = (rocket.x - target_planet.x) / AU |
| rel_y = (rocket.y - target_planet.y) / AU |
| |
| |
| |
| norm_factor_vel = 5e4 |
| rel_vx = (rocket.vx - target_planet.vx) / norm_factor_vel |
| rel_vy = (rocket.vy - target_planet.vy) / norm_factor_vel |
| |
| |
| state_list = [rel_x, rel_y, rel_vx, rel_vy] |
| |
| |
| |
|
|
| |
| state_tensor = torch.tensor(state_list, dtype=torch.float32, device=device).unsqueeze(0) |
| return state_tensor |
|
|
|
|
|
|
| def get_state_sequence( |
| action_history: deque, |
| rel_pos_x_history: deque, |
| rel_pos_y_history: deque, |
| rel_vel_x_history: deque, |
| rel_vel_y_history: deque, |
| seq_length: int = SEQ_LENGTH_DEFAULT, |
| padding_value: float = PADDING_VALUE, |
| action_padding_value: int = ACTION_PADDING_VALUE |
| ): |
| """ |
| Creates a state tensor representing the history of actions, relative positions, |
| and relative velocities, suitable for sequence models like attention networks. |
| |
| Args: |
| action_history (deque): Deque of past actions taken (integers). |
| rel_pos_x_history (deque): Deque of past relative x positions (normalized). |
| rel_pos_y_history (deque): Deque of past relative y positions (normalized). |
| rel_vel_x_history (deque): Deque of past relative x velocities (normalized). |
| rel_vel_y_history (deque): Deque of past relative y velocities (normalized). |
| seq_length (int): The desired fixed sequence length for the output tensor. |
| padding_value (float): Value used for padding position/velocity sequences. |
| action_padding_value (int): Value used for padding the action sequence. |
| |
| Returns: |
| torch.Tensor: A tensor of shape (1, 5, seq_length) containing the padded sequences. |
| Features are ordered [action, rel_pos_x, rel_pos_y, rel_vel_x, rel_vel_y]. |
| torch.Tensor: A boolean mask tensor of shape (1, seq_length) where True indicates |
| a real data point and False indicates padding. Useful for attention mechanisms. |
| """ |
| sequences = [] |
| |
| histories = [ |
| action_history, rel_pos_x_history, rel_pos_y_history, rel_vel_x_history, rel_vel_y_history |
| ] |
| |
| paddings = [action_padding_value] + [padding_value] * 4 |
|
|
| actual_len = 0 |
|
|
| for i, (history, pad_val) in enumerate(zip(histories, paddings)): |
| |
| recent_history = list(history)[-seq_length:] |
|
|
| |
| if i == 0: |
| actual_len = len(recent_history) |
|
|
| |
| pad_len = seq_length - len(recent_history) |
|
|
| |
| if isinstance(pad_val, float): |
| |
| processed_history = [float(x) for x in recent_history] |
| padded_sequence = ([pad_val] * pad_len) + processed_history |
| else: |
| |
| processed_history = [int(x) for x in recent_history] |
| padded_sequence = ([pad_val] * pad_len) + processed_history |
|
|
| sequences.append(padded_sequence) |
|
|
| |
| mask_pad_len = seq_length - actual_len |
| |
| mask = torch.tensor(([False] * mask_pad_len) + ([True] * actual_len), device=device, dtype=torch.bool).unsqueeze(0) |
|
|
| |
| |
| |
| state_tensor_stacked = torch.tensor(sequences, dtype=torch.float32, device=device) |
|
|
| |
| state_tensor_batch = state_tensor_stacked.unsqueeze(0) |
|
|
| return state_tensor_batch, mask |
|
|
| |
| |
| def calculate_current_relative_state(rocket, target_planet): |
| """Calculates the *current* relative state values.""" |
| if rocket is None or target_planet is None: |
| |
| |
| return math.nan, math.nan, math.nan, math.nan |
|
|
| |
| rel_x = (rocket.x - target_planet.x) / AU |
| rel_y = (rocket.y - target_planet.y) / AU |
| |
| rel_vx = (rocket.vx - target_planet.vx) / NORM_FACTOR_VEL |
| rel_vy = (rocket.vy - target_planet.vy) / NORM_FACTOR_VEL |
|
|
| return rel_x, rel_y, rel_vx, rel_vy |
|
|
| |
| def reset_rocket_state(rocket, planets): |
| """Resets a single rocket's position/velocity near a start planet.""" |
| start_planet_idx = 3 |
| if start_planet_idx >= len(planets): start_planet_idx = 0 |
| start_planet = planets[start_planet_idx] |
|
|
|
|
| rocket.x = start_planet.x |
| rocket.y = start_planet.y + 0.00256954 * AU |
| rocket.vx = start_planet.vx -1.022*1000 |
| rocket.vy = start_planet.vy |
| |
| rocket.reactor1 = False |
| rocket.reactor2 = False |
| rocket.reactor3 = False |
| |
| |
| |
|
|
| print("Rocket state reset.") |
| return rocket |
|
|
| |
| def reset_environment(planets): |
| """Resets rocket position/velocity for a new episode.""" |
| |
| start_planet_idx = 3 |
| if start_planet_idx >= len(planets): |
| start_planet_idx = 0 |
|
|
| start_planet = planets[start_planet_idx] |
|
|
| |
| angle = random.uniform(0, 2 * math.pi) |
| dist_factor = random.uniform(1.5, 3.0) |
| start_dist = start_planet.radius * dist_factor if hasattr(start_planet, 'radius') else 1e7 |
| |
| start_x = start_planet.x + start_dist * math.cos(angle) |
| start_y = start_planet.y + start_dist * math.sin(angle) |
|
|
| |
| |
| tangential_speed = math.sqrt(G * start_planet.mass / start_dist) * 0.5 |
| start_vx = start_planet.vx - tangential_speed * math.sin(angle) |
| start_vy = start_planet.vy + tangential_speed * math.cos(angle) |
| |
| rocket = SpaceShip(start_x, start_y, 500, 5, BLUE) |
| rocket.vx = start_vx |
| rocket.vy = start_vy |
|
|
| |
| |
|
|
| return [rocket] |
|
|
|
|
|
|
|
|
| def save_distance_data(all_acquisitions, save_folder="metrics"): |
| """Saves recorded distance data to a CSV file using pandas. |
| Each column represents one acquisition (run between resets). |
| """ |
| if not all_acquisitions or not any(all_acquisitions): |
| print("No distance data recorded to save.") |
| return |
|
|
| try: |
| |
| filename = os.path.join(save_folder, "distances_log.csv") |
|
|
| |
| data_dict = {} |
| for i, acq in enumerate(all_acquisitions): |
| |
| if acq: |
| data_dict[f"Acquisition_{i+1}"] = pd.Series(acq) |
| |
| |
|
|
| if not data_dict: |
| print("All recorded acquisitions were empty. Nothing to save.") |
| return |
|
|
| |
| |
| df = pd.DataFrame(data_dict) |
|
|
| |
| df.to_csv(filename, index=False, float_format='%.2f') |
|
|
| print(f"Distance data saved to {filename} using pandas.") |
|
|
| except ImportError: |
| print("Error: pandas library not found. Please install it (`pip install pandas`) to use this save function.") |
| except Exception as e: |
| print(f"Error saving distance data using pandas: {e}") |
| import traceback |
| traceback.print_exc() |