File size: 13,200 Bytes
ba7b9c7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | 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)):
# print(i.vx, i.vy)
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
# rockets.remove(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 = []
#if mae between core1 and core 2 get_distance < 1% return True :
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)
# --- Helper function to get the current state ---
def get_state(rocket, target_planet):
"""Calculates the state vector for the agent."""
if rocket is None or target_planet is None:
# Return a zero state if objects are missing
return torch.zeros((1, STATE_SIZE), dtype=torch.float32, device=device)
# Relative position (normalize by AU for consistency)
rel_x = (rocket.x - target_planet.x) / AU
rel_y = (rocket.y - target_planet.y) / AU
# Relative velocity (needs normalization - find typical max velocity?)
# Let's estimate a max velocity (e.g., around Earth orbital speed ~30km/s = 3e4 m/s)
# Or maybe relative to ideal orbital speed? This needs tuning.
norm_factor_vel = 5e4 # Tune this normalization factor
rel_vx = (rocket.vx - target_planet.vx) / norm_factor_vel
rel_vy = (rocket.vy - target_planet.vy) / norm_factor_vel
# State vector: [dx, dy, dvx, dvy]
state_list = [rel_x, rel_y, rel_vx, rel_vy]
# Add other components if STATE_SIZE in Const.py is > 4
# e.g., state_list.append(rocket.fuel / rocket.initial_fuel) # If fuel becomes relevant
# Convert to PyTorch tensor
state_tensor = torch.tensor(state_list, dtype=torch.float32, device=device).unsqueeze(0) # Add batch dimension
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 = []
# Combine all history deques for easier iteration
histories = [
action_history, rel_pos_x_history, rel_pos_y_history, rel_vel_x_history, rel_vel_y_history
]
# Define padding value for each sequence type
paddings = [action_padding_value] + [padding_value] * 4
actual_len = 0 # Store the actual length before padding
for i, (history, pad_val) in enumerate(zip(histories, paddings)):
# Convert deque to list and get the last seq_length items
recent_history = list(history)[-seq_length:]
# Store the actual length of the sequence before padding (use any history's length)
if i == 0:
actual_len = len(recent_history)
# Calculate padding needed (prepends padding)
pad_len = seq_length - len(recent_history)
# Create the padded sequence, ensuring consistent data types
if isinstance(pad_val, float):
# Pad with float, convert history to float
processed_history = [float(x) for x in recent_history]
padded_sequence = ([pad_val] * pad_len) + processed_history
else: # Assuming integer padding (like for actions)
# Pad with int, convert history to int
processed_history = [int(x) for x in recent_history]
padded_sequence = ([pad_val] * pad_len) + processed_history
sequences.append(padded_sequence)
# Create the attention mask (True for real data, False for padding)
mask_pad_len = seq_length - actual_len
# Mask has shape (1, seq_length)
mask = torch.tensor(([False] * mask_pad_len) + ([True] * actual_len), device=device, dtype=torch.bool).unsqueeze(0)
# Stack sequences into a tensor
# The resulting tensor shape will be (5, seq_length)
# Ensure the tensor is float32 for neural network input
state_tensor_stacked = torch.tensor(sequences, dtype=torch.float32, device=device)
# Add the batch dimension -> (1, 5, seq_length)
state_tensor_batch = state_tensor_stacked.unsqueeze(0)
return state_tensor_batch, mask
# --- Placeholder function to calculate current relative state values ---
# You'll call this in your main loop before appending to the history deques.
def calculate_current_relative_state(rocket, target_planet):
"""Calculates the *current* relative state values."""
if rocket is None or target_planet is None:
# Return zeros or handle appropriately if objects are missing
# Returning NaNs might be better to signal invalid state if not handled earlier
return math.nan, math.nan, math.nan, math.nan
# Relative position (normalize by AU) [cite: 122]
rel_x = (rocket.x - target_planet.x) / AU
rel_y = (rocket.y - target_planet.y) / AU
# Relative velocity (normalized) [cite: 124]
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
# --- Function to reset a specific rocket (useful after crash/OOB) ---
def reset_rocket_state(rocket, planets):
"""Resets a single rocket's position/velocity near a start planet."""
start_planet_idx = 3 # Assuming Earth is index 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
# Reset reactors and potentially other state if needed
rocket.reactor1 = False
rocket.reactor2 = False
rocket.reactor3 = False
# Reset mass/fuel if tracked later
# rocket.fuel = rocket.initial_fuel
# rocket.mass = rocket.structure_mass + rocket.fuel
print("Rocket state reset.")
return rocket
# --- Function to reset the environment for a new episode ---
def reset_environment(planets):
"""Resets rocket position/velocity for a new episode."""
# Choose a starting planet (e.g., Earth)
start_planet_idx = 3 # Assuming Earth is index 3
if start_planet_idx >= len(planets):
start_planet_idx = 0 # Default to Sun if Earth doesn't exist
start_planet = planets[start_planet_idx]
# Start in a somewhat random position near the starting planet
angle = random.uniform(0, 2 * math.pi)
dist_factor = random.uniform(1.5, 3.0) # Start further away than just radius
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)
# Create the rocket (assuming SpaceShip class exists)
# Give it some initial velocity, perhaps tangential to start planet
tangential_speed = math.sqrt(G * start_planet.mass / start_dist) * 0.5 # Start slower than orbit
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) # Use default parameters
rocket.vx = start_vx
rocket.vy = start_vy
# Reset target (optional, could keep same target)
# Target = random.randint(0, len(planets) - 1)
return [rocket] # Return list containing the single 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): # Check if empty or contains only empty lists
print("No distance data recorded to save.")
return
try:
# Create timestamped folder
filename = os.path.join(save_folder, "distances_log.csv") # New filename maybe
# Prepare data for DataFrame: dictionary where keys are column names
data_dict = {}
for i, acq in enumerate(all_acquisitions):
# Only include non-empty acquisitions if desired, or handle them all
if acq: # Ensure the acquisition list is not empty
data_dict[f"Acquisition_{i+1}"] = pd.Series(acq)
# else: # Optionally handle empty acquisitions if needed
# data_dict[f"Acquisition_{i+1}"] = pd.Series([]) # Add empty series
if not data_dict:
print("All recorded acquisitions were empty. Nothing to save.")
return
# Create DataFrame directly from dictionary of Series
# Pandas handles unequal lengths by padding with NaN automatically
df = pd.DataFrame(data_dict)
# Save DataFrame to CSV
df.to_csv(filename, index=False, float_format='%.2f') # Save without index, format floats
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() |