# `Monty.Frameworks.Models.AbstractMontyClasses.LearningModule`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/abstract_monty_classes/learning_module.ex#L1)

Behaviour for a learning module, mirroring
`tbp/monty/frameworks/models/abstract_monty_classes.py::LearningModule`.

Pure callbacks again: percepts and context in, next state out. The process
(`Monty.Runtime.LearningModuleServer`) holds the state and nothing else, so
the step semantics Monty defines — which inputs arrive, in which order, and
what voting exchanges — live in `Monty.Runtime.Step` where they can be read
and tested in one place.

`matching_step/3` updates evidence and hypotheses; `exploratory_step/3`
accumulates for later training. `send_out_vote/1` is called for every module
before any module receives votes, which is what makes a vote round a round.

# `ctx`

```elixir
@type ctx() :: Monty.Context.t()
```

# `mode`

```elixir
@type mode() :: :train | :eval | nil
```

Monty's `ExperimentMode`: training writes memory, evaluation does not.

# `percepts`

```elixir
@type percepts() :: [Monty.Cmp.Message.t()]
```

# `state`

```elixir
@type state() :: term()
```

# `terminal_state`

```elixir
@type terminal_state() :: :match | :no_match | :time_out | :pose_time_out | nil
```

What a module has concluded, or `nil` while it is still deciding.

`:no_match` means nothing in memory explains what it saw; `:time_out` that
the episode ended before it made up its mind; `:pose_time_out` that it chose
an object but never settled on a pose.

# `vote`

```elixir
@type vote() :: term()
```

# `add_lm_processing_to_buffer_stats`

```elixir
@callback add_lm_processing_to_buffer_stats(state(), boolean()) :: state()
```

Records whether this step's observation reached the module.

`false` for a step the module was not given (no percepts for it, or percepts
with no features): such a step is not evidence, so it neither counts towards
symmetry nor justifies a vote.

# `clear_buffers`

```elixir
@callback clear_buffers(state()) :: state()
```

Drops what the module accumulated, and nothing it learned.

Monty's `MontyForNoResetEvidenceGraphMatching._reset_modules_buffers` reaches
into every module's buffer (`lm.buffer.reset()`) rather than resetting the
module, because resetting would also throw away the hypotheses: a run without
episode boundaries keeps what it believes across an object change and only
frees the observations it will not read again. A module's state is opaque to
the coordinator here, so that reach-in is a callback.

The default is a module with no buffer — nothing to drop.

# `clear_conclusion`

```elixir
@callback clear_conclusion(state()) :: state()
```

Forgets what the module concluded, keeping everything it believes.

Monty's `NoResetEvidenceGraphMatching._reset_terminal_states` calls
`lm.set_individual_ts(None)` on every module at an object boundary: the
episode's answer is over, the hypothesis space is not. The default is the
narrowest thing that is true of every module — a module with no detected
object to forget has only its terminal state to clear.

# `conclusion`

```elixir
@callback conclusion(state()) :: terminal_state()
```

What the module has concluded so far, without asking it to reconsider.

Monty's `lm.terminal_state` field. A module writes it from its own step as
soon as it can conclude anything at all — `EvidenceGraphLM.matching_step`
concludes `:no_match` the moment nothing is possible — and the model reads it
to decide the step (`MontyForGraphMatching.check_terminal_conditions`).
Deciding what to conclude is `update_terminal_condition/1`, which Monty asks
for only once an episode is past its minimum number of steps.

# `exploratory_step`

```elixir
@callback exploratory_step(percepts(), ctx(), state()) :: {:ok, state()}
```

A step in which the module explores without matching.

# `fixme_update_ground_truth`

```elixir
@callback fixme_update_ground_truth(state()) :: state()
```

Records which objects the graph a training episode built came from.

Monty's `fixme_update_ground_truth`, called after the episode's long-term
memory is written. It is a record of the run's supervision, not something the
model worked out, and a later evaluation is scored against it.

# `get_output`
*optional* 

```elixir
@callback get_output(state()) :: {Monty.Cmp.Message.t() | nil, state()}
```

What this module tells modules below it in the hierarchy, or `nil`.

Read at the start of a step and delivered only if the receiver also saw a
sensor percept that step — Monty's rule, and the reason a silent sensor
stops a hierarchy from talking to itself.

# `graph_memory`

```elixir
@callback graph_memory(state()) ::
  Monty.Frameworks.Models.GraphMatching.GraphMemory.t() | :none
```

