レインフローカウントとは?

レインフローカウントとは、鋼材の疲労評価のための振幅カウント法の一つです。 疲労評価は、実験などで定められた各振幅における許容繰り返し回数(疲労曲線)に対して、応答繰り返し回数を比較することによって行われます。 実験では決められたスケジュールで載荷されるため、振幅の回数はわかりやすくカウントできますが、地震に対するランダム振動では内部ループの繰り返しなども発生するため、単純なカウントが難しくなります。 そこで、内部小ループがあっても大振幅をカウントするための方法としてレインフローカウントという方法があります。 この方法は「鋼構造物の疲労設計指針・同解説」、「鋼構造制振設計指針」などに説明がありますので、詳細な解説は割愛します。 文献にはソースコードが載っているものもありますが、波形がすべてそろった後にカウントすることが前提のものも多いため、改めてリアルタイムでカウントするものを作ってみました。

レインフローカウントの概念

一般的なレインフロー法の大まかなフローは以下の通りです。リアルタイムでカウントする場合でも、この流れ自体は同様です。
  • 順載荷中は、毎ステップ振幅を更新しながらカウントを進める。
  • 除荷が発生した場合、頂点を記録する。
  • 頂点が4点以上になり、小ループ除去条件を満たす場合に小ループの除去を行う。
3つ目の小ループの除去が、レインフローカウントの最大のポイントになります。 この「小ループの除去」が再帰的に行われる(小ループを除去してできた大ループがさらにより大きな振幅にとっての小ループになる)ため、リアルタイムでカウントしようとすると未来のステップで一度カウントした振幅を減じる必要がでてきてしまいます。そのため、初めから波形がすべてそろった状態で小ループの除去を行い振幅を計算するほうが直観的には理解しやすいアルゴリズムになります。 当社の開発プログラムRESP-F3Tでもレインフローカウントを行うことができます。 RESP-F3Tのマニュアルに記載の例題を参考のため下記に抜粋します。
ステップを追って、より詳細に説明します。

1. 順載荷中は、毎ステップ振幅を更新しながらカウントを進めます。

振幅1: 1回

振幅1: 1回振幅2: 1回

2. 除荷が発生した場合、頂点を記録します。

振幅2: 1回

振幅1: 1回

振幅2: 1回

振幅1: 1回振幅4: 1回

振幅1: 1回

3. 頂点が4点以上になり、小ループ除去条件を満たす場合に小ループの除去を行います。

振幅2: 1回

振幅4: 1回

振幅5: 1回

振幅1: 1回

振幅2: 1回

振幅4: 1回

振幅5: 1回(小ループの除去)

振幅1: 1回(振幅の更新+小ループ除去)

振幅1: 2(除去振幅はカウント2)

振幅6: 1回(大振幅形成)

振幅3: 1回

シミュレーター

シミュレーターは以下から実際に触っていただけます。 ソースコードは、記事末尾に載せます。 https://www4.kke.co.jp/resp/tool/rainflow/Main.html
使用方法は以下の通りです。
  • 「ADD」ボタンで現在の値に+1したプロットを追加します。
  • 「SUB」ボタンで現在の値に-1したプロットを追加します。
  • 「RESET」ボタンでプロットをクリアします。
  • 画面中央にはグラフが表示され、画面右側には現在のヒストグラム(振幅カウント)が表示されます。

まとめ

レインフローカウントの理解を深めるためのシミュレーターをつくりました。 作ってみていままで理解が不十分なところが理解できましたので、興味のある方はぜひ触ってみていただければと思います。

ソースコード(長いです)

Elmで実装しました。マイナー言語で申し訳ありませんが、こういったものを作るには非常に便利なプログラミング言語で個人的にはいま最も熱い言語です。
module Main exposing (..)


import Html exposing (..)
import Html.Attributes as Attr
import Html.Events exposing (..)

import Browser
import Browser.Navigation as Nav
import Url


import Dict exposing (Dict)
import Dict exposing (update)

import Chart as C
import Chart.Attributes as CA

{-| 
プロットの点
-}
type alias Point = 
    { x : Int
    , y : Int 
    }

{-| 
ヒストグラムに対するアクション
追加 / 削除
-}
type HistogramAction 
    = Add
    | Remove

{-| 
ヒストグラムの1レコード
-}
type alias Histogram = 
    { amplitude : Int
    , action : HistogramAction 
    }


{-| 
頂点スタック
除荷のたびに蓄積する
-}
type alias Stack = List Point



{-| 
モデル
-}
type alias Model = 
    { points : List Point
    , resolution : Int 
    , histogram : List Histogram
    , stack : Stack 
    }

