Source code for ssapy_toolkit.plots.transfer_designer_curves_plot

"""Mission-designer curves for an optimal-transfer search.

Left: the porkchop contour (departure time x time of flight -> objective
delta-v, log color scale) with infeasible candidates greyed out and the
chosen transfer starred.  Right: the delta-v versus time-of-flight trade
(Pareto front) broken out per burn -- total, departure burn, and arrival
burn -- with the chosen transfer and the delta-v budget line.

Takes the ``OptimalTransferResult`` returned by ``transfer_optimal``;
all curves are recreated from the stored search grid, so this plot can
be regenerated at any time from the result object alone.
"""

import numpy as np


[docs] def transfer_designer_curves_plot(result, save_path=None): """Plot porkchop + per-burn Pareto curves from a transfer_optimal result. Parameters ---------- result : OptimalTransferResult save_path : str, optional If given, save via ``ssapy_toolkit.plots.yufig`` and close; otherwise the figure is returned. """ import matplotlib if save_path is not None: matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.colors import LogNorm g = result.grid dep_h = (g["t_dep"] - g["t_dep"][0]) / 3600.0 tof_h = g["tof"] / 3600.0 dv_budget = getattr(result, "dv_budget", None) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13.5, 5.5)) cost = np.ma.masked_invalid(g["cost"]) pc = ax1.pcolormesh(dep_h, tof_h, cost.T, shading="nearest", norm=LogNorm(vmin=max(cost.min(), 1e-1), vmax=cost.max()), cmap="viridis") fig.colorbar(pc, ax=ax1, label="objective delta-v [m/s]") ax1.plot((result.t_depart - g["t_dep"][0]) / 3600.0, result.tof / 3600.0, "r*", ms=16, mec="w", label=f"chosen: {result.dv_total:.1f} m/s") ax1.set_xlabel("departure time into window [h]") ax1.set_ylabel("time of flight [h]") ax1.set_title("Porkchop (grey = infeasible: no 0-rev solution,\n" "perigee below margin, or burns don't fit)") ax1.set_facecolor("0.85") ax1.legend(loc="upper right", fontsize=9) p = result.pareto ax2.plot(tof_h, p["dv"], "k.-", lw=2, label="total (best per TOF)") if "dv1" in p: ax2.plot(tof_h, p["dv1"], "C0.--", lw=1.2, label="burn 1 (departure)") if "dv2" in p and result.arrival_burn: ax2.plot(tof_h, p["dv2"], "C1.--", lw=1.2, label="burn 2 (arrival)") ax2.plot(result.tof / 3600.0, result.dv_total, "r*", ms=16, mec="w", label="chosen transfer") if dv_budget is not None: ax2.axhline(dv_budget, color="k", ls="--", lw=1, label=f"delta-v budget ({dv_budget:.0f} m/s)") ax2.set_yscale("log") ax2.set_xlabel("time of flight [h]") ax2.set_ylabel("delta-v [m/s]") ax2.set_title("Delta-v vs time-of-flight trade, per burn") ax2.grid(alpha=0.3, which="both") ax2.legend(fontsize=8) mode = "rendezvous" if result.rendezvous else "insertion" burns = "both burns" if result.arrival_burn else "first burn only" fig.suptitle(f"transfer_optimal: {result.objective}, {mode}, {burns}", fontsize=12) fig.tight_layout() if save_path is not None: from ssapy_toolkit.plots import yufig yufig(fig, save_path) plt.close(fig) return None return fig