# `ExTbpMonty.Core.MatcherNif`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/ex_tbp_monty_core/matcher_nif.ex#L1)

The native kernels a matching step repeats once per node and once per
hypothesis.

The Elixir arithmetic in each calling module is the reference: it is what the
tests describe, what runs on a machine without a Rust toolchain, and what these
kernels are held to by parity tests. They exist because that reference pays
Elixir's price per value — a boxed float, a list cell, a tuple — where the
answer is the same arithmetic over a few thousand rows.

A NIF is a library the BEAM loads into itself, so its absence is a raised
`ErlangError` rather than a returned error: `loaded?/0` is the question a
caller asks, and every caller has a path for the answer `false`.

## Turning them off

`loaded?/0` also answers `false` when the application is configured with
`config :ex_tbp_monty, native_kernels?: false`. That is how a test asks for the
reference path on a machine that has the kernels, and how a run can be compared
with itself: two runs of the same episode, one with the switch either way, must
reach the same numbers.

# `argmax_index`

The first index holding the highest value of a column — numpy's `argmax`.

`column` is `n` f64; the answer is one u32. The reference is
`EvidenceGraphLM`'s `argmax/1`, which reports the first of several equal maxima.

# `available`

True, from the native side — the only way to know it is loaded.

# `blend_evidence`

The evidence column after a step, and what its most likely hypothesis earned.

`past · past_weight + present · present_weight` for every hypothesis, and
`present` at the first index holding the highest `past` — numpy's `argmax`,
which reports the first of several equal maxima. The reference is
`HypothesesDisplacer.compute_evidence/6`'s two passes in Elixir.

# `decode`

```elixir
@spec decode(binary()) :: [float() | nil]
```

A kernel's answers as Elixir values: a NaN is `nil`.

Read as the integers the bytes are, because a comprehension over
`<<value::float-64>>` does not raise on a NaN — it stops, silently returning
the answers before it.

# `displaced_locations`

Where every hypothesis' search location moves to, computed natively.

`poses` is `hypotheses * 9` f64, each rotation row by row; `locations` is the
search location each hypothesis claims (`hypotheses * 3` f64); `displacement`
is the three f64 the sensor moved by. The answer is `hypotheses * 3` f64:
`location + pose · displacement`, the same three dot products in the same order
as `SpatialArithmetics.add/2` over `Geometry.apply/2`, which is the reference
the parity test holds this to.

# `feature_evidence`

The feature evidence of every row, computed natively.

`rows` is `n * width` little-endian f64 with NaN where a node has no value;
`specs` gives each column's index and kind (two u32), `params` its observed
value, tolerance and weight (three f64). The answer is `n` f64, NaN where the
reference answers `nil`.

# `gather_rows`

The rows of a column at the given indices, in the order asked for.

`column` is `n * width` little-endian f64, `indices` is `k` u32; the answer is
`k * width` f64. Narrowing a hypothesis space keeps thousands of rows — the
copy is the cost, so it happens at the width it is asked for rather than a row
at a time in Elixir.

# `indices_at_least`

The hypotheses whose evidence has reached `threshold`, in index order.

`evidence` is `hypotheses` f64; the answer is `k` u32. The reference is the
comprehension `for {evidence, index} <- Enum.with_index(column), evidence >= threshold`.

# `indices_within`

The hypotheses whose evidence is within `percentage` per cent of the best.

Monty's `np.where(evidence > max - max / 100 * x_percent_threshold)`, once the
best clears `floor` — the module's object evidence threshold. Asked once per
object per step. The answer is `k` u32 indices: none when the column is empty,
when the best does not clear `floor`, or when any value is a NaN, because
`np.max` propagates a NaN and every comparison against one is false.

# `keep_mask`

Which hypotheses a slope threshold keeps, one byte each: `1` kept, `0` not.

The question a module asks of its slopes is nearly always this one, and asking
it here is one crossing instead of two: the slopes never become Elixir terms,
so nothing is boxed and nothing is decoded. `evidence`, `channels`, `window`
and `cursor` are as `window_slopes/4` takes them, `ages` is one u32 per
hypothesis, and the rule is the reference's:

    keep = age < min_age || (slope is not NaN && slope >= threshold)

`EvidenceSlopeTracker.select_hypotheses/2` is the reference this is held to,
and it walks the same bytes in Elixir when the kernels are off.

# `loaded?`