originPoint : Point
originPoint = { x = 0, y = 0 }

defaultModel : Model 
defaultModel = 
    { points = [ originPoint ] 
    , resolution = 1 
    , histogram = []
    , stack = [ originPoint ]
    }


type Msg 
    = None
    | UpdateResolution Int  -- 分解能を更新(現在は未実装)
    | AddPlus               -- +1として次のプロットを追加
    | AddMinus              -- -1として次のプロットを追加
    | Reset                 -- プロットをリセットする


main : Program () Model Msg
main =
  Browser.application
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    , onUrlChange = \_ -> None
    , onUrlRequest = \_ -> None 
    }

init : () -> Url.Url -> Nav.Key -> (Model, Cmd Msg)
init () url key =
    (defaultModel, Cmd.none) 


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none

{-| 
+1としてプロット追加
-}
addPlus : List Point -> List Point
addPlus points = 
    case points of 
        hd :: _ -> { hd | y = (hd.y + 1), x = (hd.x + 1) } :: points 
        [] -> [ originPoint ]


{-| 
-1としてプロット追加
-}
addMinus : List Point -> List Point
addMinus points = 
    case points of 
        hd :: _ -> { hd | y = (hd.y - 1), x = (hd.x + 1) } :: points 
        [] -> [ originPoint ]

{-| 
順載荷時に、直前の振幅を更新する
-}
updateAmplitude : List Histogram -> Int -> List Histogram
updateAmplitude histogram value = 
    case histogram of
        p1 :: tl -> { amplitude = p1.amplitude + value, action = Add } :: tl
        [] -> [ { amplitude = value, action = Add } ]

{-| 
ヒストグラムにカウントを追加
-}
addCount :Int ->  List Histogram -> List Histogram
addCount value histogram = 
    { amplitude = value, action = Add } :: histogram

{-| 
ヒストグラムからカウントを削除
小振幅除去時
-}
removeCount :Int ->  List Histogram -> List Histogram
removeCount value histogram =
    { amplitude = value, action = Remove } :: histogram


{-| 
ヒストグラム追加
小振幅除去処理含む
-}
addHistogram : Stack -> List Histogram -> (Stack, List Histogram)
addHistogram stack histogram = 
    case stack of
        p4 :: p3 :: p2 :: p1 :: tl -> 
            let
                y1 = p1.y 
                y2 = p2.y 
                y3 = p3.y 
                y4 = p4.y

                d12 = abs (y1 - y2)
                d23 = abs (y2 - y3)
                d34 = abs (y3 - y4)

            in
            if d23 <= d12 && d23 <= d34 then
                let
                    newHistogram = 
                        addCount (abs (y4 - y1)) histogram
                        |> removeCount d34 
                        |> removeCount d12 
                        |> addCount d23     -- 消去する戻り振幅は全サイクルでカウント2
                    newStack = 
                        p4 :: p1 :: tl
                in
                addHistogram newStack newHistogram
                
        
            else
                (stack, histogram)

        _ -> (stack, histogram)



{-| 
レインフローカウントメインロジック
-}
rainflowCount : Model -> (List Point -> List Point) -> Model
rainflowCount model f = 
    let
        -- アクションに応じてポイントを追加
        newPoints = f model.points 
    in
    case newPoints of 
        -- 3点以上ある場合
        -- 3点以上ないと除荷の判断ができないため
        last :: p2 :: p1 :: _ -> 
            let
                lastY = last.y

                y2 = p2.y

                y1 = p1.y

                inc2 = lastY - y2
                inc1 = y2 - y1

                isUnloading = inc1 * inc2 < 0

                (newStack, newHistogram) = 
                    if isUnloading 
                    then 
                        -- 除荷の場合、スタック追加
                        -- ヒストグラムも追加
                        let 
                            (s, h) = 
                                addHistogram 
                                    (model.stack) 
                                    model.histogram
                        in
                        (last :: s, { amplitude = abs(inc2), action = Add } :: h)
                    else
                        -- 除荷でない場合
                        let
                            updatedHistogram = updateAmplitude model.histogram <| abs(inc2) 
                        in

                        -- 直近のスタックを書き換え
                        case model.stack of
                            prevStack :: tlStack -> 
                                if prevStack == p1 then
                                    (last :: model.stack, updatedHistogram)
                                else
                                    (last :: tlStack, updatedHistogram)
                            [] -> ([ last ], updatedHistogram)
            in
            { model | points = newPoints
                    , stack = newStack
                    , histogram = newHistogram }

        last :: tl ->
            { model | points = newPoints 
                    , stack  = last :: tl
                    , histogram = updateAmplitude model.histogram <| abs(last.y) 
            }
        _ -> 
            { model | points = newPoints }
    

