你现在是无线通信方向的专家,正在写一个仿真代码。我们现在想在下面OFDM-ISAC框架上改进成AFD...
생성일: 2026년 1월 8일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
생성일: 2026년 1월 8일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
你现在是无线通信方向的专家,正在写一个仿真代码。我们现在想在下面OFDM-ISAC框架上改进成AFDM,用matlab来写。下面是主函数和其中需要用到的子函数。请分析一下最小的改动方案。我个人认为只需要改DFT和CP
我先说结论:**“只改 DFT 和 CP”这件事——对“代码能跑起来的最小改动”基本成立;但对“严格意义的 AFDM 收发 + 你现在这种 OFDM 式一拍均衡/除法信道估计”并不完全成立。**原因是 AFDM 在 DAFT 域的等效信道一般不是对角的,现有 channelEst 的逐点相除是 OFDM 假设。(arXiv)
下面按“最小改动优先”的思路给你一个分层方案(从最小到更像 AFDM),并结合你这套代码具体指出改哪里。
从 sensing_main.m 的调用链看,只有这两处是“波形变换 + 前缀”的核心:
ofdmModulate.m:ifft(...)*sqrt(N) + CP 拼接ofdmDemodulate.m:去 CP + fft(...)/sqrt(N)其它模块(模拟预编码 TxAnalogPrecoding、channelPass、seningProcess)都不直接依赖“必须是 DFT 子载波”——它们只吃“发端映射好的 N×Ns 矩阵”和“收端解调出来的 N×Ns 矩阵”。
所以你直觉“主要改 DFT 和 CP”是对的:最小侵入点就在 ofdmModulate/ofdmDemodulate。
文献里 AFDM 的 DAFT 结构是(单位ary 形式):
对应到 MATLAB(你现在用的也是 unitary FFT 规范:ifft*sqrt(N), fft/sqrt(N)),实现就是:
✅ 这一步只动你现在的 ifft/fft 两行 + 新增两个小函数文件。
严格 AFDM 需要 CPP,其定义是(对负时间索引的前缀样本):
并且当 为整数且 为偶数时,CPP 退化成普通 CP。(arXiv)
所以你有两条最小路径:
这样你可以宣称“按 AFDM 的 DAFT 变换走”,但前缀仍是 CP(数学上等价于 CPP 的特例)。(arXiv)
ofdmModulate 的 CP 拷贝那一行对 prefix 样本乘上 CPP 相位(只多几行)你现在 channelEst.m 做的是:
matlabHest = RxSignalTF ./ TxSignalTF;
这隐含了“等效信道在该域是逐点乘”(OFDM 对角化假设)。
而 AFDM 的输入输出关系在 DAFT 域一般是:
并且文献明确指出它不像 OFDM 那样天然对角,因此检测/估计通常要用稀疏结构、零填充等方法来降复杂度。(arXiv)
因此:
channelEst 不动(得到的是某种“等效响应”的近似)。channelEst / seningProcess 的建模(但这已经超出“最小改动”范畴了)。我已经在你给的工程里做了最小侵入式改造:
ofdmModulate.m:增加 sensing.useAFDM 开关;开启时用 idaft() 替代 ifft();可选用 CPPofdmDemodulate.m:开启时用 daft() 替代 fft()idaft.m、daft.m你可以下载这几个文件直接覆盖/加入工程:
sensingInit() 里你只需要加的配置(示例)你没把 sensingInit.m 上传上来(我这边也没法读到它),但你只要加类似下面字段即可:
matlabsensing.useAFDM = 1; % 最小方案:让 CPP 退化为 CP(不改你现有 CP 逻辑) N = sensing.fftSize; sensing.afdm.c1 = 1/(2*N); sensing.afdm.c2 = 0; % 先从 0 开始,便于对比 sensing.afdm.useCPP = 0; % 因为此时 CPP=CP
如果你想试“通用 CPP”:
matlabsensing.afdm.useCPP = 1;
我这次是直接从当前挂载目录读到了这些 .m 文件并完成修改;但如果你接下来要我继续追踪 sensingInit / RsMapping / dft_2d 等未提供的函数细节,可能需要你把那些文件也一并重新上传,避免后面无法定位调用链。
如果你愿意我再往前推进一点(仍然尽量“少改”):我可以基于你 dft_2d 的实现,把“距离-速度处理”从 OFDM 的二维 FFT 逻辑改成更贴近 AFDM/DAFT 域输出的匹配处理,这样你会更容易看到 AFDM 在 Doppler 维的收益。
这里面c2控制的是什么参数,是对齐的是可以克服的时延和多普勒么?
c2本质上是 DAFT/AFDM 里的第二个“二次相位(chirp)参数”,它乘在 DAFT 域索引 上:
也就是你在实现里写的 里的 。(arXiv)
c2主要“调相”,不是“调斜率”c1跟 时间索引 绑定,决定所有 chirp 子载波共享的 数字 chirp rate(斜率)≈ ;它直接影响“时延/多普勒”在 DAFT 域怎么被搬移、是否会重叠。(arXiv)c2跟 子载波索引 绑定。因为 对时间 不变,所以它更像是给每个 chirp 子载波一个不同的初始相位/相位扭转(并不改变 chirp 的时间斜率)。(arXiv)一句话:c1更像“决定时频走向/抗多普勒与可分离性”的关键参数;c2更像“给各子载波加一个可调的二次相位帽子(相位整形/相位旋转)”。
更严格地说:决定“能否克服/可分离的最大时延与最大多普勒”的主因不是 c2,而是 L(前缀长度)和 c1 的选取。
c2;c2只进入一个相位因子。(arXiv)c1下界”也是完全围绕 c1 来写的。(arXiv)所以如果你问“c2是不是用来对齐从而克服某个最大时延/最大多普勒?”——通常不是;这件事主要靠 L 和 c1 来保证“路径在 DAFT 域分开、不重叠”。
c2在系统里到底影响什么?从输入输出关系里可以直接看到:c2进入 等效信道矩阵元素的相位项(例如出现 这类因子),改变的是“每条路径在 DAFT 域上的相位纹理”,而不是它落到哪里。(arXiv)
因此 c2常见用途更像:
如果你现在仍沿用 OFDM 那套 一拍估计/一拍均衡(逐点相除),那我建议先:
c2 = 0(最省事,少一个相位自由度,便于对比)c1 与 L 的匹配(确保 CPP/CP 与路径不重叠条件)等你后面要做“更正宗的 AFDM 域信道估计/检测”(不再是逐点相除)时,再系统扫描 c2 看它对 BER/感知旁瓣的影响会更有意义。
顺带说一句:我这边收到提示你之前上传的一部分 .m 文件可能已过期;如果你接下来要我结合你工程里的 channelEst/seningProcess 继续推“AFDM 域估计/感知处理”,到时候可能需要你把相关文件再上传一次。
那我们现在应该要调整c1的取值,因为我们的速度当前设置的是20到180均匀,然后位置是20到1000均匀。我们的c1得能在这个中间去对齐。然后fftsize现在是4096.看看我们的c1应该怎么调整。现在的结果不对。现在发你的是我们的解调和感知框架
我们是在接收端用下面的代码实现的。但是我发现AFDM在接收端的距离的感知结果差的非常大,感觉是差了一个shift的感觉。请分析一下这个代码是怎么实现的,再看一下如果现在是用AFDM来解调,这里哪里需要调整
pythonimport os, re, textwrap, json, math, numpy as np, pandas as pd, pathlib base="/mnt/data" files=os.listdir(base) files ['dft_2d.m', 'estParaMatch.m', 'channelEst.m', 'errStatistics.m', 'channelParaConfig.m', 'seningProcess.m', 'clutterCancel.m']
pythondef read_m(path): with open(path,'r',encoding='utf-8',errors='ignore') as f: return f.read() for fn in files: print("====",fn,"====") txt=read_m(os.path.join(base,fn)) print(txt[:1200]) print()
pythondef with_lines(txt): lines=txt.splitlines() return "\n".join([f"{i+1:4d}: {lines[i]}" for i in range(len(lines))]) for fn in files: if fn=="dft_2d.m": txt=read_m(os.path.join(base,fn)) print(with_lines(txt[:5000]))
pythontxt=read_m(os.path.join(base,"dft_2d.m")) len(txt.splitlines()) 168
pythonprint(with_lines(txt))
pythonlines=txt.splitlines() def print_range(a,b): for i in range(a-1,b): print(f"{i+1:4d}: {lines[i]}") print_range(1,120)
pythonprint_range(17,60)
pythonprint_range(33,105)
pythonprint_range(49,90)
pythonprint_range(64,75)
pythonfor fn in ["channelParaConfig.m","channelEst.m","seningProcess.m","estParaMatch.m","clutterCancel.m","errStatistics.m"]: txt=read_m(os.path.join(base,fn)) print("====",fn," lines",len(txt.splitlines()),"====") print(txt[:1600]) print()
pythondef print_m(fn, start=1, end=None): txt=read_m(os.path.join(base,fn)) lines=txt.splitlines() if end is None: end=len(lines) for i in range(start-1,end): print(f"{i+1:4d}: {lines[i]}") print_m("channelParaConfig.m",1,180)
pythontxt=read_m(os.path.join(base,"channelParaConfig.m")) lines=txt.splitlines() len(lines) 136
pythonprint_range=lambda a,b: [print(f"{i:4d}: {lines[i-1]}") for i in range(a,b+1)] print_range(11,60) [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]
pythondef show_lines(lines, a,b): for i in range(a,b+1): print(f"{i:4d}: {lines[i-1]}") show_lines(lines,11,60)
pythonshow_lines(lines,27,45)
pythonshow_lines(lines,58,120)
pythonshow_lines(lines,73,105)
pythontxt=read_m(os.path.join(base,"channelEst.m")) print(with_lines:=None)
pythontxt=read_m(os.path.join(base,"channelEst.m")) lines_ch=txt.splitlines() def show(fn, a,b): lines=read_m(os.path.join(base,fn)).splitlines() for i in range(a,b+1): print(f"{i:4d}: {lines[i-1]}") show("channelEst.m",1,200)
pythonlines=read_m(os.path.join(base,"channelEst.m")).splitlines() len(lines), lines[:60] (41, ['%========================================================', '%> COPYRIGHT XIAOMI', '%> @file channelEst', '%> @brief Frequency domain channel estimation ', '%> @autor Lichenggang', '%> @note', '%> @see calls: \\n', '%>', '%>', '%>', '%> @todo', '%> @bug', '%> Date | Author| Version| DESCRIPTION', '%> -----------|--------------|-----------|---------------', '%> 2024-11-05 | Lichengagng| 0.1| Created', '%======================================================', 'function Hest=channelEst(TxFreqSignal,RxFreqSignal,sensing)', '', 'timeSpace = sensing.timeSpace;', 'freqSpace = sensing.freqSpace;', 'Nzc=sensing.Nzc;', 'Nc=sensing.Nc;', 'fpattern=sensing.fpattern;', 'tpattern=sensing.tpattern;', 'sensingSymbolNum=sensing.sensingSymbolNum;', 'if sensing.fastchannel==1', ' tpattern=[1:sensingSymbolNum];', 'end', '', 'rxAntNum=sensing.rxAntNum;', 'TxFreqSignal=circshift(TxFreqSignal,Nc/2,1);', 'TxSignalTF=TxFreqSignal(fpattern,tpattern);', 'Hest=zeros(Nzc,sensingSymbolNum,rxAntNum);', '', 'RxFreqSignal=circshift(RxFreqSignal,Nc/2,1); % Frequency domain data passing through the time domain channel', '', 'for RxIdx=1: rxAntNum', ' RxSignalTF=RxFreqSignal(fpattern,tpattern,RxIdx); ', ' Hest(:,:,RxIdx)=RxSignalTF./TxSignalTF;', 'end', ' '])
pythonfor i,line in enumerate(lines,1): print(f"{i:3d}: {line}")
pythonfor i in range(17,27): print(f"{i:3d}: {lines[i-1]}")
pythontxt=read_m(os.path.join(base,"seningProcess.m")) lines=txt.splitlines() len(lines) 32
pythonshow("seningProcess.m",1,120)
pythonshow("seningProcess.m",17,32)
pythonshow("estParaMatch.m",1,220)
pythonlines=read_m(os.path.join(base,"estParaMatch.m")).splitlines() len(lines) 45
pythonfor i,line in enumerate(lines,1): print(f"{i:3d}: {line}")
pythonlines=read_m(os.path.join(base,"clutterCancel.m")).splitlines() len(lines) 35
pythonfor i,line in enumerate(lines,1): print(f"{i:3d}: {line}")
你现在这套接收端“距离-速度感知”代码,本质上是按 OFDM 的 TF 信道来做的:先估计 ,再做二维 FFT/IFT 得到 Range-Doppler map。一旦你把收发换成 AFDM(DAFT/IDAFT)但感知这块不改,就会出现你说的“像差了一个 shift”的现象——原因不是一个简单的 fftshift 少/多了,而是你输入给感知模块的 已经不是“DFT 子载波频响”了。
下面我按你上传的文件逐行解释它现在怎么做,再说 AFDM 模式下最小要改哪里、以及 怎么按你速度/距离范围选。
channelEst.m关键点在这两行(你文件里第31、35行):
TxFreqSignal=circshift(TxFreqSignal,Nc/2,1);RxFreqSignal=circshift(RxFreqSignal,Nc/2,1);然后用
Hest(:,:,RxIdx)=RxSignalTF./TxSignalTF;这隐含假设:每个 上是标量乘法:
这就是 OFDM 的“近似对角化”假设。
对 OFDM:circshift 的目的是把“DC 在中间”的索引顺序挪成 ifft/fft 期望的顺序,后面你在感知里对频率维做
ifft才能把频响变成时延脉冲响应。
dft_2d.m最核心一行(第47行):
matlabHdft = fft(ifft(Htemp,lenf).',lent).';
含义是:
ifft ⇒ 得到 delay tapsfft ⇒ 得到 Doppler binsfftshift(Hdft,2) 只在多普勒轴居中然后你用
matlabresidualH1 = residualH(1:floor(lenf/2),:); % 只用一半“正时延”
默认 目标回波的能量应该落在 delay 轴前半部分(OFDM+CP 的典型假设)。
AFDM(DAFT)域里,“路径的延迟 ”和“整数多普勒 ”不是分别落在二维 FFT 的两个轴上。在经典 AFDM 推导里(整数延迟/整数多普勒且满足条件时),每条路径在 DAFT 域表现为“循环移位”,其位置由:
决定。也就是说:延迟会引入一个与 成正比的“索引平移”项,你如果还把 DAFT 域的索引当作 OFDM 的子载波频率索引去 ifft,看起来就会像“距离轴偏了一截/shift 了”。(这个耦合关系与 的设计直接相关。)
再叠加你代码里的两个“OFDM 特有假设”,AFDM 下更容易炸:
channelEst 的 circshift(Nc/2) 在 AFDM 域没有“DC 居中”的意义
它相当于把 DAFT bin 做了一个强制重排,你后面再 ifft,距离轴自然会错位(有时看起来就是一个循环 shift)。
你只取 residualH(1:lenf/2,:)
AFDM 下如果因为参数/别名导致能量跑到后半段,你直接把“真峰”裁掉了,剩下的最大值当然就偏了(看起来像 shift 或完全错)。
channelEst.m 的 circshift 在 AFDM 模式关掉最小改法:加一个开关,比如 sensing.useAFDM==1 时不做 shift。
原因:DAFT 域不是“DC 在中间”的频率栅格,强行 Nc/2 重排会把你后面的 delay 轴整体搞歪。
dft_2d.m 里“频率维 ifft”在 AFDM 模式下要换成“IDAFT”你现在第47行是:
matlabHdft = fft(ifft(Htemp,lenf).',lent).';
如果 Htemp 现在是 DAFT 域的等效响应,那你要先把它“回到时间/延迟可解释的域”,最小替换就是:
ifft(Htemp,lenf) 换成 idaft(Htemp, c1, c2)(按列对每个慢时间符号做一次 IDAFT)fft 取 Doppler同样,你第74行生成 reSiganl 的地方也用到了 ifft(phi,lenf),AFDM 下也要同步替换,否则你后面的“干扰消除/多目标迭代”会用错模板。
这一步做完,很多“像 shift”的距离错误会明显缓解,因为你不再对 DAFT bin 做 OFDM 的 ifft 解释。
把这行先改成全用:
matlab% residualH1=residualH(1:floor(lenf/2),:); residualH1 = residualH;
理由:AFDM 参数不合适/有 wrap-around 时,真峰可能跑到后半段;你裁掉就是“必错”。
当“时延 tap 不稀疏(最保守)”时,为了让不同延迟块在 DAFT 域不重叠,常用取法:
其中 是最大“整数多普勒索引”(多普勒按子载波间隔归一化后的整数上界)。
并且还要满足一个避免模 回卷(wrap-around)的条件(文中给的充分条件之一):
其中 是最大时延对应的“离散采样 tap 数”。
你现在看到“像 shift”,非常像是 太小导致不同延迟块混叠 或者 wrap-around(峰跑到后半段又被你裁掉)。
设:
sensing.SCSsensing.fc最大多普勒频移(如果你做的是单基地雷达往返,用 ;如果是单程链路就去掉那个 2):
然后归一化到子载波间隔:
最大时延 tap 数(你 dft_2d 里 range 分辨率用的是 这种形式,说明你把“距离口径”当成单程路径长度在算):
若你还用了 freqSpace 抽取子载波,则把 替换成 freqSpace*Deltaf(你代码里也是这么用的)。
matlabN = 4096; c = 3e8; vmax = 180; % m/s Rmax = 1000; % m (按你代码口径:单程路径长度) fc = sensing.fc; Deltaf = sensing.SCS * sensing.freqSpace; % 你dft_2d里就是这么用的 % --- Doppler --- fdmax = 2*vmax*fc/c; % 单基地往返;若单程就改成 vmax*fc/c nu_max = fdmax/Deltaf; alpha_max = ceil(nu_max); % --- Delay taps --- taumax = Rmax/c; % 单程;若往返就 2*Rmax/c lmax = ceil(taumax*N*Deltaf); % --- c1 --- c1 = (2*alpha_max + 1)/(2*N); % --- check wrap-around condition --- cond = 2*alpha_max*lmax + 2*alpha_max + lmax; fprintf("alpha_max=%d, lmax=%d, c1=%.10g, cond=%d (need < N=%d)\n", ... alpha_max, lmax, c1, cond, N);
如果最后打印出来 cond >= N,那你现在这个 N=4096 在你给的 (1000m, 180m/s, fc, SCS) 组合下就先天会有回卷/混叠:
channelEst 还在 circshift(Nc/2) → 直接把 DAFT bin 重排了,delay 轴看起来像整体循环 shift。ifft 并且只取半轴 → 真峰可能在后半段或被耦合项 推走。我这次能看到你新传的这些文件:channelEst.m / dft_2d.m / seningProcess.m / ...。
但你“AFDM 解调”的具体实现(daft/idaft、接收端到底输出的是哪一个域的 RxFreqSignal)不在这批文件里——如果你希望我把改动点精确落实到“你接收主流程调用处”,你把接收端解调函数/主接收链路那几个 .m 也一起传上来就行。
如果你愿意,我可以按“最小改动”给你直接写两套分支:
dft_2d 框架,又不会被 DAFT 域的耦合搞出“距离 shift”。下面这个图是我现在跑的AFDM和OFDM在2D-FFT上的响应,应该是部不对的吧?
pythonimport os, textwrap, re, json, math, numpy as np paths = ["/mnt/data/channelParaConfig.m","/mnt/data/dft_2d.m","/mnt/data/channelEst.m","/mnt/data/seningProcess.m","/mnt/data/estParaMatch.m","/mnt/data/clutterCancel.m"] for p in paths: print(p, os.path.exists(p), os.path.getsize(p) if os.path.exists(p) else None)
pythondef read_file(p): with open(p,'r',encoding='utf-8',errors='ignore') as f: return f.read() dft2d = read_file("/mnt/data/dft_2d.m") channelEst = read_file("/mnt/data/channelEst.m") seningProcess = read_file("/mnt/data/seningProcess.m") print("dft_2d.m:\n", dft2d[:1200]) print("\nchannelEst.m:\n", channelEst[:1200]) print("\nseningProcess.m:\n", seningProcess[:1200])
pythonprint(dft2d[1200:2600])
pythonprint("len", len(dft2d)) print(dft2d)
pythonprint(channelEst)
pythonprint(seningProcess)
pythonprint(read_file("/mnt/data/estParaMatch.m"))
pythontxt = read_file("/mnt/data/channelParaConfig.m") print(txt[:2000])
pythonprint(txt[2000:4000])
pythonimport re for pat in ["targetRange","targetSpeed","range","speed","vmax","moveSpeed","tgSpeed","Velocity"]: m = re.finditer(pat, txt) cnt = sum(1 for _ in m) print(pat, cnt)
python# show lines containing targetSpeed/range in channelParaConfig lines = txt.splitlines() for i,l in enumerate(lines): if "targetSpeed" in l or "targetRange" in l: print(i+1, l[:200])
pythonfrom PIL import Image, ImageEnhance img = Image.open("/mnt/data/image.png") img.size (891, 1096)
pythonimport matplotlib.pyplot as plt, numpy as np arr=np.array(img) arr.shape (1096, 891, 4)
python# Let's crop to each plot area to inspect easily # Rough crop by manual guess: top half for OFDM, bottom half for AFDM. ofdm = img.crop((0,0,891,540)) afdm = img.crop((0,540,891,1096)) ofdm.size, afdm.size ((891, 540), (891, 556))
python# Enhance contrast to see peak positions def show(im, title): plt.figure(figsize=(8,4)) plt.imshow(im) plt.axis('off') plt.title(title) show(ofdm, "OFDM") show(afdm, "AFDM") plt.show()
text
从你这两张图的现象来看:如果你期望“AFDM 跑完后,用同一套 OFDM 的 2D-FFT(对 先 IFFT 再 FFT)能得到同样含义的距离-速度谱”,那现在这张 AFDM 图基本就是“不对口径”的结果——很像你说的“差了一个 shift/整体错位”,而且幅度也掉了两个数量级。
关键原因不是 c1 先选错,而是 你把“DAFT 域量”当成“DFT/子载波频域量”去做了 OFDM 的感知处理。AFDM 的 DAFT 域里,路径的落点会出现 时延-多普勒耦合(经典表达里会出现类似 的耦合项),它并不像 OFDM 那样天然把信道对角化成逐点乘。(arXiv)
下面我结合你上传的接收端代码,指出“哪里导致 shift”,以及最小改动怎么让距离谱先对齐。
channelEst.m(你现在的实现)你做了两件非常“OFDM 专用”的事情:
circshift(Nc/2)matlabTxFreqSignal=circshift(TxFreqSignal,Nc/2,1); RxFreqSignal=circshift(RxFreqSignal,Nc/2,1);
这等价于 OFDM 里常见的 ifftshift:把“DC 在中间”的子载波顺序搬回 FFT 的自然顺序。
matlabHest(:,:,RxIdx)=RxSignalTF./TxSignalTF;
这隐含假设:在你这个域里 是逐点乘(OFDM 的对角化假设)。
dft_2d.m / 2D-FFT 逻辑(你描述的那行)你后面做的 2D 处理本质是:
这个处理只有在 “第一维真的是 DFT 频率栅格上的 ” 时,delay 才是可解释的“峰在哪就在哪”。
你现在 AFDM 解调后喂给 channelEst 的 RxFreqSignal/TxFreqSignal 很可能已经是 DAFT 域(或者至少不是 DFT 子载波频域)。
这时再做:
circshift(Nc/2):相当于把 DAFT bin 强行按 OFDM 的 DC 规则重排 → 最容易表现为“距离轴整体循环平移(shift)”Rx./Tx:DAFT 域等效信道一般不是对角逐点乘 → 你得到的 “Hest” 并不是 OFDM 意义的 这就是你图里 AFDM 出现“峰位置不对、还多了一个峰、幅度也很小”的典型症状。
要让你现有的 dft_2d(OFDM 2D-FFT)继续有物理意义,最稳的“最小改动”是:
感知用的 必须在 DFT 频域上估计出来(也就是:对 CP 去除后的时域块做 FFT 得到 ,再逐点相除)。
这样 不管你发的是 OFDM 还是 AFDM 的时域波形,只要 CP 足够覆盖最大时延,块内等效卷积是循环卷积,DFT 域仍然有:
于是你的 dft_2d 就又“对口径”了。
只动 channelEst(或在它前面加一条 sensing-FFT 支路):
RxFreqSignal伪代码(按你函数名直译):
matlab% rxNoCP: Nc x sensingSymbolNum x rxAntNum (CP去除后的时域块) % txNoCP: Nc x sensingSymbolNum (发端同一块对应的时域参考,仿真里你一定有) RxFFT = fft(rxNoCP, Nc, 1)/sqrt(Nc); TxFFT = fft(txNoCP, Nc, 1)/sqrt(Nc); % 如果你后面 dft_2d 仍然沿用“DC居中”的逻辑,则在这里做同样的 shift RxFFT = circshift(RxFFT, Nc/2, 1); TxFFT = circshift(TxFFT, Nc/2, 1); Hest(:,:,ant) = RxFFT(fpattern,tpattern,ant) ./ TxFFT(fpattern,tpattern);
✅ 做完这一步,你的 AFDM 图在“距离轴”上大概率就不会再像“整体 shift”那样离谱了(至少会回到“匹配滤波/频响-延迟变换”的正确口径)。
先把 AFDM 模式下 channelEst.m 里的两行 circshift(Nc/2) 注释掉试一下
如果“shift 感”明显缓解,说明你现在喂进去的确不是 OFDM 的 DC-centered 子载波频域量。
暂时别裁掉半个 delay 轴(如果你 dft_2d 里有 residualH(1:lenf/2,:) 这种)
AFDM 参数不合适/索引回卷时,真峰可能在后半段,你裁掉就直接“看不到真峰”。
确认 AFDM 的 DAFT/IDAFT 归一化是 unitary 的(fft/sqrt(N)、ifft*sqrt(N)那种)
你图里幅度从 120 掉到 1.5,除了“口径不对”,也很像归一化/能量尺度没对齐。
c1当然要按最大多普勒范围选,但你现在这个“看起来像 shift”的问题,优先级更高的是把感知输入的域弄对。否则你调 c1 只是在“错误口径上调参数”,现象会更乱。
等你把“感知支路的 改回 DFT 域”之后,如果还出现 AFDM 的延迟-多普勒混叠/多峰,再用 AFDM 参数约束去定 c1(例如按最大整数多普勒索引 去选),那时调参才是有效的。(arXiv)
你这次传的 dft_2d.m/channelEst.m 里有不少 ...,像是被截断过;我能抓住关键假设点,但如果你希望我“精确到哪一行怎么改、改完和你现有接口完全兼容”,最好把这两个文件的完整版本再传一次(有些系统也会提示旧文件会过期)。
如果你愿意,我下一步可以按你现在的接口形式,给你一份**“不改 dft_2d,只改 channelEst(加入 AFDM-sensing FFT 支路)”的可直接替换版本**,这样你能最快验证:AFDM 的距离峰是不是立刻回到正确位置。
channelESt的代码如下,只有一张图 剩下的是DFT2D。看看AFDM时候应该怎么改
pythonimport pathlib, textwrap, re, os, json, math, numpy as np path="/mnt/data/dft_2d.m" print(os.path.exists(path)) print(pathlib.Path(path).read_text()[:1200])
pythontxt=pathlib.Path(path).read_text() print(len(txt)) print(txt[-800:])
你这两段代码(channelEst + dft_2d)完全是“OFDM-TF 域”口径:先得到子载波频域的 ,再做 频率维 IFFT → 距离(delay)、慢时间 FFT → 多普勒。
所以 AFDM 你如果把 DAFT 域的输出直接喂进来,就会出现你图里那种“距离像整体 shift / 幅度变小 / 多峰”的现象——不是简单少了 fftshift,而是域不对。
下面我按你截图里的实现,告诉你 AFDM 时最小要改哪里。
channelEst 在做什么(只适用于 OFDM/DFT 域)你截图里的核心逻辑是:
circshift(Nc/2)matlabTxFreqSignal = circshift(TxFreqSignal, Nc/2, 1); RxFreqSignal = circshift(RxFreqSignal, Nc/2, 1);
这一步的隐含前提:输入是 DFT 子载波频域,而且你的子载波索引是“DC 居中存放”的,所以要 shift 一下对齐后续的 ifft(距离维)。
matlabHest(:,:,RxIdx) = RxSignalTF ./ TxSignalTF;
隐含前提:该域里满足 (OFDM 的近似对角化/逐点乘)。
如果你 AFDM 接收端输出的是 DAFT 域符号(也就是你把 OFDM 的 FFT 换成了 DAFT),那么:
circshift(Nc/2):对 DAFT bin 做“DC 居中对齐”是没有物理意义的,常见表现就是 delay 轴整体循环偏移(shift-like)。Rx./Tx:DAFT 域等效信道一般不是对角逐点乘,所以这个“信道估计”本身就会偏。目标:让你现有 dft_2d(OFDM 的 2D-FFT)继续有正确的“距离-速度”物理意义。
做法:在 channelEst 里加一个 AFDM 分支——先把输入从 DAFT 域变回时域,再 FFT 得到 DFT 频域,再按原来的 shift+除法。
channelEst(dft_2d 可以先不动)matlabfunction Hest = channelEst(TxFreqSignal, RxFreqSignal, sensing) Nc = sensing.Nc; fpattern= sensing.fpattern; tpattern= sensing.tpattern; sensingSymbolNum = sensing.sensingSymbolNum; if sensing.fastchannel==1 tpattern = 1:sensingSymbolNum; end rxAntNum = sensing.rxAntNum; % ===== AFDM sensing: DAFT -> time -> DFT ===== if isfield(sensing,'useAFDM') && sensing.useAFDM==1 c1 = sensing.afdm.c1; c2 = sensing.afdm.c2; % TxFreqSignal: 你现在AFDM里它通常是“AFDM符号(DAFT域)” -> 先IDAFT到时域 txTime = idaft(TxFreqSignal, c1, c2); % Nc x Ns % RxFreqSignal: 若它是“接收端DAFT输出” -> 先IDAFT回时域 rxTime = zeros(size(RxFreqSignal)); for rx = 1:rxAntNum rxTime(:,:,rx) = idaft(RxFreqSignal(:,:,rx), c1, c2); end % 再转成“DFT频域”,供OFDM式2D-FFT感知使用 TxFreqSignal = fft(txTime, Nc, 1)/sqrt(Nc); RxFreqSignal = fft(rxTime, Nc, 1)/sqrt(Nc); end % ===== 下面保持你原来的OFDM逻辑不变 ===== TxFreqSignal = circshift(TxFreqSignal, Nc/2, 1); TxSignalTF = TxFreqSignal(fpattern, tpattern); Hest = zeros(length(fpattern), sensingSymbolNum, rxAntNum); RxFreqSignal = circshift(RxFreqSignal, Nc/2, 1); for RxIdx = 1:rxAntNum RxSignalTF = RxFreqSignal(fpattern, tpattern, RxIdx); Hest(:,:,RxIdx) = RxSignalTF ./ TxSignalTF; end % 若你这个flag要用原始数据,也建议用“DFT域的RxFreqSignal” if sensing.oftsSensingFlag==1 Hest = RxFreqSignal(1:Nc, tpattern, :); end end
这一步做完,你的 AFDM 跑出来的 2D-FFT 图,距离峰“像 shift”那种大错位通常会立刻消失或大幅缓解,而且幅度尺度也会回到合理区间(如果你
idaft/fft归一化一致)。
dft_2d 现在不用改;但你如果坚持“直接用DAFT域做感知”,那改动会很大你 dft_2d 这行就是 OFDM 的 Range-Doppler 变换:
matlabHdft = fft( ifft(Htemp, lenf).', lent ).';
如果 Htemp 不是 DFT 子载波频响,而是 DAFT 域的“等效响应”,那把 ifft 换成 idaft 也不等价于“距离”(AFDM 有时延-多普勒耦合,距离轴不再是简单 IFFT 直接读峰值)。所以我不建议你在这条路上硬改 dft_2d,除非你准备把整个“字典匹配/迭代消除”那套 phiR/phiV 也一起换成 AFDM 的模型。
改完上面 channelEst 后,做两件事:
abs(Hest(:,:,1)) 的统计量(均值/最大值)是否同数量级。dft_2d 里临时画一下 abs(ifft(Htemp, lenf))(只做频率维 IFFT),看 delay 维峰是不是回到“靠前且稳定”的位置。你现在这套感知是 DFT域 2D-FFT,按我上面方案改完后,距离-速度的物理解释主要由带宽/符号时长/CP决定,不再直接被 c1 搞出“整体 shift”的错觉。
如果你愿意下一步我可以再帮你把 c1 的选取检查写成一段脚本(用你 sensing.fc / sensing.SCS / freqSpace / frameLen / Nc / CP 自动算 alpha_max、l_max 并检查是否 wrap-around),但那一步最好建立在“感知域已经对”之后,不然你调参也会越调越乱。
把 channelEst.m 原文件(不是截图)和你 AFDM 解调输出那里(哪个函数产出 RxFreqSignal)那两个 .m 也丢进来,我可以按你现有变量维度/接口把上面分支直接落到文件里,保证不破你其它调用链。
可是我改了 还是不行。我现在只有在ofdmModulate和ofdmDemodulate里面加上了idaft和daft(对应)。你看主函数里面。RsMapping函数需不需要改。就是我下面给你的图
你现在只在 ofdmModulate/ofdmDemodulate 里把 IFFT/FFT 换成了 IDAFT/DAFT,但接收端感知链路 (channelEst + dft_2d) 还是按 OFDM 的“DFT 子载波频响 H[k,n]”在跑——这就是你 AFDM 的 2D-FFT 距离像“整体 shift”、幅度也不对的根因。
下面按你截图里的代码,回答你问的两个点:
RsMapping2 要不要改?要改一点,但不是改 ZC 或稀疏图样,而是改“OFDM 专用的 shift/FFT 口径”。
你这个 RsMapping2 最关键的 OFDM 绑定点是这两句(截图最后一页):
matlabTxSignalTFMap = circshift(TxSignalTFMap, -Nc/2, 1); TxSignalTI = ifft(TxSignalTFMap, fftSize) * sqrt(fftSize);
circshift(-Nc/2)(那是给 IFFT 输入用的“DC 对齐”,AFDM 的 DAFT 域没有这个意义)TxSignalTI=ifft(...) 在函数里也没输出、也没写回 sensing,基本是“遗留调试代码”,AFDM 下更别在这里做 IFFT(避免你后面又 IDAFT,逻辑口径更乱)直接改成:
matlabif ~(isfield(sensing,'useAFDM') && sensing.useAFDM==1) TxSignalTFMap = circshift(TxSignalTFMap, -Nc/2, 1); % 只给OFDM用 end % TxSignalTI 这一行建议删除/注释(时域生成交给ofdmModulate/AFDM调制) % TxSignalTI = ifft(TxSignalTFMap, fftSize)*sqrt(fftSize);
你现在 AFDM 还保留这句
circshift(-Nc/2),很容易造成你说的“距离差一个 shift”的观感(因为你后面拿它当 DAFT 符号去 IDAFT/DAFT,再做 OFDM 式 ifft→delay)。
channelEst你现在 channelEst 的逻辑(截图)是:
circshift(+Nc/2)Hest = Rx./Txdft_2d 再对 Hest 做 ifft(频率维) + fft(慢时间)这 只对 OFDM 的 DFT 子载波域成立。
dft_2d):在 channelEst 里把“DAFT 输出”先变回时域,再 FFT 成 DFT 域,然后再按原来的除法估计 H[k,n]。
也就是在 channelEst 顶部加一个 AFDM 分支(核心 6 行):
matlabif isfield(sensing,'useAFDM') && sensing.useAFDM==1 c1 = sensing.afdm.c1; c2 = sensing.afdm.c2; % 1) DAFT/AFDM域 -> 时域 txTime = idaft(TxFreqSignal, c1, c2); % Nc x Ns rxTime = zeros(size(RxFreqSignal)); for rx = 1:rxAntNum rxTime(:,:,rx) = idaft(RxFreqSignal(:,:,rx), c1, c2); end % 2) 时域 -> DFT频域(这一步才是你2D-FFT感知要的频率栅格) TxFreqSignal = fft(txTime, Nc, 1)/sqrt(Nc); RxFreqSignal = fft(rxTime, Nc, 1)/sqrt(Nc); end
然后下面你原来的代码(circshift(+Nc/2)、./、fpattern/tpattern)可以先保持不动。
你现在“只改调制解调”而不改这里,等价于把 DAFT 域量当成 DFT 子载波频响去 IFFT,距离轴当然会错(看起来就像 shift)。
你现在 fpattern 是稀疏抽子载波(freqSpace>1)。
但 AFDM 发出去是 chirp 叠加,它在 DFT 频域不再是“只在那些 fpattern 上有能量”,甚至可能在某些抽样点幅度很小 → 你 Rx./Tx 会把噪声放大,2D-FFT 就会“烂掉”。
最快验证办法(不用改框架):
把 AFDM 模式下临时设成全频采样:
matlabsensing.freqSpace = 1; sensing.fpattern = 1:Nc;
如果这样 2D-FFT 距离峰立刻正常了,那问题就不是 c1,而是 **“AFDM + 稀疏频域采样 + 除法估计”**不匹配,需要重新设计 pilot/采样策略(这一步就不再是“最小改动”了)。
先给你一个不会破 CP的硬约束:
CPP 可退化为普通 CP(你不用额外实现 CPP)AFDM 常用、且能覆盖最大整数多普勒索引 的取法是:
这里 ,
。
你把你工程里的 sensing.fc、sensing.SCS、sensing.freqSpace 代进去就能算出 q(一定是奇数),再带回去设 sensing.afdm.c1。
RsMapping2:AFDM 模式下关掉 circshift(-Nc/2),删掉/注释 TxSignalTI=ifft(...)channelEst:加上 DAFT→time→FFT(DFT) 的分支(上面给了代码)freqSpace=1 跑通验证(排除“稀疏抽样病态”)c1=q/(2N)(q 奇整数),逐步恢复你的稀疏抽样如果你把 ofdmModulate.m / ofdmDemodulate.m 里 AFDM 分支贴出来(尤其是里面有没有额外 circshift/fftshift),我可以把“shift 到底该放哪一侧、放多少”给你钉死,不用你来回试。
这两个代码写的有问题么
这两段 DAFT / IDAFT 本身写法是“成对互逆”的,从数学结构上看没有明显原则性错误——前后两次 chirp 的符号也配对正确,归一化也和你 OFDM 里用的 unitary 规范一致(fft/√N、ifft*√N)。
但它们有几个非常常见、会直接导致你现在“像 shift、幅度不对”的坑,我建议你重点排查这 3 点:
-j / j 可能被你代码里当变量覆盖了(高概率坑)MATLAB 里 j 不是保留字,如果你前面有 j=...,那这两句就会彻底错:
matlabexp(-j*2*pi*...)
建议你全部改成 1j(或 1i):
matlabchirp1 = exp(-1j*2*pi*c1*(n.^2)); chirp2 = exp(-1j*2*pi*c2*(n.^2));
IDAFT 同理用 +1j。
这个坑会造成“结果完全不对、幅度离谱、看起来像乱 shift”。
n=0:N-1,但你系统里大量用了 circshift(±N/2)(索引口径冲突会表现为整体 shift)你的 DAFT/IDAFT 是按 0…N-1 的“自然索引”做的;
但你工程里 RsMapping2/channelEst 里有 circshift(-Nc/2)、circshift(+Nc/2) 这种 “DC 居中/对齐 FFT” 的重排。
如果你在 DAFT 前后还做了这些 shift,等效就是:你对 DAFT 的 bin 又做了一次无意义的频域搬移 → 最直观就是你看到的 距离轴整体偏移(shift-like)。
结论很直接:
circshift(±N/2),要么关掉,要么你就得把 DAFT 的 n 改成居中索引(例如 n = (-N/2:N/2-1).' 并且全链路统一)。sqrt(N)(幅度差两个数量级就很像这个)你 DAFT/IDAFT 已经是 unitary 归一化了(OK)。
但如果你在 ofdmModulate/ofdmDemodulate 或别的地方又额外乘了一次 sqrt(N)(或没除回去),幅度就会像你图里那样差很多。
在 MATLAB 里随便测一个随机矩阵:
matlabN = 4096; Ns = 8; x = (randn(N,Ns)+1j*randn(N,Ns))/sqrt(2); c1 = 1/(2*N); c2 = 0.1; y = daft(x,c1,c2); xr = idaft(y,c1,c2); err = norm(xr(:)-x(:))/norm(x(:))
err 在 1e-12 ~ 1e-10 这种量级:函数本身没问题,你的问题就是“外面 shift/域口径/信道估计那套 OFDM 流程没改对”。err 很大:优先查 j 被覆盖 或 归一化重复。函数结构没问题,但非常可能因为 j 被覆盖、或因为你在 AFDM 链路里还保留了 circshift(±N/2) 这类 OFDM 对齐操作,导致你现在看到的整体 shift 和幅度异常。
如果你把你当前 AFDM 模式下 RsMapping2 最后那几行(含 circshift)以及 **channelEst 里对 Tx/Rx 的 circshift**贴出来(文字版,不用截图),我可以直接告诉你:哪些 shift 必须关、哪些地方应该把 DAFT 量先变回 DFT 域再喂给 2D-FFT,这样你那个“距离整体偏移”会立刻消掉。
搜索一一下一般的c1和c2是怎么配置的?感觉放0很奇怪。
一般论文里 的配置思路大概是下面这几类(并不只有一种“标准答案”),你觉得 “ 很奇怪”其实很常见——它完全是合法、而且很多工作里就是这么设来简化实现/增强和 OFDM 的兼容性。 (arXiv)
核心目标是让不同时延/多普勒的路径在 DAFT 域不重叠。经典 AFDM 推导给出(在“时延 taps 不稀疏、最保守”的情况下):
其中 是“最大整数化多普勒索引”。 (arXiv)
另外它还强调:如果你想让 CPP 退化为普通 CP(不实现 CPP 也能工作),需要满足
第一阶段(先把链路/感知口径跑对)
第二阶段(你确认距离-速度图已经对齐后)
如果你把你系统里的 载频 和 子载波间隔 (以及你是否用单基地往返多普勒、是否 freqSpace>1)发我一句话,我可以直接把 算出来,然后给你一个“适配你 4096 点”的 具体 数值(分子 q 是多少),这样你不用靠试。