# `Monty.Frameworks.Models.Buffer.FeatureAtLocationBuffer`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/models/buffer/feature_at_location_buffer.ex#L1)

Features at locations arriving into one learning module, and the statistics of
an episode.

Mirrors `tbp/monty/frameworks/models/buffer.py::FeatureAtLocationBuffer`, with
the one difference a functional language forces: the buffer is a value. Every
method that mutates in Python returns a new buffer here, so a step reads as a
pipeline rather than as a side effect, and a test can hold two versions of the
same buffer and compare them.

Two shapes need saying out loud, because numpy does them implicitly in Python:

  * **Rows** are plain lists in step order, newest last. `locations` is a list
    of `[x, y, z]` in metres; `features[channel][attr]` is a list of flat rows,
    with `pose_vectors` a 3×3 matrix flattened to nine numbers exactly as
    Monty flattens it before storing.
  * **NaN padding is `nil`.** Monty pads a channel that sent nothing at a step
    with NaN so that an index means the same step in every channel, then drops
    those rows with `np.isnan` when it builds a graph. `nil` is that padding
    here: `pad_to_target_length/3` writes it, `get_all_features_on_object/1`
    keeps it, and anything reading a row must expect it.

Monty's own tests reach into `_pad_to_target_length`, so it is public here:
index alignment is the behaviour this module exists to guarantee, and a test
that cannot name it can only guess at it.

# `channel_features`

```elixir
@type channel_features() :: %{optional(String.t()) =&gt; [row()]}
```

# `row`

```elixir
@type row() :: [float()] | nil
```

# `t`

```elixir
@type t() :: %Monty.Frameworks.Models.Buffer.FeatureAtLocationBuffer{
  channel_sender_types: %{optional(String.t()) =&gt; :sm | :lm},
  displacements: %{optional(String.t()) =&gt; [row()]},
  features: %{optional(String.t()) =&gt; channel_features()},
  input_percepts: [Monty.Cmp.Message.t()],
  last_location: [float()] | nil,
  locations: [[float()]],
  on_object: [boolean()],
  start_time: integer() | nil,
  stats: %{optional(String.t()) =&gt; term()}
}
```

# `add_overall_stats`

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

Adds end-of-episode statistics, which replace rather than accumulate.

# `append`

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

Adds one step's percepts.

A step is the percepts that arrived together: its location is the mean of the
sensor-module percepts (a learning module reports hypotheses, not places), and
every percept teaches the buffer what kind of sender its channel is. A percept
that does not carry features for the learning module — a view finder, a
location-only report — contributes its location and nothing else, so its
channel gets no feature rows at all.

`{:error, :no_sensor_module_location}` for a step with no sensor module
percept that has a location: Monty asserts on it, and a step with no place in
it cannot be indexed against the rest of the episode.

# `append_input_percepts`

```elixir
@spec append_input_percepts(t(), Monty.Cmp.Message.t()) :: t()
```

Remembers a percept the buffer's learning module was given.

# `at`

```elixir
@spec at(t(), integer()) :: %{
  optional(String.t()) =&gt; %{optional(String.t()) =&gt; row()}
}
```

The features observed at one step, by channel.

Mirrors `__getitem__`. A step a channel did not reach reads as `nil` rows,
because that is what the padding says about it.

# `current_displacement`

```elixir
@spec current_displacement(t()) :: [float()] | nil
```

The most recent displacement.

# `current_location`

```elixir
@spec current_location(t()) :: [float()] | nil
```

The most recent location, or `nil` for an empty buffer.

# `current_ppf`

```elixir
@spec current_ppf(t()) :: [float()] | nil
```

The most recent point-pair feature.

# `first_displacement_len`

```elixir
@spec first_displacement_len(t()) :: float() | nil
```

The length of the first observed displacement, for scale in a displacement
learning module: the ppf's first element when there is one, otherwise the norm
of the first displacement.

Monty reads row 1, not row 0: the first step has no displacement to measure.

# `get_all_features_on_object`

```elixir
@spec get_all_features_on_object(t()) :: %{optional(String.t()) =&gt; channel_features()}
```

Every observed feature that was on the object, by channel and feature.

Each channel is padded to the episode's length first — a channel that stopped
sending keeps its index — and then read at the steps where *any* input was on
the object. A row the channel did not send at such a step stays `nil`, which is
what Monty's NaN means here.

# `get_all_input_percepts`

```elixir
@spec get_all_input_percepts(t()) :: [Monty.Cmp.Message.t()]
```

