11. Introduce ControllerVector for Indexed Sub-Controllers#
Date: 2025-11-10
Related: PR #192
Status#
Accepted
Context#
Many devices have multiple identical components that need individual control: multi-axis motion stages, multi-channel power supplies, multiple file writers, etc. Developers must manually register each indexed controller with string-based names and check for this pattern throughout the code to perform different logic
for i in range(num_axes):
motor = MotorController()
self.add_sub_controller(f"Axis{i}", motor) # String-based naming
This approach lacks collection semantics. Accessing controllers requires string manipulation (controller.sub_controllers["Axis0"]) and heuristics to test if an attribute is numerical, rather than natural indexing (controller.axes[0]).
Decision#
Introduce ControllerVector, a specialized controller type for managing collections of indexed sub-controllers with dict-like semantics.
ControllerVector implements MutableMapping with integer-only keys, providing natural indexing, iteration, and length operations. It supports non-contiguous indices and can have shared attributes alongside the indexed sub-controllers.
Key architectural changes:
ControllerVectorimplements__getitem__,__setitem__,__iter__,__len__Keys enforced to be integers only
Supports sparse indexing:
{1: motor1, 5: motor5, 10: motor10}Can be subclassed to add shared attributes
Inherits from
BaseControllerfor full integration with controller hierarchy
Consequences#
Benefits#
Natural Collection Semantics: Dict-like interface provides familiar indexing and iteration
Consistency: Integer-only keys prevent naming inconsistencies
Clear Intent: ControllerVector explicitly signals “collection of identical components”
Sparse Collections: Non-contiguous indices support flexible numbering schemes
Type Safety: Integer indexing enforced by type hints and runtime checks
Shared Attributes: Can add attributes to the vector itself, separate from indexed components
Migration Pattern#
Before (Manual registration):
class StageController(Controller):
def __init__(self, num_axes: int):
super().__init__()
for i in range(num_axes):
motor = MotorController()
self.add_sub_controller(f"Axis{i}", motor)
# Access via string keys
first = self.sub_controllers["Axis0"]
After (ControllerVector):
class StageController(Controller):
def __init__(self, num_axes: int):
super().__init__()
self.axes = ControllerVector(
{i: MotorController() for i in range(num_axes)},
description="Motor axes"
)
# Natural dict-like access
first = self.axes[0]
for i, motor in self.axes.items():
print(f"Motor {i}: {motor}")