{-| 
更新メッセージループ
メッセージが来たらここで分岐される
-}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of 
        Reset    -> ({ model | points = [ originPoint ], stack = [ originPoint ], histogram = [] }, Cmd.none) 
        AddPlus  -> (rainflowCount model addPlus, Cmd.none)
        AddMinus -> (rainflowCount model addMinus, Cmd.none)
        _        -> (model, Cmd.none) 



{-| 
グラフのView
-}
chart : Model -> Html msg
chart model = 
    let
        data = 
            model.points 
            |> List.reverse 
            |> List.map (\point -> { x = toFloat point.x, y = toFloat point.y }) 
        stackData = 
            model.stack
            |> List.reverse 
            |> List.map (\point -> { x = toFloat point.x, y = toFloat point.y }) 

    in
    
    C.chart
        [ CA.height 500
        , CA.width 1000
        ]
        [ C.xLabels []
        , C.yLabels [ CA.withGrid ]
        , C.series .x
            [ C.interpolated .y [  ] [ CA.circle ]
            ]
            data
        , C.series .x
            [ C.interpolated .y [  ] [ CA.circle ]
            ]
            stackData
        ]

{-| 
Viewのメイン関数
-}
view : Model -> Browser.Document Msg
view model = 
    let 
        _ = model |> Debug.log "model"

        buttons = 
            Html.p
                [ Attr.class "uk-margin" 
                ]
                [ Html.button 
                    [ Attr.class "uk-button uk-button-default" 
                    , Html.Events.onClick Reset
                    ]
                    [ Html.text "Reset" ] 
                , Html.button 
                    [ Attr.class "uk-button uk-button-default" 
                    , Html.Events.onClick AddPlus
                    ]
                    [ Html.text "Add" ] 
                , Html.button 
                    [ Attr.class "uk-button uk-button-default" 
                    , Html.Events.onClick AddMinus
                    ]
                    [ Html.text "Sub" ] 


                ]

        table = 
            Html.table 
                [ Attr.class "uk-table uk-table-striped" ]
                [ Html.thead
                    []
                    [ Html.tr 
                        []
                        [ Html.th 
                            []
                            [ Html.text "振幅" ]
                        , Html.th 
                            []
                            [ Html.text "回数" ]
                        ]
                    ]
                , Html.tbody 
                    []
                    (
                        model.histogram
                        |> List.reverse
                        |> List.foldl (\hist dict ->
                            let

                                updateDict value = 
                                    case value of
                                        Just count -> 
                                            -- すでにカウントされている振幅の場合
                                            case hist.action of
                                                Add    -> Just (count + 1)
                                                Remove -> Just (count - 1)
                                        Nothing -> 
                                            -- はじめての振幅の場合
                                            case hist.action of
                                                Add    -> Just ( 1)
                                                Remove -> Just (-1)
                            in
                            Dict.update hist.amplitude updateDict dict
                        ) Dict.empty 
                        |> Dict.toList
                        |> List.filter (\(_, count) -> 0 < count)
                        |> List.sortBy (\(amp, _) -> amp) 
                        |> List.map (\(amp, count) -> 
                            Html.tr
                                []
                                [ Html.td 
                                    []
                                    [ Html.text <| String.fromInt amp ] 
                                , Html.td 
                                    []
                                    [ Html.text <| String.fromInt count ] 
                                ]
                            
                        )
                    )
                ]

        histogramList = 
            Html.div
                []
                [ Html.h3 
                    []
                    [ Html.text "Histogram" ]
                , table
                ]


        body =
            Html.div 
                [ Attr.class "uk-text-center"
                , Attr.attribute "uk-grid" ""  
                ]
                [ Html.div
                    [ Attr.class "uk-width-3-4" ]
                    [ Html.div 
                        [ Attr.class "uk-section uk-section-small"
                        ]
                        [ h1 [ Attr.style "font-family" "Bad Script" ] [ text "Rainflow-Count Simulator" ]
                        , Html.div
                            [ Attr.class "uk-container" ]
                            [ buttons
                            ]
                              
                        ]
                    , Html.div 
                        [ Attr.class "uk-section uk-section-small"
                        ]
                        [ Html.div
                            [ Attr.class "uk-container" 
                            , Attr.style "height" "200px" 
                            ]
                            [ chart model ]
                        ]
                    ] 
                , Html.div
                    [ Attr.class "uk-width-1-4" ]
                    [ histogramList ]  

                ]
    in
    { title = ""
    , body = [ body ] 
    }

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です