diff --git a/evolver/device.py b/evolver/device.py index c51429a..e2b8e94 100644 --- a/evolver/device.py +++ b/evolver/device.py @@ -1,6 +1,9 @@ import logging import time from collections import defaultdict +from math import prod + +from pydantic import Field, ValidationInfo, field_validator from evolver.base import BaseInterface, ConfigDescriptor from evolver.connection.interface import Connection @@ -20,6 +23,12 @@ class Evolver(BaseInterface): class Config(BaseInterface.Config): name: str = "Evolver" experiment: str = "unspecified" + vial_layout: list[int] = Field( + default=settings.DEFAULT_VIAL_LAYOUT, + description="The layout of the vials in 2 or 3 dimensions. Always left-to-right bottom-top-top order.", + min_length=2, + max_length=3, + ) vials: list = list(range(settings.DEFAULT_NUMBER_OF_VIALS_PER_BOX)) hardware: dict[str, ConfigDescriptor | HardwareDriver] = {} controllers: list[ConfigDescriptor | Controller] = [] @@ -33,6 +42,14 @@ class Config(BaseInterface.Config): skip_control_on_read_failure: bool = True log_level: int = EVENT + @field_validator("vials", mode="after") + @classmethod + def validate_vials(cls, value: list[int], info: ValidationInfo): + total_slots = prod(info.data["vial_layout"]) + if len(value) > total_slots or max(value) >= total_slots: + raise ValueError(f"Vials configured exceed total available slots from layout {total_slots}") + return value + def __init__(self, *args, **kwargs): self.last_read = defaultdict(lambda: int(-1)) super().__init__(*args, evolver=self, **kwargs) diff --git a/evolver/settings.py b/evolver/settings.py index 9fa36fa..78dad61 100644 --- a/evolver/settings.py +++ b/evolver/settings.py @@ -14,6 +14,7 @@ class Settings(BaseSettings): CONNECTION_REUSE_POLICY_DEFAULT: bool = True DEFAULT_LOOP_INTERVAL: int = 20 DEFAULT_NUMBER_OF_VIALS_PER_BOX: int = 16 + DEFAULT_VIAL_LAYOUT: list[int] = [4, 4] LOG_LEVEL: str = "INFO" LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ROOT_CALIBRATOR_FILE_STORAGE_PATH: Path = Path("calibration_files") diff --git a/evolver/tests/test_device.py b/evolver/tests/test_device.py index aeb42da..1ef1d3c 100644 --- a/evolver/tests/test_device.py +++ b/evolver/tests/test_device.py @@ -215,3 +215,10 @@ def off(self): assert not demo_evolver.enable_control assert demo_evolver.hardware["testeffector"].aborted assert not demo_evolver.enable_control + + def test_vials_layout_validation(self): + with pytest.raises(ValueError, match="Vials configured exceed total available slots"): + Evolver.Config(vials=[0, 1, 2, 3, 4], vial_layout=[2, 2]) + with pytest.raises(ValueError, match="Vials configured exceed total available slots"): + Evolver.Config(vials=[0, 1, 2, 8], vial_layout=[2, 2]) + Evolver.Config(vials=[0, 1, 2, 3], vial_layout=[2, 2])