作者:HongXiao
本文旨在构建一种通用自动入轨算法.
我们常用的入轨方法是重力转弯+远地点附近点火, 这种方法简单可控, 但是缺点是它并不像想象中一样省油, 而且精度较差. 而在真实的火箭入轨过程中, 二级发动机不会在到达远地点附近时二次点火, 通常关机即入轨, 它们采用了一些制导算法, 其中线性正切律迭代制导是一种平衡优化程度与计算复杂度的算法. 其最早被应用于土星系列火箭, 成为执行地月转移轨道注入 (TLI) 的关键技术; 后来成为航天飞机动力显式制导 (PEG) 的基础.
我们给出的自动入轨算法包含两个阶段, 第一阶段是在稠密大气中, 火箭遵循重力转弯, 以减小空气阻力, 增强运行稳定性; 第二阶段开始于火箭到达高空后, 通常是第一级分离后, 火箭按照线性正切制导律进行闭环控制, 直到到达预定高度和速度. 本文聚焦第二阶段的具体控制算法.
我们将问题简化, 假设火箭在二维平面运动, 由于火箭入轨过程中位移相对于地球半径很小, 可以假设地面是一条直线, 由于火箭已到达高空, 可以假设无大气阻力, 推力大小恒定. 我们也假设火箭质量恒定, 重力加速度恒定, 这确实过度简化了实际情况, 因为火箭会消耗大量燃料, 且水平速度会产生离心作用抵消重力加速度, 导致我们设计的算法实际上不是最优的, 但是这种简化能让我们得到解析方程组, 便于实时的迭代求解.
问题描述
在平面直角坐标系中, 重力方向沿 轴负半轴, 当前时间为 , 火箭当前位置为 , 当前速度为 , 比推力大小恒为 , 目标是达到高度为 , 速度方向水平, 大小为 的轨道. 我们要控制火箭推力方向角 , 使终止时间 最小.
我们提出最优控制问题:
寻找控制函数 和终端时刻 使得代价函数 最小, 满足约束:
.png)
初始条件:
.png)
终端条件:
.png)
应用庞特里亚金极大值原理, 我们可以得到:
.png)
这就是线性正切制导律, 此结论的证明不在本文讨论范围内, 请参考文献[1]. 现在的问题是 的计算方式.
解析方程推导
对约束条件应用链式法则改变自变量
.png)
对 (5b), (5c) 积分
.png)
将 (6b) 代入 (5a) , 对 (5a) 积分
.png)
利用 (6), (7) 代入终端条件, 我们得到了关于 三个未知数的方程组
.png)
现在要将它们化为关于 的方程组, 为了简洁我们记
.png)
由 (8b)
.png)
代入 (8a)
.png)
由 (8b) 和 (8c) 消去
.png)
(11), (12) 是关于 的方程组, 可以用于数值求解.
数值求解方法
设
.png)
令 为 的解, 设
.png)
现在只需求 的零点, 根据物理意义, 可以发现 在 上严格增, 可以用二分法求解其零点, 前提是对于每一个 , 我们能解出 , 为此, 我们考察 的单调性, 引入双曲函数代换 简化表达式, 代换后不改变函数的单调性, 并且避免了边界奇点, 由于 , 有
.png)
代入 (13)
.png)
求导
.png)
I. 当 时, 有
.png)
是严格凸函数, 至多有两个零点, 令 , 可以找到最小值点
.png)
显然至少有一个平凡的零点 , 我们要找到另一个零点, 这个零点满足
.png)
且 在 和 上单调, 因此可以在 的一侧用牛顿迭代法寻找 . 初值设定为
.png)
第 次近似值为
.png)
II. 当 时, 先增后减. 若 , 有
.png)
此时只有平凡解, 这个情况下火箭不可能达到目标速度. 若 , 则 有零点 , 有零点
.png)
先减再增后减, 此时存在非平凡解的充要条件是 , 那么共三个解 , 考虑到
.png)
关于 严格增, 因此 应取较小的非平凡解 , 满足
.png)
我们希望初值仍然为 , 事实上, 可以证明对于满足 的
.png)
这意味着 和 在区间 和 均保号, 因此可以在这两个区间内进行牛顿迭代法求解.
末端误差补偿
请注意此制导律对于比推力敏感, 意味着我们不能在即将圆轨时随意减小推力, 因此在即将结束的几秒内需要选用其他算法以避免速度过大. 可以选择将 和 等比例降为零, 由此引入 的误差, 但是 的误差是可以估算的.
假设线性正切率制导在 时刻结束, 用线性正切率, 最后这段较短的时间 内可忽略 的变化, 由于此时接近圆轨, 重力加速度可忽略不计, 为匀减速运动, 竖直方向加速度大小为 , 竖直方向初始速度为
-Prrh.png)
现在我们使推力正比于速度减小, 时仍有竖直方向加速度 , 竖直方向速度 , 显然竖直方向加速度也正比于竖直方向速度, 即
.png)
解得竖直方向速度
.png)
当 时 , 对速度积分求高度改变量
.png)
则高度误差为
.png)
可以在线性正切律求解过程中给 减去 , 以补偿误差.
演示代码
#include <cmath>
#include <cstdio>
double pi = std::acos(-1.);
using std::sqrt;
using std::log;
using std::exp;
using std::atan;
double H;
double V;
double y_o;
double u_o;
double v_o;
double t_o;
double f;
double g;
double Delta_t = 5.;
double epsilon = pi / 2046;
double Delta_V, nu, mu, gamma_inf;
double gamma_o, gamma_f;
double R_o, T_o, S_o, R_f, T_f, S_f, G_gamma_o;
double a, tan_theta_o, t_f;
double cal_F_gamma_f() {
return nu * gamma_f + S_f - mu * T_f;
}
double cal_dF_gamma_f() {
return T_f - mu * S_f + nu;
}
void find_gamma_f() {
gamma_f = gamma_o;
T_f = T_o;
S_f = S_o;
double F_gamma = cal_F_gamma_f();
gamma_f = 2 * gamma_inf - gamma_o;
R_f = exp(gamma_f);
T_f = (R_f - 1 / R_f) / 2;
S_f = (R_f + 1 / R_f) / 2;
double Delta_gamma_f = (cal_F_gamma_f() - F_gamma) / cal_dF_gamma_f();
while (Delta_gamma_f > epsilon || Delta_gamma_f < -epsilon) {
gamma_f -= Delta_gamma_f;
R_f = exp(gamma_f);
T_f = (R_f - 1 / R_f) / 2;
S_f = (R_f + 1 / R_f) / 2;
Delta_gamma_f = (cal_F_gamma_f() - F_gamma) / cal_dF_gamma_f();
}
}
void cal_G_gamma_o() {
R_o = exp(gamma_o);
T_o = (R_o - 1 / R_o) / 2;
S_o = (R_o + 1 / R_o) / 2;
find_gamma_f();
double Delta_T = T_f - T_o;
double Delta_S = S_f - S_o;
double Delta_gamma = gamma_f - gamma_o;
double Delta_h = H + f * (T_f / S_f) * (Delta_t * Delta_t) / 2 - y_o;
G_gamma_o = (T_f * Delta_S - S_o * Delta_T - mu * Delta_T * Delta_T) / (Delta_gamma * Delta_gamma)
+ (2 * nu * Delta_T + 1) / Delta_gamma - (2 * f * Delta_h) / (Delta_V * Delta_V);
}
void cal_a_T_o_t_f() {
Delta_V = V - u_o;
nu = v_o / Delta_V;
mu = g / f;
gamma_inf = log((nu - sqrt(nu * nu - mu * mu + 1)) / (mu - 1));
gamma_o = -pi;
cal_G_gamma_o();
while (G_gamma_o > 0) {
gamma_o *= 2;
cal_G_gamma_o();
}
double lo = gamma_o;
gamma_o = pi;
cal_G_gamma_o();
while (G_gamma_o < 0) {
gamma_o *= 2;
cal_G_gamma_o();
}
double hi = gamma_o;
gamma_o = (hi + lo) / 2;
cal_G_gamma_o();
while (hi - lo > epsilon) {
if (G_gamma_o > 0) hi = gamma_o;
else lo = gamma_o;
gamma_o = (hi + lo) / 2;
cal_G_gamma_o();
}
a = f * (gamma_f - gamma_o) / Delta_V;
tan_theta_o = T_o;
t_f = (T_f - T_o) / a + t_o;
}
double theta_t(double t) {
return atan(a * (t - t_o) + T_o) * 180 / pi;
}
int main() {
y_o = 55565.8438706764;
u_o = 1328.33816599409;
v_o = 694.614420440756;
t_o = 123.883340353146;
f = 10.3611261003202;
g = 7.66935518541919;
H = 150000;
V = 3341.16908017303;
cal_a_T_o_t_f();
for (double t = t_o; t <= t_f; t += 1.) std::printf("%f: %f\n", t, theta_t(t));
}Vizzy 代码





相关作品已上传官网 Juno: New Origins | Auto Launch 可供参考.
[1] Longuski, J.M., Guzmán, J.J., Prussing, J.E. (2014). Application of the Euler-Lagrange Theorem. In: Optimal Control with Aerospace Applications. Space Technology Library, vol 32. Springer, New York, NY.

参与讨论
(Participate in the discussion)
参与讨论