#!/usr/bin/env python3 """ Inspect orbital trajectory data from binary files in a tabular format. This script reads the binary trajectory files generated by the orbital simulator and displays the data in a nicely formatted table for inspection and debugging. Usage: python inspect_trajectories.py """ import sys import struct import argparse import numpy as np from collections import defaultdict class BinaryReader: def __init__(self, data): self.data = data self.pos = 0 def read_u64(self): result = struct.unpack('= 1e6: return f"{value:.{precision}e}" else: return f"{value:.{precision}f}" def format_vector(vec, precision=3): """Format a 3D vector in a compact form.""" x, y, z = vec return f"({format_scientific(x, precision)}, {format_scientific(y, precision)}, {format_scientific(z, precision)})" def format_time(seconds): """Format time in a human-readable format.""" if seconds == 0: return "0.0s" elif seconds < 60: return f"{seconds:.1f}s" elif seconds < 3600: return f"{seconds/60:.1f}m" elif seconds < 86400: return f"{seconds/3600:.1f}h" else: return f"{seconds/86400:.1f}d" def print_summary_table(snapshots, max_rows=15): """Print a summary table of all snapshots.""" print("šŸ“Š TRAJECTORY SUMMARY") print("=" * 100) # Header print(f"{'Step':<6} {'Time':<12} {'Bodies':<8} {'Sample Position (first body)':<35} {'Sample Velocity (first body)':<35}") print("-" * 100) # Determine which snapshots to show total_snapshots = len(snapshots) if total_snapshots <= max_rows: indices = list(range(total_snapshots)) else: # Show first few, middle few, and last few start_count = max_rows // 3 end_count = max_rows // 3 middle_count = max_rows - start_count - end_count indices = [] indices.extend(range(start_count)) if middle_count > 0: middle_start = total_snapshots // 2 - middle_count // 2 indices.extend(range(middle_start, middle_start + middle_count)) indices.extend(range(total_snapshots - end_count, total_snapshots)) indices = sorted(set(indices)) # Remove duplicates and sort prev_index = -1 for i, idx in enumerate(indices): if idx > prev_index + 1 and prev_index >= 0: print(" ...") snapshot = snapshots[idx] time_str = format_time(snapshot['time']) body_count = len(snapshot['bodies']) if snapshot['bodies']: first_body = snapshot['bodies'][0] pos_str = format_vector(first_body['position'], 2) vel_str = format_vector(first_body['velocity'], 2) else: pos_str = "N/A" vel_str = "N/A" print(f"{idx:<6} {time_str:<12} {body_count:<8} {pos_str:<35} {vel_str:<35}") prev_index = idx print("-" * 100) print(f"Total snapshots: {total_snapshots}") def print_detailed_table(snapshots, body_name=None, max_rows=15): """Print detailed information for a specific body.""" if not snapshots: print("No data to display!") return # Get all body names all_bodies = set() for snapshot in snapshots: for body in snapshot['bodies']: all_bodies.add(body['name']) if body_name and body_name not in all_bodies: print(f"āŒ Body '{body_name}' not found. Available bodies: {', '.join(sorted(all_bodies))}") return # If no body specified, show the first one if not body_name: body_name = sorted(all_bodies)[0] print(f"šŸŒ DETAILED VIEW: {body_name}") print("=" * 120) # Header print(f"{'Step':<6} {'Time':<12} {'Position (x, y, z)':<40} {'Velocity (x, y, z)':<40} {'|v|':<12} {'|a|':<12}") print("-" * 120) # Determine which snapshots to show total_snapshots = len(snapshots) if total_snapshots <= max_rows: indices = list(range(total_snapshots)) else: # Show first few, middle few, and last few start_count = max_rows // 3 end_count = max_rows // 3 middle_count = max_rows - start_count - end_count indices = [] indices.extend(range(start_count)) if middle_count > 0: middle_start = total_snapshots // 2 - middle_count // 2 indices.extend(range(middle_start, middle_start + middle_count)) indices.extend(range(total_snapshots - end_count, total_snapshots)) indices = sorted(set(indices)) prev_index = -1 for idx in indices: if idx > prev_index + 1 and prev_index >= 0: print(" ...") snapshot = snapshots[idx] time_str = format_time(snapshot['time']) # Find the body in this snapshot body_data = None for body in snapshot['bodies']: if body['name'] == body_name: body_data = body break if body_data: pos_str = format_vector(body_data['position'], 3) vel_str = format_vector(body_data['velocity'], 3) vel_mag = np.linalg.norm(body_data['velocity']) acc_mag = np.linalg.norm(body_data['acceleration']) vel_mag_str = format_scientific(vel_mag, 2) acc_mag_str = format_scientific(acc_mag, 2) print(f"{idx:<6} {time_str:<12} {pos_str:<40} {vel_str:<40} {vel_mag_str:<12} {acc_mag_str:<12}") else: print(f"{idx:<6} {time_str:<12} {'BODY NOT FOUND':<40}") prev_index = idx print("-" * 120) def print_statistics(snapshots): """Print statistical information about the trajectory.""" if not snapshots: return print("\nšŸ“ˆ TRAJECTORY STATISTICS") print("=" * 80) # Time statistics times = [s['time'] for s in snapshots] time_start, time_end = times[0], times[-1] duration = time_end - time_start print(f"Time range: {format_time(time_start)} to {format_time(time_end)}") print(f"Total duration: {format_time(duration)}") print(f"Number of snapshots: {len(snapshots)}") if len(times) > 1: time_steps = [times[i+1] - times[i] for i in range(len(times)-1)] avg_step = np.mean(time_steps) print(f"Average time step: {format_time(avg_step)}") # Body statistics body_names = set() for snapshot in snapshots: for body in snapshot['bodies']: body_names.add(body['name']) print(f"Bodies tracked: {', '.join(sorted(body_names))}") # Position and velocity ranges for each body for body_name in sorted(body_names): positions = [] velocities = [] for snapshot in snapshots: for body in snapshot['bodies']: if body['name'] == body_name: positions.append(body['position']) velocities.append(body['velocity']) if positions: positions = np.array(positions) velocities = np.array(velocities) pos_min = np.min(positions, axis=0) pos_max = np.max(positions, axis=0) vel_min = np.min(np.linalg.norm(velocities, axis=1)) vel_max = np.max(np.linalg.norm(velocities, axis=1)) print(f"\n{body_name}:") print(f" Position range: X[{format_scientific(pos_min[0])}, {format_scientific(pos_max[0])}]") print(f" Y[{format_scientific(pos_min[1])}, {format_scientific(pos_max[1])}]") print(f" Z[{format_scientific(pos_min[2])}, {format_scientific(pos_max[2])}]") print(f" Speed range: {format_scientific(vel_min)} to {format_scientific(vel_max)} m/s") def main(): parser = argparse.ArgumentParser(description='Inspect orbital trajectory data in tabular format') parser.add_argument('trajectory_file', help='Binary trajectory file to inspect') parser.add_argument('--rows', '-r', type=int, default=15, help='Maximum number of rows to display (default: 15)') parser.add_argument('--body', '-b', type=str, help='Show detailed view for specific body') parser.add_argument('--summary', '-s', action='store_true', help='Show summary of all snapshots') parser.add_argument('--stats', action='store_true', help='Show trajectory statistics') parser.add_argument('--all', '-a', action='store_true', help='Show all available information') args = parser.parse_args() print(f"šŸ” Inspecting trajectory file: {args.trajectory_file}") print() try: snapshots = read_trajectory_file(args.trajectory_file) if not snapshots: print("āŒ No data found in file!") return # Determine what to show show_summary = args.summary or args.all or (not args.body and not args.stats) show_detailed = args.body or args.all show_stats = args.stats or args.all if show_summary: print_summary_table(snapshots, args.rows) print() if show_detailed: print_detailed_table(snapshots, args.body, args.rows) print() if show_stats: print_statistics(snapshots) except FileNotFoundError: print(f"āŒ Error: File '{args.trajectory_file}' not found!") except Exception as e: print(f"āŒ Error reading file: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main()