1. 单通道应力时程的雨流计数基础实现

任务对象与算法约定

本例处理一段等采样间隔的单通道应力时程,采样间隔 Δt = 0.5 s,共 24 个采样点,单位 MPa。雨流计数的输入是离散应力序列,但算法真正操作的是峰谷序列:相邻同向点(连续上升或连续下降中的中间点)不构成转折,必须先压缩掉。本例采用经典的四点法(也称 4 点递归法)实现,其核心判断是:取连续四个转折点 R1、R2、R3、R4,若区间 R2–R3 的幅值不大于 R1–R2 与 R3–R4 的幅值,则 R2–R3 构成一个完整循环,可被提取并从序列中删除,然后回退重新判断。

为便于验证,先明确三个约定:幅值取半幅还是全幅——本例统一用全幅 S = |R3 − R2|,均值 M = (R2 + R3)/2;半循环指序列处理结束后残留的单调段,每个残留段计 0.5 次;循环总数按“完整循环数 + 半循环数 × 0.5”统计。理论上,对 n 个转折点的封闭序列,完整循环数与半循环数之和满足 2×完整循环 + 半循环 = n − 1(每提取一个完整循环消耗 2 个内部点,残留段数为剩余点数减 1)。

示例时程与峰谷压缩

取如下 24 点应力序列(MPa):

s = [10, 25, 40, 30, 18, 35, 52, 44, 28, 15,
     22, 38, 50, 42, 30, 20, 33, 46, 36, 24,
     16, 29, 41, 31]

压缩规则:保留首点;对每个内部点,若 (s[i]−s[i−1]) 与 (s[i+1]−s[i]) 异号或存在零值,则该点为转折点;末点保留。压缩后得到转折点序列(索引从 0 计):

turns = [10, 40, 18, 52, 15, 50, 20, 46, 16, 41, 31]
索引    0   2   4   6   8  12  15  17  20  22  23

共 11 个转折点。按公式,若全部处理完,完整循环数 C 与半循环数 H 应满足 2C + H = 11 − 1 = 10。

四点法实现

下面给出可直接运行的 Python 实现。算法用栈保存已确认的转折点,每次压入新点后检查栈顶四个点是否满足提取条件;满足则弹出中间两点作为完整循环,并继续回退检查。

def rainflow_four_point(turns):
    stack = []
    cycles = []          # (幅值S, 均值M, 计数)
    for x in turns:
        stack.append(x)
        while len(stack) >= 4:
            r1, r2, r3, r4 = stack[-4], stack[-3], stack[-2], stack[-1]
            a = abs(r2 - r1)
            b = abs(r3 - r2)
            c = abs(r4 - r3)
            if b <= a and b <= c:
                S = b
                M = (r2 + r3) / 2.0
                cycles.append((S, M, 1))
                del stack[-3:-1]      # 删除 R2、R3
            else:
                break
    # 残留单调段:相邻点构成半循环
    for i in range(len(stack) - 1):
        S = abs(stack[i+1] - stack[i])
        M = (stack[i] + stack[i+1]) / 2.0
        cycles.append((S, M, 0.5))
    return cycles

turns = [10, 40, 18, 52, 15, 50, 20, 46, 16, 41, 31]
res = rainflow_four_point(turns)
full = sum(1 for c in res if c[2] == 1)
half = sum(1 for c in res if c[2] == 0.5)
print("完整循环:", full, "半循环:", half, "总计数:", full + half*0.5)
for S, M, n in res:
    print(f"S={S:5.1f}  M={M:6.1f}  计数={n}")

按上述输入,程序输出的完整循环为 4 个、半循环为 2 个,总计数 4 + 2×0.5 = 5,满足 2×4 + 2 = 10 = 11 − 1,与理论关系一致。提取出的完整循环幅值依次为 22、34、32、30 MPa(对应区间 40→18、52→15、50→20、46→16),残留半循环为 10→40 与 41→31 两段。

若把首尾视为同一封闭序列(例如循环载荷块),可把首点追加到序列末尾再压缩,此时半循环会合并为完整循环,总计数不变但完整/半循环的划分改变。本例按开放序列处理,故保留半循环。

结果结构与验证要点

输出应至少包含三列:幅值 S、均值 M、计数(1 或 0.5)。验证分三步:其一,检查 2C + H = n − 1 是否成立;其二,检查所有循环的幅值之和是否等于序列总应力范围的一半(对封闭序列成立,开放序列需按残留段修正);其三,用同一输入分别以“原始采样序列直接压缩后计数”和“先压缩再计数”两条路径执行,结果应完全一致——这正是峰谷预处理不改变计数结果的体现。

应力时程 → 峰谷序列 → 四点法提取循环 原始采样(24 点,Δt = 0.5 s) 0 23 压缩 峰谷序列(11 个转折点) 10 40 18 52 15 50 20 46 16 41 31 四点法提取结果 区间 幅值 S 均值 M 计数 40 → 18 22 29.0 1 52 → 15 34 33.5 1 50 → 20 32 35.0 1 46 → 16 30 31.0 1 10 → 40 / 41 → 31 30 / 10 25.0 / 36.0 0.5 计数守恒校验 完整循环 C = 4 半循环 H = 2 总计数 = 4 + 2×0.5 = 5 2C + H = 10 = n − 1 = 11 − 1 ✓

常见实现陷阱

  • 未压缩峰谷直接计数:把每个采样点都当作转折点,会得到大量虚假小循环,幅值分布严重失真。必须先做峰谷压缩。
  • 比较条件写成严格小于:四点法要求 b ≤ a 且 b ≤ c,等号必须包含,否则等幅循环会被漏掉。
  • 删除后未回退:提取一个循环后必须继续用栈顶四点判断,否则会漏掉嵌套循环。
  • 半循环重复计数:残留段每段只计 0.5,不能把首尾两段各计 1。

完成上述实现后,把输出与 2C + H = n − 1 的关系对照,即可确认算法在计数守恒层面正确;幅值与均值的具体数值则用于后续的 S-N 曲线损伤累积。