☰
简正波模型Kraken实战:从传播损耗到各阵元波形合成
2026/10/14 13:51:28 网站建设 项目流程

简介:面向水声通信与海洋声学研究,这套基于Matlab的工具可计算声波在不同海洋环境下的传播损耗,并分析水平阵与垂直阵各阵元的接收波形,适用于声纳系统设计、性能评估及阵列优化等场景。压缩包共十九个文件,容量约七百一十三KB,其中包含三个核心计算脚本、十二张结果图像、一组环境参数数据、一份公式说明以及Markdown格式的文档,覆盖从源码实现到结果可视化的完整流程。目前已有一千一百零八人学习下载,内置示例支持调整水深、盐度等环境参数以及阵列类型,便于用户开展不同条件下的对比研究。通过运行示例并结合文档说明,可快速掌握环境建模、损耗计算与波形分析的实现方法,为水下通信和探测研究提供实用参考。

1. Kraken在算什么:从传播损耗到各阵元波形的一整条链路

做水声阵列信号处理的人,大概率都卡过这一关:手里没有海试数据,又想验证波束形成或者匹配场定位算法,最后只能靠仿真把数据“造”出来。Kraken就是这套链路里最常用的一环——它属于简正波声传播模型,输入声速剖面、海底参数、频率和收发几何,输出声波从声源传到接收点的传播损耗以及完整的复数声场。有了复数声场,每个阵元上接收到的波形就能被合成出来,喂给后续算法当输入。

这篇文章会把整条链路拆开讲:先搞懂Kraken的环境文件怎么填,再跑通命令把传播损耗取出来,接着把频域声场转换成各阵元的时域接收波形,最后给一套自检方法验证波形时延对不对。适合正在做阵列信号处理仿真、又暂时没有条件出海试验证的工程师,照着步骤能在自己的机器上复现完整流程。

2. 先看懂Kraken的输入输出:环境文件里的参数决定传播损耗准不准

2.1 简正波模型拆解:本征值、本征函数和传播损耗的关系

Kraken属于简正波类模型,它把声场看成若干离散模态沿水平方向传播、在深度方向驻波叠加的结果。每个模态对应一个水平波数(本征值)和一个深度方向的振幅分布函数(本征函数)。接收点处的声压,就是所有有效模态在该点叠加后的复数值。

传播损耗本质上是把这个复声压换算成分贝值。但值得留意的是,简正波模型算出来的传播损耗不是一条平滑衰减曲线,而是多模态干涉形成的起伏结构——同一个距离上可能因为模态相位相消出现明显的损耗谷,换一个距离又因为相位相加出现增强。这种干涉结构在射线模型里很难体现出来,这也是Kraken这类简正波模型在水声仿真里不可替代的原因。

对合成波形来说,比传播损耗更重要的是复声压本身,尤其是相位项。每个模态在水平方向传播时,相位按exp(j乘以水平波数乘以距离)旋转,这个相位项就是波形时延和频散的来源。如果只取幅度、丢掉相位,后面合成的波形就只剩包络,没有真实的到达结构。所以我会强调一句:用Kraken做阵列波形合成,眼睛要盯在复数声场上,而不是只盯着TL曲线。

2.2 组织一份环境文件:声速剖面、海底参数和接收几何怎么填

Kraken的运行入口是一个纯文本的环境文件,所有物理参数都在这个文件里按固定段落组织。不同发行版的模板行序略有差异,但参数块的逻辑是一致的,下面这份骨架可以作为对照参考:

# Kraken环境文件示意骨架(具体格式以所用版本模板为准) 'Bistatic range test' # 注释行,随便写 500.0 # 频率,单位Hz 0 # 介质层数标志,0代表使用半空间海底 SSP # 声速剖面块 0.0 1500.0 # 深度(m) 声速(m/s) 50.0 1510.0 # 50m处声速 100.0 1530.0 # 更深处声速 0.0 # 海底之上最后一层介质参数 BOTM # 海底块 1500.0 1.8 0.5 # 海底声速(m/s) 密度(g/cm3) 衰减(dB/波长) RECEIVER DEPTH 60.0 # 接收深度,单位m RANGE 100.0 5000.0 100.0 # 起始距离 终止距离 步长

