17 """Load LUT from .npz, return dict with axes and data arrays."""
18 data = np.load(npz_path, allow_pickle=
True)
19 meta_raw = data[
"meta"].tolist()
20 meta = json.loads(meta_raw)
21 axes_meta = meta.get(
"axes", [])
27 axes[name] = np.asarray(data[key], dtype=np.float64)
30 for out_name
in meta.get(
"outputs", []):
31 key = f
"data/{out_name}"
33 outputs[out_name] = np.asarray(data[key], dtype=np.float64)
35 return {
"axes": axes,
"outputs": outputs,
"meta": meta}
39 """Create multi-panel plot for 4D policy LUT (P_u_fuel, P_u_ox, thrust_desired, MR_ref)."""
41 outputs = lut[
"outputs"]
43 P_fuel = axes[
"P_u_fuel"] / 1e6
44 P_ox = axes[
"P_u_ox"] / 1e6
45 thrust = axes[
"thrust_desired"]
46 MR_ref = axes[
"MR_ref"]
49 mr_idx = len(MR_ref) // 2
51 n_thrust = len(thrust)
53 n_rows = (n_thrust + n_cols - 1) // n_cols
54 fig, axes_plt = plt.subplots(
55 n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows), sharex=
True, sharey=
True
57 axes_plt = np.atleast_2d(axes_plt).
flatten()
60 for i, t_val
in enumerate(thrust):
62 F_slice = outputs[
"F"][:, :, i, mr_idx]
70 vmax=np.nanmax(outputs[
"F"])
if np.any(np.isfinite(outputs[
"F"]))
else 7000,
72 ax.set_title(f
"F @ thrust_des={t_val:.0f} N, MR_ref={MR_ref[mr_idx]:.1f}")
73 ax.set_xlabel(
"P_u_fuel (MPa)")
74 ax.set_ylabel(
"P_u_ox (MPa)")
75 ax.set_aspect(
"equal")
76 plt.colorbar(im, ax=ax, label=
"F (N)")
78 for j
in range(n_thrust, len(axes_plt)):
79 axes_plt[j].set_visible(
False)
80 plt.suptitle(
"Controller LUT: Thrust (F) vs tank pressures", fontsize=12, y=1.02)
82 fig.savefig(out_path.with_suffix(
".thrust.png"), dpi=150, bbox_inches=
"tight")
84 print(f
"Saved {out_path.with_suffix('.thrust.png')}")
87 fig2, axes2 = plt.subplots(
90 figsize=(5 * n_cols, 4 * 2 * n_rows),
94 axes2 = np.atleast_2d(axes2)
95 for i, t_val
in enumerate(thrust):
96 r, c = i // n_cols, i % n_cols
98 ax_o = axes2[n_rows + r, c]
99 duty_F = outputs[
"duty_F"][:, :, i, mr_idx]
100 duty_O = outputs[
"duty_O"][:, :, i, mr_idx]
101 im_f = ax_f.pcolormesh(
102 P_fuel, P_ox, duty_F.T, cmap=
"plasma", shading=
"auto", vmin=0, vmax=1
104 ax_f.set_title(f
"duty_F @ thrust_des={t_val:.0f} N")
105 ax_f.set_xlabel(
"P_u_fuel (MPa)")
106 ax_f.set_ylabel(
"P_u_ox (MPa)")
107 ax_f.set_aspect(
"equal")
108 plt.colorbar(im_f, ax=ax_f, label=
"duty_F")
109 im_o = ax_o.pcolormesh(
110 P_fuel, P_ox, duty_O.T, cmap=
"plasma", shading=
"auto", vmin=0, vmax=1
112 ax_o.set_title(f
"duty_O @ thrust_des={t_val:.0f} N")
113 ax_o.set_xlabel(
"P_u_fuel (MPa)")
114 ax_o.set_ylabel(
"P_u_ox (MPa)")
115 ax_o.set_aspect(
"equal")
116 plt.colorbar(im_o, ax=ax_o, label=
"duty_O")
117 for j
in range(n_thrust, n_rows * n_cols):
118 r, c = j // n_cols, j % n_cols
119 axes2[r, c].set_visible(
False)
120 axes2[n_rows + r, c].set_visible(
False)
122 "Controller LUT: Fuel and LOX duty cycles vs tank pressures",
127 fig2.savefig(out_path.with_suffix(
".duty_fuel.png"), dpi=150, bbox_inches=
"tight")
129 print(f
"Saved {out_path.with_suffix('.duty_fuel.png')}")
133 t_idx =
min(5, n_thrust - 1)
134 fig3, axes3 = plt.subplots(1, n_mr, figsize=(4 * n_mr, 4), sharex=
True, sharey=
True)
135 axes3 = np.atleast_1d(axes3)
136 for j, mr_val
in enumerate(MR_ref):
138 F_slice = outputs[
"F"][:, :, t_idx, j]
146 vmax=np.nanmax(outputs[
"F"]),
148 ax.set_title(f
"F @ thrust_des={thrust[t_idx]:.0f} N, MR_ref={mr_val:.1f}")
149 ax.set_xlabel(
"P_u_fuel (MPa)")
150 ax.set_ylabel(
"P_u_ox (MPa)")
151 ax.set_aspect(
"equal")
152 plt.colorbar(im, ax=ax, label=
"F (N)")
155 f
"Controller LUT: Thrust vs MR_ref (thrust_desired={thrust[t_idx]:.0f} N)",
160 fig3.savefig(out_path.with_suffix(
".mr_sweep.png"), dpi=150, bbox_inches=
"tight")
162 print(f
"Saved {out_path.with_suffix('.mr_sweep.png')}")
166 """Plot 2D engine LUT (P_u_fuel, P_u_ox)."""
168 outputs = lut[
"outputs"]
170 P_fuel = axes[
"P_u_fuel"] / 1e6
171 P_ox = axes[
"P_u_ox"] / 1e6
173 fig, axes_plt = plt.subplots(2, 2, figsize=(10, 9), sharex=
True, sharey=
True)
178 im = ax.pcolormesh(P_fuel, P_ox, F.T, cmap=
"viridis", shading=
"auto")
179 ax.set_title(
"Thrust F (N)")
180 ax.set_xlabel(
"P_u_fuel (MPa)")
181 ax.set_ylabel(
"P_u_ox (MPa)")
182 plt.colorbar(im, ax=ax)
187 im = ax.pcolormesh(P_fuel, P_ox, MR.T, cmap=
"coolwarm", shading=
"auto")
188 ax.set_title(
"Mixture ratio MR")
189 ax.set_xlabel(
"P_u_fuel (MPa)")
190 ax.set_ylabel(
"P_u_ox (MPa)")
191 plt.colorbar(im, ax=ax)
194 if "P_ch" in outputs:
196 Pc = outputs[
"P_ch"] / 1e6
197 im = ax.pcolormesh(P_fuel, P_ox, Pc.T, cmap=
"magma", shading=
"auto")
198 ax.set_title(
"Chamber pressure (MPa)")
199 ax.set_xlabel(
"P_u_fuel (MPa)")
200 ax.set_ylabel(
"P_u_ox (MPa)")
201 plt.colorbar(im, ax=ax)
204 if "stability_score" in outputs:
206 stab = outputs[
"stability_score"]
208 P_fuel, P_ox, stab.T, cmap=
"RdYlGn", shading=
"auto", vmin=0, vmax=1
210 ax.set_title(
"Stability score")
211 ax.set_xlabel(
"P_u_fuel (MPa)")
212 ax.set_ylabel(
"P_u_ox (MPa)")
213 plt.colorbar(im, ax=ax)
215 plt.suptitle(
"Engine LUT: Performance vs tank pressures", fontsize=12, y=1.02)
217 png_path = out_path.with_suffix(
".png")
if out_path.suffix !=
".png" else out_path
218 fig.savefig(png_path, dpi=150, bbox_inches=
"tight")
220 print(f
"Saved {png_path}")