Generated reference › Rainflow Counting — Control Systems/Vibration
kind: generated#block#control-systems-vibration

Rainflow Counting — Control Systems/Vibration

Control_Systems/Vibration/Rainflow_Counting · 1 input / 4 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Description#

The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.

Rainflow Counting

Control Systems / Vibration

Counts fatigue cycles by the three-point rainflow rule (ASTM E1049) as the signal arrives. The signal is reduced to its turning points; the last three on the stack are tested by

|s2 − s1| ≤ |s3 − s2|

and while that holds the inner pair leaves the stack as a counted cycle – a half cycle when the pair sits at the oldest end of the stack, a full one anywhere else. Each counted cycle publishes its range (the difference between the pair) and its mean, and the count accumulates. What the rule leaves behind is the residual, and its height is the fourth output.

Ports

  • u – the load or stress history, scalar. Only its turning points matter: a sample that continues the current direction is not a reversal and changes nothing, and a repeat of the previous value is ignored.
  • cycles – scalar, the cumulative count. A full cycle adds 1 and a half cycle 0.5, so the output is a non-decreasing staircase in steps of a half.
  • range – scalar, the range of the most recently counted cycle, held until the next one. Zero until the first cycle closes.
  • mean – scalar, that cycle's mean, held the same way.
  • depth – scalar, how many turning points the residual currently holds. It is the block's state made visible: it rises when a reversal is stacked and falls by two on a full cycle and by one on a half.

Parameters

  • Maximum Reversals – how tall the residual stack may grow, a whole number from 4 to 64. At the limit the oldest pair is counted as a half cycle and dropped, which is the only way a bounded machine can accept a signal whose amplitude keeps growing. Measured on white noise the residual reaches 10 at 200 samples and 15 at 5000, so the default of 32 is not normally reached. It is structural: every generated core carries an array this long and a loop this many passes.
  • Count Half Cycles – whether a half cycle contributes to cycles:
    • on – it adds 0.5, which is what ASTM E1049 and MATLAB's rainflow do. The default.
    • off – only full cycles are counted. The range and mean ports still publish a half cycle when one closes; it is the count that ignores it.
  • Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.

Code export

All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.

Maximum Reversals is baked in at export time – it sizes the array and bounds the pop loop – and so is Count Half Cycles, which decides whether the half-cycle term is emitted at all. Re-export after changing either. Nothing else is tunable on a generated core.

The three HDL targets are simulation-only. The step is a small program with a loop, a stack and a data-dependent number of iterations, which is a sequencer rather than a datapath; the bodies therefore carry the stack and the arithmetic in real and quantize only at the ports. ⚠ The VHDL body is a function, declared beside the state and called from the process: a VHDL process body is given three scratch variables and this step needs ten, and a function has a declarative region of its own. The other two declare their scratch at module scope, which is the same trick under a different roof.

⚠ The comparison is a near-tie decision, and a port quantum can flip it. An HDL port delivers the input already rounded to Q16.16, so two ranges that differ by less than one quantum can compare the other way round in a generated core than in the simulation – and a flipped pop changes the whole residual from there on, which is a large disagreement rather than a small one. It is rare (the band is about 1.5e−5 wide against a stimulus spread over 1) and it is not a codegen fault: re-run the comparison unchanged before diagnosing anything, exactly as this library's export-verification guide advises.

Simulink bridge

None (Support::None). rainflow is a Signal Processing Toolbox function, that toolbox ships no Simulink library, and a sweep of the DSP and Simulink libraries installed here matches nothing named for rainflow – so there is no path a diagram could name. The bridge reports this block rather than dropping it silently, and it therefore has no parity testbench; code export verification covers it across all ten languages.

Notes

  • Stateful, and discrete by nature (setDiscreteOnlyBlock(true)). The state is the reversal stack, its height, the previous sample, the direction of the last confirmed move, and the three published numbers.
  • ⚠ The residual is never flushed, and that is the one place this differs from rainflow. The function knows where the record ends and counts every leftover pair as a half cycle there; a stream has no end. So at any moment cycles is the count of what has actually closed, and depth says how much is still open. Run the same history through rainflow and it will report those extra half cycles as well.
  • Everything else is rainflow, and it is measured. The same machine, given the last sample and a residual flush, reproduces R2026a's table exactly – every row, in order, to the last bit – over nine records including the ASTM demo signal, white noise and a random walk.
  • The first sample is always a turning point, as it is for rainflow, and the last one never is: a stream cannot know which sample is last.
  • A repeat of the previous value is not a reversal. Among a run of equal samples the first survives and the rest change nothing, which is what rainflow does with its own reversals.
  • ⚠ At the depth limit the oldest pair is counted and dropped. A signal whose amplitude keeps growing closes nothing, so its residual would grow without bound; a bounded machine has to give something up, and giving up the oldest is the standard truncation. Raise Maximum Reversals if depth is sitting at the limit.
  • Scalar only. One load history; wire one block per channel.
  • No state space. A turning point is not a linear functional of the input, so there is no A/B/C/D pair and model reduction correctly declines to merge the block.

