Source code for tbp.monty.experiment.recognition_policy
# 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 dataclasses import dataclass
from typing import Protocol
from typing_extensions import Self
from tbp.monty.frameworks.models.monty_base import MontyBase
__all__ = [
"MinimumCount",
"RecognitionPolicy",
"RecognitionResult",
]
[docs]@dataclass
class RecognitionResult:
"""Aggregated result from the Recognition Policy."""
is_done: bool
[docs]class RecognitionPolicy(Protocol):
"""Decides what constitutes "recognition" in an Experiment.
Each Learning Module determines its own Recognition Status independently of the
others. The Recognition Policy turns the per-LM status into the single decision
of whether Monty has recognized the object.
"""
def __call__(self: Self, model: MontyBase, step: int) -> RecognitionResult:
"""Apply this policy to produce a Recognition Result from per-LM status.
Args:
model: The Monty model to be queried.
step: The Experiment step number.
Returns:
An aggregate Recognition Result based on this policy.
"""
...
class MontyIsDone(RecognitionPolicy):
"""Monty `model.is_done == True` (legacy policy)."""
_max_steps: int | None
"""The maximum number of Monty steps before reaching a conclusion."""
def __init__(self: Self, max_steps: int | None = None) -> None:
"""Initialize the policy.
Args:
max_steps: The maximum number of Monty steps before reaching a conclusion.
Raises:
ValueError: If `max_steps` is not `None` and not positive.
"""
if max_steps is not None and max_steps <= 0:
raise ValueError("max_steps must be positive")
self._max_steps = max_steps
def __call__(self: Self, model: MontyBase, step: int) -> RecognitionResult:
if self._max_steps is not None and step >= self._max_steps:
return RecognitionResult(is_done=True)
return RecognitionResult(is_done=model.is_done)
[docs]class MinimumCount(RecognitionPolicy):
"""`count` LMs have reached a conclusion, or `max_steps` have been taken."""
_count: int
"""The minimum number of LMs that must reach a conclusion."""
_max_steps: int
"""The maximum number of Monty steps before reaching a conclusion."""
[docs] def __init__(self: Self, count: int, max_steps: int) -> None:
"""Initialize the policy.
Args:
count: The number of Learning Modules that must reach a conclusion for
the policy to be satisfied.
max_steps: The maximum number of Monty steps before reaching a conclusion.
Raises:
ValueError: If `count` or `max_steps` are not positive.
"""
if count <= 0:
raise ValueError("count must be positive")
self._count = count
if max_steps <= 0:
raise ValueError("max_steps must be positive")
self._max_steps = max_steps
def __call__(self: Self, model: MontyBase, step: int) -> RecognitionResult:
if step >= self._max_steps:
return RecognitionResult(is_done=True)
num_matched = sum(
1
for lm in model.learning_modules
if lm.recognition_status.conclusion is not None
)
is_done = num_matched >= self._count
return RecognitionResult(is_done=is_done)