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

Generate sub-goals until the goal it is driving towards is achieved.

Mirrors `tbp/monty/frameworks/models/goal_generation.py::GraphGoalGenerator`,
the base goal-state generator (GSG). A GSG belongs to a learning module and
does one thing on every step: decide whether the goal it last output has been
achieved, and if so produce the next one. What "achieved" means is the only
interesting part, and the base class answers it roughly — the sensory input
changed, so something moved — which is enough for a module that is only
trying to reduce its own uncertainty.

This class generates no goals of its own: `generate_goal/2` answers `nil`,
meaning "carry on, I only want more certainty". `EvidenceGoalGenerator`
overrides that with the real thing, which is where the work is.

## Where the parent module went

Monty holds a pointer to the learning module and reads it whenever it likes —
`self.parent_lm.get_output()`. A struct cannot hold a live pointer to a value
that changes, so a caller hands the generator two things instead: the module
itself (for its buffer, its step counters and its tolerances) and, through
`:parent_output`, how to ask it for its current output. The default answers
`nil`, which is what a module with nothing to say would answer, and Monty's
only use of that call is comparing against a driving goal that experiments do
not set yet.

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.GoalGeneration.GraphGoalGenerator{
  driving_goal: Monty.Cmp.Goal.t() | nil,
  goal_tolerances: map(),
  keep_output_goal: (-&gt; boolean()) | nil,
  output_goal: Monty.Cmp.Goal.t() | nil,
  parent_lm: map() | nil,
  parent_output: (-&gt; term()) | nil
}
```

# `buffer`

```elixir
@spec buffer(t()) :: Monty.Frameworks.Models.Buffer.FeatureAtLocationBuffer.t()
```

The buffer this generator reads from and records into.

Goals are logged on the parent module's buffer rather than on the generator,
so that everything a step learned about a module is read from one place
afterwards.

# `from_config`

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

The generator a config entry describes.

On its own it emits no goal, which is what an experiment that only wants a
learning module to keep matching asks for.

# `goal_stat_keys`

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

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

# `log_goal_state`

```elixir
@spec log_goal_state(t(), boolean()) :: t()
```

Records whether the goal in hand was achieved on this step.

The goal was set — and could have been achieved — on the previous step, which
is why the recorded matching step is the one before this.

# `messages_different?`

```elixir
@spec messages_different?(
  Monty.Cmp.Message.t() | nil,
  Monty.Cmp.Message.t() | nil,
  map()
) :: boolean()
```

Whether two messages differ by more than the given tolerances.

One feature outside its tolerance is enough. An undefined message, or an
attribute both messages leave undefined, says nothing and is not a
difference — which is what makes a goal with only a location meaningful.

# `new`

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

A generator with no goals yet.

`:goal_tolerances` says how close is close enough per goal attribute — a
distance in metres for `"location"` unless an experiment says otherwise; it is
the GSG's own tolerance, not the learning module's matching tolerance, because
the question here is whether a goal was reached, not whether two things look
alike. `:parent_output` is how to ask the parent module for its output.

# `output_goal_achieved?`

```elixir
@spec output_goal_achieved?(t(), [Monty.Cmp.Message.t()]) :: boolean()
```

Whether the last output goal was achieved, judged from the sensory input.

The prediction is only "the input changed": a motor system that fails to
reach its goal usually moves back, so a step that shows the same thing twice
is a step where the agent went nowhere. That is approximate on purpose — the
alternative needs a predicted percept, which needs a model of the world from a
goal's point of view, and Monty's own comment says so.

# `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()) :: map()
```

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.

Monty holds a pointer to its module and so sees every change to it for free.
A struct holds a copy, so whatever drives the generator hands it the module
again before each step — without resetting, because the schedule the
generator has built up still belongs to this module.

# `put_parent_lm`

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

Points this generator at its learning module and resets it.

Setting the module resets, because goals left over from another module belong
to another object.

# `reset`

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

Forgets the goals held so far and starts the log over.

The log lives on the parent module's buffer, which is why this needs one.

# `set_driving_goal`

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

Receives the high-level goal to drive towards.

Monty's experiments never set one yet, so this stays `nil`: the default goal
is "be more certain", which the base class cannot check and therefore never
achieves — a module keeps gathering evidence for as long as the experiment
lets it.

# `step`

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

One step: was the last goal achieved, and if so what is next.

Running out of goals is not an error — the module carries on with no goal,
which for the base class is the whole of its behaviour.

---

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