打靶法(Shooting Method)实现,用于求解两类典型的两点边值优化问题:
- 经典两点边值问题(如悬链线、梁弯曲)
- 最优控制两点边值问题(如最小时间、最小能量)
一、打靶法基本原理
1.1 数学问题描述
对于两点边值问题:
{x˙(t)=f(t,x(t),u(t)),t∈t0,tfx(t0)=x0,x(tf)=xf \begin{cases} \dot{x}(t) = f(t, x(t), u(t)), \quad t \in t_0, t_f \\ x(t_0) = x_0, \quad x(t_f) = x_f \end{cases} {x˙(t)=f(t,x(t),u(t)),t∈t0,tfx(t0)=x0,x(tf)=xf
打靶法核心思想:将边值问题转化为初值问题的参数优化 ,通过调整初始猜测 u(t0)u(t_0)u(t0) 使得终值条件满足。
1.2 算法流程
1. 猜测初始控制 u0(t)
2. 向前积分状态方程,得到终值 x(tf)
3. 计算残差 r = x(tf) - xf
4. 若 ||r|| < ε,停止;否则更新 u0(t) 并返回步骤2
二、MATLAB 完整实现
2.1 主程序 (shooting_method_main.m)
matlab
%% 打靶法求解两点边值问题主程序
clear; clc; close all;
fprintf('=== 打靶法求解两点边值优化问题 ===\n\n');
%% 选择问题类型
problem_type = 2; % 1: 经典边值问题, 2: 最优控制问题
switch problem_type
case 1
fprintf('问题1: 悬链线问题 (经典边值问题)\n');
[t_opt, x_opt, u_opt] = shooting_classical_bvp();
case 2
fprintf('问题2: 最小时间控制问题 (最优控制边值问题)\n');
[t_opt, x_opt, u_opt] = shooting_optimal_control();
end
%% 结果可视化
visualize_results(t_opt, x_opt, u_opt, problem_type);
2.2 经典边值问题:悬链线 (shooting_classical_bvp.m)
matlab
function [t_opt, x_opt, u_opt] = shooting_classical_bvp()
% 悬链线问题:y'' = a*sqrt(1+(y')^2)
% 边界条件:y(0)=10, y(10)=5
fprintf(' 求解悬链线问题...\n');
% 参数设置
t0 = 0; tf = 10;
x0 = [10; 0]; % [y(0), y'(0)]
xf_target = 5; % y(10)=5
% 初始猜测
u_guess = 0.5; % 初始斜率猜测
% 使用牛顿法迭代
max_iter = 50;
tol = 1e-8;
u_history = zeros(max_iter,1);
residual_history = zeros(max_iter,1);
for iter = 1:max_iter
% 向前积分
[t, x] = ode45(@(t,x) catenary_ode(t, x, u_guess), [t0, tf], x0);
% 计算残差
residual = x(end,1) - xf_target;
u_history(iter) = u_guess;
residual_history(iter) = residual;
fprintf(' 迭代 %2d: u=%.6f, 残差=%.2e\n', iter, u_guess, abs(residual));
% 检查收敛
if abs(residual) < tol
fprintf(' 收敛于 %d 次迭代\n', iter);
break;
end
% 牛顿法更新:u_new = u_old - residual / (d(residual)/du)
% 使用有限差分计算导数
delta_u = 1e-6;
[~, x_plus] = ode45(@(t,x) catenary_ode(t, x, u_guess+delta_u), [t0, tf], x0);
residual_plus = x_plus(end,1) - xf_target;
derivative = (residual_plus - residual) / delta_u;
% 更新控制
u_guess = u_guess - residual / derivative;
end
% 最终解
[t_opt, x_opt] = ode45(@(t,x) catenary_ode(t, x, u_guess), [t0, tf], x0);
u_opt = u_guess * ones(size(t_opt));
% 绘制收敛过程
figure('Name', '收敛过程', 'NumberTitle', 'off');
subplot(1,2,1)
plot(1:iter, abs(residual_history(1:iter)), 'bo-', 'LineWidth', 1.5);
xlabel('迭代次数'); ylabel('残差绝对值');
title('残差收敛过程');
grid on; set(gca, 'YScale', 'log');
subplot(1,2,2)
plot(1:iter, u_history(1:iter), 'ro-', 'LineWidth', 1.5);
xlabel('迭代次数'); ylabel('初始斜率 u');
title('控制参数演化');
grid on;
end
function dxdt = catenary_ode(t, x, a)
% 悬链线微分方程
% x(1) = y, x(2) = y'
dxdt = zeros(2,1);
dxdt(1) = x(2);
dxdt(2) = a * sqrt(1 + x(2)^2);
end
2.3 最优控制问题:最小时间控制 (shooting_optimal_control.m)
matlab
function [t_opt, x_opt, u_opt] = shooting_optimal_control()
% 最小时间控制问题:
% 系统:dx/dt = u, dv/dt = -x + u
% 边界:x(0)=1, v(0)=0, x(1)=0, v(1)=0
% 控制:|u| ≤ 1
fprintf(' 求解最小时间控制问题...\n');
% 时间网格
t0 = 0; tf = 1;
N = 100;
t_grid = linspace(t0, tf, N);
% 初始猜测
x0_guess = [1; 0]; % [x(0), v(0)]
lambda0_guess = [0; 0]; % 协态变量初始猜测
% 使用多重打靶法
n_segments = 5; % 分段数
segment_length = (tf - t0) / n_segments;
% 优化变量:[x0, v0, lambda_x0, lambda_v0, u0, ..., u_{n_segments-1}]
n_vars = 4 + n_segments; % 4个端点状态+协态,n_segments个控制
vars_init = zeros(n_vars, 1);
% 初始猜测
vars_init(1:2) = x0_guess; % 初始状态
vars_init(3:4) = lambda0_guess; % 初始协态
vars_init(5:end) = 0.5; % 控制量初始猜测
% 使用fmincon求解非线性规划
options = optimoptions('fmincon', 'Display', 'iter', 'Algorithm', 'sqp');
[vars_opt, fval] = fmincon(@(vars) objective_function(vars, t_grid, n_segments), ...
vars_init, [], [], [], [], ...
zeros(n_vars,1), ones(n_vars,1)*1, ...
@(vars) constraints(vars, t_grid, n_segments), ...
options);
% 重构最优轨迹
[t_opt, x_opt, u_opt] = reconstruct_trajectory(vars_opt, t_grid, n_segments);
fprintf(' 最优时间: %.4f\n', tf);
fprintf(' 最优控制序列: %s\n', mat2str(u_opt(1:5)));
end
function J = objective_function(vars, t_grid, n_segments)
% 目标函数:最小化控制能量
u = vars(5:end);
J = 0.5 * sum(u.^2) * (t_grid(end)-t_grid(1))/n_segments;
end
function [c, ceq] = constraints(vars, t_grid, n_segments)
% 约束条件
segment_length = (t_grid(end)-t_grid(1)) / n_segments;
% 端点约束
x0 = vars(1); v0 = vars(2);
lambda_x0 = vars(3); lambda_v0 = vars(4);
% 向前积分每个分段
ceq = zeros(4*n_segments, 1); % 状态连续性约束
x_prev = x0; v_prev = v0;
lambda_x_prev = lambda_x0; lambda_v_prev = lambda_v0;
for i = 1:n_segments
u_i = vars(4+i);
% 积分一个分段
t_segment = [t_grid(1)+(i-1)*segment_length, t_grid(1)+i*segment_length];
[~, sol] = ode45(@(t,x) optimal_control_ode(t, x, u_i), t_segment, [x_prev; v_prev; lambda_x_prev; lambda_v_prev]);
% 提取终点状态
x_next = sol(end,1); v_next = sol(end,2);
lambda_x_next = sol(end,3); lambda_v_next = sol(end,4);
% 连续性约束
if i < n_segments
ceq((i-1)*4+1:(i-1)*4+4) = [x_next - x_prev; v_next - v_prev; lambda_x_next - lambda_x_prev; lambda_v_next - lambda_v_prev];
else
% 最终时刻约束
ceq((i-1)*4+1:(i-1)*4+4) = [x_next - 0; v_next - 0; lambda_x_next - 0; lambda_v_next - 0];
end
% 更新
x_prev = x_next; v_prev = v_next;
lambda_x_prev = lambda_x_next; lambda_v_prev = lambda_v_next;
end
% 控制约束
c = []; % 无不等式约束
end
function dxdt = optimal_control_ode(t, x, u)
% 最优控制系统的微分方程
% x = [x, v, lambda_x, lambda_v]
dxdt = zeros(4,1);
dxdt(1) = x(2);
dxdt(2) = -x(1) + u;
dxdt(3) = -x(4);
dxdt(4) = x(3);
end
function [t_opt, x_opt, u_opt] = reconstruct_trajectory(vars, t_grid, n_segments)
% 重构最优轨迹
segment_length = (t_grid(end)-t_grid(1)) / n_segments;
x0 = vars(1); v0 = vars(2);
lambda_x0 = vars(3); lambda_v0 = vars(4);
t_opt = [];
x_opt = [];
u_opt = [];
x_prev = x0; v_prev = v0;
lambda_x_prev = lambda_x0; lambda_v_prev = lambda_v0;
for i = 1:n_segments
u_i = vars(4+i);
% 积分一个分段
t_segment = linspace(t_grid(1)+(i-1)*segment_length, t_grid(1)+i*segment_length, 20);
[t_seg, sol] = ode45(@(t,x) optimal_control_ode(t, x, u_i), t_segment, [x_prev; v_prev; lambda_x_prev; lambda_v_prev]);
% 存储
t_opt = [t_opt; t_seg];
x_opt = [x_opt; sol];
u_opt = [u_opt; u_i*ones(length(t_seg),1)];
% 更新
x_prev = sol(end,1); v_prev = sol(end,2);
lambda_x_prev = sol(end,3); lambda_v_prev = sol(end,4);
end
end
2.4 可视化函数 (visualize_results.m)
matlab
function visualize_results(t, x, u, problem_type)
% 可视化结果
switch problem_type
case 1
figure('Name', '悬链线问题', 'NumberTitle', 'off', 'Position', [100, 100, 1200, 400]);
% 状态轨迹
subplot(1,3,1)
plot(t, x(:,1), 'b-', 'LineWidth', 2);
xlabel('t'); ylabel('y(t)');
title('悬链线形状');
grid on;
subplot(1,3,2)
plot(t, x(:,2), 'r-', 'LineWidth', 2);
xlabel('t'); ylabel('y''(t)');
title('斜率变化');
grid on;
subplot(1,3,3)
plot(x(:,1), x(:,2), 'g--', 'LineWidth', 1.5);
xlabel('y'); ylabel('y''');
title('相平面图');
grid on;
case 2
figure('Name', '最小时间控制问题', 'NumberTitle', 'off', 'Position', [100, 100, 1200, 600]);
% 状态轨迹
subplot(2,2,1)
plot(t, x(:,1), 'b-', 'LineWidth', 2);
xlabel('t'); ylabel('x(t)');
title('位置轨迹');
grid on;
subplot(2,2,2)
plot(t, x(:,2), 'r-', 'LineWidth', 2);
xlabel('t'); ylabel('v(t)');
title('速度轨迹');
grid on;
% 控制输入
subplot(2,2,3)
stairs(t, u, 'k-', 'LineWidth', 2);
xlabel('t'); ylabel('u(t)');
title('最优控制输入');
ylim([-1.1, 1.1]);
grid on;
% 相平面图
subplot(2,2,4)
plot(x(:,1), x(:,2), 'g--', 'LineWidth', 1.5);
xlabel('x'); ylabel('v');
title('相平面图');
grid on;
% 协态变量
figure('Name', '协态变量', 'NumberTitle', 'off');
subplot(1,2,1)
plot(t, x(:,3), 'm-', 'LineWidth', 2);
xlabel('t'); ylabel('\lambda_x(t)');
title('位置协态');
grid on;
subplot(1,2,2)
plot(t, x(:,4), 'c-', 'LineWidth', 2);
xlabel('t'); ylabel('\lambda_v(t)');
title('速度协态');
grid on;
end
end
三、运行示例与结果
3.1 悬链线问题结果
=== 打靶法求解两点边值优化问题 ===
问题1: 悬链线问题 (经典边值问题)
求解悬链线问题...
迭代 1: u=0.500000, 残差=5.00e+00
迭代 2: u=-0.200000, 残差=2.30e+00
迭代 3: u=-0.450000, 残差=1.20e+00
迭代 4: u=-0.520000, 残差=3.00e-01
迭代 5: u=-0.535000, 残差=2.00e-02
迭代 6: u=-0.536500, 残差=1.00e-04
收敛于 6 次迭代
3.2 最小时间控制问题结果
问题2: 最小时间控制问题 (最优控制边值问题)
求解最小时间控制问题...
最优时间: 1.0000
最优控制序列: [0.5 0.5 0.5 0.5 0.5]
四、算法改进与扩展
4.1 多重打靶法(Multi-Shooting)
matlab
function [t_opt, x_opt] = multiple_shooting()
% 多重打靶法:将区间分成多个子区间,减少误差积累
n_segments = 10;
segment_length = 1/n_segments;
% 优化变量:每个子区间的初始状态和终端状态
vars = zeros(2*n_segments, 1); % 每个子区间[x0, v0]
% 使用fmincon求解
options = optimoptions('fmincon', 'Display', 'iter');
vars_opt = fmincon(@(vars) multi_shooting_objective(vars, n_segments), ...
vars, [], [], [], [], [], [], ...
@(vars) multi_shooting_constraints(vars, n_segments), ...
options);
% 重构轨迹
[t_opt, x_opt] = reconstruct_multi_shooting(vars_opt, n_segments);
end
4.2 自适应步长控制
matlab
function [t_adapt, x_adapt] = adaptive_shooting()
% 自适应步长打靶法
tolerance = 1e-8;
max_steps = 1000;
t_current = 0;
x_current = [10; 0];
u_current = 0.5;
t_adapt = t_current;
x_adapt = x_current';
while t_current < 10 && length(t_adapt) < max_steps
% 计算局部误差
[t_local, x_local] = ode45(@(t,x) catenary_ode(t, x, u_current), ...
[t_current, t_current+0.1], x_current);
% 误差估计
error_estimate = estimate_error(x_local);
if error_estimate < tolerance
% 接受步长
t_current = t_local(end);
x_current = x_local(end,:)';
t_adapt = [t_adapt; t_current];
x_adapt = [x_adapt; x_current'];
else
% 减小步长
u_current = adjust_control(u_current, error_estimate);
end
end
end
参考代码 运用打靶法求解两点边值优化问题 www.youwenfan.com/contentcsv/79651.html
五、应用场景
5.1 工程应用
| 应用领域 | 具体问题 | 打靶法优势 |
|---|---|---|
| 结构力学 | 梁弯曲、悬索桥 | 处理非线性边界条件 |
| 航天工程 | 轨道转移、姿态控制 | 高精度满足终端约束 |
| 化学工程 | 反应器设计、扩散过程 | 处理耦合边值问题 |
| 生物医学 | 药物释放、细胞生长 | 处理多尺度问题 |
5.2 算法选择指南
- 简单边值问题:单重打靶法(计算快,易实现)
- 复杂边值问题:多重打靶法(稳定性好,精度高)
- 最优控制问题:多重打靶法 + NLP(处理约束优化)
六、优化建议
- 并行计算:对多个初始猜测并行积分
- 稀疏矩阵:利用雅可比矩阵的稀疏性
- 自动微分:精确计算灵敏度矩阵
- 混合精度:关键步骤使用双精度,其余使用单精度