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
rainflowdo. 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.
- on – it adds 0.5, which is what ASTM E1049 and MATLAB's
- 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 throughrainflowand 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
rainflowdoes 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#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Vibration/Rainflow_Counting |
| family | Control_Systems/Vibration |
| solver environment class | ICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Rainflow_Counting/ICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Rainflow_Counting/ICoreBlock_0_Control_Systems_1_Vibration_2_Rainflow_Counting.h |
| default size on canvas | 150 × 96 px |
| ports at insert | 1 in, 4 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | u |
| 2 | out | ICoreDouble | cycles |
| 3 | out | ICoreDouble | range |
| 4 | out | ICoreDouble | mean |
| 5 | out | ICoreDouble | depth |
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 variable | Default | Simulink parameter |
|---|---|---|
Maximum Reversals | 32 | — |
Count Half Cycles | on%~%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.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
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'srainflowtable 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#
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0 |
ramp | Ramp: slope 1 from t = 0 | 0 … 0 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 1.5 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | 0 … 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).