首页/文章/ 详情

基于简化雨流计数法处理随机载荷谱

2月前浏览4347

简介

机械结构在服役条件下通常承受振动载荷的作用,振动载荷可以视为一种随机载荷。在对机械结构进行疲劳可靠性分析时,直接使用随机载荷非常不便,因此往往需要将随机载荷通过特定的计数方法转化为若干恒幅载荷,并编制为由若干恒幅载荷谱块构成的变幅载荷谱。恒幅载荷作用下的疲劳寿命估算,可以借助S-N曲线解决。而通过结合Miner线性累积损伤法则,则可应用于变幅载荷下的疲劳寿命预测,最终实现估算实际服役条件下机械结构的疲劳寿命。


   

   

   

简化雨流计数法

将不规则的、随机的载荷-时间历程,转化为一系列循环的方法,即为“循环计数法”。在ASTM E1049-85 Standard Practices for Cycle Counting in FatigueAnalysis中,规定了若干种用于疲劳分析的循环计数方法,其中应用最为广泛的方法为雨流计数法(Rain-flow Counting Method)。相比于其他计数方法,雨流计数方法具有更明确的物理意义,这是因为通过雨流计数法获得的恒幅载荷,恰好对应了在该随机载荷作用下的应力-应变响应中的封闭滞回环,如图1所示。

 

图1 雨流计数法的物理意义

在图1(a)中,阴影部分的循环为通过雨流计数法计数得到的循环,在图1(b)中,阴影部分为与之对应的封闭滞回环。由于封闭滞回环代表材料产生的疲劳损伤,因此通过雨流计数法获得的恒幅载荷所产生的疲劳损伤与变幅载荷作用下产生的疲劳损伤应该是等效的。

雨流计数法又可细分为三点和四点雨流计数法,这里本文仅讨论三点雨流计数法,也称为简化雨流计数法。简化雨流计数法适用于以典型载荷谱段为基础的重复历程,也就是说,机械构件承受的随机载荷谱可以看为某一典型载荷谱段的不断重复,因此可以只针对该典型载荷谱段进行雨流计数法即可。此外,简化雨流计数法要求将典型载荷谱段的最大峰值或谷值放置在载荷谱段的起始位置,否则将会出现计数错误。

之所以得名雨流计数法,这是因为该方法的计数原理可类比雨滴沿多层屋顶流动的过程,其计数方法如下所示:

(1)从随机载荷谱中选取典型谱段,并且使得典型谱段的最大峰值或谷值位于典型谱段的起始位置,如图2所示。需要注意的是,在从随机载荷谱中选取典型谱段之前,还需要首先去除随机载荷谱中的非峰谷值,使得随机载荷谱完全由峰值和谷值构成。

 

图2 雨流计数典型段的选取

(2)将典型谱段旋转90°放置,如图3所示。将载荷历程看作多层屋顶,设想有雨滴沿最大峰或谷处开始往下流。若无屋顶阻挡,则雨滴反向,继续流至端点。图中雨滴从A处开始,沿AB流动,至B点后落至CD屋面,继续流至D处;因再无屋顶阻挡,雨滴反向沿DE流动至E处,下落至屋面JA',至A'处流动结束。所示的雨流路径为ABDEA'。

(3)记下雨滴流过的最大峰、谷值,作为一个循环。图中第一次流经的路径,给出的循环为ADA'。循环的幅值和均值可由图中读出。如ADA'循环的载荷幅值为ΔS=5-(-4)=9,平均载荷Sm=[5+(-4)]/2=0.5。

(4)从载荷历程中删除雨滴流过的部分,对各剩余历程段,重复上述雨流计数。直至再无剩余历程为止。如图所示,第二次雨流得到BCB'和EHE'循环;第三次雨流得到FGF'和IJI'循环;计数完毕。

 

图3 雨流计数过程


   

   

   

简化雨流计数法计算规则

上述图形方式虽然较为直观,但不便于程序编制。对于存在大量循环数的随机谱段,可采用下述步骤编制为简化雨流计数程序:

(1)读取由最大峰或谷处起止的典型段,按载荷谱顺序输入各峰、谷值,直至数据完毕。

(2)读入下一峰、谷值。若数据完毕,则停止。

(3)若数据点数少于3,则返回步骤(2);若数据点数大于等于3,则由最后读入的3个峰、谷值,计算幅值X和Y。这三点中,第一点与第二点之差的绝对值为Y;第二点与第三点之差的绝对值为X。

(4)比较X和Y的大小。若X<Y,则返回(2);若X≥Y,则进行步骤(5)。

(5)将幅值Y记作一个循环,删除与Y相应的峰、谷值,返回步骤(3)。

计算示例

这里,本文通过一个简单示例来展示简化雨流计数法的计数规则。考虑如下表所示的一段谱段,谱段的峰、谷点和对应的峰、谷值列于表中。图4给出了与表格对应的用于雨流计数的载荷谱,为了满足简单雨流计数法的要求,载荷谱的最大峰值位于起始点,并且起始点和终止点的数值相同,保证该载荷谱是可重复的。

 
 

图4 用于雨流计数的原始载荷谱

首先读入A,B,C三点,计算变程X和Y,有:

 
 


由于X<Y,因此返回步骤(2),需要读取下一峰谷值,即读取D。

此时B,C,D为最后读入的三个峰谷值,由此重新计算X和Y,有:

 
 


此时满足XY,进入步骤(5),即将此时的变程Y(B→C)记为一循环,并删除B,C点,返回步骤(3)。

删除B,C点后的载荷谱如下所示。

 

此时剩余的峰谷点为A和D,数量小于3,因此需读取E,并根据A,D,E计算变程X和Y,即有:

 
 


由于满足X<Y,因此进入步骤(2),读取峰谷点F,并根据D,E,F计算变程X和Y,即有:

 
 