Code facts#

FactValue
registered typeControl_Systems/Vibration/Rainflow_Counting
familyControl_Systems/Vibration
solver environment classICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Rainflow_Counting/ICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Rainflow_Counting/ICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting.h
default size on canvas150 × 96 px
ports at insert1 in, 4 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleu
2outICoreDoublecycles
3outICoreDoublerange
4outICoreDoublemean
5outICoreDoubledepth

Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.

Configuration variables#

Config variableDefaultSimulink parameter
Maximum Reversals32—
Count Half Cycleson%~%off~~on—

Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.

supportSupport::None
Simulink path—
port-count rulePortsParam::None
SampleTime parameteryes

Caveat (shown to the user): rainflow is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library, and a find_system sweep of the DSP and Simulink libraries installed here matches nothing named for rainflow -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses. The arithmetic IS that function's, measured row for row against it; what a stream cannot reproduce is the residual flush at the end of a record, because a stream has no end

Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

Description vs code#

The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.

The verdict above is tools/docs/check_block_descriptions.py (P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.

File banner (developer view)#

The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).

Rainflow Counting -- the three-point (ASTM E1049) fatigue cycle count, as a stream The signal is reduced to its turning points. The last three on the stack are tested by

|s2 - s1| <= |s3 - s2|

and while it holds the inner pair leaves the stack as a counted cycle -- a HALF cycle when the pair is at the oldest end, a FULL one anywhere else. What is left when the rule stops holding is the residual.

⚠ IT IS MATLAB'S rainflow, AND THAT IS MEASURED. The same machine, run to the end of a record and then handed the last sample and a residual flush, reproduces R2026a's rainflow table EXACTLY -- every row, in the order the function emits them, to the last bit -- over nine records: the ASTM demo signal [-2 1 -3 5 -1 3 -4 4 -2], a run with interior non-turning points, a run with repeated values, 60-, 100- and 200-sample white noise, a 150-sample random walk, [1 2 3 1] and [5 -5 5 -5 5]. Worst disagreement over every row of every case: 0. The two differ in one thing only, and it is the one a stream cannot have: the residual is never flushed, because there is no last sample to flush it at.

⚠ THE POPS NEVER MUTATE THE STACK, AND THAT IS WHAT MAKES THE BLOCK EXPORTABLE AT ALL. Written the obvious way -- pop, shift the array, test again -- a step reads back the array it has just written, which is exactly what a clocked hardware process cannot do (ADDING_NEW_BLOCKS.md §4, the registered-write rule). It does not have to: a pop only ever removes the two entries directly below the top, or the single oldest entry, so the stack after any number of pops is

s[o+j .. o+m-1-2k] followed by the new turning point

-- a SHIFT of the array it started from. The step counts k full pops and at most one head pop first, reading only, and then writes the array once. Every read in that write loop is at an index at or AHEAD of the one being written (o + j + i >= i), so the same loop is correct in place, in the same forward order, in all eleven implementations.

⚠ AND THE FLAT FORM WAS DIFFED AGAINST THE OBVIOUS ONE BEFORE ANY C++ WAS WRITTEN: both were prototyped in Python and run side by side over seven records at three stack depths and both half-cycle settings -- 42 runs, every sample, all four outputs. Worst disagreement: 0. 📌 A reformulation that exists to satisfy a hardware rule is a second chance to get the arithmetic wrong, and the only cheap way to know is to run it beside the first one.

Sample results#

Rainflow Counting — Step: 0 -> 1 at t = 1 sRainflow Counting — Step: 0 -> 1 at t = 1 s00.51012345t (s)in ICoreDouble-Out-0out ICoreDouble-Out-0out ICoreDouble-Out-1out ICoreDouble-Out-2

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)0 … 0
rampRamp: slope 1 from t = 00 … 0
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias0 … 1.5
tableRepeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample0 … 6.5

Plotted: step — Step: 0 -> 1 at t = 1 s

Category dynamic · sample time 0.1 · 60 steps · commit f97a7c85c66ba74c889b67c34aa081d38e613c66 · produced by docsSample --out <folder> --blocks Rainflow_Counting --steps 60 · data docs/generated/samples/Control_Systems__Vibration__Rainflow_Counting.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).