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

Where hypotheses move to, and how much evidence they earn there.

Mirrors `tbp/monty/frameworks/models/evidence_matching/hypotheses_displacer.py`.
Two jobs:

  * **displacement** — a hypothesis says "the sensor is here and the object is
    turned this way", so when the sensor moves by `d` the hypothesis moves to
    `pose · d`. Every hypothesis is moved, not just the likely ones: dropping
    one would lose the path information it carries.
  * **evidence** — for each hypothesis worth testing, find the graph nodes
    around its search location and score the sensed pose and features there.

## The range evidence has

Pose evidence is in `[-1, 1]`: `-0.5` for the surface normal plus `-0.5` for
the first curvature direction, each mapped so a perfect match scores `+0.5`
and a perpendicular one `-0.5`. Feature evidence is `[0, 1]`, so one channel
contributes `[-1, 2]` — `MIN_EVIDENCE`, `MAX_EVIDENCE` below. Summed over `C`
channels that is `[-C, 2C]`, which is why the prediction error divides by
`EVIDENCE_RANGE * C`. A hypothesis with no node within `max_match_distance`
scores `-1`: being wrong is worse than being uninformative.

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.EvidenceMatching.HypothesesDisplacer{
  feature_evidence_scorer: term(),
  feature_weights: term(),
  graph_memory: term(),
  max_match_distance: term(),
  max_nneighbors: term(),
  past_weight: term(),
  present_weight: term()
}
```

# `compute_evidence`

```elixir
@spec compute_evidence(
  t(),
  %{optional(String.t()) =&gt; map()},
  float(),
  String.t(),
  Monty.Frameworks.Models.EvidenceMatching.Hypotheses.t(),
  keyword()
) ::
  {Monty.Frameworks.Models.EvidenceMatching.Hypotheses.t(),
   Monty.Frameworks.Models.EvidenceMatching.HypothesesDisplacer.HypothesisDisplacerTelemetry.t()}
```

Add this observation's evidence to the hypotheses worth testing.

Hypotheses below `evidence_update_threshold` keep their evidence and gain
nothing from this step: a cheap way to keep the space bounded without throwing
the alternatives away.

Options:

  * `:node_feature_evidence` — what each stored node is worth for one channel,
    from `node_feature_evidence/4`. The same comparison decides which nodes
    burst sampling draws from and what a hypothesis earns at a node, so the
    sampler hands its answer over rather than having it computed twice per
    step. Absent, it is computed here.

# `displace_hypotheses`

```elixir
@spec displace_hypotheses(
  t(),
  Monty.Frameworks.Models.EvidenceMatching.Hypotheses.t(),
  [number()]
) ::
  Monty.Frameworks.Models.EvidenceMatching.Hypotheses.t()
```

Move every hypothesis by `displacement`, as its own object orientation sees it.

The locations stay in the form they are held in: a kernel writes the moved
rows straight into the bytes a step keeps, where the reference builds a list
and three boxed floats for every hypothesis and then throws them away.

# `new`

```elixir
@spec new(Monty.Frameworks.Models.GraphMatching.GraphMemory.t(), keyword()) :: t()
```

A displacer over `memory`.

`:max_match_distance` is how far a search location may sit from a stored node
and still match it. `:past_weight` and `:present_weight` decide how much of a
hypothesis' history survives each observation; adding to one bounds the
evidence, which is Monty's reason for having both.

# `node_feature_evidence`

```elixir
@spec node_feature_evidence(t(), String.t(), String.t(), map()) :: [number()]
```

What each stored node is worth against the sensed features, for one channel.

One comparison per node, in the nodes' own order: burst sampling draws from it
when it decides where to put new hypotheses, and the evidence kernel reads it
when it decides what a hypothesis earns at a node. Both ask the same question
of the same graph and channel in the same step, so it is answered once.

# `object_pose_evidence`

```elixir
@spec object_pose_evidence([[[number()]]], [[[number()]]]) :: [[float()]]
```

Evidence from how far two object orientations are apart, in `[-1, 1]`.

Monty's `_object_pose_evidence`: the channel's pose is an `OrientationMatrix`
rather than a surface, so the comparison is between rotations and not between
a normal and a curvature direction. The angle between the hypothesised
orientation of the sensed object (`query_poses`) and the stored one comes off
the trace of their relative rotation — `trace(R) = 1 + 2 cos θ` — which is one
element-wise product per hypothesis and node, and no `Rotation` at all. Scaled
linearly so 0° is `1` and 180° is `-1`.

Rows are hypotheses and columns are the neighbours of each: `query_poses` is
`H` orientations of three rows, `node_poses` is `H` lists of `K` stored pose
vectors, each nine numbers row by row.

A channel whose pose is a surface is scored by `pose_evidence/6` instead; this
one is what Monty chooses for an object-orientation channel.

---

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