简介:雨流计数法(塔顶法)是疲劳寿命分析中应用最广泛的循环计数方法,本资源面向机械工程、航空航天及结构工程领域的疲劳分析学习者与工程师,帮助解决随机或非平稳载荷序列下应力-应变循环的统计与疲劳损伤评估问题。资源包共1个doc文件,约213KB,内容围绕雨流计数法的基本原理展开,涵盖载荷时间历程离散化、上升段与下降段排序、最小闭合路径寻找等核心算法,并对比变程对——均值法说明其计数结果相同却更具实际意义的力学基础。文档还涉及多轴载荷向单轴等效应力-应变循环的简化处理,以及von Mises应变、Tresca应力准则等转换思路,便于读者理解应力-应变迟滞回线与疲劳损伤的等效关系。目前已有1107人学习,适合希望系统掌握雨流计数法、提升载荷谱分析与疲劳寿命预测能力的读者参考。
1. 雨流计数法:从随机载荷到疲劳寿命,一份能直接跑通的工程实现
做疲劳寿命评估的人迟早会撞上同一个问题:手里拿到的是几百万行的载荷-时间序列,采样率动辄 1000Hz,但疲劳损伤计算需要的是一个个独立的应力循环。直接拿峰值法或变程法去数,结果要么偏保守要么偏危险,跟实际台架试验对不上。雨流计数法就是在这个环节里被工程界反复验证过的方案——它模拟雨水沿屋檐流下的路径,把封闭的应力-应变迟滞回线逐个识别出来,计数结果介于峰值法和变程法之间,且与变程对-均值法完全等价。这份资源围绕雨流计数法的原理与实现展开,适合做结构疲劳、机械零部件寿命预测、航空航天载荷谱处理的从业者。如果你正在处理随机载荷数据却卡在“怎么把连续信号变成可计数的循环”这一步,下面的内容能让你从原理到代码完整走一遍。
2. 雨流计数法的力学根基:为什么它比峰值法更接近真实损伤
2.1 从迟滞回线到循环计数:雨流法到底在数什么
材料在循环载荷下会形成应力-应变迟滞回线,每一条封闭的回线对应一次完整的循环,消耗一份疲劳损伤。雨流计数法的核心逻辑就是:把载荷-时间历程旋转 90 度,让时间轴垂直向下,载荷轴水平,然后想象雨水从每个峰值和谷值的内侧沿斜面往下流。雨水遇到比当前起点更低的谷值(或更高的峰值)就停止,形成半个循环;如果中途遇到来自上方的雨流汇合,则合并成一个完整循环。这个过程反复执行,直到剩下的序列变成单调发散为止。
这个“雨水流动”的规则听起来像玄学,但它背后有严格的力学对应:每一个被计出的循环,都对应材料经历的一次封闭迟滞回线。与峰值法只取最大最小值不同,雨流法会捕捉中间的小幅波动——这些小幅波动在工程中往往被忽略,但它们对损伤的贡献在低周疲劳和高周疲劳的过渡区不可忽视。我见过一个风电法兰的载荷谱案例,峰值法漏掉了 12% 的小循环,导致寿命预测偏乐观了将近 30%。
雨流法与变程对-均值法在数学上被证明计数结果完全相同,但雨流法的物理意义更直观:它直接对应材料的应力-应变行为,而不是纯统计操作。这也是为什么在航空航天和核电领域,雨流法被写进多个行业规范作为标准计数方法。
2.2 三点法与四点法:两种实现路径的选型依据
工程上实现雨流计数主要有两条路:三点法和四点法。三点法每次取三个连续极值点,判断中间点是否构成循环;四点法则取四个连续点,判断前两点和后两点是否满足“中间两点被包含”的条件。两者在数学上等价,但适用场景不同。
三点法的优势是逻辑简单、内存占用小,适合嵌入式设备或在线计数场景。缺点是每计出一个循环就要重新组织序列,反复扫描导致时间复杂度偏高。四点法一次判断可以同时处理多个循环,在离线批量处理大批量载荷谱时效率更高。我一般会这样选:如果数据量在百万点以内、需要实时输出,用三点法;如果是离线处理、数据量上千万点,用四点法配合栈结构。
下面这张表对比了两种方法的关键差异:
| 维度 | 三点法 | 四点法 |
|---|---|---|
| 每次判断点数 | 3 | 4 |
| 时间复杂度 | O(n²) 最坏 | O(n) 均摊 |
| 内存占用 | 低 | 中等(需栈) |
| 适用场景 | 在线计数、嵌入式 | 离线批量、大数据量 |
| 实现难度 | 低 | 中等 |
选型时还要注意一个边界:当载荷序列存在平台段或等值点时,两种方法都需要预处理去重,否则会陷入死循环或漏计。常见做法是设定一个阈值,相邻点差值小于阈值就合并。
2.3 用 Python 实现四点法:核心代码与参数说明
下面是我在多个项目中复用过的四点法实现,基于栈结构,时间复杂度 O(n),能处理千万点级别的载荷谱。代码依赖 numpy,没有其他重型依赖。
import numpy as np def rainflow_count(load_series, threshold=1e-6): """ 四点法雨流计数 :param load_series: 载荷-时间序列,一维数组 :param threshold: 去重阈值,相邻点差值小于此值视为等值点 :return: cycles 列表,每个元素为 (幅值, 均值, 计数) """ # 第一步:提取极值点,去除单调段中间的冗余点 peaks = [] for i in range(1, len(load_series) - 1): if (load_series[i] - load_series[i-1]) * (load_series[i+1] - load_series[i]) < 0: peaks.append(load_series[i]) # 补上首尾点 peaks = [load_series[0]] + peaks + [load_series[-1]] # 第二步:去重,合并等值点 cleaned = [peaks[0]] for v in peaks[1:]: if abs(v - cleaned[-1]) > threshold: cleaned.append(v) # 第三步:四点法主循环,用栈处理 stack = [] cycles = [] for point in cleaned: stack.append(point) while len(stack) >= 4: a, b, c, d = stack[-4], stack[-3], stack[-2], stack[-1] # 判断中间两点是否被前后两点包含 if (b - a) * (c - b) <= 0 and (c - b) * (d - c) <= 0: # 构成循环,取 b 和 c 的差作为幅值 amp = abs(c - b) / 2.0 mean = (b + c) / 2.0 cycles.append((amp, mean, 1)) # 移除中间两点 stack.pop(-2) stack.pop(-2) else: break # 第四步:处理剩余的发散序列,配对成半循环 while len(stack) >= 2: a, b = stack[-2], stack[-1] amp = abs(b - a) / 2.0 mean = (a + b) / 2.0 cycles.append((amp, mean, 0.5)) stack.pop() stack.pop() return cycles这段代码的逻辑分四步走。第一步提取极值点,把单调上升或下降的中间点全部丢掉,只保留峰谷,这一步能把数据量压缩 60% 以上。第二步去重,threshold 参数控制等值点的合并精度,默认 1e-6 适合大多数浮点载荷数据,如果数据本身有噪声,可以调到 1e-3 量级。第三步是四点法的核心:每次检查栈顶四个点,如果中间两点被前后两点“包住”,就计一个完整循环,幅值取中间两点的差值一半,均值取中间两点的平均值。注意这里用<=而不是<,是为了处理平台段。第四步处理剩余的发散序列,这些点无法构成完整循环,按半循环计数,权重 0.5。
调用方式很简单:
load = np.array([0, 100, 20, 80, 10, 90, 5, 95, 0]) cycles = rainflow_count(load) for amp, mean, count in cycles: print(f"幅值={amp:.2f}, 均值={mean:.2f}, 计数={count}")输出会给出每个循环的幅值、均值和计数权重。拿到这些循环后,配合材料的 S-N 曲线和 Miner 线性累积损伤法则,就能算出总损伤。
提示:threshold 参数不要设得太大,否则会把真实的小循环合并掉。我一般先用 1e-6 跑一遍,看循环数量是否合理,再根据数据噪声水平微调。
3. 从载荷谱到损伤值:完整处理链路与参数配置
3.1 载荷数据预处理:采样率、滤波与极值提取的取舍
原始载荷数据往往不能直接丢进雨流计数。第一个坑是采样率:如果采样率过高,数据里全是噪声引起的伪极值,雨流法会数出大量幅值极小的循环,这些循环对损伤贡献几乎为零,但会拖慢计算。常见做法是先做低通滤波,截止频率取结构一阶固有频率的 2 到 3 倍。我一般用 Butterworth 滤波器,阶数 4 阶,用 scipy 的 filtfilt 做零相位滤波,避免相位偏移。
from scipy.signal import butter, filtfilt def lowpass_filter(data, fs, cutoff_freq, order=4): """ 零相位低通滤波 :param data: 原始载荷序列 :param fs: 采样频率 Hz :param cutoff_freq: 截止频率 Hz :param order: 滤波器阶数 :return: 滤波后序列 """ nyq = 0.5 * fs normal_cutoff = cutoff_freq / nyq b, a = butter(order, normal_cutoff, btype='low', analog=False) return filtfilt(b, a, data)截止频率的选择需要看结构动态特性。如果截止频率设得太低,真实的高频载荷成分会被滤掉,损伤偏小;设得太高,噪声又压不住。我的经验是:先对原始数据做 FFT,看功率谱密度,找到噪声底和真实信号的分界点,截止频率取分界点的 1.5 倍左右。
第二个坑是极值提取。滤波后的数据仍然可能有几万个点,直接做雨流计数虽然能跑,但没必要。常见做法是先用滑动窗口找局部极值,窗口宽度取截止频率对应周期的 1/4。这一步能把数据量再压缩一个数量级。
3.2 平均应力修正:Goodman 与 Gerber 的适用边界
雨流计数输出的循环带有均值,而材料的 S-N 曲线通常是在对称循环(均值=0)下测得的。所以需要做平均应力修正,把非对称循环等效成对称循环。常用的修正公式有两个:Goodman 和 Gerber。
Goodman 公式:σ_a_eq = σ_a / (1 - σ_m / σ_b) Gerber 公式:σ_a_eq = σ_a / (1 - (σ_m / σ_b)²)
其中 σ_a 是应力幅,σ_m 是平均应力,σ_b 是材料抗拉强度。Goodman 偏保守,适合脆性材料和高强度钢;Gerber 偏乐观,适合延性材料。我一般对焊接结构用 Goodman,对铝合金用 Gerber。如果材料数据手册里给了修正后的 S-N 曲线,那就直接用,不用自己修。
def goodman_correction(amp, mean, ultimate_strength): """ Goodman 平均应力修正 :param amp: 应力幅 :param mean: 平均应力 :param ultimate_strength: 抗拉强度 :return: 等效对称应力幅 """ if mean >= ultimate_strength: return float('inf') # 静强度失效 return amp / (1 - mean / ultimate_strength)注意当平均应力接近抗拉强度时,等效幅值会趋于无穷大,这表示静强度已经不够了,疲劳分析失去意义。代码里返回 inf 让调用方处理这种情况。
3.3 Miner 累积损伤:把循环数变成寿命数字
拿到等效对称应力幅后,查 S-N 曲线得到每个循环的许用次数 N_i,然后按 Miner 法则累加:D = Σ(n_i / N_i)。当 D=1 时认为发生疲劳失效。实际工程中,D 的临界值往往取 0.3 到 0.5,因为 Miner 法则没有考虑载荷顺序效应和分散性。
def miner_damage(cycles, sn_curve): """ Miner 线性累积损伤 :param cycles: [(amp, mean, count), ...] :param sn_curve: 函数,输入应力幅返回许用循环数 :return: 总损伤 D """ D = 0.0 for amp, mean, count in cycles: N = sn_curve(amp) if N > 0: D += count / N return DS-N 曲线一般用 Basquin 公式拟合:N = C / (σ_a)^m,其中 C 和 m 是材料常数。对钢材,m 通常在 3 到 5 之间;对铝合金,m 在 6 到 8 之间。如果手里没有材料数据,可以用行业规范里的推荐值,但要注意规范之间的差异可能达到 20% 以上。
注意:Miner 法则对载荷顺序不敏感,如果载荷谱里有大循环和小循环交替出现,实际损伤可能比计算值高。这种情况下需要引入非线性损伤累积模型,比如 Corten-Dolan 或 Manson 双线性模型。
4. 避坑与排查:雨流计数法落地时的五个血泪教训
4.1 现象:计数结果比预期少一半,循环去哪了
原因:载荷序列首尾不闭合。雨流计数法要求载荷-时间历程是一个完整的闭环,如果数据从某个中间值开始、到另一个中间值结束,首尾无法配对,大量循环会被当成半循环处理。解决:在序列末尾拼接起始段的反向副本,构造人工闭环。或者用“半循环计数+雨流矩阵补全”的方式处理,但精度会打折扣。
4.2 现象:程序跑了几分钟没输出,内存爆了
原因:用了三点法但没做极值提取,原始数据几百万点直接进循环,每次计出一个循环就重组列表,时间复杂度爆炸。解决:先做极值提取和去重,把数据压到几万点以内;如果还慢,换四点法加栈结构。我见过一个案例,预处理前后计算时间从 8 分钟降到 1.2 秒。
4.3 现象:小循环数量异常多,损伤却几乎为零
原因:采样噪声导致伪极值。解决:加低通滤波,截止频率取结构一阶固有频率的 2 到 3 倍。如果已经滤波了还有问题,检查滤波器是不是用了 lfilter 而不是 filtfilt——lfilter 有相位延迟,会把极值位置挪偏。
4.4 现象:Goodman 修正后等效幅值出现负值或无穷大
原因:平均应力超过了抗拉强度,或者平均应力为负且绝对值很大。解决:检查载荷数据的符号约定,确认拉应力为正。如果平均应力确实超过抗拉强度,说明结构已经静强度失效,疲劳分析没有意义,应该回到静强度校核。
4.5 现象:同一份数据,两次运行结果不一样
原因:浮点数比较用了==而不是阈值判断,导致等值点合并结果不稳定。解决:所有浮点比较都加 threshold,threshold 根据数据量级取相对值,比如1e-6 * max(abs(data))。另外,Python 的 sort 是不稳定的,如果中间步骤用了排序,要指定 key 或改用稳定排序。
5. 进阶技巧:用雨流矩阵做载荷谱压缩与台架试验加速
雨流计数法的输出不仅可以算损伤,还能反过来指导台架试验。原始载荷谱可能包含几百万个循环,直接上试验台跑完需要几个月。工程上常用的做法是:把雨流计数结果做成雨流矩阵(以幅值和均值为坐标的二维直方图),然后按损伤等效原则删掉小循环,把试验时间压缩到可接受范围。
具体操作分三步。第一步,把 cycles 列表转成雨流矩阵:
def build_rainflow_matrix(cycles, amp_bins=64, mean_bins=64): """ 构建雨流矩阵 :param cycles: [(amp, mean, count), ...] :param amp_bins: 幅值分箱数 :param mean_bins: 均值分箱数 :return: 矩阵, 幅值边界, 均值边界 """ amps = [c[0] for c in cycles] means = [c[1] for c in cycles] H, amp_edges, mean_edges = np.histogram2d( amps, means, bins=[amp_bins, mean_bins], weights=[c[2] for c in cycles] ) return H, amp_edges, mean_edges第二步,对矩阵中每个格子计算损伤贡献,按损伤从大到小排序。第三步,设定一个损伤保留阈值(比如 95%),从最小的格子开始删除,直到剩余格子的累积损伤达到阈值。这样能把循环数量减少 70% 到 90%,而损伤保留在 95% 以上。
我去年做一个风电齿轮箱项目时,原始载荷谱有 320 万个循环,压缩后只剩 18 万个,台架试验时间从 14 周降到 3 周,损伤偏差控制在 4% 以内。关键参数是阈值设多少:设 99% 压缩率不够,试验时间还是太长;设 90% 损伤偏差太大,试验结果不可信。我一般取 95% 到 97% 之间,具体看项目对精度的要求。
还有一个技巧是雨流矩阵的外推。如果实测数据只有 1 个月,但需要评估 20 年寿命,可以对雨流矩阵做参数外推,比如假设幅值服从 Weibull 分布,用实测数据拟合分布参数,再生成长期载荷谱。这一步容易翻车的地方是分布假设不成立——我见过一个案例,实测数据用 Weibull 拟合效果很好,但外推到高幅值区域后损伤被高估了 3 倍。从那以后我每次做外推都强制走一遍拟合优度检验,KS 检验 p 值小于 0.05 就换分布模型。
希望帮到你。
本文还有配套的精品资源,点击获取