Source code for tbp.monty.attention.goal_filter
# Copyright 2026 Thousand Brains Project
#
# Copyright may exist in Contributors' modifications
# and/or contributions to the work.
#
# Use of this source code is governed by the MIT
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
from __future__ import annotations
from typing import Protocol, Sequence
import numpy as np
from tbp.monty.attention.voxel_grid import VoxelGrid
from tbp.monty.cmp import Goal
[docs]class GoalFilter(Protocol):
def __call__(self, voxel_grid: VoxelGrid, goals: Sequence[Goal]) -> list[Goal]: ...
[docs]class NoopGoalFilter(GoalFilter):
"""Pass every goal through unchanged."""
def __call__(
self,
voxel_grid: VoxelGrid, # noqa: ARG002
goals: Sequence[Goal],
) -> list[Goal]:
"""Return the goals unchanged.
Args:
voxel_grid: Unused.
goals: The goals to (not) filter.
Returns:
The goals, unfiltered.
"""
return list(goals)
[docs]class HardGoalFilter(GoalFilter):
"""Keep only the goals that fall within a positively weighted voxel.
Rules:
- If the attention grid is empty, let all goals through. Otherwise...
- If all voxels are inhibited (negative-weighted), allow all goals to pass
that do not land in negative voxels.
- If a goal does not fall within a voxel, filter it out. Otherwise...
- If the goals' voxel weight is <= 0, filter the goal out. Otherwise,
let it through.
"""
def __call__(self, voxel_grid: VoxelGrid, goals: Sequence[Goal]) -> list[Goal]:
"""Filter the goals against the grid.
Args:
voxel_grid: The current voxel grid.
goals: The goals to filter.
Returns:
The goals filtered according to the rules.
"""
if len(voxel_grid) == 0 or len(goals) == 0:
return list(goals)
located = [g for g in goals if g.location is not None]
unlocated = [g for g in goals if g.location is None]
if not located:
return unlocated
points = np.array([g.location for g in located])
voxel_weights = voxel_grid.weights_at_points(points, fill_value=np.nan)
all_inhibited = bool((voxel_grid.weights() < 0).all())
kept = [
goal
for goal, voxel_weight in zip(located, voxel_weights)
if voxel_weight > 0 or (all_inhibited and np.isnan(voxel_weight))
]
return kept + unlocated