Random Forest — Machine Learning/Classical Models
Machine_Learning/Classical_Models/Random_Forest · 1 input / 1 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.
Random Forest
Machine Learning / Classical Models
A fitted forest, evaluated at inference. Every tree is walked exactly as Decision Tree walks one – left when u[Feature] ≤ Threshold – and the leaves the T trees reach are combined:
- Average (regression) – y = (1/T)·Σ leafValue, the mean of the trees' predictions.
- Vote (classification) – each tree's leaf names a class, and y is the class the most trees chose.
The whole forest arrives as one stacked table: every tree's nodes laid
end to end in the same five columns a single tree uses, plus a Tree Roots
column naming the row each tree starts at. That is what
RandomForestClassifier.estimators_ gives you once flattened, and it
is the same table Gradient Boosted Trees and Isolation Forest
Score read.
Ports
- u – the feature column, [d,1]. Every entry of Feature Index must name a row of it.
- y – a [1,1] scalar: the averaged prediction, or the winning class label.
Parameters
The five node columns are all [N,1], one row per node across the whole
forest, in the layout scikit-learn's tree_ already uses.
- Feature Index – which entry of u the node tests, zero-based. Ignored at leaves.
- Threshold – the value it is tested against. Ignored at leaves.
- Left Child – the row taken when u[Feature] ≤ Threshold. A negative entry marks a leaf, scikit-learn's own convention.
- Right Child – the row taken otherwise. Ignored at leaves.
- Leaf Value – under Average, the value this leaf predicts; under Vote, the class index it votes for, a whole number from 0 to Class Count−1.
- Tree Roots – a [T,1] column: the row each tree's root sits at. Its height is the number of trees, and it is the only thing that tells one tree from the next in the stacked table.
- Aggregation – how the T leaves are combined. Each is a
different code path, so each is verified as a mode of its own:
- Average (regression) – the mean of the leaf values.
- Vote (classification) – the most-chosen class.
- Class Count – how many classes the vote ranges over, used by Vote only. Leaf values must then be whole numbers in [0, Class Count−1].
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Children must have a higher row number than their parent, counted in the stacked table's own numbering. Every fitted-tree exporter emits them that way, and requiring it means a malformed table is reported at the start of the run rather than sending the code generator into an endless walk.
Ties in the vote go to the lowest class index – the scan replaces its best only on a strictly greater count. That is Argmax Decision's rule, used here deliberately rather than re-decided, so a diagram holding both blocks cannot get two different answers to the same question.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Every target is exact, and the three HDL targets are genuinely synthesizable Q16.16: the body is comparisons, additions of baked constants, and one constant multiply by 1/T, which is folded at configuration load so that no divider ever appears.
The forest is emitted as nested if cascades, one per tree, for the reason Decision Tree documents: a literal walk needs a data-dependent index into five arrays per level, which in hardware is a multiplexer over every node. Under Vote the block emits one cascade per class, in which a leaf contributes 1 if it votes for that class and 0 if it does not – so the emitted size is proportional to nodes × classes, and a forest with many classes produces a correspondingly large core.
Simulink bridge
None, for exactly the reasons Decision Tree gives: the reference
would be the Statistics and Machine Learning Toolbox, which is not
installed on this machine, so a bridge could not be run against a parity
testbench even if one were written – and its predict blocks take a fitted
model object from a workspace variable, which no parameter mapping can
carry. That is why the forest is pasted here as plain columns. The bridge reports
the block rather than dropping it silently, and it has no parity
testbench, which is the documented consequence of
Support::None. Code export verification still covers it across all
ten languages, in both modes.
Notes
- Algebraic and stateless: the output depends only on the current sample.
- Piecewise constant in both modes, so no state space, for the reason Decision Tree gives: no A/B/C/D is true of a staircase, and model reduction correctly refuses the block.
- Averaging is what a forest is for. A single tree's output jumps at every split; T of them averaged jump by roughly 1/T as much, which is the whole reason a forest is smoother and more accurate than its members.
- The comparison is ≤ going left, matching scikit-learn. A sample landing exactly on a threshold goes left, and in fixed point that is not rare: an HDL port delivers Q16.16, so an exactly representable threshold is landed on rather than approached.
- Under Vote the HDL targets can disagree at a near-tie, the way Argmax Decision documents: they compare Q16.16 counts, and a sample whose features sit within one quantum of a split can send a tree the other way and move the winning class by a whole index. That is inherent to reducing a continuous quantity to a discrete decision in fixed point.
Code facts#
| Fact | Value |
|---|---|
| registered type | Machine_Learning/Classical_Models/Random_Forest |
| family | Machine_Learning/Classical_Models |
| solver environment class | ICoreBlock_0_Machine_Learning_1_Classical_Models_2_Random_Forest |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Classical_Models/Random_Forest/ICoreBlock_0_Machine_Learning_1_Classical_Models_2_Random_Forest.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Classical_Models/Random_Forest/ICoreBlock_0_Machine_Learning_1_Classical_Models_2_Random_Forest.h |
| default size on canvas | 140 × 85 px |
| ports at insert | 1 in, 1 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 | y |
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 |
|---|---|---|
Feature Index | [0; 1; -2; -2; -2; 1; 2; -2; -2; -2; 2; -2; -2] | — |
Threshold | [0.35; -0.2; 0; 0; 0; 0.1; 0.5; 0; 0; 0; -0.3; 0; 0] | — |
Left Child | [1; 2; -1; -1; -1; 6; 7; -1; -1; -1; 11; -1; -1] | — |
Right Child | [4; 3; -1; -1; -1; 9; 8; -1; -1; -1; 12; -1; -1] | — |
Leaf Value | [0; 0; -1.5; 0.4; 1.75; 0; 0; 0.6; -0.8; 1.2; 0; -0.5; 1.4] | — |
Tree Roots | [0; 5; 10] | — |
Aggregation | Average (regression)%~%Vote (classification)~~Average (re… | — |
Class Count | 3 | — |
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 verifiable Simulink equivalent. The reference would be the Statistics and Machine Learning Toolbox (TreeBagger / fitcensemble and their predict blocks), which is not installed on this machine, so a bridge could not be run against a parity testbench even if one were written. Those blocks also take a fitted model OBJECT from a workspace variable, which no parameter mapping can carry -- which is exactly why this block takes the forest as plain stacked columns instead
Catalog contract: src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B0every stimulus in the sample errored — cross-checks skipped
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).
Random Forest — Decision_Tree's walk over a STACKED table, accumulated across the trees Average: y = (1/T) * SUM over trees of leafValue(leaf reached) Vote: y = argmax over classes k of (how many trees reached a leaf labelled k)
⚠ ONE EMITTED SHAPE SERVES BOTH MODES: every leaf action is
acc = acc + <constant>. Average adds the leaf's value; Vote emits one cascade per class in which a leaf adds 1.0 when its label is that class and 0.0 when it is not. Adding a literal zero rather than pruning the branch is deliberate -- a pruned branch leaves an empty block, which is a syntax error in Python and a special case in the other nine, and the pruning rule would then be a tenth thing ten backends could disagree about. Adding zero is exact everywhere, Q16.16 included.⚠ COUNTS ARE COMPUTED ONE CLASS AT A TIME, never held in an array, so the vote needs exactly three live values: the running count, the best count, and the winning class. That is what makes it emittable in VHDL at all, where a process has
acc,acc2and the output field and nothing else (§4's HDL base). It costs K passes over the table in emitted size and buys a body that is the same shape in all ten languages.⚠ Every target is EXACT and the three HDLs are genuinely SYNTHESIZABLE: comparisons, adds of baked constants, and one constant multiply by 1/T at the end. The reciprocal is folded at config load rather than divided per sample -- Standard_Scaler's rule, and the reason no divider appears in the datapath.
Sample results#
No stimulus produced a sampled output in this rig — Feature index out of range at: ICore Blocks/Home/Random Forest. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.
Category unsampled · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60
Sample data: docs/generated/samples/Machine_Learning__Classical_Models__Random_Forest.json