2023数学建模国赛B题解析:多波束测线覆盖宽度与布线优化
2026/9/9 20:20:28 网站建设 项目流程

简介:这是一份面向2023年全国大学生数学建模竞赛B题参赛者的完整代码与资料包,适用于已掌握微积分、线性代数、优化理论等基础建模知识、需要实战参考的团队或个人。压缩包共24个文件,大小2.34MB,包含4个Python脚本(q1.py至q4.py)、7个xlsx结果表、5个xml工程配置,以及PDF与Word版赛题说明、PNG路径与分层图、RAR压缩包等,覆盖从题目理解、模型建立到求解与结果分析的主要环节,尤其适合在备赛冲刺阶段对照完整求解流程梳理思路。目前已有2068人浏览学习。除核心分类代码外,资源还提供了README说明、附录表格与绘图输出,可帮助读者快速定位每一次运行的输入输出,同时理解多问拆分、结果汇总与可视化呈现的工程组织方式。参考代码使用Python科学计算生态实现,思路清晰,便于二次修改与扩展,值得结合赛题原文逐模块研读。 2023年国赛B题公布那天,不少队伍的第一反应都一样:多波束测线问题,听名字像工程题,翻完题目发现是几何优化题,真正动手写代码才发现,几何推导只占三分,剩下七分全在怎么把公式变成一条条可靠的测线。我这份《2023年数学建模国赛B题代码.zip》不是赛后拼凑的,是比赛期间边建模边调试沉淀下来的最终版,里面没有黑科技,就是把覆盖宽度模型、自适应扫描布线、迭代补测这些方法老老实实落到了Python代码里。这篇文章会把代码包的结构、四个子问题的实现逻辑、最容易翻车的几个细节完整讲一遍。不管是准备下次国赛的新手,还是想复盘B题的老手,都可以拿这份代码当参照系。

1. 赛题回顾:2023年B题到底在考什么

1.1 从海洋测绘工程到递进的三个层次

多波束测深系统(MBES)是海洋测绘里的主力设备,换能器向海底发射一个扇形波束,开角通常120°,通过波束往返时间和角度解算水深。单次测量得到一条横跨测线方向的条带,条带宽度受水深、海底坡度和波束开角共同影响。相邻测线之间必须保持一定重叠率,防止漏测,但重叠太多又会白白增加航程成本。于是题目把工程问题抽象成了递进的三层:第一问是单点几何计算,给定深度、坡度、开角,算覆盖宽度和重叠率;第二问在一维方向上做布设优化,找满足重叠率约束的最短测线组合;第三、四问把地形从斜坡换成起伏曲面,再进一步加上"地形未知"的条件。

这题表面考几何,实际考的是优化建模和代码执行力。很多队伍第一问算得飞快,到了第二问就开始纠结测线间距怎么取,第三问一看到二维地形就不知道如何把局部坡角算出来,第四问更是直接懵掉。B题拿奖的分水岭不在公式推导,而在于能不能把几何模型封装成可复用的代码,然后一层层往上叠优化逻辑。

1.2 代码包里应该包含哪些文件

我的zip包结构是这样的:

2023_国赛B题/ ├── data/ # 题目附带的采样点数据、测区参数 ├── geometry.py # 覆盖宽度、重叠率、等效坡角等几何函数 ├── scan_line.py # 扫描布线逻辑,第二第三问共用 ├── iteration.py # 第四问的迭代补测 ├── q1.py / q2.py / q3.py / q4.py ├── plot_utils.py # 覆盖条带可视化 └── README.md # 运行说明

把几何模型单独放一个文件是这次做得最对的决定。四个子问题全部要用同一套覆盖宽度函数,如果每一问都把公式复制一遍,后续调整一个符号就要改四个地方,很容易漏。扫描布线单独抽出来,是因为第二问和第三问的区别只是"地形参数从哪来",布线循环本身完全一样。第四问的迭代逻辑又复杂一些,所以单独一个文件。

2. 多波束几何模型:覆盖宽度与重叠率的推导和实现

2.1 垂直于测线方向的等效坡角

多波束条带是在垂直于测线方向的剖面内形成的,所以真正决定覆盖宽度的不是海底最大坡度,而是海底坡面在这个剖面里的"投影坡角"。如果测线方向与坡面倾向之间的夹角是β,垂直测线方向看到的等效坡角γ_eff满足:

tanγ_eff = tanγ × |sinβ|

当测线沿着坡面走向走,β=0,剖面里看海底是平的,等效坡角为0;当测线沿着坡面倾向走,β=90°,等效坡角等于真实坡角。这个关系特别容易记反,有人用cos,有人用sin,其实只要想清楚"测线沿坡面走向时地形在剖面里应该看不到坡度"就不会错。

2.2 覆盖宽度公式与代码落地

覆盖宽度的推导可以这样理解:多波束像一只手电筒斜着照在斜坡上,照向坡上的一侧光斑被拉长,照向坡下的一侧被压缩,左右两个边缘波束的交点要分别列三角方程求解,不能用一个平均角度近似。