写这个文件最容易出错的地方在海底参数块。密度单位一般是g/cm3,衰减系数有两种常见单位:dB/波长和dB/m,填错一个数量级,传播损耗会差出几十dB。我习惯先把单位写在注释里,跑批量任务前再核对一次。声速剖面的采样密度也很关键——声道轴附近的声速梯度变化最快,采样点至少要能把这个极小值点卡住,否则后续模态计算会在不该拐弯的地方拐弯。

接收深度和距离范围决定了Kraken场计算的网格。接收深度一次只能写一个值,如果阵列有多个阵元深度,你需要在循环里改写这一行,或者直接在环境文件里由脚本生成多个变体。距离范围用起始-终止-步长三段式表达,步长取多少取决于你要看多细的干涉结构——粗略看趋势用大步长,做波形合成时每个阵元的精确坐标必须落在网格点上,所以步长要单独控制。

2.3 跑之前的三个选型判断:频率、海底模型和简正波截断

动手跑之前有三个参数值得先想清楚。第一是频率。Kraken是单频模型,环境文件里只有一个频率值。宽带仿真需要在多个频点上分别跑,然后插值。频率越高,有效模态数越多,计算量成倍上涨。做阵列波形合成时,我一般先把信号的频带下限和上限确定好,再在中频附近试跑一两个点,确认模态数在可接受范围内。

第二是海底模型。环境文件里最省事的是半空间海底,只需填海底声速、密度和衰减三个值。浅海环境下多途结构对海底参数非常敏感,半空间模型算出的干涉条纹往往比实测更光滑。当你的仿真区域存在明显分层地质结构时,要切换到多层海底模型,逐层给出厚度、声速、密度和衰减。判断标准很简单:把TL曲线画出来,如果理论上的干涉谷明显偏浅或偏宽,多半是海底模型太简单了。

第三是简正波截断。Kraken在计算模态时会有一个截断判据,限制参与叠加的模态数量。截断数设太小,远的传播模态被丢掉,近场的波形会出现不该有的毛刺;设太大,数值噪声会被带进来。我的习惯是先按默认值跑一次,看TL曲线尾部是否出现高频抖动,如果抖了就逐步降低截断数,直到曲线光滑为止。这个参数在很多项目里被当成玄学,其实有迹可循——你观察的点位越深、频率越高,需要保留的模态就越多。

3. 跑通Kraken取传播损耗:命令流程与输出文件解读

3.1 最小运行流程:环境文件进、MOD文件出

Kraken的典型调用是两段式的:第一段计算本征值与本征函数,生成模态文件;第二段用模态文件计算接收点处的声场,生成包含传播损耗和复数声压的场文件。命令流程大致如下:

# 第一步:计算简正波模态 # 读入环境文件 test_500hz.env,输出 test_500hz.mod kraken.exe test_500hz.env # 第二步:计算接收声场 # fields程序读同一个环境文件,输出.shd和.tl文件 fields.exe test_500hz.env

两个程序的调用顺序不能反——fields的运行依赖第一步生成的模态文件,跳过第一步直接跑fields,程序会直接退出或生成空文件。文件名里的500hz只是我自己的标记习惯,真正决定计算内容的是环境文件里的频率字段,改名不影响计算结果。

跑完后你会在当前目录看到几个新文件。.mod文件存的是各模态的水平波数和深度函数,这是第二步的输入;.shd文件存的是接收网格上的复数声压;.tl文件存的是整理好的传播损耗值。如果程序运行结束没有任何输出,多半是环境文件里某个数值段格式不对,先检查是否有中文字符或多余空行混进数值区。

3.2 从输出里取TL:画传播损耗曲线的Python脚本

拿到.tl文件后,最直接的操作是把它读进来画成曲线,先肉眼判断声场结构是否合理。不同发行版输出的文本格式略有区别,但最常见的布局是按距离逐行列出传播损耗值。下面的脚本适用于这种纯文本格式:

import numpy as np import matplotlib.pyplot as plt # 读取Kraken输出的传播损耗文件 data = np.loadtxt('test_500hz.tl') # 约定第一列是水平距离(米),第二列是传播损耗(dB) r = data[:, 0] tl = data[:, 1] # 横轴转为公里,纵轴反转让损耗值向下增长 plt.semilogx(r / 1000.0, tl, linewidth=1.2) plt.gca().invert_yaxis() plt.xlabel('水平距离 (km)') plt.ylabel('传播损耗 (dB)') plt.grid(True, which='both', alpha=0.3) plt.show()