Every percept the buffer's learning module has been given.

# `get_all_locations_on_object`

```elixir
@spec get_all_locations_on_object(t()) :: [[float()]]
```

Every location from the steps where at least one input was on the object.

# `get_current_pose`

```elixir
@spec get_current_pose(t(), String.t()) :: {:ok, [[float()]]} | {:error, term()}
```

The currently sensed location and orientation, as four rows: the location,
then the channel's pose matrix.

`"first"` means the first sensory input channel. A channel that sent no
features at this step keeps the pose from the step it did send one, so the
location and the orientation can come from different steps; Monty warns when
that happens and so does this, because the combination is a quietly wrong pose
rather than an error.

# `get_currently_on_object`

```elixir
@spec get_currently_on_object(t()) :: boolean()
```

Whether the last sensation was on the object.

Monty's message carries a boolean, and a recorded one may carry the same fact
as `1.0`/`0.0` — so the answer is the truth of what was stored rather than the
stored value itself, which callers then combine with `and`.

# `get_first_sensory_input_channel`

```elixir
@spec get_first_sensory_input_channel(t()) :: {:ok, String.t()} | {:error, term()}
```

The name of the first channel that is a sensor module.

`{:error, {:no_sensory_channel, types}}` when there are channels but none is a
sensor module — a learning module fed only by a view finder or by lateral
votes has nothing of its own to sense with, which Monty raises on too.

# `get_infos_for_graph_update`

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

Everything a graph update needs from the buffer.

# `get_last_obs_processed`

```elixir
@spec get_last_obs_processed(t()) :: boolean()
```

Whether the last sensation was processed by the learning module.

# `get_matching_step_when_output_goal_set`

```elixir
@spec get_matching_step_when_output_goal_set(t()) :: integer() | nil
```

The matching step at which the last goal was generated.

# `get_num_goals_generated`

```elixir
@spec get_num_goals_generated(t()) :: non_neg_integer()
```

How many goals the learning module's goal generator has produced.

# `get_num_matching_steps`

```elixir
@spec get_num_matching_steps(t()) :: non_neg_integer()
```

How many steps the learning module processed (exploration steps included).

# `get_num_observations_on_object`

```elixir
@spec get_num_observations_on_object(t()) :: non_neg_integer()
```

The number of steps recorded.

# `get_num_steps_post_output_goal_generated`

```elixir
@spec get_num_steps_post_output_goal_generated(t()) :: integer()
```

How many matching steps have elapsed since the last output goal: all of them
when there has never been one.

# `get_previous_input_percepts`

```elixir
@spec get_previous_input_percepts(t()) :: Monty.Cmp.Message.t() | nil
```

The percept before the last one, or `nil`.

# `new`

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

A buffer with nothing in it yet.

The clock starts when the buffer does: the `"time"` statistic is
elapsed-since-episode-start, not a timestamp.

# `nth_displacement`

```elixir
@spec nth_displacement(t(), integer()) :: [float()] | nil
```

The nth displacement (negative counts from the end, as Monty uses it).

# `pad_to_target_length`

```elixir
@spec pad_to_target_length(t(), [row()], keyword()) :: [row()]
```

Pads rows to `target_length` with `nil`, keeping the data at the beginning.

`:target_length` defaults to the buffer's length; `:width` defaults to the
width the existing rows already have. A caller adding the first row of a new
feature must say how wide it is, because an empty list has no width to read.
Monty raises `ValueError` for the same two mistakes and so does this: a row of
the wrong width silently shifts every feature of a step.

# `reset`

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

Empties the buffer, as Monty's `reset` does.

# `set_individual_ts`

```elixir
@spec set_individual_ts(t(), term(), term()) :: t()
```

Records the terminal state of an individual learning module.

Only the first time the terminal condition is met is kept: an episode ends
once, and the first ending is the one that mattered.

# `size`

```elixir
@spec size(t()) :: non_neg_integer()
```

How many observations the buffer holds.

# `update_last_stats_entry`

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

Overwrites the last entry of the named statistics (after voting, say).

# `update_stats`

```elixir
@spec update_stats(t(), map(), keyword()) :: t()
```

Records statistics for a step.

Options mirror Monty's keyword arguments: `:update_time` appends the elapsed
time, `:append` adds a row to an existing statistic rather than replacing it,
and `:init_list` starts a new list for a statistic seen for the first time. A
statistic the buffer has never held is added either way: statistics are a log,
and a module that invents one is doing something Monty's own modules do.

---

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