Temporal Majority Vote — Machine Learning/Postprocessing
Machine_Learning/Postprocessing/Temporal_Majority_Vote · 1 input / 2 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.
Temporal Majority Vote
Machine Learning / Postprocessing
Reports the most common class among the last N indices – the current sample plus the N−1 before it. The index passes through unchanged in value; what changes is that a single odd sample no longer reaches the output.
Put it after Argmax Decision, whose index it takes and whose index it returns. A lone disagreeing sample is outvoted by the rest of the window, which is the SMOOTHING form of a decision stabiliser. If you would rather a lone sample were unable to move the output at all – and reset the evidence when it appears – use Debounced Decision, the latching form of the same idea.
Ties go to the lowest class index, which is Argmax Decision's rule. Over a short window ties are common rather than exceptional, so this decides a great many samples and not just a few.
Ports
- k – the raw class index, a scalar [1,1]. Class i is the one whose
window
[i−0.5, i+0.5)the index falls in, after Index Base has been subtracted. - cls – a scalar [1,1]: the winning class, reported in the same base as the input.
- votes – a scalar [1,1]: how many of the N window slots voted for the winner. It is at most N, and it is 0 only when no slot in the window holds a valid class at all – in which case cls falls back to Initial Class. Read it as a confidence: a winner with 2 votes of 5 is a much weaker answer than one with 5 of 5.
Parameters
- Number Of Classes – K, a whole number ≥ 1. The classes are the K windows starting at the index base; anything outside them is not a class and votes for nothing.
- Index Base – what the first class is called:
- Zero-based (PyTorch, numpy) – the first class is 0. The default.
- One-based (MATLAB) – the first class is 1.
- Window Length – N, a whole number ≥ 1: how many samples vote, including the current one. At N = 1 the block is a pass-through, which is the documented degenerate case. An odd N is usually what you want with two classes, since an even one can only tie.
- Initial Class – the class position the window is seeded with, counted from 0 whatever the base. It must be less than K. It is what the block reports before any real evidence has arrived, and what it falls back to if the window ever holds no valid class.
- 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. K, the base, N and the seed are baked into the body at export time – each of them changes which comparisons are emitted, so none could be a tunable parameter. The window and the tally are fully unrolled, at K×N comparisons.
The three HDL targets are genuine synthesizable Q16.16: comparisons and increments only, with no division and no transcendental. The window is a shift register of N−1 registers.
Simulink bridge
None. Simulink has no modal filter over a decision stream: Median
Filter belongs to the DSP System Toolbox and answers a different question (the
median of a numeric window is not the mode of a categorical one – it interpolates
between classes, which for class labels is meaningless). There is therefore no parameter
set this block could cross through, and no parity testbench, which is the
documented consequence of Support::None rather than a gap. Code export
verification still covers it across all ten languages.
Notes
- Stateful, and inherently discrete – the window advances once per sample,
so the block declares
setDiscreteOnlyBlockand takes its rate from its own Sampling Time (s). It carries no state space and model reduction correctly refuses it. - An index outside every class window votes for nothing, but it still occupies a slot and still ages the window along. A burst of invalid indices therefore weakens the vote rather than freezing it, which is what you want from a sensor dropout.
- The window is seeded with Initial Class, so the first N samples are a blend of the seed and real evidence rather than undefined.
- At a window boundary the HDL targets can decide differently. They compare Q16.16 values, one quantum of which is about 1.5e-5, where the in-app run compares doubles – so an index within that distance of a ±0.5 boundary can fall in the neighbouring class. This is inherent to reducing a continuous quantity to a discrete class in fixed point, and it is the same caveat Argmax Decision and Embedding Lookup carry. Feed this block a genuinely integral index and no sample is ever near a boundary.
Code facts#
| Fact | Value |
|---|---|
| registered type | Machine_Learning/Postprocessing/Temporal_Majority_Vote |
| family | Machine_Learning/Postprocessing |
| solver environment class | ICoreBlock_0_Machine_Learning_1_Postprocessing_2_Temporal_Majority_Vote |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Postprocessing/Temporal_Majority_Vote/ICoreBlock_0_Machine_Learning_1_Postprocessing_2_Temporal_Majority_Vote.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Postprocessing/Temporal_Majority_Vote/ICoreBlock_0_Machine_Learning_1_Postprocessing_2_Temporal_Majority_Vote.h |
| default size on canvas | 150 × 80 px |
| ports at insert | 1 in, 2 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 | k |
| 2 | out | ICoreDouble | cls |
| 3 | out | ICoreDouble | votes |
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 |
|---|---|---|
Number Of Classes | 4 | — |
Index Base | Zero-based (PyTorch, numpy)%~%One-based (MATLAB)~~Zero-ba… | — |
Window Length | 5 | — |
Initial Class | 0 | — |
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): no Simulink equivalent: nothing in the base library takes the MODE of a window, and the DSP System Toolbox's Median Filter answers a different question -- the median of a numeric window interpolates between neighbouring values, which for class labels produces a label nobody voted for. There is therefore no parameter set to map onto and no reference to run a parity testbench against
Catalog contract: src/ICoreSDK/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).
Temporal Majority Vote — the modal class of the last N decisions Class i is observed when (k - base) - i lies in [-0.5, +0.5) -- One_Hot_Encoder's rule verbatim, so no integer conversion appears in any of the ten backends.
The vote, in the order every backend spells it:
v = the current sample's slot value: position + 1, or 0 for "no class" tally_i = how many of {v} + the N-1 stored slots equal i + 1 best = the FIRST i with a strictly greater tally (ties -> LOWEST index, always) shift = the slots move down one and v takes the front
Two encodings carry the whole design and both are deliberate:
- Slots hold position + 1 so that ZERO can mean "no vote". An index outside every class
window has to be able to occupy a slot without voting, and a zeroed HDL reset then reads as an empty window rather than as N votes for class 0.
- The scan replaces its best only on a STRICTLY greater tally, scanning upwards, which is
Argmax_Decision's tie rule. Over a short window ties are the common case rather than the edge case, so this is what the block's answer mostly rests on.
The window is seeded with the Initial Class rather than with emptiness, so the output starts somewhere defined and decays away over the first N samples.
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 … 3 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 1 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | 0 … 1 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Machine_Learning__Postprocessing__Temporal_Majority_Vote.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).