这段脚本有两点值得注意:一是横轴用semilogx,因为水声传播损耗曲线通常在近场变化剧烈、远场趋缓,线性横轴会丢掉近场细节;二是invert_yaxis()把纵轴反转,让损耗值大的位置显示在下方,符合水面以下深度的直觉。画出来之后,先看近场起始位置的损耗是否接近0dB基准,再看是否有周期性的干涉谷,最后看远场是否单调上升——这三个特征只要有一个不对,就要回头查环境文件。

如果输出的是二进制.shd文件,就不能用np.loadtxt直接读了。不同语言绑定提供的读取函数名不一样,但返回的结构一致:水平距离数组、接收深度数组、以及一个复数声压矩阵。要合成阵元波形,必须走这条路取复数声压,纯文本TL文件里没有相位信息。

3.3 输出文件家族:哪些给后续合成波形用

跑完一次Kraken,目录里会多出好几个文件,它们各自的用途整理如下:

文件后缀内容主要用途
.env环境文件(输入)参数记录、后续批量修改的模板
.mod模态数据:本征值、本征函数场计算的输入,一般不改动
.shd接收网格复数声压合成波形、匹配场处理的核心输入
.tl传播损耗纯文本快速画TL图、趋势判断

合成波形时,请务必从.shd取复数声压,而不是从.tl猜相位。.tl文件为了体积紧凑,往往只保留幅度信息,直接拿它做波形,反变换出来的信号会缺失多途结构,看起来像是噪声。.shd里的相位项才是决定波形时延和到达结构的核心。

还有一个细节:.shd里每个网格点上的声压值是单频的复数,对应环境文件里写的那个频率。如果你要做宽带信号,需要把多个频点各自跑一遍,再把每个频点的声压值按频率顺序拼成一条频域响应。这个拼接过程就是下一章的核心步骤——从单频传播损耗走向宽带阵元波形。

4. 把传播损耗变成各阵元接收波形:频域叠加与阵列几何

4.1 合成思路:用Kraken的复声压代替简单时延

很多人想到阵列波形合成时,第一反应是给发射信号加不同的时延来模拟阵元间的传播差。这种粗暴做法只适合自由场、单路径的场景,放在浅海多途环境里完全失真——真实接收信号是直达波、海面反射、海底反射多条路径的叠加,每条路径有自己的幅度衰减和到达时延。

正确做法是把Kraken算出的复声压当成信道的频域传输函数。对每个阵元,取其坐标对应的复数声压值H(f),把发射信号的频谱S(f)乘上这个传输函数,再做逆傅里叶变换,就得到该阵元的时域接收波形。整个过程可以表达为:

y(t) = IFFT[ S(f) * H(f) ]

这个表达式虽然简单,但背后隐含了一个重要前提:H(f)必须是在多个频点上分别算出来的。Kraken是单频模型,你算多少个频点,就得到多少个离散的H值;频点之间的H值用插值补齐。频点间隔的选择非常关键,间隔太大,信道的时间窗会被截短,尾部到达的多途路径会被折叠回波形头部,产生虚假回声。

我在实际项目里通常这样定频点间隔:先在一个粗频点上跑一次宽带粗略仿真,估计信道冲激响应的最大时延扩展,然后让频点间隔小于这个时延扩展的倒数。比如估计信道时延扩展有100ms,那么频点间隔至少要小于10Hz,留30%的余量会更稳。

4.2 阵列几何怎么摆:水平阵、垂直阵对波形的影响

阵列几何决定了每个阵元在环境文件里的坐标表达方式。水平阵最直接:所有阵元深度相同,水平距离不同。在Kraken的环境文件里,接收深度只写一次,距离范围覆盖整个阵列跨度,然后从.shd结果里按阵元的水平坐标取值即可。

垂直阵是匹配场处理里最常用的布阵方式,各阵元水平距离相同、深度不同。这种情况下不能用一个接收深度覆盖所有阵元,需要在循环里逐个改写环境文件的接收深度字段,重新跑fields程序。好在Kraken的模态计算只依赖环境参数和频率,阵元深度变化时不用重算.mod文件,只需重跑fields程序,能省掉不少时间。