设换能器正下方的海水深度为D,波束半开角为α(开角120°对应α=60°),等效坡角为γ_eff,覆盖宽度W为:

W = D × sinα × [ 1/(cosα - sinα·tanγ_eff) + 1/(cosα + sinα·tanγ_eff) ]

代码实现:

import numpy as np def cover_width(depth, half_angle_deg, slope_eff_deg): alpha = np.deg2rad(half_angle_deg) gamma = np.deg2rad(slope_eff_deg) cos_a, sin_a, tan_g = np.cos(alpha), np.sin(alpha), np.tan(gamma) denom_up = cos_a - sin_a * tan_g denom_down = cos_a + sin_a * tan_g if denom_up <= 0 or denom_down <= 0: return np.inf return depth * sin_a * (1.0 / denom_up + 1.0 / denom_down)

当denom_up算出来小于等于0时,说明坡太陡,坡上一侧的边缘波束已经无法正常触底,几何上不能按常规方式覆盖。赛题参数不会出现这种情况,但如果你改参数测试边界条件就会遇到,返回inf至少能让后续布线代码不崩溃。

重叠率公式很简单:相邻测线间距为d时,重叠率η = 1 - d/W。反向用就是d = (1 - η)·W,这个式子是整个布线优化的基石。

2.3 用第一问的数据校验几何模型

第一问本质上是公式的直接套用:把题目给的几组β值逐个换算成γ_eff,代入覆盖宽度函数,再算重叠率。写完函数第一件事不是急着填表,而是画一条W随β变化的曲线:

betas = np.arange(0, 91, 5) Ws = [cover_width(70, 60, calc_eff_slope(1.5, beta)) for beta in betas] plt.plot(betas, Ws) plt.xlabel('beta / deg') plt.ylabel('W / m') plt.grid(True)

这条曲线应该是光滑且单调的。如果画出来有突变或者摆动,说明等效坡角公式的符号或者角度转换出了问题。这个校验动作只花一分钟,但能省掉后面所有问题排错的时间。第一问表格里每一列数值都要能在这个函数上复现,否则说明你的几何模型和题目设定理解不一致,得回头改模型而不是改数据。

3. 自适应扫描布线:第二问和第三问的优化逻辑

3.1 从一端推到另一端:重叠率约束下的最大间距

第二问给定测线方向后,问题变成沿着垂直测线方向找一组测线位置。很多人第一反应是均匀布线,拿测区宽度除以某个间距。但海底深度和坡度在变化,固定间距在浅水区可能重叠率爆表,到了深水区又重叠不足,所以间距必须随地形自适应。

方法其实很朴素:从测区边界开始,在当前位置算一个覆盖宽度W,按最小重叠率要求算出这一步最大允许间距d = (1 - η_min)·W,把下一条测线放到这个位置,重复直到覆盖整个测区。这是一个贪心策略,每一步都取最大间距,得到的测线数量就是最少的,论文里用反证法几句话就能说明。

def scan_lines(W_at, span, eta_min): left, right = span lines = [] pos = left while True: W_now = W_at(pos) if not lines: line_pos = left + W_now / 2.0 else: line_pos = pos lines.append(line_pos) if line_pos + W_now / 2.0 >= right: break step = (1 - eta_min) * W_now pos = line_pos + step return lines

关键点在于每次计算W用的必须是当前这条测线位置的海底深度和等效坡角,不是上一条的。在海深变化快的区域,这两个位置算出来的W可能差很多。

3.2 边界处理与首末测线的偏移

边界是最容易翻车的地方。如果直接把第一条测线放在测区边界x=left上,条带有一半面积伸到测区外,测区内的覆盖范围反而不完整;到最后一条测线同样可能漏掉右边界一小条。

正确做法是让第一条测线的条带左边缘刚好贴在测区左边界上,也就是把测线位置放在left + W/2处。这样从left开始,条带正好完全覆盖测区边缘。扫描停止条件也不能写成pos >= right,而要判断当前测线的条带右边缘是否越过right,见上面代码里的if条件。

这个偏移量看着小,但直接决定了边界处有没有漏测。去年的参赛队伍里不少就是栽在这,检查重叠率时每一段都满足,画覆盖图一看边缘白了一块。

3.3 第三问地形变化时的局部等效参数

第三问的海底不再是简单斜坡,深度是二维函数D(x,y),坡面方向和坡角在测区内到处变化。处理方法是把地形网格化,对每个可能放置测线的位置,用数值梯度算垂直测线方向的局部坡度。

如果测线沿x方向走,垂直测线方向就是y方向,那么局部等效坡度直接就是∂D/∂y,等效坡角γ_eff = arctan(∂D/∂y)。根本不需要去算坡面倾向再投影,因为垂直于测线方向上的偏导数本身就是投影结果。代码上用scipy.interpolate构造深度插值函数,再用np.gradient求梯度:

