# `Monty.Frameworks.Models.GoalGeneration.EvidenceGoalGenerator`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/goal_generation/evidence_goal_generator.ex#L1)

Look where the candidates disagree most, then look again.

Mirrors `tbp/monty/frameworks/models/goal_generation.py::EvidenceGoalGenerator`,
the goal-state generator that turns "I cannot tell these objects apart" into
one concrete place to look. It compares the graph of the most likely object
with the graph of the second most likely one, and asks for the point on the
first that is farthest from anything on the second: a mug's handle, when the
alternatives are handleless cups.

Monty subclasses `GraphGoalGenerator`; Elixir has no inheritance, so this
holds one instead and delegates the parts Monty does not override — the parent
module, the goal itself, and the crude "did the sensory input change" test for
whether the last goal was reached. What the subclass does override is all
here: when to look somewhere new, where, and the goal that points there.

## The schedule

A hypothesis jump is a gamble: move to where the two candidates disagree, and
the observation there should favour one of them. Gambling every step makes the
agent walk back and forth over the object and learn nothing, so
`check_conditions_for_hypothesis_test/2` spends most steps matching and jumps
when the odds are good:

  * never, until `min_post_goal_success_steps` steps have passed since the
    last goal — Monty's default is `:infinity`, so only an experiment that
    asks for it jumps at all;
  * when one object is left but several poses, to separate the poses;
  * when one object is clearly ahead but not certain, on half the occasions;
  * when the top two objects change, or the most likely pose does;
  * otherwise, every `wait_factor * elapsed_steps_factor` steps, with
    `wait_factor` doubling after each jump so a test that taught nothing is
    not repeated on a schedule that keeps spending.

## A quirk kept on purpose

Monty writes both the top-two check and the pose check as `np.all(...)`, so
they fire only when *every* compared value differs: both object ids must
change, and all three Euler angles must change. That is stricter than the
comment above it ("a change or shuffle in top-two MLH IDs") describes, and it
is reproduced rather than corrected, because correcting it would change when
the agent moves and make our numbers incomparable with Monty's.

# `mlh`

```elixir
@type mlh() ::
  Monty.Frameworks.Models.EvidenceMatching.LearningModule.EvidenceGraphLM.mlh()
```

The most likely hypothesis of an object, or of everything: what
`EvidenceGraphLM.get_mlh_for_object/2` answers.

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.GoalGeneration.EvidenceGoalGenerator{
  base: Monty.Frameworks.Models.GoalGeneration.GraphGoalGenerator.t(),
  desired_object_distance: number(),
  elapsed_steps_factor: pos_integer(),
  focus_on_pose: boolean(),
  min_post_goal_success_steps: non_neg_integer() | :infinity,
  prev_top_mlhs: [mlh()] | nil,
  wait_factor: pos_integer(),
  wait_growth_multiplier: number(),
  x_percent_scale_factor: number()
}
```

# `target_info`

```elixir
@type target_info() :: %{
  hypothesis_to_test: mlh(),
  target_loc: [float()],
  target_surface_normal: [float()]
}
```

Where to look: which hypothesis would be tested, and the point on its graph.

`target_surface_normal` is the learned normal of that node, in the object's
own frame — the direction a sensor has to face to observe it.

# `check_conditions_for_hypothesis_test`

```elixir
@spec check_conditions_for_hypothesis_test(t(), Monty.Context.t()) :: {boolean(), t()}
```

Whether there is a good chance of separating the competing objects or poses.

Returns the answer together with the generator, because deciding changes it:
`focus_on_pose` is cleared at the start of every call, and the elapsed-steps
branch grows `wait_factor`. Monty mutates both in place; a struct cannot.

# `compute_goal_for_target_loc`

```elixir
@spec compute_goal_for_target_loc(t(), [Monty.Cmp.Message.t()], target_info()) ::
  {:ok, Monty.Cmp.Goal.t()} | {:error, term()}