倾斜阵更麻烦一些,每个阵元的水平距离和深度都不同,相当于要在环境和场的两个维度上做双重循环。我处理这类几何时,会先把阵元坐标整理成一张表格,然后在脚本里对每个阵元做一次完整的取值操作。还有一点需要提前注意:阵元间距决定了阵列的空间采样能力,间距超过信号最高频率对应波长的一半,波束形成时就会出现栅瓣。这个关系在合成波形之前就要算好,否则等波形合成完毕才发现,只能重新布阵。

4.3 各阵元宽带波形合成Python实现

下面这段代码把整条合成链路串起来,从发射信号生成到频域响应拼接,再到时域波形输出:

import numpy as np # ---- 全局参数 ---- fs = 40000 # 采样率 40kHz,覆盖目标频带上限 T = 1.0 # 信号总时长 1s N = int(fs * T) t = np.arange(N) / fs # 频点数组:500Hz到1000Hz,间隔5Hz # 5Hz间隔对应最大不模糊时间窗 200ms freqs = np.arange(500, 1000, 5) # ---- 发射信号:线性调频脉冲 ---- t0, Tp = 0.2, 0.5 s = np.zeros(N) idx = (t >= t0) & (t <= t0 + Tp) f0, k = 500.0, 800.0 s[idx] = np.cos(2 * np.pi * (f0 * (t[idx] - t0) + 0.5 * k * (t[idx] - t0)**2)) S = np.fft.fft(s) f_axis = np.fft.fftfreq(N, 1/fs) # ---- 假设已通过循环得到每个阵元的频域响应H_freqs ---- # H_freqs[m][n] 表示第m个阵元在第n个频点的复声压 def synth_element(H_freqs): # 把离散频点处的H插值到FFT坐标轴上 H_interp = np.interp(f_axis, freqs, H_freqs) # 频域相乘后反变换回时域 Y = S * H_interp return np.real(np.fft.ifft(Y))

代码里的H_freqs来源于每个频点的一次Kraken计算,伪代码展开是这样的:

for f in freqs: write_env_with_frequency(f) # 改写环境文件中的频率字段 run_kraken_and_fields() # 调用kraken.exe和fields.exe H_element = read_complex_pressure(element_coord) # 从.shd取坐标处复声压

np.interp做的是线性插值,落在频点之外的频谱分量会被线性过渡。这里有个经验:Kraken声压幅度在频带边缘往往变化较快,线性插值可能略微低估边缘处的幅度,条件允许时尽量加密频点采样。synth_element返回的是实信号,如果后续需要做复数基带处理,可以再用希尔伯特变换包一层。

每个阵元的坐标、频点数和.shd读取结构都不变,变化的只是坐标值。整个合成过程最耗时的部分是多频点循环调用Kraken,这个循环可以用多进程并行加速——把频点列表拆成几段,丢给多个进程分别跑,最后再汇总频域响应。我在做64阵元、200个频点的仿真时,并行后耗时从小时级降到十几分钟。

5. 避坑指南:Kraken仿真最常见的5个翻车现场

5.1 频点间隔过大导致波形尾部出现虚假回声

现象:合成波形的主脉冲之后,出现一个本不该存在的“重复回波”,且位置不随物理参数变化而移动。

原因:频点间隔的倒数就是合成波形的时间窗长度。比如间隔25Hz,时间窗只有40ms,信道中超过40ms的多途延时会折返回窗内,造成叠加的假脉冲。这是离散频域采样带来的循环卷积效应。

解决:先粗测信道的时延扩展,让频点间隔小于时延扩展倒数的三分之一。如果不知道时延扩展,可以分两步走——先用较密的频点(间隔1Hz)做一次试验合成,测量脉冲响应尾部持续长度,再据此放宽频点间隔以节省计算量。

5.2 海底衰减系数单位搞混导致传播损耗差几十dB

现象:同一份环境参数,有人算出30km处损耗70dB,有人算出120dB,谁也说不清谁对。

原因:Kraken的海底参数里,衰减系数可以按dB/波长填,也可以按dB/m填。两者数值相差四个数量级左右,填反了之后热量损耗被放大或缩小成完全不同的物理场景。

解决:写环境文件时在数值后面加单位注释,跑批量前先用一个已知的参考案例校准。最稳妥的做法是找一篇公开论文或标准算例中的TL曲线,用同一组参数跑Kraken做对比,偏差在2dB以内再开始批量任务。

