STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
engine_lut_wrapper.py
Go to the documentation of this file.
1"""Engine wrapper backed by a precomputed LUT.
2
3Stems from engine config + tank pressure range. Provides everything the robust DDP
4needs (F, MR, mdot, injector_dp, stability) via fast multilinear interpolation
5instead of calling the physics pipeline.
6"""
7
8from __future__ import annotations
9
10import json
11from pathlib import Path
12from typing import Any, Dict, Optional
13
14import numpy as np
15
16from .engine_wrapper import EngineEstimate
17
18
20 """EngineWrapper interface backed by a precomputed engine performance LUT.
21
22 Loads .npz from generate_controller_lut (engine-only mode). Axes: P_u_fuel, P_u_ox.
23 Outputs: F, MR, P_ch, mdot_F, mdot_O, injector_dp_F, injector_dp_O, stability_score,
24 injector_stiffness_ok.
25 """
26
27 def __init__(self, lut_path: str | Path):
28 path = Path(lut_path)
29 data = np.load(path, allow_pickle=True)
30 if "meta" not in data:
31 raise ValueError(f"LUT {path} missing 'meta' entry")
32 meta = json.loads(data["meta"].tolist())
33 self._axes = []
34 for ax in meta.get("axes", []):
35 name = ax["name"]
36 key = f"axes/{name}"
37 if key not in data:
38 raise ValueError(f"LUT missing axis '{name}'")
39 self._axes.append({"name": name, "values": np.asarray(data[key], dtype=float)})
40 self._outputs: Dict[str, np.ndarray] = {}
41 for out_name in meta.get("outputs", []):
42 key = f"data/{out_name}"
43 if key in data:
44 self._outputs[out_name] = np.asarray(data[key], dtype=float)
45 self.meta = meta
46 self.path = path
47
49 self,
50 P_d_F: float,
51 P_d_O: float,
52 use_cache: bool = True,
53 ) -> EngineEstimate:
54 """
55 Estimate engine performance from tank/ullage pressures via LUT lookup.
56
57 Parameters P_d_F, P_d_O are tank pressures (P_u_fuel, P_u_ox).
58 """
59 point = {"P_u_fuel": P_d_F, "P_u_ox": P_d_O}
60 out = self._evaluate(point)
61
62 stability_metrics: Optional[Dict[str, Any]] = None
63 if "stability_score" in out or "injector_stiffness_ok" in out:
64 stability_metrics = {}
65 if "stability_score" in out and np.isfinite(out["stability_score"]):
66 stability_metrics["stability_score"] = out["stability_score"]
67 if "injector_stiffness_ok" in out:
68 ok = out["injector_stiffness_ok"]
69 stability_metrics["injector_stiffness_ok"] = bool(ok > 0.5) if np.isfinite(ok) else False
70
71 return EngineEstimate(
72 P_ch=float(out.get("P_ch", np.nan)),
73 F=float(out.get("F", np.nan)),
74 mdot_F=float(out.get("mdot_F", np.nan)),
75 mdot_O=float(out.get("mdot_O", np.nan)),
76 MR=float(out.get("MR", np.nan)),
77 injector_dp_F=float(out.get("injector_dp_F", np.nan)),
78 injector_dp_O=float(out.get("injector_dp_O", np.nan)),
79 stability_metrics=stability_metrics,
80 diagnostics=None,
81 )
82
83 def _evaluate(self, point: Dict[str, float]) -> Dict[str, float]:
84 """Multilinear interpolation at point."""
85 n = len(self._axes)
86 indices = []
87 weights = []
88 for ax in self._axes:
89 name = ax["name"]
90 x = point.get(name, np.nan)
91 grid = ax["values"]
92 hi = int(np.searchsorted(grid, x))
93 if hi <= 0:
94 hi = 1
95 if hi >= len(grid):
96 hi = len(grid) - 1
97 lo = hi - 1
98 x0, x1 = grid[lo], grid[hi]
99 t = 0.0 if x1 == x0 else float(np.clip((x - x0) / (x1 - x0), 0.0, 1.0))
100 indices.append((lo, hi))
101 weights.append((1.0 - t, t))
102
103 result = {k: 0.0 for k in self._outputs}
104 n_corners = 1 << n
105 for corner in range(n_corners):
106 w = 1.0
107 idx = []
108 for i in range(n):
109 lo, hi = indices[i]
110 w0, w1 = weights[i]
111 if (corner >> i) & 1:
112 idx.append(hi)
113 w *= w1
114 else:
115 idx.append(lo)
116 w *= w0
117 if w < 1e-10:
118 continue
119 for name, arr in self._outputs.items():
120 val = arr[tuple(idx)]
121 if np.isfinite(val):
122 result[name] += w * float(val)
123 return result
Dict[str, float] _evaluate(self, Dict[str, float] point)
EngineEstimate estimate_from_pressures(self, float P_d_F, float P_d_O, bool use_cache=True)