Generated reference › Decision Tree — Machine Learning/Classical Models
kind: generated#block#machine-learning-classical-models

Decision Tree — Machine Learning/Classical Models

Machine_Learning/Classical_Models/Decision_Tree · 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.

Decision Tree

Machine Learning / Classical Models

A fitted decision tree, evaluated at inference. The tree arrives as five flat columns – exactly the arrays scikit-learn's tree_ exposes – and the block walks it from the root: at each internal node it compares one feature against that node's threshold, going left when u[Feature] ≤ Threshold and right otherwise, until it reaches a leaf and outputs that leaf's value.

It is the workhorse of interpretable machine learning, and unlike almost everything else in this family it involves no transcendental and no arithmetic at all – only comparisons – so it is exact on every target, including hardware.

Ports

  • u – the feature column, [d,1]. Every entry of Feature Index must name a row of it; d itself comes from whatever is connected, and is checked against the largest index used.
  • Outputy, a [1,1] scalar carrying the value of the leaf reached. For a regression tree that is the predicted value; for a classifier, the predicted class label as you stored it in Leaf Value.

Parameters

All five are columns of the same height N, one row per node, with the root at row 0. This is the layout scikit-learn already gives you: tree_.feature, tree_.threshold, tree_.children_left, tree_.children_right and tree_.value.

  • Feature Index – which entry of u the node tests, zero-based. Ignored at leaves, where scikit-learn stores −2.
  • Threshold – the value it is tested against. Ignored at leaves.
  • Left Child – the row taken when u[Feature] ≤ Threshold. A negative entry marks the node as a leaf, which is scikit-learn's own convention (children_left is −1 there).
  • Right Child – the row taken otherwise. Ignored at leaves.
  • Leaf Value – the output produced when the walk stops at this node. Ignored at internal nodes.
  • 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. 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.

Code export

All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Every target is exact rather than merely within tolerance, and the three HDL targets are genuinely synthesizable: the block only compares and assigns.

The tree is emitted as a nested if cascade rather than as a loop over the five arrays. That is what keeps it synthesizable – a literal walk would need a data-dependent index into five arrays on every iteration, which in hardware is a multiplexer over every node, once per level. As a cascade each node appears exactly once and no index arithmetic exists at all, so the emitted code is proportional to the number of nodes rather than to nodes × depth.

Simulink bridge

None. The reference would be the Statistics and Machine Learning Toolbox and its fitctree / 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: that is the same obstacle that makes this whole family Support::None, and it is the reason the tree is pasted here as plain columns instead. The bridge reports the block rather than dropping it silently, and it has 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

  • Algebraic and stateless: the output depends only on the current sample.
  • Piecewise constant. The output changes only when a sample crosses a split, so it is a staircase, not a curve – and its derivative is zero almost everywhere. That is worth remembering before putting one inside a feedback loop.
  • No state space, deliberately, for exactly that reason: no A/B/C/D is true of a piecewise-constant map. Model reduction reports the block as unmergeable, which is the honest answer.
  • The comparison is going left, matching scikit-learn. A sample landing exactly on a threshold therefore goes left, and in fixed point that is not a rare case – an HDL port delivers Q16.16, so a threshold that is exactly representable is landed on rather than approached.
  • Random Forest, Gradient Boosted Trees and Isolation Forest Score are this same walk with a different accumulator over a stacked table.

Code facts#

FactValue
registered typeMachine_Learning/Classical_Models/Decision_Tree
familyMachine_Learning/Classical_Models
solver environment classICoreBlock_0_Machine_Learning_1_Classical_Models_2_Decision_Tree
sourcesrc/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Classical_Models/Decision_Tree/ICoreBlock_0_Machine_Learning_1_Classical_Models_2_Decision_Tree.cpp
headersrc/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Classical_Models/Decision_Tree/ICoreBlock_0_Machine_Learning_1_Classical_Models_2_Decision_Tree.h
default size on canvas130 × 80 px
ports at insert1 in, 1 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleu
2outICoreDoubley

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
Feature Index[0; 1; -2; -2; -2]
Threshold[0.35; -0.2; 0; 0; 0]
Left Child[1; 2; -1; -1; -1]
Right Child[4; 3; -1; -1; -1]
Leaf Value[0; 0; -1.5; 0.4; 1.75]

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): no verifiable Simulink equivalent. 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. Its predict 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 tree as plain columns instead. Nothing in DSP System or Signal Processing, both of which are installed, is a decision tree

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:

  • B0 every 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).

Decision Tree — a fitted tree as five flat columns, evaluated at inference node = 0 while node is internal: node = (u[Feature[node]] <= Threshold[node]) ? Left[node] : Right[node] y = LeafValue[node]

The five columns are exactly what scikit-learn's tree_ exposes, including its leaf marker: node i is a LEAF when Left[i] < 0 (sklearn's TREE_LEAF is -1).

⚠ The generated code is a NESTED IF CASCADE, not a loop over the arrays, and that is the decision that makes the HDL targets work at all. A literal walk needs a data-dependent index into five arrays every iteration -- in hardware, a mux over N nodes per level, plus fx_to_int to turn a Q16.16 child index into an address. Emitted as nested ifs, each node appears exactly ONCE, no index arithmetic exists anywhere, and the emitted size is O(N) rather than O(N x depth). Every tree-to-code exporter does this, for the same reason.

⚠ Children must have a HIGHER index than their parent. Every fitted-tree exporter emits them that way, and requiring it makes a cycle impossible by construction -- which matters here far more than it would elsewhere, because a cycle in the table would make the emitter recurse until the stack ran out rather than produce a diagnosable error. It is checked, with that reason in the message.

⚠ Take this block before Random_Forest, Gradient_Boosted_Trees and Isolation_Forest_Score: all three are this walker with a different accumulator over a stacked node table.

Sample results#

No stimulus produced a sampled output in this rig — Feature index out of range at: ICore Blocks/Home/Decision Tree. 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__Decision_Tree.json