5.3 简正波截断不足导致近场波形毛刺

现象:近场(几百米内)接收波形的幅度抖动剧烈,而远场看起来反而正常。直观感觉是反转了——实际是近场处高阶模态贡献大,被截断后局部叠加失衡。

原因:Kraken默认的模态截断判据在浅海和低频场景下偏保守,部分传播模态虽然能量占比低,但和主模态叠加后能抵消掉数值振荡。截断了它们,振荡就暴露出来。

解决:手动抬高截断阈值,逐步增加模态数,观察近场波形是否收敛。波形形状不再变化时,把该值再加20%作为安全余量。这个调整过程像玄学,实则是找到数值收敛点的过程。

5.4 只拿.tl文件当频响导致合成波形信息缺失

现象:合成出来的波形看上去很像带限噪声,完全没有清晰的到达结构。

原因:.tl文件的传播损耗只有幅度值,不包含相位信息。丢掉相位相当于把信道变成零相位滤波器,多途干涉全部消失。

解决:始终从.shd文件读取复数声压,并确认读取的是复数矩阵而不是只取了幅度。读出来后可以打印几个频点的实部/虚部值做抽查,验证相位随频率是连续变化的——如果相位乱跳,多半是读取解析程序写错了。

5.5 阵元间距超过半波长导致波束形成栅瓣

现象:用合成波形做常规波束形成,出现和真实目标方向成周期性重复的峰值,目标角度模糊。

原因:阵列空间采样不满足奈奎斯特定理。计算空间采样要求时,很多人直接用1500m/s的标称声速,但浅海海底附近声速可能只有1480m/s甚至更低,波长偏短,间距对应的采样能力不够。

解决:用声速最小值除以信号最高频率算半波长,再取阵元间距的限度。布阵前先列一张频率-声速-极限间距对照表,确认最大间距不超过该值的一半。合成完再做一次波束形成验证,栅瓣位置出现峰值就说明几何设计没有对上。

6. 合成波形对不对?用互相关和理论时延做一次自检

波形合成完之后,先别急着把它喂给波束形成器。花十分钟做一个自检,能挡掉大部分环境文件填错和坐标错位的问题。自检的核心思路很简单:用互相关估计每个阵元首达波的到时,和几何声程理论时延做对比,误差在几个采样点内,说明整条链路是通的。

下面这段代码用互相关估算阵元波形和发射信号之间的时延:

def est_delay(waveform, reference, fs): # 波形与参考信号做互相关 corr = np.correlate(waveform, reference, 'full') # 相关峰位置换算成时延(秒) lag = np.argmax(corr) - (len(reference) - 1) return lag / fs ref = s.copy() for idx, wf in enumerate(array_waveforms): delay = est_delay(wf, ref, fs) geometric = slant_range[idx] / c_min # c_min取环境中最小声速 print(f'阵元{idx}: 估计时延 {delay*1000:.3f}ms, 理论时延 {geometric*1000:.3f}ms')

自检判定标准可以参照这张表:

检查项判定条件失败时排查方向
首达波时延与几何时延偏差小于3个采样周期阵元坐标是否错位、声速取值是否合理
波形包络宽度与发射信号脉冲宽度相当或更宽频点数是否过少、插值是否引入异常
相邻阵元时延差与阵元间距/声速的理论值方向一致阵元编号顺序是否颠倒
远场波形幅度随距离单调衰减、无异常回升海底衰减单位、模态截断数

这个自检过程针对的是“链路是否打通”,它验证的是Kraken跑通了、坐标取对了、插值和傅里叶变换没有引入额外错误。如果你的环境文件里海底参数填错,首达波时延可能仍然能对上,因为那条路径的能量还没经过海底反射——所以我习惯在首达波之外,额外找一个明显的多途到达结构(比如海面反射路径),用同样的互相关方法核对其相对时延,对得上再进入下游算法。

把自检写成流程而不是一次性的调试脚本,每次改环境文件之后跑一遍。我有一次改了声速剖面采样密度,自检时发现第二个多途峰的时延偏了20ms,追查下去是声道轴附近的声速梯度被采样间隔抹平了。这个自检流程每次仿真都会跑一遍,它帮我挡掉了好几次环境文件填错导致的翻车。希望帮到你。

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

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

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

立即咨询