Isolation Forest Score — Machine Learning/Anomaly Detection
Machine_Learning/Anomaly_Detection/Isolation_Forest_Score · 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.
Isolation Forest Score
Machine Learning / Anomaly Detection
A fitted isolation forest, evaluated at inference. Every tree is walked as Decision Tree walks one, and what is accumulated is how deep the sample had to be buried before it was isolated:
h(x) = (1/T)·Σ ( depth(leaf) + adjustment(leaf) ), then score = 2−h(x)/c(n)
The idea is an inversion worth stating plainly: an anomaly is easy to isolate. A few random splits already separate it, so it comes to rest in a shallow leaf, h is small and the score approaches 1. A normal point sits among its neighbours and needs many splits, so h is large and the score falls towards 0.5 and below. Around 0.5 means "as hard to isolate as an average training point"; near 1 means "isolated almost immediately".
Unlike Mahalanobis Distance, which needs a covariance and assumes one elliptical blob, this makes no distributional assumption at all – which is why it is the usual first choice on data whose shape nobody wants to commit to.
Ports
- u – the feature column, [d,1]. Every entry of Feature Index must name a row of it.
- score – a [1,1] scalar in (0, 1]: the anomaly score above. Higher is more anomalous.
- path – a [1,1] scalar: the mean path length h(x) itself, before the exponential. Lower is more anomalous. See Code export – on hardware this is the output to use.
Parameters
The four node columns and the adjustment are all [N,1], one row per node across the whole forest – the same stacked layout Random Forest reads.
- Feature Index – which entry of u the node tests, zero-based. Ignored at leaves.
- Threshold – the split value. In a fitted isolation forest this is a random value between two observed points, not an optimised one.
- Left Child – the row taken when u[Feature] ≤ Threshold. A negative entry marks a leaf.
- Right Child – the row taken otherwise.
- Leaf Path Adjustment – at each leaf, the c(size) term for the samples that were never separated there: the path length they would still have needed. It is 0 for a leaf holding one sample, and for internal nodes it is ignored. This is what stops a depth-limited forest from pretending that a crowded leaf isolated anything.
- Tree Roots – a [T,1] column giving the row each tree's root sits at. Its height is the number of trees.
- Sample Count – n, the number of samples each tree was
fitted on (scikit-learn's
max_samples, commonly 256). It sets the normalizer c(n) = 2(ln(n−1) + γ) − 2(n−1)/n, which is the average path length of an ordinary point – the yardstick the score is measured against. - Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
The depth is not configured: it is a property of the table, so the block computes it by walking each tree once. Asking for it as a column would be a second source of truth that could disagree with the one the walk uses.
Children must have a higher row number than their parent, in the stacked table's numbering – the rule Decision Tree documents, which here also guarantees the depth walk terminates.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Each leaf's depth + adjustment is a single baked constant, so the body is one comparison cascade per tree adding constants, then one multiply and one exponential. c(n), the change of base and the 1/T average are folded into a single constant at configuration load; no harmonic number is ever computed at run time.
The three HDL targets are simulation-only for score: they
evaluate the exponential in real and quantize once at the port
boundary, exactly as Mahalanobis Distance does. The walk and the
path output stay exact fixed point, and that is the reason there
are two outputs – 2−h/c is strictly decreasing in h, so
thresholding path from below is exactly equivalent to
thresholding score from above. A synthesizable deployment uses
path and pays for no exponential at all.
Simulink bridge
None, for the reasons Decision Tree and Random Forest
give: the reference would be the Statistics and Machine Learning Toolbox
(iforest), which is not installed on this machine, so a bridge could
not be run against a parity testbench even if one were written – and it
takes a fitted model object, which no parameter mapping can carry. 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.
Notes
- Algebraic and stateless: both outputs depend only on the current sample.
- Piecewise constant, so no state space – the score changes only when a sample crosses a split. Model reduction correctly refuses the block.
- The score is a ranking, not a probability. 2−h/c is bounded by (0, 1] and is calibrated so that an average training point sits near 0.5, but it is not the probability of anything. Choose an operating point by looking at scores on known-good data, which is what path makes easy.
- A depth-limited forest needs its adjustments. If every leaf carries adjustment 0 while the trees were cut short at a depth limit, every deep sample reports the same saturated path length and the score stops discriminating among exactly the points it was supposed to rank.
Code facts#
| Fact | Value |
|---|---|
| registered type | Machine_Learning/Anomaly_Detection/Isolation_Forest_Score |
| family | Machine_Learning/Anomaly_Detection |
| solver environment class | ICoreBlock_0_Machine_Learning_1_Anomaly_Detection_2_Isolation_Forest_Score |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Anomaly_Detection/Isolation_Forest_Score/ICoreBlock_0_Machine_Learning_1_Anomaly_Detection_2_Isolation_Forest_Score.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Machine_Learning/Anomaly_Detection/Isolation_Forest_Score/ICoreBlock_0_Machine_Learning_1_Anomaly_Detection_2_Isolation_Forest_Score.h |
| default size on canvas | 150 × 85 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 | u |
| 2 | out | ICoreDouble | score |
| 3 | out | ICoreDouble | path |
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 Path Adjustment | [0; 0; 0; 0; 0.4; 0; 0; 0; 0; 0.7; 0; 0; 1.2] | — |
Tree Roots | [0; 5; 10] | — |
Sample Count | 256 | — |
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 (iforest), which is not installed on this machine, so a bridge could not be run against a parity testbench even if one were written -- and it takes a fitted model OBJECT from a workspace variable, which no parameter mapping can carry. That is why the forest is pasted here as plain stacked columns
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).
Isolation Forest Score — how few splits it took to isolate this sample h(x) = (1/T) * SUM over trees of ( depth(leaf) + adjustment(leaf) ) score = 2^( -h(x) / c(n) ) c(n) = 2*(ln(n-1) + gamma) - 2*(n-1)/n
Random_Forest's stacked table and Decision_Tree's walk, with a different accumulator. An anomaly is easy to isolate and comes to rest SHALLOW, so it scores near 1; a normal point needs many splits and scores near 0.5 or below.
Three folds make this cheap, and each is a decision rather than an optimization:
- EVERY LEAF'S PATH LENGTH IS A COMPILE-TIME CONSTANT. Depth is a property of the table, so
it is computed here by walking each tree once; the adjustment comes from the fit. Their sum is baked into the cascade, so no target counts steps and no target indexes anything -- the emitted body has exactly Random_Forest's shape,
acc = acc + <constant>.
- THE WHOLE NORMALIZER IS ONE CONSTANT: score = exp(-acc * kExp) with kExp = ln2/(c(n)*T).
The harmonic number, the change of base and the 1/T average are all resolved before a line of code is generated.
pathIS A SECOND OUTPUT, not a debug aid. It is the mean path length in pure fixedpoint, and 2^(-h/c) is strictly DECREASING in h -- so thresholding
pathfrom below is exactly equivalent to thresholdingscorefrom above, and a synthesizable deployment can drop the exponential entirely.⚠ The three HDL targets are SIMULATION-ONLY for
scorebecause of that exponential, the way Mahalanobis_Distance and the activation blocks are. The walk itself, andpath, stay exact Q16.16 -- which is the point of splitting the outputs.
Sample results#
No stimulus produced a sampled output in this rig — Feature index out of range at: ICore Blocks/Home/Isolation Forest Score. 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__Anomaly_Detection__Isolation_Forest_Score.json