```

A goal that puts the sensor above the target point, facing its surface.

Monty's `_compute_goal_for_target_loc`. The displacement from the hypothesis's
own location to the target is rotated out of the object's frame by the object
pose, added to where the sensor is now, and then pushed off the surface along
the (equally rotated) normal so that the sensor starts outside the object and
looks down at it.

The roll is left undefined, as in Monty: which way is up does not matter for
telling two objects apart, and the policy that acts on this goal decides it.

# `compute_graph_mismatch`

```elixir
@spec compute_graph_mismatch(t()) :: {:ok, non_neg_integer(), t()} | {:error, term()}
```

The node of the most likely object that the alternative explains worst.

Both graphs are brought into the same frame first: the MLH's rotation is the
rotation that would turn a displacement into the object's pose, so its inverse
is the object's pose, and applying it puts the graph in environmental
coordinates. The second object's stored rotation then does the opposite, so
both graphs are expressed as they were learned and the comparison is between
shapes rather than between placements.

Returns the index into the *most likely* object's positions — the point to
look at — and the generator, whose `prev_top_mlhs` now records what was
compared, so the next step can notice if either changed.

# `from_config`

```elixir
@spec from_config(map()) :: t()
```

The generator a config entry describes.

The entry is Monty's `gsg:` under a learning module — `goal_tolerances` by
feature, then the schedule: how many steps without a goal before one is worth
trying, the `x` percent scale factor, how close to the object a goal counts as
reached, and how fast the wait between attempts grows.

# `goal_stat_keys`

```elixir
@spec goal_stat_keys() :: [String.t()]
```

The statistics this generator keeps on its parent's buffer.

# `new`

```elixir
@spec new(keyword()) :: t()
```

A generator that has not looked anywhere yet.

Options are `:parent_lm` (the learning module to read), `:goal_tolerances` and
`:parent_output` (handed to the base generator), and Monty's five schedule
parameters: `:elapsed_steps_factor`, `:min_post_goal_success_steps`,
`:x_percent_scale_factor`, `:desired_object_distance` and
`:wait_growth_multiplier`.

# `output_goals`

```elixir
@spec output_goals(t()) :: [Monty.Cmp.Goal.t()]
```

The goal this generator wants others to achieve, or none.

# `parent_lm`

```elixir
@spec parent_lm(t()) :: struct()
```

The learning module this generator reads, or an error when there is none.

# `point_at`

```elixir
@spec point_at(t(), map()) :: t()
```

The same generator, now reading `parent_lm` as it stands.

Someone driving this generator step by step has the module's current value
in hand and the generator has a copy; this swaps the copy without throwing
away the schedule. See `GraphGoalGenerator.point_at/2`.

# `put_parent_lm`

```elixir
@spec put_parent_lm(t(), struct()) :: t()
```

Points this generator at its learning module and resets it.

The schedule is part of the reset: `wait_factor` and `prev_top_mlhs` describe
what this module has already tried, and another module's tries say nothing
about this one.

# `reset`

```elixir
@spec reset(t()) :: t()
```

Forgets the goal in hand, the schedule, and the log.

# `set_driving_goal`

```elixir
@spec set_driving_goal(t(), Monty.Cmp.Goal.t() | nil) :: t()
```

Receives the high-level goal to drive towards; unused, as in Monty.

# `step`

```elixir
@spec step(t(), Monty.Context.t(), [Monty.Cmp.Message.t()]) :: t()
```

One step: log whether the last goal was reached, then decide what to do.

Reaching the goal is *not* a reason to want a new one — it means the agent
arrived, and arriving is where the judging happens — so an achieved goal is
dropped. A goal that was not achieved is dropped too: hypothesis tests are
one-off attempts, because an unreachable goal repeated forever is a loop.

# `target_loc_info`

```elixir
@spec target_loc_info(t(), non_neg_integer()) ::
  {:ok, target_info()} | {:error, term()}
```

Where the point to test is, on the most likely object's graph.

Monty's `_get_target_loc_info`. The normal is the first three numbers of the
node's `pose_vectors`, which is the surface normal the sensor has to face.

---

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