1. 从“简单编程”到“有效建模”一个数模老兵的解题视角看到“2022数模国赛B题无人机第一题第一小问的简单编程”这个标题很多刚接触数学建模的同学可能会松一口气觉得“简单编程”意味着代码量少、逻辑直接照着题目描述翻译成代码就行。但作为一个带过好几届数模队伍、自己也从参赛者一路走过来的“老油条”我必须给你泼点冷水在数模竞赛的语境里尤其是国赛B题这种偏向工程应用的题目“简单编程”往往是最容易踩坑的地方。它考验的从来不是你能否写出几行代码而是你能否将一段模糊的自然语言描述精准地转化为一个逻辑严密、边界清晰、结果可靠的数学模型和计算过程。2022年国赛B题是关于无人机遂行编队飞行中的纯方位无源定位问题。第一题第一小问通常是一个“开胃菜”目的是让你建立基础模型熟悉问题场景。题目可能会给出若干架无人机包括一架发射信号的无人机FY00和若干架接收信号的无人机FY01, FY02...的初始位置以及它们测量到的相对于FY00的方向角要求你根据这些信息计算出某些无人机的坐标。听起来是不是挺直白不就是解个三角形或者用最小二乘法嘛。但魔鬼藏在细节里“纯方位”意味着只有角度信息没有距离信息这是一个典型的非线性优化问题测量值带有误差你如何处理这些误差让结果更鲁棒坐标系怎么选初始值怎么给算法迭代不收敛怎么办所以这篇内容我不想仅仅给你一段可以“复制粘贴”的代码。那样做你在下次遇到类似问题比如路径规划、传感器融合时依然会束手无策。我想带你走一遍一个建模者真正的思考过程如何从题目中抽象出数学模型如何根据模型特点选择合适的算法如何用编程这里主要指Python实现它以及如何验证和解读你的结果。我们会把“简单编程”背后的“不简单”逻辑彻底拆解清楚。无论你是正在备赛2025年数模的队员还是对无人机定位、优化算法感兴趣的朋友这篇基于实战的拆解都能给你带来直接的启发。2. 问题重述与数学抽象把题目翻译成数学语言拿到题目第一步不是打开编程软件而是拿出纸笔或你的笔记软件把问题用数学语言重新描述一遍。这是建模的基石决定了你后续所有工作的方向。2.1 理解场景与变量定义以2022年B题第一问的典型描述为例这里根据常见赛题结构进行合理重构并非原题一字不差的复现假设有9架无人机其中一架编号FY00位于圆心其余8架FY01-FY08均匀分布在某个半径为100米的圆周上。FY00向其他无人机发射信号其他无人机只能测量到来自FY00信号的方向角即与正北方向的夹角顺时针为正。但由于测量存在误差实际测得的方向角与理论方向角存在微小偏差。已知FY00的坐标和FY01-FY08的理论方向角以及它们带有误差的实测方向角请根据实测方向角重新估计FY01-FY08的位置坐标。首先我们需要定义清晰的数学符号坐标系通常建立平面直角坐标系以FY00为原点(0, 0)。这是最自然的选择能简化计算。已知量(x0, y0) (0, 0)FY00的坐标已知。R 100理论圆形编队的半径米。theta_i_theoretical第i架无人机FY0i相对于FY00的理论方向角弧度或度。对于均匀分布在圆上的8架无人机这个角度是已知的例如FY01: 0°, FY02: 45°, ... FY08: 315°。theta_i_measured第i架无人机测量到的方向角theta_i_measured theta_i_theoretical error_i其中error_i是一个小随机误差通常题目会给出误差范围或具体数值。未知量待求量(x_i, y_i)第i架无人机FY0i的估计坐标i 1, 2, ..., 8。2.2 建立数学模型从几何关系到优化目标关键的一步来了如何用数学公式描述“根据方向角估计位置”几何关系对于第i架无人机如果它的真实坐标是(x_i, y_i)那么从原点FY00看向它的真实方向角alpha_i满足tan(alpha_i) y_i / x_i注意这里要用atan2(y_i, x_i)函数来计算角度因为它能正确处理四个象限返回[-π, π]范围内的值。atan2是编程实现中的关键细节。测量方程我们拥有的是带有噪声的测量值theta_i_measured。因此对于每一架无人机我们有一个“观测方程”theta_i_measured ≈ atan2(y_i, x_i)这里的“≈”意味着两者不会完全相等因为测量有误差。优化模型为了找到最可能的位置(x_i, y_i)一个最直观的想法是让估计位置计算出的方向角与测量方向角之间的差距最小。对于每一架无人机这是一个独立的优化问题因为题目假设测量相互独立这里需要仔细读题有时可能需要联合优化。我们定义残差residual_i atan2(y_i, x_i) - theta_i_measured我们的目标是找到(x_i, y_i)使得residual_i的绝对值或平方最小。但注意atan2函数的存在使得residual_i关于(x_i, y_i)是非线性的。因此这是一个非线性最小二乘问题。对于单架无人机我们可以构建如下优化问题Minimize: f(x_i, y_i) [atan2(y_i, x_i) - theta_i_measured]^2 Subject to: 无约束或根据题意可能有距离约束如 x_i^2 y_i^2 ≈ R^2对于多架无人机如果独立求解就是8个独立的上述问题。如果题目暗示或要求考虑某种联合约束虽然第一小问通常独立目标函数就是所有残差平方和。为什么选择最小二乘因为它是最常用且数学上非常优美的处理测量误差的方法它假设误差服从高斯分布求出的解是最大似然估计。在数模中除非题目特别说明否则用最小二乘作为起点是稳妥且合理的。3. 算法选择与编程实现Python求解非线性优化模型建立了接下来就是算法和编程。题目说“简单编程”对于这个问题利用Python强大的科学计算库代码确实可以很简洁但理解其背后的调用逻辑至关重要。3.1 工具选型为什么是SciPyPython中解决优化问题SciPy库的optimize模块是首选。它提供了多种优化算法。对于我们的非线性最小二乘问题scipy.optimize.least_squares函数是专门为此设计的比通用的minimize函数更高效、更稳定。为什么不直接用解析解因为atan2是非线性的。如果问题是“已知角度和距离求坐标”那直接x R*cos(theta),y R*sin(theta)就行。但现在是“只有带噪声的角度距离未知”坐标和角度之间是非线性关系必须借助数值优化迭代求解。为什么不自己写梯度下降在数模竞赛有限的时间内使用成熟、稳定的库函数是明智之举。自己实现算法容易引入bug且调试耗时。least_squares内部实现了先进的算法如Trust Region Reflective, Levenberg-Marquardt能自动处理数值稳定性问题。3.2 代码实现分步拆解下面我们一步步构建求解程序。我会先给出代码框架然后解释每一个关键步骤的意图和注意事项。import numpy as np from scipy.optimize import least_squares import matplotlib.pyplot as plt # 3.2.1 参数设置与数据准备 R 100.0 # 理论半径单位米 num_drones 8 # 除了FY00外的无人机数量 # 理论位置均匀分布在半径为R的圆上 angles_theoretical np.linspace(0, 2*np.pi, num_drones, endpointFalse) # [0, 45, 90, ...] 度但这里用弧度 # 理论坐标 x_theoretical R * np.cos(angles_theoretical) y_theoretical R * np.sin(angles_theoretical) # 模拟带有测量误差的方位角读数 np.random.seed(42) # 设置随机种子确保结果可复现竞赛中可根据题目给的误差具体设置 # 假设测量误差服从均值为0标准差为sigma的正态分布题目可能给出最大误差可按3sigma原则估算 sigma_deg 2.0 # 假设误差标准差为2度 sigma_rad np.deg2rad(sigma_deg) # 转换为弧度 measurement_errors np.random.normal(0, sigma_rad, num_drones) angles_measured angles_theoretical measurement_errors # 打印查看数据 print(理论角度度:, np.rad2deg(angles_theoretical)) print(测量角度度:, np.rad2deg(angles_measured)) print(误差度:, np.rad2deg(measurement_errors))注意在真实比赛中angles_measured应该是题目直接给出的数据而不是自己模拟的。这里模拟是为了构建一个完整的可运行示例。np.random.seed(42)在调试时非常有用它能保证每次运行生成的随机误差相同便于对比算法改动前后的效果。# 3.2.2 定义残差函数 def residual(params, measured_angle): 计算残差的函数用于least_squares优化。 params: 包含当前估计坐标 [x, y] 的数组 measured_angle: 该无人机测量到的方向角弧度 x, y params # 计算当前坐标对应的角度 calculated_angle np.arctan2(y, x) # 计算残差计算值与测量值之差 # 注意处理角度周期性问题确保残差在[-pi, pi]附近避免因2pi跳变导致优化困难 residual_value calculated_angle - measured_angle # 将残差调整到 [-pi, pi] 区间这对于优化器稳定很重要 residual_value (residual_value np.pi) % (2 * np.pi) - np.pi return residual_value # 3.2.3 单架无人机的定位求解函数 def locate_one_drone(measured_angle, initial_guess): 给定一个测量角度估计无人机坐标。 measured_angle: 测量方位角弧度 initial_guess: 优化初始猜测值 [x0, y0] # 调用 least_squares 进行优化 # residual 是接受单变量参数并返回残差的函数这里我们用lambda包装一下 result least_squares(lambda p: residual(p, measured_angle), initial_guess, methodlm) # 使用Levenberg-Marquardt算法 return result.x # 返回优化得到的坐标 [x_opt, y_opt] # 3.2.4 为所有无人机提供初始猜测并求解 estimated_positions [] for i in range(num_drones): # 初始猜测使用理论位置作为初始值。这是一个非常好的起点因为误差通常很小。 initial_guess [x_theoretical[i], y_theoretical[i]] # 求解 pos_est locate_one_drone(angles_measured[i], initial_guess) estimated_positions.append(pos_est) estimated_positions np.array(estimated_positions) # 转换为NumPy数组方便计算3.3 初始猜测的重要性为什么不能随便设在上面的代码中我将理论位置(R*cos(theta_theoretical), R*sin(theta_theoretical))作为优化迭代的初始值initial_guess。这看似简单却是保证算法成功收敛的关键。非线性优化算法如least_squares本质上是一种局部搜索方法。它从一个初始点开始沿着使目标函数下降的方向迭代。如果初始点离真实解太远算法可能会收敛到一个错误的局部最优解例如收敛到圆上另一个对称点。迭代发散无法找到有效解。收敛速度极慢浪费计算时间。在本题中我们知道无人机大致分布在半径为R的圆上且测量误差较小通常题目说明是“微小偏差”。因此理论位置是一个极其优秀的初始猜测它几乎就在真实解附近能确保优化算法快速、准确地收敛到正确的局部最优解也就是全局最优解。如果题目没有给出理论位置呢这就需要一些技巧了。例如你可以假设无人机在半径为R的圆上那么初始猜测可以设为(R*cos(theta_measured), R*sin(theta_measured))。或者如果连R也不知道你可能需要引入距离作为变量并使用更复杂的全局优化方法或提供多个初始点进行尝试。但在国赛第一小问通常信息是充分的。4. 结果验证、可视化与误差分析算出坐标不是终点验证结果的合理性和分析误差才是建模报告中的加分项。这部分往往被“简单编程”思维忽略。4.1 计算估计误差并可视化我们需要量化估计效果最直接的方式是计算估计位置与理论位置之间的欧氏距离误差。# 4.1.1 计算估计误差 position_errors np.sqrt((estimated_positions[:, 0] - x_theoretical)**2 (estimated_positions[:, 1] - y_theoretical)**2) print(\n 定位结果与误差分析 ) for i in range(num_drones): print(fFY0{i1}:) print(f 理论坐标: ({x_theoretical[i]:.2f}, {y_theoretical[i]:.2f})) print(f 估计坐标: ({estimated_positions[i, 0]:.2f}, {estimated_positions[i, 1]:.2f})) print(f 位置误差: {position_errors[i]:.2f} 米) print(f 角度测量误差: {np.rad2deg(measurement_errors[i]):.2f} 度) print(f\n平均位置误差: {np.mean(position_errors):.2f} 米) print(f最大位置误差: {np.max(position_errors):.2f} 米) # 4.1.2 可视化 plt.figure(figsize(10, 8)) # 绘制理论位置绿色圆圈 plt.scatter(x_theoretical, y_theoretical, cgreen, s100, markero, labelTheoretical Positions, edgecolorsk, zorder5) # 绘制估计位置红色叉号 plt.scatter(estimated_positions[:, 0], estimated_positions[:, 1], cred, s100, markerx, labelEstimated Positions, zorder5) # 绘制FY00原点蓝色星号 plt.scatter(0, 0, cblue, s150, marker*, labelFY00 (Reference), zorder5) # 绘制从原点到估计位置的连线灰色虚线 for i in range(num_drones): plt.plot([0, estimated_positions[i, 0]], [0, estimated_positions[i, 1]], gray, linestyle--, linewidth0.8, alpha0.7) # 绘制理论圆形轨迹 circle_theta np.linspace(0, 2*np.pi, 100) circle_x R * np.cos(circle_theta) circle_y R * np.sin(circle_theta) plt.plot(circle_x, circle_y, b--, alpha0.5, labelfTheoretical Circle (R{R}m)) plt.axhline(y0, colork, linestyle-, linewidth0.5, alpha0.3) plt.axvline(x0, colork, linestyle-, linewidth0.5, alpha0.3) plt.xlabel(X Position (m)) plt.ylabel(Y Position (m)) plt.title(Drone Positioning: Theoretical vs. Estimated (with bearing-only measurements)) plt.legend() plt.grid(True, alpha0.3) plt.axis(equal) # 重要确保x轴和y轴比例相同圆看起来才是圆的 plt.show()可视化能直观地展示所有无人机的估计效果。理想情况下红色的“x”应该紧密地环绕在绿色的“o”周围。如果某个“x”偏离很远你就需要回头检查该无人机的数据或优化过程是否出了问题。4.2 误差传播分析角度误差如何影响位置误差这是体现建模深度的部分。我们可以进行一个简单的理论分析。假设无人机真实位置在极坐标下为(R, theta)。测量得到角度theta_m theta delta_theta其中delta_theta是小误差。如果我们简单地用测量角度和理论半径来估计坐标即(R*cos(theta_m), R*sin(theta_m))那么位置误差的模大约为position_error ≈ R * |delta_theta|当delta_theta以弧度为单位时例如半径R100米角度误差delta_theta 2° ≈ 0.035 弧度那么粗略的位置误差可达100 * 0.035 3.5 米。我们的优化算法因为同时优化了半径和角度实际上放松了半径固定为R的约束得到的位置误差通常会比这个粗略估计小因为它用角度残差最小作为目标找到了一个在测量角度方向上“最好”的点这个点不一定在半径为R的圆上。你可以将这种理论估算与你程序输出的实际平均误差进行对比。如果数量级吻合说明你的模型和结果基本合理。如果相差巨大例如理论估算3.5米实际输出30米那就要警惕了很可能你的模型、算法或代码有误。5. 从“解出来”到“拿高分”建模报告的撰写要点编程求解只是数模竞赛的一部分甚至不是最难的部分。如何将你的思路、模型和结果清晰、专业地呈现在论文中才是决定成绩的关键。针对这个“简单编程”问题在论文中你需要阐述清楚以下几点问题分析明确指出这是一个“纯方位无源定位”问题其本质是非线性参数估计。说明已知量、未知量以及它们之间的数学关系。模型建立坐标系选择说明选择以FY00为原点的直角坐标系的原因简化计算。目标函数清晰写出最小二乘目标函数min Σ [atan2(y_i, x_i) - theta_i_measured]^2。解释为什么使用最小二乘处理随机测量误差的常用且最优方法。约束条件说明本题是否为无约束优化。如果题目暗示无人机距离大致已知可以提及可将x_i^2 y_i^2 ≈ R^2作为软约束或先验信息融入模型例如作为正则化项但第一小问通常不需要。模型求解算法选择说明选用SciPy.optimize.least_squares中的Levenberg-Marquardt (LM) 算法。简要解释LM算法是求解非线性最小二乘问题的有效方法兼具梯度下降和高斯-牛顿法的优点能快速收敛。初始值设置强调使用理论位置作为初始猜测的重要性这是保证算法收敛到正确解的关键策略。求解过程说明对每架无人机独立求解如果是独立问题。给出核心代码片段如残差函数定义和优化调用并加以解释。结果分析数据展示以表格形式列出所有无人机的理论坐标、估计坐标、位置误差和对应的角度测量误差。误差分析计算平均误差、最大误差。结合可视化图形将理论位置、估计位置和理论圆形轨迹画在一张图上直观展示定位效果。灵敏度或误差传播分析加分项如上节所述简要讨论角度测量误差对最终定位误差的影响量级体现你对模型鲁棒性的思考。模型评价与推广优点模型直观基于最小二乘原理数学基础扎实利用成熟优化库求解稳定高效。缺点本模型假设各无人机测量独立未考虑可能存在的系统误差或无人机间的几何约束这是后续问题可能深入的。对于大角度误差或初始猜测很差的情况可能收敛到局部最优解。推广指出该模型是纯方位定位的基础可扩展到三维空间使用方位角和俯仰角或与测距信息融合成为更强大的定位系统。最后记住数模论文的黄金法则假设要合理符号要清晰模型要可读结果要可信分析要深入。即使第一问的编程很简单但如果你能按照上述逻辑完整、严谨地呈现出来就已经远超许多仅仅“交出一串数字”的队伍了。编程是工具数学建模才是灵魂。把这个“简单”的问题做深、做透你就能为后面更复杂的问题打下坚实的基础。