レインフローカウントとは?
レインフローカウントとは、鋼材の疲労評価のための振幅カウント法の一つです。 疲労評価は、実験などで定められた各振幅における許容繰り返し回数(疲労曲線)に対して、応答繰り返し回数を比較することによって行われます。 実験では決められたスケジュールで載荷されるため、振幅の回数はわかりやすくカウントできますが、地震に対するランダム振動では内部ループの繰り返しなども発生するため、単純なカウントが難しくなります。 そこで、内部小ループがあっても大振幅をカウントするための方法としてレインフローカウントという方法があります。 この方法は「鋼構造物の疲労設計指針・同解説」、「鋼構造制振設計指針」などに説明がありますので、詳細な解説は割愛します。 文献にはソースコードが載っているものもありますが、波形がすべてそろった後にカウントすることが前提のものも多いため、改めてリアルタイムでカウントするものを作ってみました。レインフローカウントの概念
一般的なレインフロー法の大まかなフローは以下の通りです。リアルタイムでカウントする場合でも、この流れ自体は同様です。- 順載荷中は、毎ステップ振幅を更新しながらカウントを進める。
- 除荷が発生した場合、頂点を記録する。
- 頂点が4点以上になり、小ループ除去条件を満たす場合に小ループの除去を行う。
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 ]
}