由于满足X<Y,返回步骤(2),读取峰谷点G,并根据E,F,G计算变程X和Y,即有:

 
 


由于满足X<Y,返回步骤(2),读取峰谷点H,并根据F,G,H计算变程X和Y,即有:

 
 


由于X≥Y,进入步骤(5),将变程Y(G→F)记为一循环,并删除G,F点,返回步骤(3)。

删除G,F点之后的载荷谱如下所示。

 

此时剩余的峰谷点为A,D,E,H,由于数据点数大于3,因此基于D,E,H点计算变程X和Y,即有:

 
 


由于X<Y,因此返回步骤(2),读取峰谷点I,并根据E,H,I点计算变程X和Y,即有:

 
 


由于X<Y,返回步骤(2),读取峰谷点J,并根据H,I,J点计算变程X和Y,即有:

 
 


由于X<Y,返回步骤(2),读取峰谷点A',并根据I,J,A'计算变程X和Y,即有:

 
 


由于X≥Y,进入步骤(5),将变程Y(I→J)记为一循环,并删除I和J点,返回步骤(3)。

删除I和J点之后的载荷谱如下所示。

 

此时剩余的峰谷点为A,D,E,H和A',数量大于3,因此根据E,H,A'点计算变程X和Y,即有:

 
 


由于X≥Y,因此将变程Y(E→H)记为一循环,并删除E和H点,返回步骤(3)。

删除E,H点之后的载荷谱如下所示。

 

此时剩余的峰谷点为A,D和A',数据点数大于等于3,根据A,D,A'计算变程X和Y,即有:

 
 


此时满足X≥Y,进入步骤(5),将变程Y(A→D)记为一循环,并删除A和D,返回步骤(3)。

此时剩余峰谷点的数量不足3,返回步骤(3),需读取下一峰谷点,但此时数据已读取完毕,计数过程结束。

最终简化雨流计数法的计数结果如下所示。

 

程序实现

为了实现通过简化雨流计数法处理随机载荷谱,本文基于上述计数原理编制了相应的MATLAB程序。该程序由Rainflow_PreArrange和Rainflow_Count两个文件构成。Rainflow_PreArrange文件用于读取用户指定的载荷序列(以列数据的形式指定),去除数据中的非峰谷值,并调整载荷序列的顺序,使得最大峰值位于载荷序列的起始位置。例如,图5给出了包含非峰谷值的原始载荷谱数据,通过Rainflow_PreArrange程序去除非峰谷值和调整载荷顺序之后的载荷谱如图6和图7所示。

 

图5 包含非峰谷值的原始载荷谱数据

 

图6 去除非峰谷值之后的载荷谱数据

 

图7 调整载荷顺序之后的载荷谱数据(适用于简化雨流计数法)

在获得适用于简化雨流计数法的载荷谱数据之后,即可通过Rainflow_Count程序对其进行简化雨流计数。例如,对于图7中所示的载荷谱数据,Rainflow_Count程序给出的简化雨流计数法结果如下所示:

Range        Mean         Start value  Target value
4.0000       1.0000       -1.0000      3.0000      
3.0000       -0.5000      -2.0000      1.0000      
7.0000       0.5000       4.0000       -3.0000     
9.0000       0.5000       5.0000       -4.0000 

事实上,MATLAB内置的信号处理工具箱也内置了rainflow函数用于对载荷谱进行雨流计数。对于上述载荷谱数据(存放于变量Spectrum_Data中),通过下述命令对其进行雨流计数:

[c,hist,edges,rmm,idx] = rainflow(Spectrum_Data);
T = array2table(c,'VariableNames',{'Count','Range','Mean','Start','End'})

得到对应的输出结果如下所示:

T =

  5×5 table

    Count    Range    Mean    Start    End
    _____    _____    ____    _____    ___

       1       4         1      2       3 
       1       3      -0.5      6       7 
       1       7       0.5      5       8 
     0.5       9       0.5      1       4 
     0.5       9       0.5      4       9 

注意,上述结果中的Start和End代表载荷数据的索引值,而非载荷值,Count中的0.5代表半个循环。通过对比由本文程序给出的计数结果可以发现,二者是完全一致的,这证明本文编制的简化雨流计数程序是正确的。

附录


   

   

   

Rainflow_PreArrange程序

%程序用于对输入的载荷谱进行预排序 以满足简化雨流计数法的要求(ASTM E1049-85)
%输入载荷谱数据的要求:(1)数据必须只包含峰谷值 (2)首尾数据的数值必须一致(构成可重复的谱块)
clear,clc

%------------------------------载荷谱数据导入-------------------------------
%导入原始载荷谱数据
Spectrum_Data_Origin=importdata("Spectrum_Data1.txt");
%绘制原始载荷谱数据
plot(Spectrum_Data_Origin,"-o");
title("Original spectrum data");xlabel("Inversal");ylabel("Load");
%--------------------------------------------------------------------------

%-----------------------------载荷谱数据预检查1------------------------------
%统计原始数据点的数量
Origin_Data_Num=size(Spectrum_Data_Origin,1);

%检查数据维度是否符合要求(一维列向量数据)
if size(Spectrum_Data_Origin,2)~=1
    error("Error:The input load spectrum data must be in "+...
        "the form of a one-dimensional column vector..");
end

%检查首尾数据的数值是否一致(必须构成可重复的谱块)
if Spectrum_Data_Origin(1)~=Spectrum_Data_Origin(end)
    error("Error: The initial and final data points differ,"+...
        "which obstructs the creation of a repeatable load spectrum block.");
end
%--------------------------------------------------------------------------

