porkchop_plot_analysis
Analyze optimal interplanetary transfer windows by generating porkchop plots for departure and arrival date combinations between celestial bodies.
Instructions
Generate porkchop plot for interplanetary transfer opportunities.
Args: departure_body: Departure celestial body name arrival_body: Arrival celestial body name departure_date_range: Range of departure dates (ISO format) arrival_date_range: Range of arrival dates (ISO format) analysis_options: Optional analysis settings
Returns: JSON string with porkchop plot data
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| departure_body | Yes | ||
| arrival_body | Yes | ||
| departure_date_range | Yes | ||
| arrival_date_range | Yes | ||
| analysis_options | No |
Implementation Reference
- aerospace_mcp/fastmcp_server.py:128-129 (registration)Registration of the porkchop_plot_analysis tool in the FastMCP server.mcp.tool(porkchop_plot_analysis) mcp.tool(monte_carlo_uncertainty_analysis)
- The MCP tool handler: a wrapper that attempts to delegate to trajopt.porkchop_plot_analysis (stubbed) and returns JSON or error message. Type hints define input schema.def porkchop_plot_analysis( departure_body: str, arrival_body: str, departure_date_range: list[str], arrival_date_range: list[str], analysis_options: dict | None = None, ) -> str: """Generate porkchop plot for interplanetary transfer opportunities. Args: departure_body: Departure celestial body name arrival_body: Arrival celestial body name departure_date_range: Range of departure dates (ISO format) arrival_date_range: Range of arrival dates (ISO format) analysis_options: Optional analysis settings Returns: JSON string with porkchop plot data """ try: from ..integrations.trajopt import porkchop_plot_analysis as _porkchop result = _porkchop( departure_body, arrival_body, departure_date_range, arrival_date_range, analysis_options or {}, ) return json.dumps(result, indent=2) except ImportError: return ( "Porkchop plot analysis not available - install space trajectory packages" ) except Exception as e: logger.error(f"Porkchop plot error: {str(e)}", exc_info=True) return f"Porkchop plot error: {str(e)}"
- aerospace_mcp/fastmcp_server.py:48-55 (registration)Import of porkchop_plot_analysis from optimization module for server registration.from .tools.optimization import ( genetic_algorithm_optimization, monte_carlo_uncertainty_analysis, optimize_thrust_profile, particle_swarm_optimization, porkchop_plot_analysis, trajectory_sensitivity_analysis, )
- Comprehensive helper implementation of porkchop plot analysis logic, including planetary positions, Lambert transfers, C3 computation, and optimal transfer identification. Not directly linked to the registered tool.def porkchop_plot_analysis( departure_body: str = "Earth", arrival_body: str = "Mars", departure_dates: list[str] = None, arrival_dates: list[str] = None, min_tof_days: int = 100, max_tof_days: int = 400, ) -> dict[str, Any]: """ Generate porkchop plot analysis for interplanetary transfers. Args: departure_body: Departure celestial body (default: Earth) arrival_body: Arrival celestial body (default: Mars) departure_dates: List of departure dates (ISO format) arrival_dates: List of arrival dates (ISO format) min_tof_days: Minimum time of flight (days) max_tof_days: Maximum time of flight (days) Returns: Dictionary containing transfer analysis grid """ # Default date ranges if not provided if departure_dates is None: # Earth-Mars synodic period is ~26 months, sample over 2 years departure_dates = [ "2025-01-01T00:00:00", "2025-03-01T00:00:00", "2025-05-01T00:00:00", "2025-07-01T00:00:00", "2025-09-01T00:00:00", "2025-11-01T00:00:00", "2026-01-01T00:00:00", "2026-03-01T00:00:00", "2026-05-01T00:00:00", "2026-07-01T00:00:00", "2026-09-01T00:00:00", "2026-11-01T00:00:00", ] if arrival_dates is None: # Generate arrival dates based on TOF constraints arrival_dates = [ "2025-06-01T00:00:00", "2025-08-01T00:00:00", "2025-10-01T00:00:00", "2025-12-01T00:00:00", "2026-02-01T00:00:00", "2026-04-01T00:00:00", "2026-06-01T00:00:00", "2026-08-01T00:00:00", "2026-10-01T00:00:00", "2026-12-01T00:00:00", "2027-02-01T00:00:00", "2027-04-01T00:00:00", ] # Simplified planetary positions (circular orbits for demonstration) # In production, would use SPICE kernels or JPL ephemeris earth_orbit_radius = 1.0 # AU mars_orbit_radius = 1.524 # AU earth_period_days = 365.25 mars_period_days = 686.98 def get_planet_position(body: str, date_iso: str) -> list[float]: """Get simplified planet position at given date.""" # Parse date (simplified) import datetime try: dt = datetime.datetime.fromisoformat(date_iso.replace("Z", "+00:00")) except Exception: dt = datetime.datetime.fromisoformat(date_iso) # Days since J2000 j2000 = datetime.datetime(2000, 1, 1, 12, 0, 0) days_since_j2000 = (dt - j2000).total_seconds() / 86400 if body.lower() == "earth": # Earth mean anomaly mean_anomaly = 2 * math.pi * days_since_j2000 / earth_period_days x = earth_orbit_radius * math.cos(mean_anomaly) y = earth_orbit_radius * math.sin(mean_anomaly) return [x * 1.496e11, y * 1.496e11, 0.0] # Convert AU to meters elif body.lower() == "mars": # Mars mean anomaly mean_anomaly = 2 * math.pi * days_since_j2000 / mars_period_days x = mars_orbit_radius * math.cos(mean_anomaly) y = mars_orbit_radius * math.sin(mean_anomaly) return [x * 1.496e11, y * 1.496e11, 0.0] # Convert AU to meters else: # Default to Earth position mean_anomaly = 2 * math.pi * days_since_j2000 / earth_period_days x = earth_orbit_radius * math.cos(mean_anomaly) y = earth_orbit_radius * math.sin(mean_anomaly) return [x * 1.496e11, y * 1.496e11, 0.0] # Generate porkchop data grid porkchop_data = [] for dep_date in departure_dates: for arr_date in arrival_dates: # Calculate time of flight import datetime try: dep_dt = datetime.datetime.fromisoformat( dep_date.replace("Z", "+00:00") ) except Exception: dep_dt = datetime.datetime.fromisoformat(dep_date) try: arr_dt = datetime.datetime.fromisoformat( arr_date.replace("Z", "+00:00") ) except Exception: arr_dt = datetime.datetime.fromisoformat(arr_date) tof_days = (arr_dt - dep_dt).total_seconds() / 86400 # Filter by TOF constraints if tof_days < min_tof_days or tof_days > max_tof_days: continue # Get planetary positions r1 = get_planet_position(departure_body, dep_date) r2 = get_planet_position(arrival_body, arr_date) # Calculate transfer using simplified Lambert solver try: mu_sun = 1.32712440018e20 # Sun's gravitational parameter (m³/s²) tof_seconds = tof_days * 86400 # Use simplified Lambert solver lambert_result = lambert_solver_simple(r1, r2, tof_seconds, mu_sun) # Calculate characteristic energy (C3) if "v1_vec_ms" in lambert_result: v1 = lambert_result["v1_vec_ms"] elif "initial_velocity_ms" in lambert_result: v1 = lambert_result["initial_velocity_ms"] else: v1 = [0, 0, 0] # Fallback # Earth's orbital velocity at 1 AU earth_v_orbit = math.sqrt(mu_sun / (1.496e11)) # m/s # Excess velocity magnitude v_inf_vec = [ v1[i] - earth_v_orbit if i == 1 else v1[i] for i in range(3) ] v_inf_mag = math.sqrt(sum(v**2 for v in v_inf_vec)) # Characteristic energy C3 (km²/s²) c3 = (v_inf_mag / 1000) ** 2 # Convert to km/s then square # Delta-V estimate (simplified) delta_v_ms = v_inf_mag porkchop_data.append( { "departure_date": dep_date, "arrival_date": arr_date, "time_of_flight_days": tof_days, "c3_km2_s2": c3, "delta_v_ms": delta_v_ms, "departure_position_m": r1, "arrival_position_m": r2, "transfer_feasible": c3 < 100.0, # Reasonable C3 limit } ) except Exception as e: # Add failed transfer case porkchop_data.append( { "departure_date": dep_date, "arrival_date": arr_date, "time_of_flight_days": tof_days, "c3_km2_s2": float("inf"), "delta_v_ms": float("inf"), "departure_position_m": r1, "arrival_position_m": r2, "transfer_feasible": False, "error": str(e), } ) # Find optimal transfer feasible_transfers = [t for t in porkchop_data if t["transfer_feasible"]] optimal_transfer = None if feasible_transfers: # Find minimum C3 transfer optimal_transfer = min(feasible_transfers, key=lambda x: x["c3_km2_s2"]) # Generate summary statistics if feasible_transfers: c3_values = [t["c3_km2_s2"] for t in feasible_transfers] tof_values = [t["time_of_flight_days"] for t in feasible_transfers] summary_stats = { "feasible_transfers": len(feasible_transfers), "total_transfers_computed": len(porkchop_data), "min_c3_km2_s2": min(c3_values), "max_c3_km2_s2": max(c3_values), "mean_c3_km2_s2": sum(c3_values) / len(c3_values), "min_tof_days": min(tof_values), "max_tof_days": max(tof_values), "mean_tof_days": sum(tof_values) / len(tof_values), } else: summary_stats = { "feasible_transfers": 0, "total_transfers_computed": len(porkchop_data), "min_c3_km2_s2": None, "max_c3_km2_s2": None, "mean_c3_km2_s2": None, "min_tof_days": min_tof_days, "max_tof_days": max_tof_days, "mean_tof_days": (min_tof_days + max_tof_days) / 2, } return { "departure_body": departure_body, "arrival_body": arrival_body, "transfer_grid": porkchop_data, "optimal_transfer": optimal_transfer, "summary_statistics": summary_stats, "constraints": { "min_tof_days": min_tof_days, "max_tof_days": max_tof_days, "max_feasible_c3_km2_s2": 100.0, }, "note": "Simplified analysis using circular planetary orbits. Use SPICE kernels for production applications.", }