The long-term memory this module holds, or `:none` for a module that keeps none.

A run hands the memory of a training episode to the next one, and it is the
module that holds it — `GraphLM.graph_memory`, which Monty reads straight off
the attribute. A module's state is opaque here, so the module answers.

# `matching_step`

```elixir
@callback matching_step(percepts(), ctx(), state()) :: {:ok, state()}
```

A step in which recognised objects are being matched against memory.

# `observation_processed?`

```elixir
@callback observation_processed?(state()) :: boolean()
```

Whether the last observation buffered was one this module processed.

# `propose_goals`
*optional* 

```elixir
@callback propose_goals(state()) :: {[Monty.Cmp.Goal.t()], state()}
```

A goal this module wants pursued (usually a hypothesis test).

# `propose_region`
*optional* 

```elixir
@callback propose_region(state()) :: {Monty.Cmp.AttentionRegion.t(), state()}
```

The region this module wants attended to.

# `receive_votes`

```elixir
@callback receive_votes(vote(), state()) :: {:ok, state()}
```

Votes gathered from the modules wired to this one.

What a vote *is* differs by family: the displacement modules exchange sets of
objects they have excluded, the evidence modules a map of object id to the
hypotheses worth voting for. The coordinator passes on whatever the senders'
`send_out_vote/1` produced, and this callback decides how to weigh it.

# `reset_stm`

```elixir
@callback reset_stm(state()) :: state()
```

Clears short-term memory before a new episode.

# `send_out_vote`

```elixir
@callback send_out_vote(state()) :: {vote(), state()}
```

What this module believes, for other modules to weigh.

# `set_experiment_mode`

```elixir
@callback set_experiment_mode(state(), mode()) :: state()
```

Tells the module whether this episode trains or evaluates.

# `set_primary_target`

```elixir
@callback set_primary_target(state(), map() | nil) :: state()
```

Names the object this episode is about, and leaves the module's history alone.

Monty's `MontyForNoResetEvidenceGraphMatching.fixme_set_ground_truth` writes
`lm.primary_target` and `lm.primary_target_rotation_quat` directly after the
first episode, instead of the `fixme_reset_ground_truth` an ordinary episode
calls — which would empty `stepwise_targets_list`, the record of what the
sensor was on, step by step, across every object of the run.

# `set_stepwise_target`

```elixir
@callback set_stepwise_target(state(), String.t()) :: state()
```

Records which object the module's sensor is on this step.

Monty's `_set_stepwise_targets`: the model, not the module, reads the label
off the percept's semantic id, and hands it here. It is logging information —
the stepwise performance asks "was the object under the patch one the module
was considering", which an episode reports and nothing matches on.

# `set_target`

```elixir
@callback set_target(state(), map() | nil) :: state()
```

Names the object this episode is about, and the pose it was placed with.

Monty's `fixme_reset_ground_truth`: the target is what makes a performance
measurable — "correct" means "recognised the object it was asked about" — so a
module that is never told one cannot report a performance at all. `nil` leaves
whatever it knew alone.

# `set_terminal_state`

```elixir
@callback set_terminal_state(state(), terminal_state()) :: state()
```

Sets the module's conclusion without asking it to reconsider.

The model uses this to time out the modules that have not concluded once the
system decision is made: they take part in the episode's statistics but claim
nothing.

# `update_ltm_from_stm`

```elixir
@callback update_ltm_from_stm(state()) :: state()
```

Folds what the episode saw into long-term memory, at the end of an episode.

Only a training episode writes anything; the module decides that from the mode
it was given.

# `update_stats_after_vote`

```elixir
@callback update_stats_after_vote(state()) :: state()
```

Monty's `update_stats_after_vote`: what a vote round left this module
believing.

Asked after every round, for every module, whether or not it heard anything.
Monty uses it to settle the module's own conclusion: nothing possible is
`:no_match`, and a module that was `:no_match` is let back when other modules'
votes have given it possibilities again — the terminal check never clears
`:no_match`, so without this a module that lost its hypotheses once could
never recover in the same episode.

# `update_terminal_condition`

```elixir
@callback update_terminal_condition(state()) :: {state(), terminal_state()}
```

Checks the module's own terminal condition and reports what it concluded.

Called once per step in which the module processed an observation, so a
module that counts evidence of symmetry sees every step in which it could
have grown.

# `__using__`
*macro* 

Defaults for the parts not every learning module has: no goals, no region,
no output, and no bookkeeping of its own after a vote.

---

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