
简介这是一套面向非线性系统分析研究者的MATLAB谐波平衡法HBM完整实现代码适用于电子电路振荡分析、周期性稳态响应求解等工程与学术场景。打包内共58个文件以53个m脚本为主辅以README说明、许可证及CITATION引用文件整体仅67KB结构按核心求解器、时域辅助函数、通用矩阵工具、扩展版3D求解、测试用例、参数设置等模块划分。文件编排紧凑既可直接运行示例验证算法也便于读者对照代码理解谐波平衡法的频域离散、迭代收敛与雅可比计算等关键环节同时支持Simulink联合仿真与并行拓展。目前已有109人学习适合希望掌握HBM算法原理并开展二次开发的研究生、工程师和科研人员。 做非线性系统仿真的时候很多人第一反应就是打开MATLAB直接ode45跑时域。直到我遇到一个带强非线性的电路模型用ode45跑一个参数扫描每次都要等几个小时的瞬态衰减才发现谐波平衡法harmonic balance method, HBM是真正该早点学的东西。用MATLAB把谐波平衡法实现出来后同一组参数扫描从小时级降到分钟级而且解出来的本身就是稳态响应完全不用关心瞬态波形对不对。这篇文章我打算把整个实现过程拆开讲从数学原理到MATLAB代码再到我踩过的坑写成一套你拿过去就能用的完整方案。适合正在做射频电路非线性分析、机械振动、电力电子稳态仿真的人也适合那些想理解频域方法为什么快的MATLAB用户。1. 谐波平衡法与常见时域仿真的本质差异1.1 时域仿真在周期激励场景下的痛点考虑一个典型的非线性振动方程 mx cx kx αx^3 F*cos(ωt)输入是单频周期信号响应在足够长时间后会变成一个周期稳态。用ode45去算最尴尬的问题是仿真时长必须覆盖瞬态衰减过程而不是只算稳态。阻尼c越小衰减越慢时间越长。有些电路模型甚至存在两个数量级以上的时间常数差异刚性问题逼得ode45步长缩到极小一个扫描点都要跑几十万步。更麻烦的是扫频。每换一个激励频率就必须重新从零开始算一次瞬态。如果扫描50个频率点就意味着50次完整的瞬态等待。这时候你会开始思考一个问题如果我只在乎稳态并且确定它是周期的能不能直接把稳态解“假设”出来然后解一个方程组1.2 频域频点上的平衡思想谐波平衡法的思路非常直接既然响应是周期的就把它写成有限阶傅里叶级数之和 x(t) a0 Σ(a_ncos(nωt) b_nsin(nωt))把这个展开式代回微分方程利用三角函数的正交性每个谐波频率nω上都能得到一个代数平衡方程。线性部分的微分运算在频域里可以精确写出来d/dt换成j nω非线性项则通过“时域重构-计算非线性函数-投影回频域”的方式处理。所有谐波上的方程合起来就得到一个关于未知系数(a0, a_n, b_n)的非线性代数方程组用Newton-Raphson迭代求解即可。这个思路解决了时域仿真两个致命问题第一不需要模拟瞬态过程直接求稳态第二扫频时可以用上一个频率点的解做初值实现连续的延拓计算非常顺滑。对比项时域瞬态仿真谐波平衡法求解对象完整时间历程稳态频域系数刚性问题步长受限耗时无步长概念扫频效率每点重新仿真可延拓非常快非线性处理天然支持需AFT重构投影实现难度低中高2. 从数学方程到可编程模型2.1 一个连续方程如何变成一组代数方程以Duffing方程为例把x(t)的傅里叶展开式代入逐项分析线性部分非常规整。对第n次谐波x的贡献是a_ncos(nωt)b_nsin(nωt)。微分后mx 对应 -m(nω)^2 * (a_ncos b_nsin)cx 对应 c * nω * (b_ncos - a_n*sin)kx 对应 k(a_ncos b_nsin)括号里cos前的所有系数之和要等于该谐波上外力的cos分量sin前同理。这里就能看出频域方法的优势微分算子变成了代数乘法积分边界条件全都消掉了。非线性项α*x^3没法直接在频域展开。x^3会有三次多项式展开最高产生3N次谐波且各组合频率之间交叉耦合。这就是为什么谐波平衡法不能纯在频域做需要借助时域来完成非线性映射这就是所谓的交替时域/频域AFT技术。2.2 交替时域/频域AFT的核心逻辑AFT的核心思想可以理解成“频域建模时域采样”。每次给定一组频域系数X我就重构出周期信号x(t)在若干等间隔时间点上的值在原方程中对x做非线性函数运算这里是x^3得到一个同样周期的时域信号y(t)再用三角函数正交性把y(t)投影回各个频点上得到非线性项的频域表示。这个过程有点像一个翻译官方程的主体在线性频域空间里工作但非线性项这个“外来者”不会说频域话于是我们要把它带回时域老家里表达清楚再把它的意见翻译成频域语言带回讨论桌。这样虽然没有闭式表达式但数值上是精确可控的。用更数学的话说这等价于构造一个残差向量R(X)使线性项、非线性项、外力在所有谐波上完全平衡。求解目标从“求一个连续函数满足微分方程”转化为“求一组离散系数使R(X)0”。3. MATLAB实现从0到13.1 准备与分析对象我用的实例参数如下m1c0.05k1α0.5F0.3激励角频率ω0.8 rad/s。这个参数配置保证系统有清晰的周期稳态又避开了线性共振点同时三次非线性会产生足够明显的谐波畸变方便验证算法效果。未知量排列方式我选的是 X [a0; a1; b1; a2; b2; ...; aN; bN]这是一个长度2N1的列向量。N选择5阶时未知数是11个Newton迭代的Jacobian矩阵是11×11完全没问题。作为对比等效的时域仿真可能需要几千个时间步上的未知数规模不在一个量级。3.2 残差函数与数值Jacobian核心残差函数代码如下。这里特别要注意采样点数M不能只取2N1因为x^3会把谐波扩展到3N所以我设置M为大于等于6N1的2的幂避免混叠。function R hbm_residual(X, p) % 拆分频域系数 a0 X(1); a X(2:2:end); % a1, a2, ..., aN b X(3:2:end); % b1, b2, ..., bN % 时域重构 C cos(p.omega * p.t * (1:p.N)); S sin(p.omega * p.t * (1:p.N)); x a0 C * a S * b; % 非线性项时域计算 y p.alpha * x.^3; % 投影回频域 Y0 mean(y); Ya zeros(p.N, 1); Yb zeros(p.N, 1); for n 1:p.N Ya(n) 2 * mean(y .* cos(n * p.omega * p.t)); Yb(n) 2 * mean(y .* sin(n * p.omega * p.t)); end % 残差组装 R zeros(2 * p.N 1, 1); R(1) p.k * a0 Y0; for n 1:p.N w n * p.omega; Ra (p.k - p.m * w^2) * a(n) p.c * w * b(n) Ya(n); Rb (p.k - p.m * w^2) * b(n) - p.c * w * a(n) Yb(n); if n 1 Ra Ra - p.F; end R(2 * n) Ra; R(2 * n 1) Rb; end end残差函数的实现有几点值得注意。投影时用mean(y .* cos(nωt))而不是分箱求和这在采样点均布于整个周期时等价于傅里叶级数系数的梯形积分近似采样点数足够时精度完全够实现又简单。线性部分的正负号很容易搞错尤其是阻尼项建议每次改完都先构造一个已知频域解的例子验证残差是否为零。Jacobian我用的是一阶中心差分代码很简洁function J hbm_jacobian(X, p) n length(X); J zeros(n); R0 hbm_residual(X, p); for j 1:n d 1e-6 * max(1, abs(X(j))); e zeros(n, 1); e(j) 1; Rp hbm_residual(X d * e, p); Rm hbm_residual(X - d * e, p); J(:, j) (Rp - Rm) / (2 * d); end end数值差分步长用了相对步长max(1,abs(X(j)))来缩放这个细节很关键。直接用固定小量e-10遇到谐波幅值大于1的数量级时会有严重截断误差用相对步长则稳健得多。3.3 Newton迭代主程序与结果验证主程序里我加了阻尼牛顿策略每一步先算出完整牛顿步如果残差范数反而变大就把步长减半重试。对于一个11维问题这个策略几乎不会失败。% 参数初始化 p.m 1; p.c 0.05; p.k 1; p.alpha 0.5; p.F 0.3; p.omega 0.8; p.N 5; M 2^nextpow2(6 * p.N 1); p.t (0:M-1) / M * 2 * pi / p.omega; % 初始猜测忽略非线性取线性解 X0 zeros(2 * p.N 1, 1); X0(2) p.F / (p.k - p.m * p.omega^2); % 一阶cos分量 X X0; for iter 1:50 R hbm_residual(X, p); if norm(R) 1e-10 break; end J hbm_jacobian(X, p); dX -J \ R; lambda 1; while norm(hbm_residual(X lambda * dX, p)) norm(R) lambda 1e-4 lambda lambda / 2; end X X lambda * dX; end如果一切正常5次左右迭代就能收敛到残差1e-12以下。取N5时得到的解和ode45跑到稳态的结果对比时域波形几乎完全重合。区别在于ode45跑50周期需要计算上万个时间点而谐波平衡法全程只计算了五次迭代、每次几十次残差函数求值速度差距明显。4. 避坑指南与常见问题排查4.1 采样点数一个容易被忽视的混叠陷阱我在第一次实现时天真地把时域采样点数设成2N1想着“刚刚好能分辨这些谐波”。结果非线性投影算出来的所有谐波系数全都不对而且迭代完全发散。原因在于x^3这个运算在时域是逐点乘法它生成的信号频谱会扩展到3N阶谐波。采样点数过少时这些高频分量会折叠回低频污染你要计算的1到N阶谐波。正确的做法是让采样点数至少覆盖非线性映射后可能产生的最高阶谐波。对三次非线性最大阶数是3N所以我取M2^nextpow2(6N1)这个量级留下一倍余量。实际工程中如果你不确定非线性有多强可以多取唯一代价是矩阵维度变大性能影响很小。4.2 收敛不了先换初值别急着加谐波最常见的问题就是Newton迭代发散。我的经验是先怀疑初值再怀疑Jacobian最后才怀疑谐波数不够。一个好的初值可以这样构造忽略非线性项解线性系统的频域响应这就是谐波平衡解的线性近似。对weakly nonlinear系统这个初值已经很接近真解了。如果非线性很强比如α特别大线性解可能远离真解这时候就要用延拓法从一个小参数α开始求解再把α逐步增加每一步都以上一步的解作为初值。这个思路在扫频时尤其好用一个个频率点连续算下去不会中途跳飞。还有一个被低估的技巧阻尼Newton。我主程序里的lambda减半策略在残差振荡期作用非常大。从某个角度说谐波平衡法的矩阵规模小多几次阻尼迭代的成本完全可忽略。4.3 数值Jacobian的步长与精度数值差分Jacobian不是精度越高越好。步长设置太大会引入截断误差太小会让差分运算在浮点数减法中失掉有效位。折中办法是使用相对步长d1e-6*max(1,abs(X(j)))正好落在双精度浮点最稳定的范围。如果一次Newton迭代中残差始终在某个水平震荡而非单调下降可以输出Jacobian的条件数看看我见过条件数达到1e12的情况此时数值Jacobian已经不可信。针对多项式的非线性项其实可以手推解析Jacobian也就是对每个残差函数关于每个频域系数求偏导。实测下来解析Jacobian收敛速度略快但调试成本高建议先把数值方案跑通再说。4.4 多解、分歧点与物理合理性检查谐波平衡法解的是一个非线性代数方程组和时域仿真不同它不能保证只有唯一解。同一个激励频率下系统可能存在稳定的周期解和不稳定的周期解Newton迭代可能收敛到任何一个。最直接的检查办法是用不同初值测试看是否收敛到不同解然后回去用ode45验证哪一个是物理上实际会出现的。硬非线性系统在扫频时还会出现跳变现象谐波平衡法能算到多解分支但不会告诉你哪些分支稳定。这时候需要结合稳定性分析比如Floquet理论来判定。如果你是刚接触这个方法建议先用弱非线性调通流程再逐步加大非线性强度就能体会到多解现象是怎么出现的。5. 个人经验与扩展建议我最初在功率放大器非线性模型上实现谐波平衡法时N取到7采样点数设到256Newton迭代平均只需要4步单频点计算时间在毫秒级。对我来说最大的收获不仅是一个算法而是一种思维方式当系统被周期激励驱动时与其追着它跑几千步不如直接假设稳态形态再反解参数。最后分享一个我一直在用的小技巧输出求解结果时把最后一步残差和各谐波幅值画在一起检查。残差保持在1e-10量级说明代数方程解得足够准再看最高次谐波幅值是否比基波小了几个数量级。如果最大阶谐波幅值仍然很大说明N取小了这时候才需要增加谐波数重算。这样先检查后加N比盲目加大N高效得多。这个方法在整个变频电路和Duffing系统扫频分析里都验证过每次都很可靠。本文还有配套的精品资源点击获取