STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
RobustDDPController.hpp
Go to the documentation of this file.
1#ifndef ROBUST_DDP_CONTROLLER_HPP
2#define ROBUST_DDP_CONTROLLER_HPP
3
4#include <Eigen/Dense>
5#include <array>
6#include <chrono>
7#include <memory>
8#include <mutex>
9#include <optional>
10#include <vector>
11
12namespace fsw {
13namespace control {
14
15// ────────────────────────────────────────────────────────────────────────
16// Constants (match Python dynamics.py)
17// ────────────────────────────────────────────────────────────────────────
18constexpr int N_STATE = 11; // [P_copv, P_reg, P_u_F, P_u_O, P_d_F, P_d_O,
19 // V_u_F, V_u_O, m_gas_copv, m_gas_F, m_gas_O]
20constexpr int N_CONTROL = 2; // [u_F, u_O] ∈ [0,1]
21
22// State indices
23constexpr int IDX_P_COPV = 0;
24constexpr int IDX_P_REG = 1;
25constexpr int IDX_P_U_F = 2;
26constexpr int IDX_P_U_O = 3;
27constexpr int IDX_P_D_F = 4;
28constexpr int IDX_P_D_O = 5;
29constexpr int IDX_V_U_F = 6;
30constexpr int IDX_V_U_O = 7;
31constexpr int IDX_M_GAS_COPV = 8;
32constexpr int IDX_M_GAS_F = 9;
33constexpr int IDX_M_GAS_O = 10;
34
35// Physical constants
36constexpr double GAMMA_N2 = 1.4; // N₂ specific heat ratio
37constexpr double R_GAS_N2 = 296.8; // N₂ gas constant [J/(kg·K)]
38constexpr double G0 = 9.807; // gravitational accel [m/s²]
39
40/**
41 * @brief Robust Hybrid Optimal Controller
42 *
43 * Implements the full mathematical framework from the paper:
44 * §4 Blowdown ullage dynamics (ideal-gas law, choked/subsonic flow)
45 * §6 Injector + chamber model (Cd·A·√(2ρΔP), chamber root-find)
46 * §7 Hard combustion constraints (MR, injector stiffness)
47 * §9 Robust min-max MPC
48 * §10 Enumerative robust MPC for binary valves
49 * §11 Open/close economic decision logic
50 * §12 Supervisory cutoff
51 * + DDP-based continuous relaxation (iLQR-style)
52 */
54public:
55 // ── Configuration ──────────────────────────────────────────────────
56 struct Config {
57 // Horizon
58 int N = 20; // Prediction horizon
59 double dt = 0.01; // Timestep [s]
60
61 // Cost weights (match robust_ddp_default.yaml)
62 double qF = 1.0; // Thrust tracking
63 double qMR = 10.0; // Mixture ratio
64 double qGas = 0.1; // Gas consumption
65 double qSwitch = 0.01; // Control switching
66
67 // Constraints
68 double MR_min = 1.5;
69 double MR_max = 3.0;
70 double P_u_max = 10e6; // [Pa]
71 double P_copv_min = 1e6; // [Pa]
72 double injector_dp_frac = 0.1; // Minimum ΔP / Pch
73 double headroom_dp_min = 50000; // [Pa]
74
75 // Robustness
76 double rho = 0.1; // Disturbance bound
77 double eta = 0.01; // Robustness margin
78
79 // Actuation
80 double dwell_time = 0.05; // [s]
81 double duty_quantization = 0.01;
82
83 // Dynamics (COPV / regulator / feed)
84 double copv_cF = 1e5; // COPV consumption fuel [Pa/s·u]
85 double copv_cO = 1e5; // COPV consumption ox [Pa/s·u]
86 double copv_loss = 1e3; // COPV leak [Pa/s]
87 double V_copv = 0.006; // COPV volume [m³]
88 double reg_setpoint = -1.0; // Regulator setpoint [Pa] (<0 = ratio mode)
89 double reg_ratio = 0.8; // P_reg / P_copv
90 double alpha_F = 10.0; // Pressurization coeff [1/s]
91 double alpha_O = 10.0;
92 double rho_F = 800.0; // Fuel density [kg/m³]
93 double rho_O = 1140.0; // Oxidizer density [kg/m³]
94 double tau_line_F = 0.01; // Feed-line τ [s]
95 double tau_line_O = 0.01;
96
97 // Injector model
98 double Cd_fuel = 0.7; // Discharge coeff
99 double Cd_ox = 0.7;
100 double A_inj_fuel = 1e-4; // Injector orifice area [m²]
101 double A_inj_ox = 1e-4;
102 double c_star = 1600.0; // Characteristic velocity [m/s]
103 double Cf = 1.5; // Thrust coefficient
104 double A_throat = 1e-3; // Nozzle throat area [m²]
105
106 // Initial ullage volumes [m³]
107 double V_u_F_init = 0.01;
108 double V_u_O_init = 0.01;
109
110 // Polytropic blowdown
111 double n_polytropic = 1.2;
112 double T_gas_init = 293.0; // [K]
113
114 // DDP solver
116 double convergence_tol = 1e-4;
117
118 // Supervisory cutoff (§12)
119 double altitude_target = -1.0; // h* [m] (< 0 = disabled)
120 double cutoff_delta = 50.0; // δ [m]
121 };
122
123 // ── Sensor measurements ────────────────────────────────────────────
124 struct Measurement {
125 double P_copv;
126 double P_reg;
127 double P_u_fuel;
128 double P_u_ox;
129 double P_d_fuel;
130 double P_d_ox;
131 double P_ch_mp1 = 0.0; // Chamber Mid PT 1 [Pa] — for P_ch estimate
132 double P_ch_mp2 = 0.0; // Chamber Mid PT 2 [Pa] — for P_ch estimate
133 std::chrono::steady_clock::time_point timestamp;
134 };
135
136 // ── Navigation state ───────────────────────────────────────────────
137 struct NavState {
138 double h = 0.0; // Altitude [m]
139 double vz = 0.0; // Vertical vel [m/s]
140 double theta = 0.0; // Tilt angle [rad]
141 double mass = 10.0; // Vehicle mass [kg]
142 };
143
144 // ── Command ────────────────────────────────────────────────────────
146 struct Command {
148 double thrust_desired = 0.0;
149 double altitude_goal = 0.0;
150 double P_fuel_target = 0.0; // Target fuel tank pressure [Pa]
151 double P_ox_target = 0.0; // Target ox tank pressure [Pa]
152 };
153
154 // ── Actuation output ───────────────────────────────────────────────
156 double duty_F = 0.0; // Fuel duty [0,1]
157 double duty_O = 0.0; // Oxidizer duty [0,1]
158 bool u_F_on = false;
159 bool u_O_on = false;
160 bool valid = false;
161 };
162
163 // ── Engine estimate ────────────────────────────────────────────────
165 double F = 0.0; // Thrust [N]
166 double MR = 0.0; // Mixture ratio
167 double P_ch = 0.0; // Chamber pressure [Pa]
168 double mdot_F = 0.0; // Fuel flow [kg/s]
169 double mdot_O = 0.0; // Ox flow [kg/s]
170 double dp_F = 0.0; // Injector ΔP fuel [Pa]
171 double dp_O = 0.0; // Injector ΔP ox [Pa]
172 bool valid = false;
173 };
174
175 // ── Diagnostics ────────────────────────────────────────────────────
176 struct Diagnostics {
177 double F_ref = 0.0;
178 double MR_ref = 0.0;
179 double F_estimated = 0.0;
180 double MR_estimated = 0.0;
181 double P_ch = 0.0;
182 double cost = 0.0;
183 bool safety_filtered = false;
184 bool cutoff_active = false;
186 std::vector<double> u_sequence;
187 };
188
189 // ── Controller state ───────────────────────────────────────────────
191 Eigen::VectorXd x; // Full state [N_STATE]
192 double V_u_F = 0.01;
193 double V_u_O = 0.01;
194 Eigen::VectorXd w_bar; // Residual bounds [N_STATE]
195 };
196
197 // ── Public API ─────────────────────────────────────────────────────
200
201 bool initialize(const Config& config);
202 void reset();
203
204 /**
205 * @brief Execute one control step.
206 *
207 * Pipeline:
208 * 1. Build state x from measurements
209 * 2. Build reference (F_ref, MR_ref) over horizon
210 * 3. Update robustness bounds
211 * 4. Check supervisory cutoff (§12)
212 * 5. Solve DDP or enumerative MPC (§10)
213 * 6. Safety filter — reachable tube (§9.3)
214 * 7. Actuation quantization + dwell
215 */
216 std::pair<ActuationCommand, Diagnostics> step(const Measurement& meas, const NavState& nav,
217 const Command& cmd);
218
220
221private:
222 // ── Internal types ─────────────────────────────────────────────────
223 struct Reference {
225 std::vector<double> F_ref;
226 std::vector<double> MR_ref;
227 std::vector<double> P_fuel_ref;
228 std::vector<double> P_ox_ref;
229 };
230
231 struct DDPSolution {
232 Eigen::MatrixXd u_seq; // (N, 2)
233 Eigen::MatrixXd x_seq; // (N+1, N_STATE)
234 double cost = 0.0;
235 int iterations = 0;
236 bool converged = false;
237 };
238
239 // ── Dynamics (§4–5) ────────────────────────────────────────────────
240 Eigen::VectorXd dynamicsStep(const Eigen::VectorXd& x, const Eigen::VectorXd& u, double mdot_F,
241 double mdot_O) const;
242
243 void linearize(const Eigen::VectorXd& x, const Eigen::VectorXd& u, double mdot_F, double mdot_O,
244 Eigen::MatrixXd& A, Eigen::MatrixXd& B) const;
245
246 // ── Engine model (§6) ──────────────────────────────────────────────
247 EngineEstimate estimateEngine(double P_d_fuel, double P_d_ox) const;
248
249 // ── Reference generation (§8) ──────────────────────────────────────
250 Reference buildReference(const NavState& nav, const Measurement& meas,
251 const Command& cmd) const;
252
253 // ── State building ─────────────────────────────────────────────────
254 Eigen::VectorXd buildState(const Measurement& meas) const;
255
256 // ── Constraint checking (§7) ───────────────────────────────────────
257 bool isStateSafe(const Eigen::VectorXd& x,
259
260 // ── DDP solver (§9) ────────────────────────────────────────────────
261 DDPSolution solveDDP(const Eigen::VectorXd& x0, const Reference& ref,
262 const Eigen::MatrixXd& u_init) const;
263
264 double runningCost(const Eigen::VectorXd& x, const Eigen::VectorXd& u, const Reference& ref,
265 int k, const Eigen::VectorXd& u_prev) const;
266
267 // ── Enumerative robust MPC (§10) ───────────────────────────────────
268 Eigen::VectorXd enumerativeMPC(const Eigen::VectorXd& x0, const Reference& ref, int k) const;
269
270 // ── Open/close decision logic (§11) ────────────────────────────────
271 Eigen::VectorXd economicDecision(const Eigen::VectorXd& x, const Reference& ref, int k) const;
272
273 // ── Safety filter — tube propagation (§9.3) ────────────────────────
274 Eigen::VectorXd filterAction(const Eigen::VectorXd& x, const Eigen::VectorXd& u_proposed,
275 const Reference& ref, int k) const;
276
277 bool isActionSafe(const Eigen::VectorXd& x, const Eigen::VectorXd& u, CommandType cmd_type,
278 int num_steps = 2) const;
279
280 // ── Supervisory cutoff (§12) ───────────────────────────────────────
281 bool shouldCutoff(const NavState& nav) const;
282
283 // ── Actuation ──────────────────────────────────────────────────────
284 ActuationCommand computeActuation(const Eigen::VectorXd& u_relaxed) const;
285
286 // ── State ──────────────────────────────────────────────────────────
289 mutable std::mutex state_mutex_;
290
291 std::optional<Eigen::VectorXd> x_prev_;
292 std::optional<Eigen::VectorXd> u_prev_applied_;
293 std::optional<Eigen::MatrixXd> u_seq_prev_; // warm start
294 int tick_ = 0;
295};
296
297} // namespace control
298} // namespace fsw
299
300#endif // ROBUST_DDP_CONTROLLER_HPP
Eigen::VectorXd filterAction(const Eigen::VectorXd &x, const Eigen::VectorXd &u_proposed, const Reference &ref, int k) const
Eigen::VectorXd dynamicsStep(const Eigen::VectorXd &x, const Eigen::VectorXd &u, double mdot_F, double mdot_O) const
EngineEstimate estimateEngine(double P_d_fuel, double P_d_ox) const
void linearize(const Eigen::VectorXd &x, const Eigen::VectorXd &u, double mdot_F, double mdot_O, Eigen::MatrixXd &A, Eigen::MatrixXd &B) const
Eigen::VectorXd economicDecision(const Eigen::VectorXd &x, const Reference &ref, int k) const
std::optional< Eigen::VectorXd > x_prev_
Eigen::VectorXd enumerativeMPC(const Eigen::VectorXd &x0, const Reference &ref, int k) const
ActuationCommand computeActuation(const Eigen::VectorXd &u_relaxed) const
bool shouldCutoff(const NavState &nav) const
double runningCost(const Eigen::VectorXd &x, const Eigen::VectorXd &u, const Reference &ref, int k, const Eigen::VectorXd &u_prev) const
bool isStateSafe(const Eigen::VectorXd &x, CommandType cmd_type=CommandType::THRUST_DESIRED) const
Reference buildReference(const NavState &nav, const Measurement &meas, const Command &cmd) const
bool isActionSafe(const Eigen::VectorXd &x, const Eigen::VectorXd &u, CommandType cmd_type, int num_steps=2) const
bool initialize(const Config &config)
std::pair< ActuationCommand, Diagnostics > step(const Measurement &meas, const NavState &nav, const Command &cmd)
Execute one control step.
DDPSolution solveDDP(const Eigen::VectorXd &x0, const Reference &ref, const Eigen::MatrixXd &u_init) const
Eigen::VectorXd buildState(const Measurement &meas) const
std::optional< Eigen::MatrixXd > u_seq_prev_
std::optional< Eigen::VectorXd > u_prev_applied_
constexpr int N_CONTROL
constexpr double G0
constexpr int IDX_M_GAS_O
constexpr int N_STATE
constexpr int IDX_P_D_O
constexpr int IDX_P_REG
constexpr int IDX_P_U_F
constexpr int IDX_P_COPV
constexpr double GAMMA_N2
constexpr int IDX_P_D_F
constexpr int IDX_M_GAS_F
constexpr int IDX_V_U_F
constexpr int IDX_P_U_O
constexpr int IDX_V_U_O
constexpr int IDX_M_GAS_COPV
constexpr double R_GAS_N2
Motorized ball valve position: raw ADC counts -> % open.
std::chrono::steady_clock::time_point timestamp