简介:这份资源面向水声通信、海洋声学方向的研究人员与工程技术人员,也适合具备一定Matlab基础的高校学生,用于计算水声传播损耗并分析水平阵、垂直阵各阵元的接收波形。包内共19个文件,以12张jpg结果图、3个m脚本、1个mat数据文件为主,另有png任务说明、docx公式文档与md说明,压缩包约713KB,体积轻便。资源围绕传播损耗曲线、声场分布、声速剖面及阵列方向图等结果展开,脚本涵盖水平阵与垂直阵两类计算流程,配合公式文档与任务说明可帮助读者理解算法逻辑与参数含义。已有1109人学习下载,读者可据此复现传播损耗与接收波形计算,并在此基础上修改环境参数或阵列配置,研究不同条件下的水声传播差异,为声纳设计与阵列优化提供参考。
1. 从一条“波形对不上”的报错说起:Kraken 与 Matlab 联合仿真的定位
水下声场建模里,最让人抓狂的不是跑不出结果,而是跑出来了、波形却对不上。某实验室一位做阵列信号处理的同行,用 Kraken 算完传播损耗,再把各阵元接收波形拼出来,发现近场阵元的幅度比远场还低,相位差也乱成一锅粥。排查了两天,问题出在把 Kraken 输出的场点复数声压直接当成了时域波形,忘了乘发射信号频谱再做逆变换。这类“Kraken Matlab 联合仿真”的坑,几乎每个刚接触简正波模型的人都会踩一遍。
这份资源围绕的就是这件事:用 Kraken 计算水平分层海洋环境下的传播损耗,并进一步得到各阵元位置的接收波形。它适合做水声通信、阵列信号处理、声呐性能评估的从业者和研究生,前提是你已经知道声速剖面、海底参数大概长什么样,但还没把“场计算”和“波形生成”这两步串起来。下面按“环境文件怎么写、损耗怎么算、波形怎么拼、坑在哪”的顺序拆开讲,每一步都能照着复现。
2. 环境文件与传播损耗:Kraken 的输入到底在描述什么
Kraken 是简正波(normal mode)模型,核心思路是把声场展开成一组沿深度方向的模态,再在水平方向按模态传播。它吃的输入是一个扩展名为.env的环境文件,里面用关键字分段描述海洋环境。很多人第一次写.env是照着模板改数字,改完能跑就以为对了,其实里面几个参数的物理含义直接决定损耗曲线的形状。
2.1.env文件的关键字段与常见取值
一个典型的 Pekeris 波导(均匀水层加半空间海底)环境文件长这样,字段顺序在 Kraken 里是有讲究的,'R'之后到下一个字母之间是标题行,可以随便写:
'Demo_env' ! 标题行 50.0 ! 水深 (m) 1 ! 声速剖面点数 Nsvp 0.0 1500.0 0.0 1.0 0.0 0.0 / ! 深度 声速 密度 衰减 ... 1 ! 海底半空间标志 Nsds 50.0 1700.0 1.5 0.5 / ! 海底深度 声速 密度 衰减 1 ! 声源个数 Nsds 50.0 ! 声源深度 (m) 1 ! 接收器深度个数 Nrd 50.0 ! 接收器深度 (m) 1 ! 频率个数 Nfreq 100.0 ! 频率 (Hz) 1 ! 模态数上限 Nmodes(0 表示自动) 0 ! 输出选项这里几个参数值得单独说。声速剖面那行的0.0 1500.0 0.0 1.0 0.0 0.0依次是深度、声速、密度、衰减、以及两个保留位,密度单位是 g/cm³,衰减单位是 dB/λ,写 0 就是无衰减。海底那行50.0 1700.0 1.5 0.5是深度、声速、密度、衰减,1700 m/s 是砂质海底的常见值,泥质海底会低到 1500 左右。频率 100 Hz 对应波长 15 m,水深 50 m 大约是 3 个波长,模态数不会太多,Nmodes写 0 让程序自动截断通常够用。
提示:
.env里深度必须单调递增,声速剖面点从水面往下排。如果声速剖面有跃层,点数要加密,否则模态求解会漏掉高阶模态,损耗曲线在中远距离会明显偏低。
2.2 用 kraken 可执行程序算传播损耗
环境文件写好后,调用 Kraken 主程序生成模态文件.mod,再用field程序算场。命令行顺序不能反:
kraken Demo_env # 生成 Demo_env.mod,求解模态 field Demo_env # 读取 .mod,按 .env 里的接收器深度算场field默认输出一个.shd文件,里面是复数声压随距离和深度的分布。传播损耗就是对这个复数声压取模、取对数:
import numpy as np import scipy.io as sio # 读取 field 生成的 .shd(这里假设已用工具转成 mat,或直接读二进制) # 常见做法是用 read_shd 之类的辅助函数解析 data = sio.loadmat('Demo_env.mat') pressure = data['pressure'] # 复数声压,形状 [nrd, nr, nfreq] r = data['r'].flatten() # 距离轴 (m) tl = -20 * np.log10(np.abs(pressure[0, :, 0]) + 1e-12) print(tl[:5]) # 前几个距离点的传播损耗 (dB)这段代码的逻辑是:pressure是复数,取模得到幅度,-20log10转成 dB 就是传播损耗。加1e-12是防止远距离幅度趋零时取对数出-inf。pressure[0, :, 0]取的是第一个接收深度、所有距离、第一个频率。参数上要注意,field输出的距离轴默认从 1 m 开始按.env里的步长递增,如果没在.env里指定距离范围,默认是 1 km、步长 1 m,做远场分析时记得改。
2.3 传播损耗曲线的合理性检查
算完别急着往下走,先看曲线形状。均匀波导的传播损耗在近场接近球面扩展(约 20log10 r),过了某个距离后过渡到柱面扩展(约 10log10 r),曲线斜率会变缓。如果整条曲线斜率一直是 20log10 r,说明模态没激励起来,多半是声源深度正好落在某个模态的零点上;如果曲线在中距离出现剧烈振荡,那是模态干涉,属于正常现象,但振荡幅度过大可能是海底衰减给太小。
我一般会拿一个解析可算的等声速 Pekeris 案例对一遍:水深 100 m、声速 1500、海底 1800、频率 50 Hz,手算前几个模态的截止频率,和 Kraken 输出的.mod文件里模态数对一下。对不上就回头查.env,十有八九是密度或衰减的单位写错了。
3. 从复数声压到阵元波形:频域场怎么变成时域信号
传播损耗只是幅度信息,接收波形要的是完整的时间序列。这一步是整份资源里最容易翻车的地方,因为 Kraken 给的是单频点的复数声压,而波形需要一段带宽内的频谱。思路不复杂:对发射信号做 FFT 得到频谱,在每个频点上用 Kraken 算出的场值去乘,再逆变换回时域。
3.1 多频点场计算的批处理
Kraken 一次只能算一个频率(.env里Nfreq写多个也行,但输出组织麻烦),做宽带波形通常要循环多个频点。常见做法是写个脚本批量改.env里的频率再调用:
import subprocess, os freqs = np.arange(80, 121, 1) # 80~120 Hz,步长 1 Hz for f in freqs: with open('Demo_env.env', 'r') as fh: lines = fh.readlines() # 定位频率行并替换(行号依实际文件而定,这里假设在第 12 行) lines[11] = f'{f}\n' with open(f'env_{int(f)}.env', 'w') as fh: fh.writelines(lines) subprocess.run(['kraken', f'env_{int(f)}'], check=True) subprocess.run(['field', f'env_{int(f)}'], check=True)逻辑说明:每个频点生成独立的环境文件,避免互相覆盖;check=True让子进程失败时立刻抛异常,不然 Kraken 报错被吞掉、后面拿空文件算波形,排查起来就是黑匣子。参数上,频率步长 1 Hz 对应时域长度 1 s,如果你的信号脉宽只有 0.1 s,步长可以放到 10 Hz,省一半以上的计算时间。接收器深度如果有多个阵元,.env里Nrd改成阵元数,深度列表逐个写,field会一次输出所有深度的场。
3.2 频域相乘再逆变换拼波形
拿到每个频点的复数声压后,按阵元位置组装频谱,乘发射信号频谱,再做 IFFT:
import numpy as np fs = 1000.0 # 采样率 (Hz) T = 0.5 # 信号时长 (s) t = np.arange(0, T, 1/fs) fc = 100.0 # 载频 (Hz) tx = np.cos(2*np.pi*fc*t) * np.hanning(len(t)) # 发射信号:加窗单频脉冲 TX = np.fft.fft(tx) freqs_fft = np.fft.fftfreq(len(tx), 1/fs) # 假设已收集每个频点的场值 H(f),插值到 FFT 频点上 H_interp = np.interp(freqs_fft, freqs, H_list, left=0, right=0) RX = TX * H_interp rx = np.fft.ifft(RX).real # 归一化并画图 rx = rx / np.max(np.abs(rx))关键在H_interp这一步:Kraken 算的频点是离散的(80~120 Hz),而 FFT 的频点覆盖 0~500 Hz,中间大量频点没有场值。np.interp在带内线性插值,带外置零,相当于给信号加了个矩形带通。这里有个细节,left=0, right=0必须写,否则np.interp默认用端点值外推,会把 120 Hz 的场值一路平推到 500 Hz,波形高频全是假的。ifft后取.real是因为理论上虚部应为零,数值误差留下的微小虚部直接丢掉。
3.3 阵元间相位差与波束形成的对应关系
多阵元场景下,各阵元波形的相对相位差是后续做波束形成的基础。Kraken 输出的复数声压本身带相位,np.angle(pressure[i])就是第 i 个阵元相对参考点的相位。这里要注意符号约定:Kraken 的时谐因子是exp(-iωt)还是exp(+iωt),不同版本可能不同,相位差的正负会反过来。验证方法很简单,取两个已知间距的阵元,用平面波近似算理论相位差2πd·sinθ/λ,和 Kraken 结果对一下符号,对不上就在np.angle结果前加负号。
注意:阵元间距超过半波长时会出现栅瓣,这是阵列设计的常识,但在 Kraken 仿真里容易被忽略——因为场计算本身不限制间距,你写 1 m 间距、100 Hz(波长 15 m)没问题,写 10 m 间距也不会报错,但波束图会多出旁瓣。仿真前先按
d ≤ λ/2检查一遍。
4. 避坑与排查:五个让波形“看起来对、其实错”的细节
这一章是我自己踩过、也见过别人反复踩的坑,每条按现象、原因、解决写。有些坑不会让程序报错,只会让结果悄悄偏掉,比报错更难查。
4.1 传播损耗曲线整体偏移十几 dB
现象:算出来的损耗比教科书案例整体高或低 10~20 dB,形状却对。原因:.env里声源深度或接收深度写成了 0,Kraken 对 0 深度有特殊处理(贴近海面),实际声源在 50 m 却写成 0,激励的模态组合完全不同。解决:声源和接收深度都避开 0 和正好等于水深的位置,往内缩 0.5 m 以上;改完重跑kraken,看.mod文件里前几个模态的激励系数是否合理。
4.2 波形首尾出现振铃
现象:IFFT 出来的时域波形在两端有明显振荡,中间部分正常。原因:频域插值时带外突然置零,相当于乘了矩形窗,时域对应 sinc 拖尾。解决:插值时用带过渡的窗函数,比如在 80 Hz 和 120 Hz 附近各留 5 Hz 做余弦过渡,或者直接对H_interp做平滑。另一个办法是加长信号时长 T,让振铃落在有效信号之外再截断。
4.3 多频点循环时模态数跳变
现象:相邻频点的传播损耗曲线在某个距离后突然分叉。原因:.env里Nmodes写死了固定值,低频时实际模态数少于设定值,高频时又不够,Kraken 在不同频点截断的模态集合不一致。解决:Nmodes一律写 0 让程序自动判定,或者按Nmodes ≈ 2Hf/c估算后留 20% 余量,且所有频点用同一个上限。
4.4 阵元波形幅度随距离不单调
现象:近场阵元幅度反而比远场低。原因:把复数声压的实部直接当波形用了,或者忘了乘发射信号频谱,相当于拿单频点的场值当时域采样。解决:回到第 3 章的流程,确认RX = TX * H_interp这一步没漏,且H_interp是复数不是模值。用np.abs(rx)和np.abs(pressure)对比一下量级,差一个数量级以上基本就是漏乘了。
4.5field输出距离轴与预期不符
现象:损耗曲线的距离范围只有 1 km,想算 10 km 却没有数据。原因:.env里没指定距离范围,field用默认值。解决:在.env末尾加一行距离参数,格式是最大距离 步长,比如10000.0 10.0表示算到 10 km、步长 10 m。步长别设太小,10 km 步长 1 m 就是一万个点,field跑得慢不说,后面插值也浪费内存。
5. 进阶:用模态叠加验证波形,以及一个我常备的检查习惯
到这一步,传播损耗和阵元波形都能跑通了,但“跑通”和“算对”是两回事。我习惯用模态叠加做一次交叉验证:Kraken 的.mod文件里存了每个模态的本征值和本征函数,理论上场可以写成各模态贡献之和。自己用 Python 按模态公式重算一遍某个距离点的声压,和field的输出对比,误差在 1% 以内才放心往下做波束形成。
模态叠加的核心公式是p(r,z) ≈ Σ Φ_m(zs)·Φ_m(zr)·exp(i·k_m·r)/sqrt(k_m·r),其中Φ_m是本征函数、k_m是水平波数。.mod文件是二进制,常见做法是用read_modes类工具解析成数组。下面这段是验证脚本的骨架:
import numpy as np # modes: 本征函数 [nmodes, nz],k: 水平波数 [nmodes] # zs, zr: 声源、接收深度索引;r: 距离 p_sum = 0 for m in range(nmodes): p_sum += modes[m, zs] * modes[m, zr] * np.exp(1j*k[m]*r) / np.sqrt(k[m]*r) p_field = pressure_at(r, zr) # field 输出的对应值 err = np.abs(p_sum - p_field) / np.abs(p_field) print(f'相对误差: {err:.4f}')参数上,zs和zr要换算成.mod里的深度网格索引,不是物理深度直接当索引用;k[m]取实部,虚部是衰减,验证幅度时可以先忽略。如果误差偏大,先查模态数是否截断太多,再查本征函数的归一化方式——不同版本 Kraken 的归一化可能差一个常数因子,这个因子对所有模态相同,不影响波形形状但影响绝对幅度。
还有一个习惯:每次改完.env,先只跑一个频点、一个接收深度,把损耗曲线和上一次的结果叠在一张图上,确认变化方向和幅度符合预期,再放开多频点多阵元批量跑。批量跑一次动辄十几分钟,改错一个参数重跑的成本太高,单点验证花两分钟,能省掉后面反复翻车的后悔药。从那以后我每次做 Kraken 联合仿真,都强制先走一遍“单频点对解析解、单阵元对模态叠加”这两步,确认无误再上批量。希望帮到你。
本文还有配套的精品资源,点击获取