# `Monty.Frameworks.Models.EvidenceMatching.FeatureEvidence.Calculator.DefaultFeatureEvidenceCalculator`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/evidence_matching/feature_evidence/calculator/default_feature_evidence_calculator.ex#L1)

Feature evidence as "how far inside the tolerance is this value".

Mirrors
`tbp/monty/frameworks/models/evidence_matching/feature_evidence/calculator.py::DefaultFeatureEvidenceCalculator`.

Each stored value is compared with the observation, and the difference is
read as evidence: no difference is 1, a difference at the tolerance is 0, and
it falls off linearly in between. The per-feature scores are then averaged
with the feature weights, so a feature an experiment distrusts can be made
quiet without being switched off.

## Three kinds of comparison

  * **numeric** — the absolute difference;
  * **circular** — the difference the short way round, so a hue of 0.99 and
    one of 0.01 are neighbours rather than 0.98 apart. Only the first value of
    a circular feature wraps (`hsv`'s hue); saturation and value do not;
  * **categorical** — equal or not, with the same linear falloff applied to
    that 0-or-1 difference.

# `column`

```elixir
@type column() :: %{
  index: non_neg_integer(),
  kind: :numeric | :circular | :categorical,
  value: float(),
  tolerance: float(),
  weight: float()
}
```

One comparison: which value of a row it reads, how, and what the answer is
worth.

The index counts positions in the row, so the columns of a call are numbered
`0, 1, 2, ...` as a whole: a three-valued feature after a one-valued one starts
at one.

# `columns`

```elixir
@spec columns([String.t()], map(), map(), map()) :: [column()]
```

The comparisons one observation makes against every row.

Public because it is the whole description of the arithmetic: the rows are
compared column by column and there is nothing else to say about it. A native
kernel is handed these columns rather than deciding for itself what to compare.

# `decode_answers`

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

A native kernel's answers, as this module's: a NaN is `nil`.

The answers cross as bytes; `ExTbpMonty.Core.MatcherNif.decode/1` is the one place
that turns them back into values, because a NaN there means the same thing for
every kernel: no answer for this row.

# `encode_columns`

```elixir
@spec encode_columns([column()]) :: {binary(), binary()}
```

The columns as the two binaries a native kernel reads.

`specs` carries each column's row index and what kind of comparison applies
(two u32); `params` carries its observed value, tolerance and weight (three
f64). Split in two because a kind is a small integer and the rest are doubles,
and because the kernel reads both in the same order the weights are summed in.

---

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