Monty.Frameworks.Models.FeatureLocationMatching.FeatureGraphMemory (ExTbpMonty.Core v0.1.0)

Copy Markdown View Source

Monty's FeatureGraphMemory: the same library, read by feature.

The feature-at-location module matches an observation in two questions. Which nodes could I be standing on — that is this module's match_all_node_features — and, once a pose is being tested, does the pose agree. The first question is what narrows a graph of twenty thousand nodes to the handful worth walking from, so it is asked of the whole graph at once.

Why this is a module of functions and not a second struct

Monty spells the difference as a subclass, and the difference is four functions: hypotheses with poses in them, a node's frame, the matching nodes, and the comparison behind them. Everything else — the models, the feature rows, the learning — is GraphMemory's, and a second struct would be the same fields with a different name. So new/1 builds a GraphMemory and these functions read it, which is the same shape Monty.Frameworks.Models.GraphMatching.GraphMemory already has for the evidence module's EvidenceGraphMemory.

The comparison

Monty builds two arrays per column — the observed value plus and minus its tolerance — and asks numpy whether each node's value lies between them. A feature that is circular (hue, the first of hsv's three) wraps through 1, so a tolerance of 0.1 around 0.05 covers [0.95, 1] and [0, 0.15] and the test becomes "outside" rather than "inside". A node with no stored value has no evidence for that column and does not match, which is what Monty's NaN pre-fill does.

Where the tolerances come from

Monty's memory holds features_to_use and the tolerances, because in Python the module assigns its own tolerances to it. This port's memory holds the feature names it keeps rows for, which is what the evidence kernel reads, so the tolerances are passed in by the learning module that owns them — the same numbers, in the same place as the config that stated them.

Summary

Functions

Monty's FeatureGraphMemory.get_initial_hypotheses.

Monty's get_nodes_with_matching_features: the nodes worth walking from.

Monty's get_rotation_features_at_node: a node's frame, as three directions.

Monty's _match_all_node_features: which nodes could be where I am.

A memory that can answer which nodes have which features.

Functions

get_initial_hypotheses(memory)

Monty's FeatureGraphMemory.get_initial_hypotheses.

Three answers, not two: every object is possible, every node of it is a place to have started — as a path of one location, because that is what the next step extends — and no pose is ruled out yet.

get_nodes_with_matching_features(memory, graph_id, features, tolerances, options \\ [])

Monty's get_nodes_with_matching_features: the nodes worth walking from.

:list_of_lists wraps every location in a list of its own, which is how Monty hands back a path that starts on a node — the first observation has no displacement behind it, so a path is one location.

Only the first input channel is matched: this module is not used in a heterarchy, and Monty says the same in the same place.

get_rotation_features_at_node(memory, graph_id, node_id, channel)

Monty's get_rotation_features_at_node: a node's frame, as three directions.

The stored pose vectors of one node, shaped as the surface normal and the two principal curvature directions — what align_orthonormal_vectors/2 expects.

match_all_node_features(memory, features, channel, graph_id, tolerances)

Monty's _match_all_node_features: which nodes could be where I am.

The answer is the indices of the nodes whose every stored feature lies within the observed value's tolerance — the row layout and its column names being the memory's own (feature_order), which is why the observation is walked in that order rather than in the order its features arrive.

A graph being matched always has rows: they are built when the episode starts, which is what FeatureGraphLM.init_from_ltm/1 does before the first step.

new(options \\ [])

A memory that can answer which nodes have which features.

Monty's FeatureGraphMemory.__init__ takes only the thresholds that decide when two observations are the same node; the memory it returns is the base one, because there is nothing else to store.