```elixir
@spec loaded?() :: boolean()
```

Whether this VM built and loaded the native kernels, and the configuration has
not turned them off.

# `min_value`

The lowest evidence in the column — the threshold that updates everything.

`evidence_threshold_config: "all"` is Monty's default and what the experiments
run, and a step asked for it once per graph. The answer is one number out of a
column of thousands, so it is read out of the bytes: decoding them into a list
to take a minimum of it was the largest single Elixir cost in a ten-object
step. `nil` when every value is a NaN, which is `Enum.min/1`'s answer for a
column of `nil`s.

# `neighbour_evidence`

The best evidence around each hypothesis, computed natively.

`ids` is `hypotheses * k` u32 as the index answered them. `hypotheses` is what
was asked — the search locations (3 f64 each), the hypothesis rotations (9 f64
each, row by row) and the observation's own normal and first curvature
direction (6 f64) — and `graph` is what was asked of: every node's location
(3 f64), its normal and first curvature direction (6 f64), whether its pose was
fully defined, and what its features were worth. `params` is `flatness`,
`max_match_distance`, the normal weight, the curvature weight, whether the
curvature direction was defined (`1.0` or `0.0`), and the evidence of a
hypothesis with no neighbour in range.

The answer is one f64 per hypothesis.

# `not_a_number`

```elixir
@spec not_a_number() :: binary()
```

The bytes of a missing value: the IEEE quiet NaN.

Erlang has no NaN — the pattern is not a float the syntax can build — so this
is the one way to write the value both sides agree means "no answer": a node
with no feature, a comparison that produced nothing, evidence there is none of.

# `principal_curvatures`

A patch's principal curvatures at its middle pixel, computed natively.

`points` is the patch as one flat binary of ``count * stride`` f64 — three
coordinates and, last of all, the on-surface mask — and `params` is the width of
the Gaussian that weights the fit, whether to weight it, and whether to fit an
intercept. The answer is nine f64: the two curvatures, their directions (three
values each) and whether they were defined.

# `second_argmax_index`

The index of the second highest value — the runner-up hypothesis.

A vote weighs the best hypothesis against the next one, so a step asks this
once per object. The Elixir it replaces decoded the column, made a tuple per
hypothesis and sorted all of them to read one index off the end; the answer is
in the bytes. `nil` for fewer than two values, and ties go the way
`Enum.sort_by(fn {value, index} -> {value, index} end) |> Enum.at(-2)` sent
them.

# `spread_add`

One channel's evidence, spread over the whole hypothesis set.

`totals` is `hypotheses` f64, what each hypothesis has earned from the channels
so far; `tested` is `k` u32, the indices a kernel answered for; `values` is the
`k` f64 it answered. The answer is `hypotheses` f64: `total + value` for a
tested hypothesis, and `total + max(min(values), 0)` for the rest — an
untested hypothesis must not be rewarded for a step it did not take part in.

`HypothesesDisplacer.evidence_to_add/7` is the reference this is held to.

# `top_indices`

The indices of the `count` highest values in a column, lowest first.

`evidence` is `nodes` f64 — one feature-evidence answer per node, NaN where a
node has no features — and the answer is `count` u32 in the order the sampler
takes them: evidence ascending, ties by index ascending, which is how
`Enum.sort_by(&{evidence, index}) |> Enum.take(-count)` reads the same column.

`DefaultFeatureEvidenceScorer.evidence/4` is where the column comes from and
`BurstSamplingHypothesesUpdater.top_indices/2` is the reference.

# `window_slopes`

The average per-channel slope of every hypothesis' evidence window.

`evidence` is `hypotheses * window` f64 in ring order — one row a hypothesis,
NaN for a slot that has not been filled — and `cursor` is the slot the next
update overwrites, which is also the oldest value every window holds.
`channels` is the channel count at the later slot of each consecutive pair.

The answer is one f64 per hypothesis: the mean of the steps that have two
observations, NaN where the window has none. `EvidenceSlopeTracker` is the
reference this is held to, and it reads the same bytes when the kernels are
off.

# `write_slot`

One column written into every row of `evidence`: `values` at `offset` in each
`stride`-long row, everything else untouched.

A tracker's windows are a ring and a step writes the same slot in all of them,
so this is a copy with a column replaced. `EvidenceSlopeTracker.update/3` is
the reference — it walks the same bytes in Elixir when the kernels are off.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