from scipy.interpolate import RegularGridInterpolator D_interp = RegularGridInterpolator((x_grid, y_grid), D, method='linear')

接下来只是把W_at函数从"传深度和坡角"改成"传坐标,在函数内部插值出D和γ_eff",扫描布线循环完全复用第二问的代码。这就是把布线逻辑单独抽出来的好处,第三问基本没有新增逻辑量。

4. 第四问未知地形的迭代补测策略

4.1 先粗测后补盲的思路

第四问的难点是"地形未知",题目只给了有限的深度采样点。第一次扫描时只能用现有采样点插值出粗略地形,插值结果在采样点稀疏的区域误差很大,按它布出的测线很可能在局部漏测。

实际做法是迭代式的:先按第三问流程生成一组初始测线,然后把这些测线上的"实测深度"当作新信息合并进已知数据,重新插值地形,找出仍未被覆盖的空白区,在空白区补测线,重复两三轮直到完全覆盖。这个思路在论文里可以包装成"基于反馈修正的逐步加密策略",本质上就是个先粗后细的自适应采样。

竞赛环境下不需要把迭代写得特别复杂,两三轮就够。第一轮用初始采样点插值,第二轮补测后一般就能把主要漏测区域封住,第三轮查漏。

4.2 用插值模拟地形更新的代码框架

核心需要一个覆盖掩码函数,把每条测线对应的条带区域在网格上标记出来:

def coverage_mask(lines, D_interp, grid_x, grid_y): mask = np.zeros_like(grid_x, dtype=bool) for line in lines: line_depth = D_interp((grid_x, line)) slope = np.gradient(line_depth, axis=0) # 按垂直测线方向 W = cover_width(line_depth, 60, np.rad2deg(np.arctan(slope))) mask |= (np.abs(grid_y - line) <= W / 2.0) return mask

主循环结构:

known = initial_samples for it in range(max_iter): D_interp = build_interpolant(known) lines = scan_lines(lambda pos: W_at(pos, D_interp), span, eta_min) mask = coverage_mask(lines, D_interp, grid_x, grid_y) uncovered_ratio = np.mean(~mask) if uncovered_ratio < 0.01: break known = merge_samples(known, sample_along_lines(lines, D_interp))

每次迭代后把mask画出来,用颜色区分覆盖和漏测区域。这个可视化比任何指标都直观,一眼就能看出漏测是集中在某个角落还是均匀分布。比赛时间紧张,不要等程序自动迭代到完美,看到mask基本干净就收手。

5. 踩坑记录:最容易出错的四个细节

5.1 角度弧度不分,结果完全不对

这是最常见的低级错误。numpy的三角默认输入弧度,题目给的全是度,如果直接把beta=45传给np.sin,画出来的曲线乱七八糟。我的习惯是函数入口统一接收度,在函数内部第一行做deg2rad转换,所有三角函数都用转换后的弧度。这样调用方永远不用关心单位,风险最小。

5.2 坡角方向的正负号判断

覆盖宽度公式里坡上侧和坡下侧的分母符号不一样,一个用减号一个用加号。写代码时如果没有把坐标系定义清楚,很容易把两个符号写反,结果算出来的覆盖宽度明显偏小甚至为负。我的建议是先画一张示意图,标清楚x正方向、坡面朝哪个方向倾斜、波束开角朝哪边,然后对着图写公式。每次检查代码看到这张图,符号对不对一目了然。

5.3 边界覆盖偏移量容易漏

边界偏移这个问题在第一版代码里几乎必现。原因很隐蔽:你按间距逐条推进,中间的覆盖重叠率都正常,只有首尾两条测线各多出半条带宽度在外面,肉眼不容易发现。用网格离散化的掩码一画,边界漏测就现出原形。解决方式就是3.2里写的,首线放在left + W/2,末线以条带右边缘覆盖到right为停止条件。

5.4 验证函数:用网格离散化检查漏测

最后强烈建议写一个小小的网格校验函数,不需要参与正式流程,只在调试验证时用。做法就是把测区切成细格子,对每条测线根据覆盖宽度把条带内的格子标为已覆盖,最后统计没被覆盖的格子比例。这个函数能一次性发现几何模型错误、边界偏移错误、间距步进步长错误三类问题。当年我第一版代码里坡角符号写反,覆盖宽度偏小导致中间漏了一大片,就是靠这个方法查出来的,比肉眼盯数据高效太多。

多波束测线问题的代码工程量不大,难在几何模型和布线逻辑的每一处细节都得严丝合缝。最后再分享一个我从这次比赛中沉淀下来的习惯:写代码之前先画一张坐标系示意图,把x正方向、坡面倾斜方向、测线方向、波束开角全部标上去。虽然多花五分钟,但这张图几乎能解决上面所有符号问题,写论文时也能直接复用。B题的模型本身不复杂,真正拉开差距的,往往就是这种细节上的严谨程度。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询