Skip to main content
Glama
trajopt.py18.3 kB
""" Trajectory optimization tools for aerospace MCP. Provides basic pitch optimization and trajectory analysis for rocket ascent. Uses lightweight numerical methods with optional advanced optimization libraries. """ import math from collections.abc import Callable from dataclasses import asdict, dataclass from typing import Any from .rockets import RocketGeometry, analyze_rocket_performance, rocket_3dof_trajectory @dataclass class OptimizationParameters: """Parameters for trajectory optimization.""" max_iterations: int = 100 tolerance: float = 1e-3 step_size: float = 0.1 parameter_bounds: dict[str, tuple[float, float]] = None @dataclass class TrajectoryOptimizationResult: """Result from trajectory optimization.""" optimal_parameters: dict[str, float] optimal_objective: float iterations: int converged: bool trajectory_points: list[Any] # RocketTrajectoryPoint performance: Any # RocketPerformance def simple_golden_section_search( objective_func: Callable[[float], float], lower_bound: float, upper_bound: float, tolerance: float = 1e-3, max_iterations: int = 100, ) -> tuple[float, float]: """ Golden section search for 1D optimization. Returns: Tuple of (optimal_x, optimal_value) """ # Golden ratio phi = (1 + math.sqrt(5)) / 2 resphi = 2 - phi # Initial points x1 = lower_bound + resphi * (upper_bound - lower_bound) x2 = upper_bound - resphi * (upper_bound - lower_bound) f1 = objective_func(x1) f2 = objective_func(x2) for _i in range(max_iterations): if abs(upper_bound - lower_bound) < tolerance: break if f1 < f2: # f1 is better (assuming minimization) upper_bound = x2 x2 = x1 f2 = f1 x1 = lower_bound + resphi * (upper_bound - lower_bound) f1 = objective_func(x1) else: # f2 is better lower_bound = x1 x1 = x2 f1 = f2 x2 = upper_bound - resphi * (upper_bound - lower_bound) f2 = objective_func(x2) # Return best point if f1 < f2: return x1, f1 else: return x2, f2 def simple_gradient_descent( objective_func: Callable[[list[float]], float], initial_params: list[float], param_bounds: list[tuple[float, float]], learning_rate: float = 0.01, tolerance: float = 1e-3, max_iterations: int = 100, ) -> tuple[list[float], float, int, bool]: """ Simple gradient descent optimization for multi-dimensional problems. Returns: Tuple of (optimal_params, optimal_value, iterations, converged) """ current_params = initial_params.copy() current_value = objective_func(current_params) for iteration in range(max_iterations): # Numerical gradient estimation gradient = [] h = 1e-6 # Small step for numerical differentiation for i, param in enumerate(current_params): # Forward difference params_plus = current_params.copy() params_plus[i] = min(param_bounds[i][1], param + h) value_plus = objective_func(params_plus) # Backward difference params_minus = current_params.copy() params_minus[i] = max(param_bounds[i][0], param - h) value_minus = objective_func(params_minus) # Central difference grad = (value_plus - value_minus) / (2 * h) gradient.append(grad) # Update parameters new_params = [] max_change = 0.0 for i, (param, grad) in enumerate(zip(current_params, gradient, strict=False)): new_param = param - learning_rate * grad # Apply bounds new_param = max(param_bounds[i][0], min(param_bounds[i][1], new_param)) new_params.append(new_param) max_change = max(max_change, abs(new_param - param)) # Check convergence new_value = objective_func(new_params) if max_change < tolerance and abs(new_value - current_value) < tolerance: return new_params, new_value, iteration + 1, True current_params = new_params current_value = new_value return current_params, current_value, max_iterations, False def optimize_launch_angle( geometry: RocketGeometry, objective: str = "max_altitude", angle_bounds: tuple[float, float] = (80.0, 90.0), ) -> TrajectoryOptimizationResult: """ Optimize launch angle for maximum altitude or range. Args: geometry: Rocket geometry and properties objective: "max_altitude" or "max_range" angle_bounds: (min_angle_deg, max_angle_deg) Returns: Optimization result """ def objective_function(angle_deg: float) -> float: """Objective function to minimize (negative for maximization).""" try: trajectory = rocket_3dof_trajectory(geometry, launch_angle_deg=angle_deg) if not trajectory: return float("inf") # Invalid trajectory performance = analyze_rocket_performance(trajectory) if objective == "max_altitude": return -performance.max_altitude_m # Negative for maximization elif objective == "max_range": # Estimate range from final horizontal position (simplified) final_horizontal_distance = ( 0.0 # Placeholder - would need 2D integration ) return -final_horizontal_distance else: return float("inf") except Exception: return float("inf") # Optimize using golden section search optimal_angle, optimal_value = simple_golden_section_search( objective_function, angle_bounds[0], angle_bounds[1], tolerance=0.1, # 0.1 degree tolerance max_iterations=50, ) # Generate final trajectory with optimal parameters final_trajectory = rocket_3dof_trajectory(geometry, launch_angle_deg=optimal_angle) final_performance = analyze_rocket_performance(final_trajectory) return TrajectoryOptimizationResult( optimal_parameters={"launch_angle_deg": optimal_angle}, optimal_objective=-optimal_value, # Convert back to positive iterations=50, # Approximation converged=True, trajectory_points=final_trajectory, performance=final_performance, ) def optimize_thrust_profile( geometry: RocketGeometry, burn_time_s: float, total_impulse_target: float, n_segments: int = 5, objective: str = "max_altitude", ) -> TrajectoryOptimizationResult: """ Optimize thrust profile for better performance. Args: geometry: Base rocket geometry burn_time_s: Total burn time total_impulse_target: Target total impulse n_segments: Number of thrust profile segments objective: "max_altitude", "min_max_q", or "min_gravity_loss" Returns: Optimization result """ # Create initial thrust profile (constant thrust) avg_thrust = total_impulse_target / burn_time_s segment_time = burn_time_s / n_segments # Initial parameters: thrust multipliers for each segment initial_multipliers = [1.0] * n_segments multiplier_bounds = [(0.1, 3.0)] * n_segments # 10% to 300% of average def objective_function(multipliers: list[float]) -> float: """Objective function for thrust profile optimization.""" try: # Create thrust curve from multipliers thrust_curve = [] for i, mult in enumerate(multipliers): time_start = i * segment_time time_end = (i + 1) * segment_time thrust_level = avg_thrust * mult thrust_curve.append([time_start, thrust_level]) if i == len(multipliers) - 1: # Last segment thrust_curve.append([time_end, 0.0]) # End thrust # Normalize to maintain total impulse current_impulse = sum( avg_thrust * mult * segment_time for mult in multipliers ) if current_impulse <= 0: return float("inf") scale_factor = total_impulse_target / current_impulse thrust_curve = [[t, thrust * scale_factor] for t, thrust in thrust_curve] # Update geometry with new thrust curve opt_geometry = RocketGeometry( dry_mass_kg=geometry.dry_mass_kg, propellant_mass_kg=geometry.propellant_mass_kg, diameter_m=geometry.diameter_m, length_m=geometry.length_m, cd=geometry.cd, thrust_curve=thrust_curve, ) # Run trajectory simulation with reasonable parameters trajectory = rocket_3dof_trajectory( opt_geometry, dt_s=0.2, max_time_s=150.0 ) if not trajectory: return 1e6 # Large penalty instead of inf performance = analyze_rocket_performance(trajectory) if objective == "max_altitude": # Return negative altitude for minimization, but ensure it's not zero result = -max( 1.0, performance.max_altitude_m ) # At least -1 to avoid zero return result elif objective == "min_max_q": return performance.max_q_pa elif objective == "min_gravity_loss": # Estimate gravity loss (simplified) gravity_loss = 9.80665 * performance.burnout_time_s return gravity_loss else: return 1e6 except Exception: return 1e6 # Large penalty for failed cases # Optimize using gradient descent optimal_multipliers, optimal_value, iterations, converged = simple_gradient_descent( objective_function, initial_multipliers, multiplier_bounds, learning_rate=0.05, tolerance=1e-3, max_iterations=100, ) # Generate final thrust curve and trajectory final_thrust_curve = [] for i, mult in enumerate(optimal_multipliers): time_start = i * segment_time time_end = (i + 1) * segment_time thrust_level = avg_thrust * mult final_thrust_curve.append([time_start, thrust_level]) if i == len(optimal_multipliers) - 1: final_thrust_curve.append([time_end, 0.0]) # Normalize final curve current_impulse = sum( avg_thrust * mult * segment_time for mult in optimal_multipliers ) scale_factor = total_impulse_target / current_impulse final_thrust_curve = [ [t, thrust * scale_factor] for t, thrust in final_thrust_curve ] # Generate final trajectory final_geometry = RocketGeometry( dry_mass_kg=geometry.dry_mass_kg, propellant_mass_kg=geometry.propellant_mass_kg, diameter_m=geometry.diameter_m, length_m=geometry.length_m, cd=geometry.cd, thrust_curve=final_thrust_curve, ) final_trajectory = rocket_3dof_trajectory(final_geometry) final_performance = analyze_rocket_performance(final_trajectory) # Prepare optimal parameters optimal_params = { f"thrust_mult_seg_{i + 1}": mult for i, mult in enumerate(optimal_multipliers) } optimal_params["thrust_curve"] = final_thrust_curve return TrajectoryOptimizationResult( optimal_parameters=optimal_params, optimal_objective=( -optimal_value if objective == "max_altitude" else optimal_value ), iterations=iterations, converged=converged, trajectory_points=final_trajectory, performance=final_performance, ) def compare_trajectories( geometries: list[RocketGeometry], names: list[str], launch_angle_deg: float = 90.0 ) -> dict[str, Any]: """ Compare multiple rocket trajectories. Args: geometries: List of rocket geometries to compare names: Names for each geometry launch_angle_deg: Launch angle for all rockets Returns: Comparison results dictionary """ results = {} for _i, (geometry, name) in enumerate(zip(geometries, names, strict=False)): try: trajectory = rocket_3dof_trajectory( geometry, launch_angle_deg=launch_angle_deg ) performance = analyze_rocket_performance(trajectory) results[name] = { "geometry": asdict(geometry), "performance": asdict(performance), "trajectory_length": len(trajectory), "success": True, } except Exception as e: results[name] = { "geometry": asdict(geometry), "error": str(e), "success": False, } # Add comparison metrics if len([r for r in results.values() if r.get("success", False)]) > 1: successful_results = [r for r in results.values() if r.get("success", False)] # Find best performance in each category best_altitude = max( r["performance"]["max_altitude_m"] for r in successful_results ) best_velocity = max( r["performance"]["max_velocity_ms"] for r in successful_results ) best_efficiency = max( r["performance"]["specific_impulse_s"] for r in successful_results ) results["comparison"] = { "best_altitude_m": best_altitude, "best_velocity_ms": best_velocity, "best_efficiency_isp_s": best_efficiency, "num_successful": len(successful_results), "total_compared": len(geometries), } return results def trajectory_sensitivity_analysis( base_geometry: RocketGeometry, parameter_variations: dict[str, list[float]], objective: str = "max_altitude", ) -> dict[str, Any]: """ Perform sensitivity analysis on trajectory parameters. Args: base_geometry: Baseline rocket geometry parameter_variations: Dict with parameter names and variation values objective: Objective to analyze sensitivity for Returns: Sensitivity analysis results """ baseline_trajectory = rocket_3dof_trajectory(base_geometry) baseline_performance = analyze_rocket_performance(baseline_trajectory) if objective == "max_altitude": baseline_value = baseline_performance.max_altitude_m elif objective == "max_velocity": baseline_value = baseline_performance.max_velocity_ms elif objective == "specific_impulse": baseline_value = baseline_performance.specific_impulse_s else: baseline_value = baseline_performance.max_altitude_m sensitivity_results = {} for param_name, variations in parameter_variations.items(): param_results = [] for variation in variations: # Create modified geometry modified_geometry = RocketGeometry( dry_mass_kg=base_geometry.dry_mass_kg, propellant_mass_kg=base_geometry.propellant_mass_kg, diameter_m=base_geometry.diameter_m, length_m=base_geometry.length_m, cd=base_geometry.cd, thrust_curve=base_geometry.thrust_curve, ) # Apply variation if param_name == "dry_mass_kg": modified_geometry.dry_mass_kg = variation elif param_name == "propellant_mass_kg": modified_geometry.propellant_mass_kg = variation elif param_name == "diameter_m": modified_geometry.diameter_m = variation elif param_name == "cd": modified_geometry.cd = variation try: trajectory = rocket_3dof_trajectory(modified_geometry) performance = analyze_rocket_performance(trajectory) if objective == "max_altitude": current_value = performance.max_altitude_m elif objective == "max_velocity": current_value = performance.max_velocity_ms elif objective == "specific_impulse": current_value = performance.specific_impulse_s else: current_value = performance.max_altitude_m # Calculate sensitivity percent_change_param = ( (variation - getattr(base_geometry, param_name)) / getattr(base_geometry, param_name) * 100 ) percent_change_objective = ( (current_value - baseline_value) / baseline_value * 100 ) sensitivity = ( percent_change_objective / percent_change_param if percent_change_param != 0 else 0 ) param_results.append( { "parameter_value": variation, "objective_value": current_value, "percent_change_param": percent_change_param, "percent_change_objective": percent_change_objective, "sensitivity": sensitivity, } ) except Exception: param_results.append( { "parameter_value": variation, "error": "Simulation failed", "sensitivity": None, } ) sensitivity_results[param_name] = param_results return { "baseline_value": baseline_value, "objective": objective, "parameter_sensitivities": sensitivity_results, "baseline_geometry": asdict(base_geometry), } # Update availability try: from . import update_availability update_availability("trajopt", True, {}) except ImportError: pass

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cheesejaguar/aerospace-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server