%-----------------------------载荷谱数据预检查2------------------------------
%检查数据是否只由峰谷值构成
%查找数据中的所有峰值
[~,locs_pks]=findpeaks(Spectrum_Data_Origin);
%查找数据中的所有谷值(通过取负值来实现)
[~,locs_vls]=findpeaks(-1*Spectrum_Data_Origin);
%合并和排序峰值和谷值的位置(不含首尾位置)
CombinedLocs=sort([locs_pks;locs_vls]);
%合并和排序峰值和谷值的位置(增加首尾位置)
CombinedLocs=[1;CombinedLocs;Origin_Data_Num];
%检查峰值和谷值的位置是否覆盖原始数据的全部位置
if isequal(CombinedLocs,(1:Origin_Data_Num)')
else
    warning("Warning: The input load spectrum data includes "+...
        "other points besides peaks and valleys.");
end
%重新组合剔除非峰谷值之后的数据
Spectrum_Data_Filtered=Spectrum_Data_Origin(CombinedLocs);
%--------------------------------------------------------------------------

%-------------------------重新绘制预检查之后的数据---------------------------
figure;
plot(Spectrum_Data_Filtered,"-o");
title("Filtered spectrum data");xlabel("Inversal");ylabel("Load");
%--------------------------------------------------------------------------

%------------------------------载荷谱数据重排序------------------------------
%移动载荷谱中最大峰值至起始点 使其满足简化雨流计数法的要求
%统计剔除非峰谷值之后的载荷谱数据点数量
Filtered_Data_Num=size(Spectrum_Data_Filtered,1);
%判断剔除非峰谷值之后的载荷谱数据点数量是否满足要求(不能小于3个点)
if Filtered_Data_Num<3
    error("Error:The number of valid data points in "+...
        "the input load spectrum is insufficient.");
end


%重新确定剔除非峰谷值后的载荷谱中的峰谷值及对应索引
[pks,locs_pks]=findpeaks(Spectrum_Data_Filtered);
[vls,locs_vls]=findpeaks(-1*Spectrum_Data_Filtered);
vls=-1*vls;

%判断起始点和终止点为峰值还是谷值(上述判断未包含起始和终止点)
if Spectrum_Data_Filtered(1)<Spectrum_Data_Filtered(2)
    %起始点和终止点为谷值
    vls=[Spectrum_Data_Filtered(1);vls;Spectrum_Data_Filtered(1)];
    locs_vls=[1;locs_vls;Filtered_Data_Num];
else
    %起始点和终止点为峰值
    pks=[Spectrum_Data_Filtered(1);pks;Spectrum_Data_Filtered(1)];
    locs_pks=[1;locs_pks;Filtered_Data_Num];
end

%判断最大峰值所在索引
[~,index]=max(pks);
locs_max_pks=locs_pks(index);

%移动载荷谱使其满足简化雨流计数法的要求
%初始化存放重排序之后载荷谱的列向量数组
Spectrum_Data_PreArranged=nan(Filtered_Data_Num,1);
%载入数据至重排序之后的载荷谱
%移动最大峰值之后的数据
Data_Moved=Spectrum_Data_Filtered(locs_max_pks:end);
Data_Moved_Num=size(Data_Moved,1);
Spectrum_Data_PreArranged(1:Data_Moved_Num)=Data_Moved;
%移动最大峰值之前的数据
if locs_max_pks==1
    %最大峰值出现在起始点 无需进一步处理
elseif locs_max_pks==2
    %最大峰值出现在第二个点 只需在数据末尾补充最大峰值点
    Data_Moved=Spectrum_Data_PreArranged(1);
    Spectrum_Data_PreArranged(Data_Moved_Num+1)=Data_Moved;
else
    %最大峰值未出现在前两个点 将前面的数据补至数据末尾
    Data_Moved=Spectrum_Data_Filtered(2:locs_max_pks);
    Spectrum_Data_PreArranged(Data_Moved_Num+1:end)=Data_Moved;
end
%--------------------------------------------------------------------------

%----------------------------绘制重排序之后的载荷谱--------------------------
figure;
plot(Spectrum_Data_PreArranged,"-o");
title("Rearranged spectrum data");xlabel("reversal");ylabel("Load");
%输出载荷谱数据
fprintf("Processed load spectrum data:\n");
for i=1:Filtered_Data_Num
    fprintf("%-12.4f\n",Spectrum_Data_PreArranged(i));
end
%--------------------------------------------------------------------------



   

   

   

Rainflow_Count程序

%程序用于基于简化雨流计数法(ASTM E1049-85)对载荷谱进行计数
%输出载荷谱必须满足采用简化雨流计数法进行计数的要求(可重复的载荷谱)
clear,clc

%------------------------------载荷谱数据导入-------------------------------
%导入载荷谱数据
Spectrum_Data=importdata("Spectrum_Data2.txt");
%绘制载荷谱数据
plot(Spectrum_Data,"-o");
title("Spectrum data");xlabel("Inversal");ylabel("Load");
%--------------------------------------------------------------------------

%--------------------------------变量初始化---------------------------------
%统计数据点的总数
Spectrum_Data_Num=size(Spectrum_Data,1);
%初始化计数得到的循环总数
Rainflow_Counted_Num=0;
%初始化存放计数后的循环的起始和终止索引的数组(数组维度小于等于数据点总数)
Rainflow_Start_Index=nan(Spectrum_Data_Num,1);          %存放起始索引
Rainflow_Target_Index=nan(Spectrum_Data_Num,1);         %存放终止索引
%初始化存放计数后的循环的幅值和均值的数组(数组维度小于等于数据点总数)
Rainflow_Amplitude=nan(Spectrum_Data_Num,1);            %存放循环幅值
Rainflow_Mean=nan(Spectrum_Data_Num,1);                 %存放循环均值
%初始化存放计数后的循环的起始和终止值的数组(数组维度小于等于数据点总数)
Rainflow_Start_Val=nan(Spectrum_Data_Num,1);            %存放循环起始值
Rainflow_Target_Val=nan(Spectrum_Data_Num,1);           %存放循环终止值
%初始化存放临时读取的峰谷点的数组
Temp_PV_Point=nan(Spectrum_Data_Num,1);
%初始化存放临时读取的峰谷点索引值的数组
Temp_PV_Point_Index=nan(Spectrum_Data_Num,1);
%--------------------------------------------------------------------------

%------------------------采用简化雨流计数法进行计数--------------------------
%判断当前载荷谱中的数据点数量是否满足计数要求(数量需大于等于3)
if Spectrum_Data_Num<3
    error("Error:Insufficient number of data points "+...
        "in the input load spectrum.");
end

%读取载荷谱中的三个初始峰谷点的数值
Temp_PV_Point(1:3)=Spectrum_Data(1:3);
%读取载荷谱中三个初始峰谷点的索引值
Temp_PV_Point_Index(1:3)=[1;2;3];

%记录此时读取的峰谷点的总数
Temp_PV_Point_Num=3;

%设定是否读取下一个峰谷点的标志符
ReadNext=0;

%循环读取其余峰谷点用于计数
while(1)
    %判断是否需要读取下一峰谷值(步骤2)
    if ReadNext==0
        %无需读取下一峰谷值 跳过当前步骤
    else
        %判断所有峰谷点是否均已读取完毕
        %获取当前已读取的峰谷值的最大索引
        PV_Max_Index=Temp_PV_Point_Index(Temp_PV_Point_Num);
        %判断是否能够读取下一峰谷点
        if PV_Max_Index>=Spectrum_Data_Num
            break;
        end

        %读取下一峰谷点
        %读取下一峰谷点的数值至数组
        Temp_PV_Point(Temp_PV_Point_Num+1)=Spectrum_Data(PV_Max_Index+1);
        %读取下一峰谷点的索引至数组
        Temp_PV_Point_Index(Temp_PV_Point_Num+1)=PV_Max_Index+1;

        %更新存放的峰谷点数量
        Temp_PV_Point_Num=Temp_PV_Point_Num+1;

    end

    %判断峰谷点数量是否满足要求
    if Temp_PV_Point_Num<3
        continue;
    end

    %由读取到的最后三个峰谷点计算变程X和Y(步骤3)
    X=abs(Temp_PV_Point(Temp_PV_Point_Num)-...
        Temp_PV_Point(Temp_PV_Point_Num-1));
    Y=abs(Temp_PV_Point(Temp_PV_Point_Num-1)-...
        Temp_PV_Point(Temp_PV_Point_Num-2));

    %比较变程X和Y的大小(步骤4)
    if X<Y
        %返回步骤(2) 读取下一峰谷值
        ReadNext=1;
    else
        %记变程Y为一循环 删除与Y对应的峰谷值(步骤5)
        %更新雨流计数得到的循环总数
        Rainflow_Counted_Num=Rainflow_Counted_Num+1;
        %记录变程Y的起始和终止索引
        Rainflow_Start_Index(Rainflow_Counted_Num)=...
            Temp_PV_Point_Index(Temp_PV_Point_Num-2);
        Rainflow_Target_Index(Rainflow_Counted_Num)=...
            Temp_PV_Point_Index(Temp_PV_Point_Num-1);
        %记录变程Y的起始和终止值
        Rainflow_Start_Val(Rainflow_Counted_Num)=...
            Temp_PV_Point(Temp_PV_Point_Num-2);
        Rainflow_Target_Val(Rainflow_Counted_Num)=...
            Temp_PV_Point(Temp_PV_Point_Num-1);
        %计算变程Y的幅值和均值
        Rainflow_Amplitude(Rainflow_Counted_Num)=...
            abs(Rainflow_Start_Val(Rainflow_Counted_Num)-...
            Rainflow_Target_Val(Rainflow_Counted_Num));
        Rainflow_Mean(Rainflow_Counted_Num)=...
            0.5*(Rainflow_Start_Val(Rainflow_Counted_Num)+...
            Rainflow_Target_Val(Rainflow_Counted_Num));

        %删除与变程Y对应的峰谷值
        %复 制数据至临时数组
        temp_Array=Temp_PV_Point(1:Temp_PV_Point_Num);
        %重置存放峰谷值的数组
        Temp_PV_Point(1:Temp_PV_Point_Num)=nan;
        %重新存放剔除变程Y的峰谷值至数组
        j=1;
        for i=1:Temp_PV_Point_Num
            if (i==Temp_PV_Point_Num-1)||(i==Temp_PV_Point_Num-2)
                %不存入变程Y对应的峰谷值
            else
                Temp_PV_Point(j)=temp_Array(i);
                j=j+1;
            end
        end

        %删除与变程Y对应的索引
        %复 制数据至临时数组
        temp_Array=Temp_PV_Point_Index(1:Temp_PV_Point_Num);
        %重置存放峰谷值索引的数组
        Temp_PV_Point_Index(1:Temp_PV_Point_Num)=nan;
        %重新存放剔除变程Y的峰谷值索引值至数组
        j=1;
        for i=1:Temp_PV_Point_Num
            if (i==Temp_PV_Point_Num-1)||(i==Temp_PV_Point_Num-2)
                %不存入变程Y对应的峰谷值
            else
                Temp_PV_Point_Index(j)=temp_Array(i);
                j=j+1;
            end
        end

        %更新数组中存放的峰谷点数量
        Temp_PV_Point_Num=Temp_PV_Point_Num-2;

        %更新是否读取下一峰谷值的标志符
        if Temp_PV_Point_Num<3
            %剩余峰谷点数量小于3需读入下一峰谷值
            ReadNext=1;
        else
            ReadNext=0;
        end

    end
end
%--------------------------------------------------------------------------

%-------------------------输出简化雨流计数法计数结果--------------------------
%输出标题行
fprintf("%-12s %-12s %-12s %-12s\n","Range","Mean","Start value","Target value");

%输出数据
for i=1:Rainflow_Counted_Num
    fprintf("%-12.4f %-12.4f %-12.4f %-12.4f\n",Rainflow_Amplitude(i),...
        Rainflow_Mean(i),Rainflow_Start_Val(i),Rainflow_Target_Val(i));
end
%--------------------------------------------------------------------------


 

 

 

 

 

 

 


来源:FEM and FEA
ACT振动疲劳MATLABUM材料Origin
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2024-09-29
最近编辑:2月前
追逐繁星的Mono
硕士 签名征集中
获赞 47粉丝 92文章 66课程 0
点赞
收藏
作者推荐

基于Abaqus求解考虑裂纹面接触的裂尖应力强度因子的可行性分析

对于承受滚动接触载荷的含裂纹结构,如车轮、轴承等,由于压缩载荷的作用,裂纹面有可能会出现闭合的情况,为了准确考虑裂纹面闭合对于疲劳裂纹扩展的影响,往往需要在考虑裂纹面接触的情况下求解裂纹尖端的应力强度因子。通用有限元软件Abaqus具备求解含裂纹结构在任意载荷作用下的应力强度因子K、J积分和T应力等断裂参量的功能。然而,一些研究表明,Abaqus无法准确求解考虑裂纹面接触时裂纹尖端的应力强度因子。为此,本文以承受滚动接触载荷作用的含斜裂纹平板为例,分别基于有限元软件Abaqus、断裂分析软件Franc3D在考虑和不考虑裂纹面接触的情况下求解裂纹尖端的应力强度因子,并分析了在此类情况下采用Abaqus准确求解裂尖应力强度因子的可行性。有限元建模 模型简介考虑长度为30 mm,高度和厚度均为10 mm的平板,在平板中部存在一条长度为4 mm的穿透型斜裂纹,如图1所示。假设平板为线弹性材料,材料的弹性模量E为200 GPa,泊松比ν为0.3。 图1 含斜裂纹平板示意图该平板受到滚动接触载荷的作用,当滚动接触载荷移动到斜裂纹上方所在的区域时,裂纹面将会产生接触。在本例中,将该滚动接触载荷简化为Hertz接触。因此,作用在平板上的接触压力p(x)可表示为: 式中,p0为接触中心的最大接触压力,本例中取为100 MPa;a为接触斑半径,取为0.5mm。载荷的移动速度v取为30 mm/s。下面,需要计算在该滚动接触载荷作用下,裂纹尖端应力强度因子的变化。 基于有限元软件Abaqus的建模方法当采用有限元软件Abaqus进行建模时,需建立含有裂纹的平板有限元模型,并通过内置的围线积分法求解裂纹体的应力强度因子。为保证计算精度,在平板表面和裂纹尖端区域采用细密网格进行划分,网格尺寸分别取为0.2 mm和0.1 mm,对于远离平板表面和裂纹尖端的区域,则采用较为稀疏的网格划分。为模拟裂纹面间的接触行为,本文在上、下裂纹面之间建立自接触(Self contact),并取裂纹面间的摩擦系数为0.1。约束平板底面的全部位移自由度,并通过用户子程序DLOAD施加滚动接触载荷。最终建立的有限元模型如图2所示。图2 含斜裂纹平板示意图(有限元软件Abaqus) 基于断裂分析软件Franc3D的建模方法当采用断裂分析软件Franc3D求解应力强度因子时,需首先在有限元软件Abaqus中建立不含裂纹的平板模型,并设置好载荷和边界条件。然后,将不含裂纹的有限元模型导入到Franc3D中,并在Franc3D中植入裂纹和设置裂纹面接触,并递交有限元软件Abaqus求解。最后,Franc3D将读取Abaqus的结果文件,以计算裂纹尖端的应力强度因子。在Abaqus中建立的不含裂纹的平板有限元模型如图3所示,图中不同颜色 区域具有相同的材料属性,但材料名称不同,以便在导入Franc3D时区分裂纹重划分区域。图3 不含裂纹的平板有限元模型(断裂分析软件Franc3D)待定义完成后,将上述模型导入到断裂分析软件Franc3D中。需要注意的是,一般情况下,Franc3D会将含有边界条件的单元面或节点设定为保留区域。此时,在植入裂纹或者重划分裂纹的过程中,保留区域的节点布置不会被改变,能够更加方便且精确地将不含裂纹的有限元模型的边界条件映射到含有裂纹的有限元模型的边界条件。如果没有将含有边界条件的单元面或节点设定为保留区域,则这些区域在网格重划分之后可能发生改变,此时Franc3D需要根据原有模型的边界条件,通过外插来重新计算重划分之后模型的边界条件。需要注意的是,无论是否设定保留区域,Franc3D都会执行边界条件的映射操作,但通过设定保留区域,可以尽可能保证映射之后的边界条件仍然是准确的。但是,在本例中,裂纹重划分区域恰好位于边界条件的施加区域内,在植入裂纹的过程中,必然会破坏原有的节点分布。因此,不能将图3中黄色 区域的上表面设定为保留区域,图4给出了在Franc3D中取消选择保留的边界条件表面的设置,图中Load_Surf即为滚动接触载荷的施加表面。图4 取消选择保留的边界条件表面然后,在Franc3D中通过预制的裂纹模版植入穿透型斜裂纹,Franc3D将自动完成裂纹网格的重划分工作。待裂纹网格重划分完成后,在分析菜单中选择执行静态裂纹分析,并勾选定义裂纹面接触,如图5所示。为了与采用Abaqus分析时的模型一致,取接触类型同样为自接触,并且摩擦系数为0.1。图5 裂纹面接触定义待定义完成后,即可递交Abaqus执行计算。需要注意的是,与常规计算不同是,本文在施加移动载荷时调用了用户子程序DLOAD,因此在执行计算之前必须指定定义有DLOAD子程序的Fortran文件。一种指定方法为在图6所示的界面中选择查看/编辑命令,并在调用Abaqus的命令行中增加对子程序文件的引用。另一种方法为在图6所示的界面中勾选写入文件但不运行分析,此时Franc3D将会写入含斜裂纹平板的求解文件(工作文件名+_full.inp),但不会递交Abaqus计算。此时用户需要手动递交Abaqus进行计算。需要注意的是,当使用该方法进行计算时,Franc3D无法直接计算应力强度因子,而是需要用户在计算完成后,在Abaqus后处理界面中运行与求解文件同时生成的writeDtpFile.py脚本文件。该脚本文件将读取Abaqus的数据文件(.odb格式),以提取裂尖区域的位移等参量,并输出为dtp文件。随后,通过在Franc3D中读取该文件,即可计算应力强度因子。图6 生成求解文件第二种方法的优点为由于用户可以手动提交Franc3D生成的inp文件,因此对于Franc3D不支持的关键字类型,可以在提交计算之前修改该inp文件,加入Franc3D不支持的关键字类型,然后再提交计算。基于这种方法,理论上可以实现任意复杂载荷条件下的应力强度因子计算。因此,本文采用第二种方法来计算裂纹尖端的应力强度因子,具体的计算流程将在下一节中介绍。应力强度因子提取方法 基于有限元软件Abaqus的提取方法当采用有限元软件Abaqus进行求解时,可以使用Abaqus内置的围线积分法求解并提取裂纹前缘的应力强度因子。此时,需要在相互作用模块中通过指定裂纹前缘节点和裂纹扩展方向以定义裂纹,并在分析步模块中以时间历程变量的形式定义应力强度因子输出,如图7所示。图7 应力强度因子输出定义待计算完成后,裂纹前缘的应力强度因子和节点坐标值将以时间历程变量的形式给出。关于在有限元软件Abaqus中提取应力强度因子的流程,这里不再赘述。 基于断裂分析软件Franc3D的提取方法当采用Franc3D进行计算时,需要首先提交Franc3D生成的含斜裂纹平板模型的求解文件。需要注意的是,Franc3D会生成三个inp文件,其中以_GLOBAL和_LOCAL结尾的inp文件分别代表重划分区域以外和重划分区域的网格文件,只有以_full结尾的inp文件才是完整的求解文件。将该求解文件提交Abaqus计算,以生成结果文件(.odb格式)。然后,在Abaqus后处理界面中运行随inp文件同时生成的writeDtpFile.py脚本文件。在本例中,其对应的writeDtpFile.py脚本文件内容如下所示。from odbAccess import *import os# analysis specific datadirectory = &#39;.&#39;file_name = &#39;Inclined_Crack_CT_full&#39;node_set = &#39;LOCAL_CRACKED_NODES&#39;elem_set = &#39;TEMPLATE_ELEMENTS&#39;do_all_frames = Truedo_nodal_strain = Falsedo_integration_sed = Falsedo_integration_stress = False# function to output the data for one nodal field valuedef OutputNodeValues(fw,dtp_label,frame,odb_key,node_set): print &gt;&gt; fw, dtp_label field_output = frame.fieldOutputs[odb_key] if node_set != None: field_output = field_output.getSubset(region=node_set) if len(field_output.values) == 0: return data_item = field_output.values[0].data if field_output.values[0].type == SCALAR: for field_value in field_output.values: data = float(field_value.data) if data != 0.0: nid = field_value.nodeLabel print &gt;&gt; fw, nid, float(data) elif len(data_item) == 3: for field_value in field_output.values: nid = field_value.nodeLabel data = field_value.data print &gt;&gt; fw,nid,float(data[0]),float(data[1]),float(data[2])# function to output the data for one element value at the nodesdef OutputElemAtNodeValues(fw,dtp_label,frame,odb_key,elems,elem_set): print &gt;&gt; fw, dtp_label field_output = frame.fieldOutputs[odb_key] if elem_set != None: field = field_output.getSubset(position=ELEMENT_NODAL,region=elem_set) else: field = field_output.getSubset(position=ELEMENT_NODAL) nc = 0 el_id = 0 for field_value in field.values: elem = field_value.elementLabel if el_id != elem: el_id = elem nc = 0 else: nc += 1 conn = elems[elem] data = field_value.data if len(data) &gt;= 6: print &gt;&gt; fw,elem,conn[nc],float(data[0]),float(data[1]),float(data[2]), \ float(data[3]),float(data[4]),float(data[5])# function to output the data for one element value at the gauss pointsdef OutputElemAtGpValues(fw,dtp_label,frame,odb_key,elem_set): print &gt;&gt; fw, dtp_label field_output = frame.fieldOutputs[odb_key] if elem_set != None: field = field_output.getSubset(position=INTEGRATION_POINT,region=elem_set) else: field = field_output.getSubset(position=INTEGRATION_POINT) el_id = 0 ip = 0 for field_value in field.values: elem = field_value.elementLabel if el_id != elem: el_id = elem ip = 0 else: ip += 1 data = field_value.data if field_value.type == SCALAR: print &gt;&gt; fw, elem,ip,float(data) elif len(data) &gt;= 6: print &gt;&gt; fw, elem,ip,float(data[0]),float(data[1]),float(data[2]), \ float(data[3]),float(data[4]),float(data[5])# function to find element connectivitydef GetElemConn(assembly): elems = {} for name, instance in assembly.instances.items(): num_elem = len(instance.elements) for element in instance.elements: elems[element.label] = element.connectivity return elems# function to check for a uniform temperaturedef SameTemp(frame,odb_key): temp = None for field_out in frame.fieldOutputs[odb_key].values: if temp == None: temp = field_out.data else: if field_out.data != temp: return False return True# function to process one framedef DoFrame(frame): # create a dictionary that maps the first word of a field output # to the full keys key_map = {} for key in frame.fieldOutputs.keys(): v = key.split() key_map[v[0]] = key# print &gt;&gt; fw, &#39;STEPTIME&#39;, frame.description print &gt;&gt; fw, &#39;TIME&#39;, frame.frameValue # output the data for specific types of data if key_map.has_key(&#39;U&#39;): OutputNodeValues(fw,&#39;DISPLACEMENT&#39;,frame,key_map[&#39;U&#39;],node_set) if key_map.has_key(&#39;NT11&#39;): if SameTemp(frame,key_map[&#39;NT11&#39;]): fT = frame.fieldOutputs[&#39;NT11&#39;].values[0].data print &gt;&gt; fw, &#39;TEMPERATURE&#39; print &gt;&gt; fw, &#39;ALL &#39;, fT else: OutputNodeValues(fw,&#39;TEMPERATURE&#39;,frame,key_map[&#39;NT11&#39;],node_set) if key_map.has_key(&#39;CPRESS&#39;): OutputNodeValues(fw,&#39;PRESSURE&#39;,frame,key_map[&#39;CPRESS&#39;],node_set) if key_map.has_key(&#39;CSHEAR1&#39;): OutputNodeValues(fw,&#39;SHEAR STRESS 1&#39;,frame,key_map[&#39;CSHEAR1&#39;],node_set) if key_map.has_key(&#39;CSHEAR2&#39;): OutputNodeValues(fw,&#39;SHEAR STRESS 2&#39;,frame,key_map[&#39;CSHEAR2&#39;],node_set) if do_nodal_strain: if key_map.has_key(&#39;E&#39;): OutputElemAtNodeValues(fw,&#39;STRAIN ELEMENT NODAL&#39;,frame,key_map[&#39;E&#39;],elems,elem_set) if key_map.has_key(&#39;EE&#39;): OutputElemAtNodeValues(fw,&#39;ELASTIC STRAIN ELEMENT NODAL&#39;,frame,key_map[&#39;EE&#39;],elems,elem_set) if key_map.has_key(&#39;THE&#39;): OutputElemAtNodeValues(fw,&#39;THERMAL STRAIN ELEMENT NODAL&#39;,frame,key_map[&#39;THE&#39;],elems,elem_set) if do_integration_sed: if key_map.has_key(&#39;SENER&#39;): OutputElemAtGpValues(fw,&#39;ELASTIC_STRAIN_ENERGY_DENSITY ELEMENT INTEGRATION POINTS&#39;, frame,key_map[&#39;SENER&#39;],elem_set) if key_map.has_key(&#39;PENER&#39;): OutputElemAtGpValues(fw,&#39;PLASTIC_STRAIN_ENERGY_DENSITY ELEMENT INTEGRATION POINTS&#39;, frame,key_map[&#39;PENER&#39;],elem_set) if do_integration_stress: if key_map.has_key(&#39;S&#39;): OutputElemAtGpValues(fw,&#39;STRESS ELEMENT INTEGRATION POINTS&#39;, frame,key_map[&#39;S&#39;],elem_set)def GetFrameLabel(frame): # extract the frame &#39;description&#39; and get the increment desc = odb.steps[step_name].frames[i].description di = desc.split() if di[0] == &#39;Increment&#39;: ilabel = di[1].split(&#39;:&#39;) return ilabel[0] else: return None# process the fileos.chdir(directory)odb = openOdb(&#39;%s.odb&#39; % (file_name,))fw = open(&#39;%s.dtp&#39; % (file_name,),&#39;w&#39;)if do_nodal_strain or do_integration_sed or do_integration_stress: elems = GetElemConn(odb.rootAssembly)if odb.rootAssembly.instances[&#39;PART-1-1&#39;].nodeSets.has_key(node_set): node_set = odb.rootAssembly.instances[&#39;PART-1-1&#39;].nodeSets[node_set]else: node_set = Noneif odb.rootAssembly.instances[&#39;PART-1-1&#39;].elementSets.has_key(elem_set): elem_set = odb.rootAssembly.instances[&#39;PART-1-1&#39;].elementSets[elem_set]else: elem_set = None# loop through the load stepsprint &gt;&gt; fw, &#39;NUM STEP&#39;, len(odb.steps.keys())for step_name in odb.steps.keys(): step = odb.steps[step_name] step_num = step.number print &gt;&gt; fw, &#39;LOADSTEP&#39;, step_num# print &gt;&gt; fw, &#39;TIMEPERIOD&#39;, step.timePeriod num_frames = len(odb.steps[step_name].frames) if num_frames == 1: start_frame = 0 end_frame = 0 else: start_frame = 0 end_frame = num_frames - 1 if not do_nodal_strain: start_frame += 1 if not do_all_frames: start_frame = num_frames - 1 end_frame = start_frame if end_frame &gt; start_frame:# substep_cnt = 1 for i in xrange(start_frame,end_frame+1): ilab = GetFrameLabel(odb.steps[step_name].frames[i]) if ilab != None: print &gt;&gt; fw,&#39;SUBSTEP&#39;,ilab else: print &gt;&gt; fw,&#39;SUBSTEP&#39;,i# print &gt;&gt; fw,&#39;SUBSTEP&#39;,substep_cnt DoFrame(odb.steps[step_name].frames[i])# substep_cnt += 1 else: DoFrame(odb.steps[step_name].frames[num_frames-1])fw.close() 可以看到,该脚本文件主要是在Abaqus后处理界面中基于Python语言读取Abaqus生成的脚本文件,并将裂纹尖端节点的位移等信息写入到dtp文件。Franc3D可以读取dtp文件中的信息,并基于这些信息,采用M积分法或其他数值方法计算裂纹尖端的应力强度因子等断裂参量。在该脚本文件中,directory代表数据文件所在的文件路径,file_name代表数据文件名称,node_set为Franc3D自动创建的节点集,存放了整个裂纹重划分区域的节点,elem_set为Franc3D自动创建的单元集,存放了裂纹前缘模版内的单元,如图8中的红色 区域所示。图8 裂纹前缘模版内的单元需要注意的是,如果直接运行该脚本文件,则生成的dpt文件中只会包含结果文件中最后一个增量步的信息。如果需要计算每个增量步下应力强度因子,以输出应力强度因子的时程曲线,则需要将逻辑变量do_all_frames的取值更改为True,即输出每一个增量步下的信息。待提取完成后,在Franc3D中重新打开Franc3D的项目文件(.fdb格式),并点击文件菜单中的读取结果选项,即可读取脚本文件生成的dpt文件,以计算裂纹前缘的应力强度因子,如图9所示。图9 dpt文件读取计算结果分析 不考虑裂纹面接触的情况通过取消裂纹面间定义的自接触,图10给出了不考虑裂纹面接触时,由Abaqus和Franc3D计算得到的裂纹前缘中部的I型应力强度因子和II型应力强度因子的变化曲线。图10 应力强度因子变化曲线(不考虑裂纹面接触)从图中可以看到,由Abaqus计算得到的I型应力强度因子KI和II型应力强度因子KII与Franc3D给出的计算结果完全吻合,这证明在不考虑裂纹面接触的情况下,基于Abaqus能够准确计算裂纹体的应力强度因子,这符合本文的预期。 考虑裂纹面接触的情况图11给出了当考虑裂纹面接触时,由Abaqus和Franc3D给出的I型和II型应力强度因子的变化曲线。图11 应力强度因子变化曲线(考虑裂纹面接触)从图中可以看到,在考虑裂纹面接触的情况下,I型和II型应力强度因子的峰值要明显偏低,这意味着在不考虑裂纹面接触的情况下,将给出偏于危险的预测结果。同时,由Abaqus给出的II型应力强度因子变化曲线与Franc3D给出的结果较为吻合,而I型应力强度因子的预测结果则存在较大偏差。为了分析产生偏差的原因,图12给出了在考虑裂纹面接触的情况下,由Abaqus计算得到的不同围线上I型应力强度因子的变化曲线。从图中可以看到,在计算初期,不同围线上的I型应力强度因子非常吻合,这是因为采用围线积分法计算应力强度因子时,应力强度因子的计算值并不依赖于积分路径。然而,当滚动载荷靠近裂纹时,不同围线上的I型应力强度因子出现了明显的发散,对比图11可以看到,这正好对应了Abaqus与Franc3D计算结果存在明显差异的区域。需要注意的是,图11是通过将不同围线上的计算结果取平均值获得的。图12 不同围线上的I型应力强度因子变化曲线(考虑裂纹面接触)理论上来说,当裂纹面受到压应力作用而产生闭合时,裂纹尖端的I型应力强度因子应该为零。从图12中可以看到,应力强度因子出现不收敛的起始位置恰好出现在裂纹面产生闭合时。因此,本文认为,Abaqus和Franc3D的计算结果存在差异的原因为:Abaqus在采用围线积分法计算应力强度因子时,并未考虑裂纹面间的接触力对应力强度因子的影响。当滚动载荷远离裂纹尖端时,裂纹面不存在接触力,因此Abaqus与Franc3D的计算结果吻合;而随着滚动载荷靠近裂纹尖端,裂纹面将逐渐接触,并产生接触应力,对于不同围线形成的封闭区域,作用在裂纹面上的接触力显然并不相同,Abaqus并未考虑裂纹面间接触力的影响,导致不同围线上的应力强度因子出现了不收敛的情况。事实上,在采用围线积分法求解应力强度因子时,通过在围线积分中引入考虑裂纹面接触应力的修正项,可以在考虑裂纹面接触的情况下准确计算裂纹尖端的应力强度因子,但似乎在笔者使用的Abaqus 2020版本以及更高的版本,目前仍未解决该问题。相比之下,由于Franc3D能够考虑裂纹面接触对应力强度因子的影响,因此能够给出更为精确的结果。需要注意的是,从图11中可以看到,当裂纹闭合时,由Franc3D给出的结果要略小于零,这可能是由于Franc3D默认的裂纹模版中,裂尖环形单元的层数较少,导致裂纹尖端区域的接触应力存在较大误差,导致应力强度因子的计算结果不准确,如果增加裂尖环形单元的层数,预计计算结果将更接近零。此外,在图11中,由Abaqus和Franc3D给出的II型应力强度因子曲线非常吻合,这主要是由于在此种载荷条件下,裂纹面间的摩擦剪切应力较小,因此裂纹面接触对II型应力强度因子的影响不明显。当裂纹面间摩擦剪切应力较大时,不同围线上的II型应力强度因子仍然会出现发散的现象。总结 本文以受滚动接触载荷作用的含斜裂纹平板为例,分别基于有限元软件Abaqus和断裂分析软件Franc3D求解了裂纹尖端的应力强度因子变化曲线。研究发现,当不考虑裂纹面接触时,Abaqus和Franc3D的计算结果吻合;当考虑裂纹面接触时,Abaqus和Franc3D的计算结果不吻合,Abaqus给出的不同围线上的应力强度因子出现了发散现象,这表明在存在裂纹面接触的情况下,现有版本的Abaqus(Abaqus 2020)不能准确计算裂纹尖端的应力强度因子。来源:FEM and FEA

未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习 福利任务 兑换礼品
下载APP
联系我们
帮助与反馈