66 Eigen::VectorXd x_pred =
68 Eigen::VectorXd residual = (x - x_pred).cwiseAbs();
101 Eigen::VectorXd u_relaxed = (cost_bin <= cost_econ) ? u_binary : u_econ;
111 for (
int i = 0; i <
config_.N; ++i)
112 u_init.row(i) = u_relaxed.transpose();
122 if (cost_ddp < cost_bin && cost_ddp < cost_econ) {
123 u_relaxed = sol.
u_seq.row(0).transpose();
129 Eigen::VectorXd u_safe =
filterAction(x, u_relaxed, ref, 0);
130 if ((u_safe - u_relaxed).norm() > 1e-6)
143 std::cout <<
"[PRESSURE_MODE] tick=" <<
tick_ <<
" P_f_d=" << x(
IDX_P_D_F) / 6894.76
144 <<
"psi / " << cmd.
P_fuel_target / 6894.76 <<
"psi" <<
" u_safe=[" << u_safe(0)
145 <<
", " << u_safe(1) <<
"]" <<
" cost_bin=" << cost_bin
146 <<
" cost_econ=" << cost_econ <<
"\n";
167 const Eigen::VectorXd& u,
double mdot_F,
168 double mdot_O)
const {
183 double u_F = std::clamp(u(0), 0.0, 1.0);
184 double u_O = std::clamp(u(1), 0.0, 1.0);
185 double u_total = std::max(u_F, u_O);
187 const double T =
config_.T_gas_init;
188 const double n =
config_.n_polytropic;
191 double P_reg_eff = (
config_.reg_setpoint > 0 && P_copv >=
config_.reg_setpoint)
195 double mdot_gas_reg = 0.0;
196 if (u_total > 1e-6 && P_copv > P_reg_eff) {
198 double pr = P_reg_eff / P_copv;
199 double Cd_reg = 0.7, A_reg = 2e-5;
202 double C_star = std::sqrt(
204 mdot_gas_reg = Cd_reg * A_reg * P_copv / std::sqrt(
R_GAS_N2 * T) * C_star;
208 mdot_gas_reg = Cd_reg * A_reg * P_copv * std::sqrt(t1 * std::max(t2, 0.0));
210 mdot_gas_reg *= u_total;
211 mdot_gas_reg = std::clamp(mdot_gas_reg, 0.0, 0.2);
215 auto calc_tank_flow = [&](
double u_v,
double P_tank) ->
double {
216 if (u_v < 1e-6 || P_reg_eff <= P_tank)
218 double pr = P_tank / P_reg_eff;
220 double Cd_v = 0.65, A_v = 5e-5;
223 double C_star = std::sqrt(
225 flow = Cd_v * A_v * P_reg_eff / std::sqrt(
R_GAS_N2 * T) * C_star;
229 flow = Cd_v * A_v * P_reg_eff * std::sqrt(t1 * std::max(t2, 0.0));
233 if (u_total > 1e-6 && mdot_gas_reg > 0.0)
234 flow = std::min(flow, mdot_gas_reg * u_v / u_total);
235 return std::max(flow, 0.0);
238 double mdot_gas_F = calc_tank_flow(u_F, P_u_F);
239 double mdot_gas_O = calc_tank_flow(u_O, P_u_O);
243 double mdot_total = mdot_gas_reg + mdot_loss;
246 double m_copv_next = std::max(m_copv - dt * mdot_total, 0.0);
247 double m_F_next = std::max(m_F + dt * mdot_gas_F, 0.0);
248 double m_O_next = std::max(m_O + dt * mdot_gas_O, 0.0);
251 double P_copv_next = 0.0;
254 if (m_copv > 1e-10) {
255 double rho_ratio = m_copv_next / m_copv;
256 T_copv = T * std::pow(std::max(rho_ratio, 1e-6),
n - 1.0);
257 T_copv = std::clamp(T_copv, 200.0, 400.0);
261 P_copv_next = std::max(P_copv_next, 0.0);
265 if (
config_.reg_setpoint > 0 && P_copv_next >=
config_.reg_setpoint)
266 P_reg_next =
config_.reg_setpoint;
268 P_reg_next =
config_.reg_ratio * P_copv_next;
271 double mdot_F_safe = std::max(mdot_F, 0.0);
272 double mdot_O_safe = std::max(mdot_O, 0.0);
275 const double P_ref = 5e6;
276 double scale_F = std::sqrt(std::max(P_u_F, 1e5) / P_ref);
277 double scale_O = std::sqrt(std::max(P_u_O, 1e5) / P_ref);
278 scale_F = std::clamp(scale_F, 0.1, 2.0);
279 scale_O = std::clamp(scale_O, 0.1, 2.0);
281 if (u_total < 1e-6) {
286 double V_u_F_next = V_u_F + dt * mdot_F_safe * scale_F /
config_.rho_F;
287 double V_u_O_next = V_u_O + dt * mdot_O_safe * scale_O /
config_.rho_O;
288 V_u_F_next = std::max(V_u_F_next, 0.0);
289 V_u_O_next = std::max(V_u_O_next, 0.0);
292 auto ullage_P = [&](
double m_gas,
double V_u) {
295 return std::max(m_gas *
R_GAS_N2 * T / V_u, 0.0);
297 double P_u_F_next = ullage_P(m_F_next, V_u_F_next);
298 double P_u_O_next = ullage_P(m_O_next, V_u_O_next);
301 double P_d_F_next = P_d_F + dt * (P_u_F - P_d_F) / (
config_.tau_line_F * 2.0);
302 double P_d_O_next = P_d_O + dt * (P_u_O - P_d_O) / (
config_.tau_line_O * 2.0);
305 Eigen::VectorXd x_next(
N_STATE);
306 x_next << P_copv_next, P_reg_next, P_u_F_next, P_u_O_next, P_d_F_next, P_d_O_next, V_u_F_next,
307 V_u_O_next, m_copv_next, m_F_next, m_O_next;
337 double P_d_ox)
const {
343 double P_ch_lo = 0.0;
344 double P_ch_hi = std::min(P_d_fuel, P_d_ox);
350 auto mdot_at_Pch = [&](
double P_ch) -> std::pair<double, double> {
351 double dP_F = std::max(P_d_fuel - P_ch, 0.0);
352 double dP_O = std::max(P_d_ox - P_ch, 0.0);
359 auto residual = [&](
double P_ch) ->
double {
360 auto [mF, mO] = mdot_at_Pch(P_ch);
361 double mdot_total = mF + mO;
363 return mdot_total - mdot_nozzle;
367 for (
int i = 0; i < 30; ++i) {
368 double P_mid = 0.5 * (P_ch_lo + P_ch_hi);
369 if (residual(P_mid) > 0)
373 if (P_ch_hi - P_ch_lo < 10.0)
376 double P_ch = 0.5 * (P_ch_lo + P_ch_hi);
378 auto [mF, mO] = mdot_at_Pch(P_ch);
383 est.
MR = (mF > 1e-8) ? mO / mF : 0.0;
385 est.
dp_F = std::max(P_d_fuel - P_ch, 0.0);
386 est.
dp_O = std::max(P_d_ox - P_ch, 0.0);
387 est.
valid = (P_ch > 1e3 && std::isfinite(est.
F));
544 const Eigen::VectorXd& x0,
const Reference& ref,
const Eigen::MatrixXd& u_init)
const {
549 Eigen::MatrixXd x_seq(N + 1,
N_STATE);
550 double best_cost = std::numeric_limits<double>::infinity();
551 Eigen::MatrixXd best_u = u_init;
554 const double eps = 1e-6;
556 for (
int iter = 0; iter <
config_.max_iterations; ++iter) {
558 x_seq.row(0) = x0.transpose();
559 double total_cost = 0.0;
560 Eigen::VectorXd u_p = Eigen::VectorXd::Zero(
N_CONTROL);
562 for (
int k = 0; k < N; ++k) {
563 Eigen::VectorXd xk = x_seq.row(k).transpose();
564 Eigen::VectorXd uk = sol.
u_seq.row(k).transpose();
568 x_seq.row(k + 1) =
dynamicsStep(xk, uk, eng.mdot_F, eng.mdot_O).transpose();
571 if (total_cost < best_cost) {
572 best_cost = total_cost;
577 if (iter > 0 && std::abs(total_cost - sol.
cost) <
config_.convergence_tol) {
581 sol.
cost = total_cost;
584 Eigen::VectorXd Vx = Eigen::VectorXd::Zero(
N_STATE);
587 std::vector<Eigen::VectorXd> k_gains(N, Eigen::VectorXd::Zero(
N_CONTROL));
588 std::vector<Eigen::MatrixXd> K_gains(N, Eigen::MatrixXd::Zero(
N_CONTROL,
N_STATE));
590 for (
int k = N - 1; k >= 0; --k) {
591 Eigen::VectorXd xk = x_seq.row(k).transpose();
592 Eigen::VectorXd uk = sol.
u_seq.row(k).transpose();
595 Eigen::MatrixXd A, B;
596 linearize(xk, uk, eng.mdot_F, eng.mdot_O, A, B);
599 Eigen::VectorXd u_pr = Eigen::VectorXd::Zero(
N_CONTROL);
601 u_pr = sol.
u_seq.row(k - 1).transpose();
605 for (
int i = 0; i <
N_STATE; ++i) {
606 Eigen::VectorXd xp = xk;
608 lx(i) = (
runningCost(xp, uk, ref, k, u_pr) - c0) / eps;
612 Eigen::VectorXd up = uk;
613 up(i) = std::clamp(up(i) + eps, 0.0, 1.0);
614 lu(i) = (
runningCost(xk, up, ref, k, u_pr) - c0) / eps;
618 Eigen::VectorXd Qx = lx + A.transpose() * Vx;
619 Eigen::VectorXd Qu = lu + B.transpose() * Vx;
620 Eigen::MatrixXd Qxx = A.transpose() * Vxx * A;
621 Eigen::MatrixXd Quu = B.transpose() * Vxx * B;
622 Eigen::MatrixXd Qux = B.transpose() * Vxx * A;
625 Qxx += Eigen::VectorXd(
state_.w_bar.array().square()).asDiagonal();
631 Eigen::MatrixXd Quu_inv = Quu.inverse();
632 k_gains[k] = -Quu_inv * Qu;
633 K_gains[k] = -Quu_inv * Qux;
636 Vx = Qx + K_gains[k].transpose() * Quu * k_gains[k] + K_gains[k].transpose() * Qu +
637 Qux.transpose() * k_gains[k];
638 Vxx = Qxx + K_gains[k].transpose() * Quu * K_gains[k] + K_gains[k].transpose() * Qux +
639 Qux.transpose() * K_gains[k];
640 Vxx = 0.5 * (Vxx + Vxx.transpose());
645 bool improved =
false;
646 while (alpha > 1e-4) {
648 Eigen::MatrixXd x_new(N + 1,
N_STATE);
649 x_new.row(0) = x0.transpose();
651 Eigen::VectorXd u_p2 = Eigen::VectorXd::Zero(
N_CONTROL);
653 for (
int k = 0; k < N; ++k) {
654 Eigen::VectorXd dx = x_new.row(k).transpose() - x_seq.row(k).transpose();
655 Eigen::VectorXd du = alpha * k_gains[k] + K_gains[k] * dx;
656 Eigen::VectorXd uk_new = sol.
u_seq.row(k).transpose() + du;
657 uk_new = uk_new.cwiseMax(0.0).cwiseMin(1.0);
658 u_new.row(k) = uk_new.transpose();
661 c_new +=
runningCost(x_new.row(k).transpose(), uk_new, ref, k, u_p2);
664 dynamicsStep(x_new.row(k).transpose(), uk_new, eng.mdot_F, eng.mdot_O)
668 if (c_new < total_cost) {
688 reg = std::max(
reg / 5.0, 1e-6);
696 sol.
cost = best_cost;
712 static const std::array<Eigen::Vector2d, 4> actions = {
713 {{0.0, 0.0}, {0.0, 1.0}, {1.0, 0.0}, {1.0, 1.0}}};
715 double best_cost = std::numeric_limits<double>::infinity();
716 Eigen::VectorXd best_u = Eigen::VectorXd::Zero(
N_CONTROL);
721 const int depth = std::min(
config_.N, 3);
724 for (
const auto& u_cand : actions) {
726 Eigen::VectorXd x_wc = x0;
727 double total_cost = 0.0;
728 bool feasible =
true;
732 total_cost +=
runningCost(x_wc, u_cand, ref, k, u_prev);
735 Eigen::VectorXd x_next =
dynamicsStep(x_wc, u_cand, eng.mdot_F, eng.mdot_O);
738 x_next = x_next.cwiseMax(0.0);
751 if (total_cost < best_cost) {
752 best_cost = total_cost;
825 Eigen::VectorXd x_lo = x;
826 Eigen::VectorXd x_hi = x;
828 for (
int s = 0; s < num_steps; ++s) {
830 Eigen::VectorXd x_lo_next =
dynamicsStep(x_lo, u, eng.mdot_F, eng.mdot_O) -
state_.w_bar;
831 Eigen::VectorXd x_hi_next =
dynamicsStep(x_hi, u, eng.mdot_F, eng.mdot_O) +
state_.w_bar;
832 x_lo_next = x_lo_next.cwiseMax(0.0);
833 x_lo = x_lo_next.cwiseMin(x_hi_next);
834 x_hi = x_lo_next.cwiseMax(x_hi_next);
843 const Eigen::VectorXd& u_proposed,
845 Eigen::VectorXd u_safe = u_proposed.cwiseMax(0.0).cwiseMin(1.0);
851 static const std::array<Eigen::Vector2d, 9> candidates = {
852 {{0, 0}, {0, 0.5}, {0, 1}, {0.5, 0}, {0.5, 0.5}, {0.5, 1}, {1, 0}, {1, 0.5}, {1, 1}}};
854 double best_cost = std::numeric_limits<double>::infinity();
855 Eigen::VectorXd best_u = Eigen::VectorXd::Zero(
N_CONTROL);
858 for (
const auto& c : candidates) {
862 if (cost < best_cost) {