STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
extract_thrust_curve_from_config.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Extract thrust curve from engine config pressure_curves.
4Evaluates engine at each (P_fuel, P_lox) point and reports thrust + pressure range.
5Used to set policy LUT axes to cover the config's thrust curve.
6"""
7from __future__ import annotations
8
9import argparse
10import sys
11from pathlib import Path
12
13import numpy as np
14
15
16# Add engine_sim to path
17def _add_engine_sim(project_root: Path) -> None:
18 engine_sim_root = project_root / "engine_sim"
19 if str(engine_sim_root) not in sys.path:
20 sys.path.insert(0, str(engine_sim_root))
21
22
23def main() -> None:
24 parser = argparse.ArgumentParser(
25 description="Extract thrust curve from engine config"
26 )
27 parser.add_argument(
28 "--config",
29 type=Path,
30 default=Path("engine_sim/configs/default.yaml"),
31 help="Engine config path (relative to project root)",
32 )
33 parser.add_argument("--project-root", type=Path, default=Path.cwd())
34 parser.add_argument(
35 "--json", action="store_true", help="Output JSON for downstream use"
36 )
37 parser.add_argument(
38 "--output",
39 "-o",
40 type=Path,
41 default=None,
42 help="Write thrust curve (time_s, thrust_N) to CSV for FSW",
43 )
44 args = parser.parse_args()
45
46 project_root = args.project_root.resolve()
47 config_path = project_root / args.config
48 if not config_path.exists():
49 print(f"Config not found: {config_path}", file=sys.stderr)
50 sys.exit(1)
51
52 _add_engine_sim(project_root)
53
54 from engine.pipeline.io import load_config as load_engine_config
55 from engine.core.runner import PintleEngineRunner
57 generate_pressure_curve_from_segments,
58 )
59
60 cfg = load_engine_config(str(config_path))
61 pc = cfg.pressure_curves
62 if pc is None:
63 print("No pressure_curves in config", file=sys.stderr)
64 sys.exit(1)
65
66 n_points = pc.n_points
67 lox_segments = [
68 {
69 "length_ratio": s.length_ratio,
70 "type": s.type,
71 "start_pressure": s.start_pressure_pa,
72 "end_pressure": s.end_pressure_pa,
73 "k": s.k or 0.3,
74 }
75 for s in pc.lox_segments
76 ]
77 fuel_segments = [
78 {
79 "length_ratio": s.length_ratio,
80 "type": s.type,
81 "start_pressure": s.start_pressure_pa,
82 "end_pressure": s.end_pressure_pa,
83 "k": s.k or 0.3,
84 }
85 for s in pc.fuel_segments
86 ]
87
88 P_lox = generate_pressure_curve_from_segments(lox_segments, n_points)
89 P_fuel = generate_pressure_curve_from_segments(fuel_segments, n_points)
90
91 runner = PintleEngineRunner(cfg)
92 thrust = np.zeros(n_points)
93 for i in range(n_points):
94 try:
95 res = runner.evaluate(
96 P_tank_O=float(P_lox[i]), P_tank_F=float(P_fuel[i]), silent=True
97 )
98 thrust[i] = float(res.get("F", np.nan))
99 except Exception:
100 thrust[i] = np.nan
101
102 valid = np.isfinite(thrust)
103 F_min = float(np.min(thrust[valid])) if np.any(valid) else 0.0
104 F_max = float(np.max(thrust[valid])) if np.any(valid) else 0.0
105 P_fuel_min = float(np.min(P_fuel))
106 P_fuel_max = float(np.max(P_fuel))
107 P_lox_min = float(np.min(P_lox))
108 P_lox_max = float(np.max(P_lox))
109
110 if args.output is not None:
111 burn_time_s = pc.target_burn_time_s
112 time_s = np.linspace(0.0, burn_time_s, n_points)
113 out_path = args.output
114 out_path.parent.mkdir(parents=True, exist_ok=True)
115 with open(out_path, "w") as f:
116 f.write("time_s,thrust_N\n")
117 for t, F in zip(time_s, thrust):
118 if np.isfinite(F):
119 f.write(f"{t:.6f},{F:.2f}\n")
120 print(
121 f"Wrote thrust curve to {out_path} ({n_points} points, {burn_time_s:.2f} s)"
122 )
123
124 if args.json:
125 import json
126
127 out = {
128 "thrust_min_N": F_min,
129 "thrust_max_N": F_max,
130 "P_u_fuel_min_Pa": P_fuel_min,
131 "P_u_fuel_max_Pa": P_fuel_max,
132 "P_u_ox_min_Pa": P_lox_min,
133 "P_u_ox_max_Pa": P_lox_max,
134 "n_points": n_points,
135 "burn_time_s": pc.target_burn_time_s,
136 }
137 print(json.dumps(out, indent=2))
138 elif args.output is None:
139 print(f"Thrust: {F_min:.0f} – {F_max:.0f} N")
140 print(f"P_u_fuel: {P_fuel_min/1e6:.2f} – {P_fuel_max/1e6:.2f} MPa")
141 print(f"P_u_ox: {P_lox_min/1e6:.2f} – {P_lox_max/1e6:.2f} MPa")
142 print(f"Points: {n_points}, burn_time: {pc.target_burn_time_s} s")
143
144
145if __name__ == "__main__":
146 main()