Monty.Frameworks.Models.Buffer.FeatureAtLocationBuffer (ExTbpMonty.Core v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

Adds one step's percepts.

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

The features observed at one step, by channel.

The most recent displacement.

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

The most recent point-pair feature.

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.

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

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

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

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

Whether the last sensation was on the object.

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

Everything a graph update needs from the buffer.

Whether the last sensation was processed by the learning module.

The matching step at which the last goal was generated.

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

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

The number of steps recorded.

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

The percept before the last one, or nil.

A buffer with nothing in it yet.

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

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

Empties the buffer, as Monty's reset does.

Records the terminal state of an individual learning module.

How many observations the buffer holds.

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

Records statistics for a step.

Types

channel_features()

@type channel_features() :: %{optional(String.t()) => [row()]}

row()

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

t()

@type t() :: %Monty.Frameworks.Models.Buffer.FeatureAtLocationBuffer{
  channel_sender_types: %{optional(String.t()) => :sm | :lm},
  displacements: %{optional(String.t()) => [row()]},
  features: %{optional(String.t()) => 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()) => term()}
}

Functions

add_overall_stats(buffer, stats)

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

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

append(buffer, percepts)

@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(buffer, percept)

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

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

at(feature_at_location_buffer, index)

@spec at(t(), integer()) :: %{
  optional(String.t()) => %{optional(String.t()) => 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(buffer)

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

The most recent displacement.

current_location(feature_at_location_buffer)

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

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

current_ppf(feature_at_location_buffer)

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

The most recent point-pair feature.

first_displacement_len(feature_at_location_buffer)

@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(buffer)

@spec get_all_features_on_object(t()) :: %{optional(String.t()) => 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(feature_at_location_buffer)

@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(buffer)

@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(buffer, input_channel \\ "first")

@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(feature_at_location_buffer)

@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(feature_at_location_buffer)

@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(buffer)

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

Everything a graph update needs from the buffer.

get_last_obs_processed(feature_at_location_buffer)

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

Whether the last sensation was processed by the learning module.

get_matching_step_when_output_goal_set(feature_at_location_buffer)

@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(feature_at_location_buffer)

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

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

get_num_matching_steps(feature_at_location_buffer)

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

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

get_num_observations_on_object(buffer)

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

The number of steps recorded.

get_num_steps_post_output_goal_generated(buffer)

@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(feature_at_location_buffer)

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

The percept before the last one, or nil.

new()

@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(feature_at_location_buffer, n)

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

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

pad_to_target_length(buffer, rows, opts \\ [])

@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(feature_at_location_buffer)

@spec reset(t()) :: t()

Empties the buffer, as Monty's reset does.

set_individual_ts(buffer, object_id, pose)

@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(feature_at_location_buffer)

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

How many observations the buffer holds.

update_last_stats_entry(buffer, stats)

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

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

update_stats(buffer, stats, opts \\ [])

@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.