简介:面向水声学研究与工程应用,AcTUP v2_bellhop 工具箱将 AcTUP 声学模型更新与 BELLHOP 射线追踪算法合为一体,可用于声速剖面设置、波束传播模拟、传播损失计算、二维/三维声场分布绘图以及声纳性能评估,覆盖从环境建模到结果后处理的完整流程。资源包共 1293 个文件,大小约 44.77MB,主要包括 Matlab 脚本、环境参数文件(env)、Fortran 源码(f90)、声场数据(shd)、可执行程序(exe)、批处理脚本以及 txt/doc 说明文档,不同目录按建模、传播、绘图、工具等模块做了归类,方便按需检索和二次开发。V2 版本在算法精度与计算效率上做了针对性提升,附带的说明和示例脚本可以帮助初学者快速掌握环境建模、声线追踪、声场后处理等关键步骤。目前已有 1324 人学习下载,适合水声专业学生、科研人员以及从事水下通信、声纳系统设计和海洋探测的工程师作为参考工具或教学素材。
1. AcTUP v2 bellhop工具箱:水声仿真与实验方案之间的第一道快算门槛
搞水声通信、水下目标探测或者声呐阵列布设方案的时候,最怕的是海试前没有可信的传播评估,拿着一个声速剖面就估链路余量。AcTUP v2 bellhop工具箱恰好是堵住这个缺口最顺手的一套水声工具箱:它以bellhop射线追踪模型做计算内核,外面套一层AcTUP的配置和批处理界面,让传播损失、声线轨迹、到达时延这些结果可以快速随env文件变化。用它不需要把海洋声学从头学一遍,只要理解频率、声速剖面、海底参数与环境文件的关系,就能在几十分钟内做成一套参数化的快算方案。适合水声工程师、通信算法预研和系统验证场景。下面按我实际的落地流程来写,尽量讲到能复现的程度。
2. 装好AcTUP v2并编译bellhop mex:目录、编译器与首个算例
AcTUP v2相比v1最大的改变是后端不再依赖老式MATLAB图形控件,对R2018b以后版本友好得多。我最早是在R2014a上用v1,后来切到v2之后,最直观的感受是环境文件生成和后处理面板的响应速度都正常了,不再无限转圈。如果你已经装了新版MATLAB,直接从v2开始,没必要回头用v1。下面这套安装流程我自己走了很多遍,每一步都能对上现象。
2.1 拿到AcTUP v2文件后,先确认版本和目录结构
AcTUP v2的发行包通常是一个压缩包,解压后会看到bellhop、At、Kraken、Scooter、Field这几个核心目录,每个目录对应一个水声计算程序,bellhop是我们这次的主角。At目录里是AcTUP的图形界面和公用库,后处理函数比如plotshd就在这个目录下。有的发行包还自带了一个dock.m或者acTUP_start.m,作用是帮你把整个目录加入MATLAB路径。我自己习惯手动加,务必把bellhop和At两个目录加进去,漏加At目录会出现调不到plotshd的情况。
拿到压缩包之后,先别急着解压到桌面。bellhop的Fortran源码对路径里的空格和中文非常敏感,MATLAB查找mex文件时也会因为路径里有空格出现奇怪的找不到文件报错。我一般把整个工具箱放到一个干净的路径下,比如D:\Acoustics\AcTUP_v2。解压后,先打开README.txt,确认这个版本是否支持你当前MATLAB版本。AcTUP v2的主要发行版本支持R2016a到R2023b之间的常用版本,但个别老编译器场景下mex接口会有差异。
| 目录或文件 | 作用 |
|---|---|
| bellhop | 射线模型源文件和mex编译入口,本文核心目录 |
| At | AcTUP界面代码与后处理函数,plotshd等依赖它 |
| Kraken | 简正波模型,适合低频深水场景 |
| Scooter | 抛物方程模型,适合陡变环境 |
| Field | 场计算与后处理扩展 |
| README.txt | 版本兼容说明,建议先读 |
2.2 编译bellhop mex文件的最小步骤
AcTUP v2的bellhop部分用Fortran写成,在MATLAB里直接调用前必须编译成mex可执行文件。这一步是新手最容易卡住的点,而且失败方式五花八门。我给的顺序是:先装编译器,再编译,再验证,不要跳步。
% 1. 把bellhop目录设为当前目录 cd D:\Acoustics\AcTUP_v2\bellhop % 2. 确认MATLAB使用的编译器 mex -setup % 3. 编译bellhop主程序,生成bellhop.mexw64 mex bellhop.f -output bellhop这里说下参数:-output bellhop 控制生成文件名,必须和调用函数名保持一致,否则MATLAB调用bellhop(...)时找不到对象;mex -setup 是确认编译器,MATLAB会列出可用的C编译器,选MinGW-w64或者Visual Studio。如果没装编译器,mex -setup会直接报“未找到支持的编译器”,这时要去MATLAB的附加功能里装MinGW-w64,或者确认Visual Studio的C++桌面开发组件已经装上。
还有一个更稳的编译入口。很多AcTUP v2发行包里带了make_bellhop.m脚本,里面列出的是完整源文件列表,不只是一个bellhop.f。因为我遇到过直接用mex bellhop.f编译成功、但调用时报“Invalid MEX file”的情况,排查到最后是同一个公共块源文件没有一起编进去。所以如果mex bellhop.f这条路走不通,就打开make_bellhop.m,按它里面写好的源文件列表执行,比如make_bellhop;。在Linux环境下,常见做法是进bellhop目录直接make,生成bellhop.mexa64,然后把生成的mex文件放到MATLAB搜索路径下。
编译成功后,用which bellhop确认MATLAB能找到这个函数,输出里应该带完整路径。如果显示是某个.m文件,说明目录里存在同名脚本,这是另一个常见雷点,后面避坑章节专门讲。
2.3 用自带算例验证安装
编译完之后不要急着写自己的env文件,先跑自带的bounce算例。这个算例是验证安装的黄金标准,也是我第一次跑通bellhop时用的测试用例。它模拟的是声线在海面与海底之间来回反射的经典场景,数据量小、输出清晰,非常适合用来确认编译产物是好的。
% 在bellhop目录下运行自带bounce算例 bellhop('bounce') % 检查是否生成传播损失文件 exist('bounce.shd', 'file') % 检查是否生成声线文件 exist('bounce.ray', 'file')exist返回2表示文件存在。如果bounce.shd存在,说明mex编译和env读取都正常。如果只生成了bounce.ray而没有bounce.shd,多半是env文件里的接收深度设置或者距离范围配置有问题,但既然自带算例也会出这种问题,更可能的原因是编译器导致的计算异常,需要重新编译。如果直接报“Invalid MEX file”,说明mex文件和当前MATLAB版本不兼容,或者编译器ABI不匹配,这时候不要想着修mex,直接换推荐的编译器版本重编。
还有一个容易忽略的细节:当前文件夹下不能放一个叫bellhop.m的脚本。MATLAB搜索路径里.m文件优先级高于.mexw64,一旦有同名.m文件,你敲bellhop('bounce')实际跑的是脚本而不是编译好的模型。我见过同事在这个问题上耗了大半天,最后发现是他前一次调试留下的bellhop.m。
3. 用AcTUP v2配置bellhop环境:env文件、声速剖面和海底参数
bellhop有一个和其他水声工具箱不太一样的设计:所有场景参数都收敛到一个env文件里。不管是用AcTUP v2的图形面板还是直接编辑文本,最终喂给bellhop的只有一个主文件名STEM,它会自动读STEM.env。这也意味着你只要能控制这个文本文件,就掌握了整个仿真链路。下面我把env文件里最关键的三个部分拆开讲。
3.1 env文件是bellhop唯一的输入入口
env文件本质上是一段按固定顺序排列的文本,内容包括标题行、频率、介质层数、声速剖面定义、声源深度、接收深度区间、距离范围、上下边界和海底模型参数。AcTUP v2的图形界面能帮你生成这个文件,但我还是建议至少看懂它的结构,因为批量扫描时手改文本比反复点界面快得多,也更容易回溯。
% 用MATLAB读取env文件并替换频率,这是批量扫描时的基础操作 fid = fopen('bounce.env', 'r'); lines = {}; while ~feof(fid) lines{end+1, 1} = fgetl(fid); end fclose(fid); % bellhop的env文件里频率一般在第2行 lines{2} = '2500.0'; % 写回新文件 fid = fopen('mycase.env', 'w'); fprintf(fid, '%s\n', lines{:}); fclose(fid);这个脚本不关心env文件里其他行内容,只用fgetl把整个文件读成字符串元胞数组,替换频率后再逐行写回。参数说明:fgetl按行读取并丢弃换行符,所以在写回时要手动补%s\n;lines{2}是第二行的文本,bellhop标准env的第二行就是频率(单位Hz)。这里没有做数值判断,如果模板文件第二行不是频率,替换会出错,所以操作前最好先打开env文件看一眼。
我一般不在空白env上从零手写。更可靠的做法是复制bellhop目录里的自带算例env,再在上面改参数。比如bounce.env能改成绝大多数浅海场景,只需要改频率、声速剖面和海底参数。这比从头写格式要省事得多,也能避开不少因为格式老而导致的解析问题。
3.2 声速剖面SSP的插值和分层
声速剖面是水声传播仿真里影响最大的输入之一。大多数实际场景拿到的CTD数据是离散的深度-声速点,而bellhop的env需要一组深度和声速对。直接扔原始CTD数据进去不是不行,但数据点过多会让射线追踪变慢,而且CTD数据的噪声会被bellhop当成真实梯度,导致传播损失结果出现不该有的抖动。
% 把CTD原始深度/声速转成bellhop需要的SSP数据对 depth = ctd(:,1); % 原始深度,单位米 sv = ctd(:,2); % 原始声速,单位m/s % 按2米间隔重采样,pchip避免强跃层处的过冲 depth_i = (0:2:max(depth))'; sv_i = interp1(depth, sv, depth_i, 'pchip'); % 补水面第一个点,很多env解析器要求深度从0开始 if depth_i(1) ~= 0 depth_i = [0; depth_i]; sv_i = [sv_i(1); sv_i]; end % 把结果格式化写入env的SSP段 ssp_lines = ''; for k = 1:length(depth_i) ssp_lines = sprintf('%s%8.2f %8.2f /\n', ssp_lines, depth_i(k), sv_i(k)); end关键参数说明:interp1的pchip方法比linear更平滑,在温跃层拐点处不会出现折线式的突变;重采样间隔2米是浅海常用值,深海可以放宽到5米或10米;水面第一个点必须存在,且深度为0,否则env解析器会认为SSP没有覆盖到海面。补值时用第一个声速值延拓,比用线性外推更稳,因为海表面混合层的声速变化通常很小。
SSP的深度覆盖范围也是一个高频坑点。如果水深是120米,SSP给出了从0到150米的深度序列,bellhop会怎样处理?一般直接报错或者异常终止,因为声速剖面的深度超过了海底深度。反过来,如果SSP只给到80米,100米深处的声速就缺失了,模型会按最底部的值外推,这一般不会报错,但结果会有偏差。我习惯把SSP深度严格卡到水深,并让最后一个点落在海底边界上。
3.3 海底模型和底质参数的影响
海底在浅海场景里几乎是和声速剖面同等重要的参数。bellhop的env里可以选半空间海底、层状海底和粗糙界面对流模型,实际工程中最常用的是半空间模型,因为它只需要三个参数:海底压缩波速度、密度、衰减系数。
| 底质类型 | 压缩波速度 (m/s) | 密度 (g/cm³) | 衰减 (dB/λ) |
|---|---|---|---|
| 细砂 | 1650 | 1.9 | 0.8 |
| 粉砂 | 1550 | 1.7 | 0.6 |
| 淤泥 | 1450 | 1.5 | 0.2 |
| 岩石 | 5000 | 2.5 | 0.5 |
以上是工程估算常用的底质参数近似值,不需要精确到实验测量级,但在链路预算里足够判断海底反射损失的量级。需要注意单位:bellhop里声速用m/s,密度用g/cm³,衰减用dB/波长。前后单位不统一是很容易翻车的地方,把密度写成kg/m³会直接让海底反射特性完全错乱。
如果场景里有沉积层,比如掩埋目标探测,那我一般会在env里加一层沉积物,而不是直接用半空间。层状海底多出来的每一层都要给厚度和压缩波速度、密度、衰减。层数越多,env文件越复杂,计算时间也越长。AcTUP v2的界面里有个Bottom按钮,可以填层参数,但我还是倾向于直接改env文本,因为界面里的层顺序不够直观,改错层不容易发现。
4. 跑通bellhop并获取声线、传播损失与到达结构
env文件配置完成之后,剩下的就是调用bellhop并处理输出。bellhop一次运行通常会产生三类结果:声线轨迹、传播损失场和到达结构。不同选项控制生成哪些文件,下面按我常用的顺序讲。
4.1 调用bellhop并生成声线图
声线是最直观的验证手段。它可以看作一声源的声线,看它是在海面和海底之间来回反射,还是一路打向海底。AcTUP v2里不要求手动写复杂命令,只需要让bellhop识别你的env主文件名。
% 假设env文件叫mycase.env,注意bellhop只接受不带扩展名的主文件名 bellhop('mycase', 'ray'); % 画声线轨迹,先查看接收深度和距离范围是否覆盖了目标区 plotray('mycase.ray');第二个参数'ray'告诉bellhop生成.ray声线文件。如果不传这个参数,bellhop默认只算传播损失场,生成.shd文件,不保留声线图。plotray是AcTUP At目录下提供的函数,它会读取ray文件并把每条声线画在距离-深度平面里。第一次跑通时,建议先画声线,确认声线能到达目标深度区域,再去看传播损失,这样出问题时能快速定位是环境配置的问题还是后处理的问题。
我习惯在plotray之后顺手加一句xlabel('Distance (m)')和ylabel('Depth (m)'),因为不同发行版的plotray默认坐标标注风格不一样。如果声线图里全是密密麻麻的线,说明发射角步长太小或者声线数太多,可以调大env里的角度步长,或者在AcTUP面板里减少射线条数。声线图的另一个用途是判断阴影区位置,阴影区是声线覆盖不到的区域,对水声通信来说基本就是断联区域。
4.2 读取shd文件并绘制传播损失场
传播损失是链路预算的核心结果。bellhop生成的.shd文件是网格化的传播损失矩阵,横轴是距离,纵轴是深度,数值是相对声源级衰减的dB值。AcTUP v2的plotshd可以直接把这个矩阵画出来。
% 运行bellhop生成传播损失场 bellhop('mycase'); % 用AcTUP自带函数画传播损失场 plotshd('mycase.shd'); colormap(flipud(gray)); colorbar;这里不传第二个参数,bellhop默认生成.shd文件。plotshd会把距离-深度平面的传播损失画成伪彩图,数值通常是负的,比如-60表示该点的声强相对声源降低了60dB。colormap(flipud(gray))把默认的jet伪彩色改成灰度反向,这样深色区域表示低传播损失、浅色区域表示高传播损失,在论文图里比彩虹色更适合表达。colorbar加上后才能知道色标对应的dB范围。
很多新手拿到.shd之后会想去解析二进制格式。AcTUP本身提供了plotshd,我一般不建议自己写读取函数,除非你要做批处理或者从传播损失场里提取某个距离上的垂直衰减曲线。真需要提取时,可以先用plotshd把图画出来,再用MATLAB的数据游标读取,但这个方法不适合批量。更可靠的做法是参考AcTUP里plotshd的源码,它用的是低层fopen和fread,按bellhop的标准输出格式读的,直接复用那个读取逻辑最稳妥。
4.3 到达结构arr与时延扩展
水声通信里最关心的往往不是总传播损失,而是多径时延扩展。bellhop可以输出每条本征声线的到达时间、强度、发射角和接收角,这些信息就记录在.arr文件里。
% 生成到达结构文件,选项arr表示输出每条声线的到达时间与强度 bellhop('mycase', 'arr');这会在当前目录生成mycase.arr。arr文件里的数据可以用文本编辑器打开看,每一行代表一条声线到达,包括到达时间、强度、相位、发射角和接收角。实际处理时,我一般只用到达时间和强度两列:把时间从秒换成毫秒,强度从负dB换成线性幅度,就可以画出类似信道冲激响应的多径图。
到达结构还有一个常见用途是验证声线图。如果声线图里看到一条声线走了很远的路程,那么arr里应该有对应的一条到达时间明显偏大的记录。两者对照着看,能判断模型的声线是否出现了不合理的跳变。arr文件默认只保存可分辨的到达声线,如果env里的最大发射角设置太大,声线数量会膨胀,arr文件也会变得非常大,这种情况建议缩小发射角范围,而不是通过后处理去截断。
5. AcTUP v2 + bellhop 最容易翻车的6个坑:现象、原因与排查
这一章写我实际踩过或者帮别人排查过的坑。每一条都是现象、原因、解决按顺序给,你可以直接照着对号入座。
5.1 现象:bellhop调用后没有任何输出文件
敲了bellhop('mycase'),命令窗口不报错,但当前目录里什么新文件都没有。
原因:最常见的是当前目录下根本没有mycase.env,或者env文件名拼错。bellhop对文件不存在的情况不一定会抛Java异常,Fortran代码拿到空白文件后可能直接结束。还有一个隐蔽原因是MATLAB保存过同名bellhop.m脚本,调用被脚本截胡。
解决:先用exist('mycase.env','file')确认env文件存在,再which bellhop确认调用的是mex版本。如果which输出显示bellhop.m的路径,把那个脚本改个名字并清理路径缓存。
5.2 现象:报错提示SSP深度超过水深处
运行后立刻弹出一段Fortran风格的错误信息,大意是声速剖面深度超出介质深度。
原因:CTD数据或者手工填写的SSP深度序列延伸到了海底以下。很多人在处理实测数据时没有把深度截断到水深,导致声速剖面比场景水深多出几十米。
解决:把SSP序列里所有大于水深的点删掉,并把最后一个点设置为恰好等于水深,声速值用插值计算。同时检查第一个点是否为深度0,很多解析器要求必须从海面开始。
5.3 现象:传播损失图出现大范围空白或数值断层
plotshd画出来,大片区域是NaN或者数值发生明显跳变,有些地方甚至出现条纹状断层。
原因:最大发射角设置太小,声线覆盖不到远距离和深层区域;另一种可能是声速剖面里存在强负梯度,导致声线发生折射,形成了大面积的声影区。
解决:把env里发射角范围从正负80度开始试,逐步缩小到能覆盖目标区域的最小角度。如果只是某个深度的断层,检查该深度是否位于声影区边界,这时候画声线图能更直观地判断原因。
5.4 现象:mex文件在服务器上运行时出现Segmentation fault
在Linux服务器上调用bellhop时,MATLAB直接崩溃,报Segmentation fault,无任何错误提示。
原因:服务器上的GCC编译器版本和编译bellhop时用的版本不一致,或者MATLAB版本升级后旧的mex文件不再兼容。这类问题在共享服务器上特别容易出现,因为系统默认编译器可能被人更新过。
解决:不要在服务器上沿用Windows编译好的mexw64,一分钱用不了。进入bellhop目录,用make重新编译Linux版本,并确保编译时使用的gcc版本满足MATLAB的兼容性要求。如果服务器上没有MATLAB预配的编译器,可以用MATLAB自带工具安装合适版本的GCC。
5.5 现象:arr文件生成后文件大小异常巨大
加了'arr'选项后,arr文件生成了几百MB甚至超过1GB,磁盘瞬间被塞满。
原因:发射角范围设置太大,角度步长又很细,导致声线数量爆炸。每条声线对应一次到达记录或者多次上穿到达,总记录数等于声线数乘以上穿次数,很容易撑爆文件。
解决:先按正负90度步长10度跑一次,看声线覆盖范围,再逐步缩小到正负40度左右并加大步长。arr文件不需要太多声线,它要的是有代表性的本征声线,一般几十条到几百条足够画出多径时延扩展。
5.6 现象:批量扫描时多个case的结果互相覆盖
循环里用同一个模板跑不同频率,最后发现所有频率的传播损失图长得一模一样。
原因:每次循环里env文件名都叫case.env,bellhop输出文件名固定由env主文件名决定。下一个case运行时直接覆盖了上一个case的.shd和.arr文件。
解决:在循环里为每个case单独设置主文件名,不要复用同一个stem。常见做法是给模板文件重新复制一份并改名为case_500Hz.env、case_1000Hz.env这种带参数的命名,然后再调用bellhop。下面批量扫描章节会给完整脚本。
6. 批量扫描频率与距离:用AcTUP v2把单点仿真变成参数平面
单次仿真只能回答一个固定场景的问题,但工程预研通常要把频率、距离、声源深度都扫一遍。AcTUP v2并没有提供特别高级的批处理面板,所以这块我建议在MATLAB里自己写循环,反而更灵活。
% 批量扫描频率,每个频率生成独立的env文件和结果文件 freqs = [500 1000 2000 4000]; for f = freqs stem = sprintf('case_%dHz', f); % 从模板复制env,再替换频率 copyfile('template.env', [stem '.env']); fid = fopen([stem '.env'], 'r'); lines = {}; while ~feof(fid) lines{end+1, 1} = fgetl(fid); end fclose(fid); lines{2} = sprintf('%.1f', f); fid = fopen([stem '.env'], 'w'); fprintf(fid, '%s\n', lines{:}); fclose(fid); % 运行bellhop并保存传播损失 bellhop(stem); disp(['finished ' stem]); end这个脚本的核心思想是先把模板env复制成带频率标识的新文件,再读取文本并替换第二行频率,最后调用bellhop生成对应频率的结果。copyfile在这里不会覆盖模板本身,因为目标文件名每次都不同。替换频率时需要先把lines{2}中旧频率字符串替换掉,直接用赋值方式覆盖整行,比regexprep更省事。运行结束后,每个频率都有一份独立的.shd文件,不会互相覆盖。
在这个基础上,我一般还会做一步:从所有shd里提取同一深度上的传播损失,拼成距离-频率矩阵,画成二维图。这一步要先确定目标深度,比如接收水听器深度是60米,然后从每个.shd里把那一条深度线上的数据读出来。如果用AcTUP自带函数不方便,可以先用plotshd画出图,再用imagesc的思路把多个频率的结果横向排列。更稳妥的做法是从plotshd源码里取读取逻辑,因为每个.shd的头部信息都包含深度网格和距离网格,统一解析后可以直接拼接。
批量扫描最大的价值不是画图,而是帮你快速找到频率和距离的联合边界。比如某个水深环境下,低频信号能传播到10公里外,而高频到2公里就衰减到噪声底以下,这种结论对系统设计非常直接。我曾经在预研阶段用这套流程跑了24个频率、8种声源深度,总共192次仿真,整个批量脚本跑了一个晚上,第二天直接给系统方案划定了可用频率区间。
我自己在这件事上吃过亏:第一次写批量脚本时偷懒,所有case共用一个env文件名,结果睡醒发现跑了一晚上只留下最后一组结果,前面的数据全被覆盖,只能重跑。后来我强制自己每条仿真必须生成独立文件夹或者独立文件名,并且每次跑完先检查输出文件的时间戳和大小,确认没有异常被覆盖,再开始下一批。这个习惯一直保留到现在,也希望帮到你。
本文还有配套的精品资源,点击获取