Source code for saealib.core.contracts.feedbacks

"""Feedback batches and consumer-side feedback contracts."""

from __future__ import annotations

from dataclasses import dataclass

import numpy as np

from saealib.core.contracts.feedback import (
    BY_PROPOSAL,
    COMPLETE_BATCH,
    COMPLETION_MODES,
    FEEDBACK_CHANNELS,
    FEEDBACK_GROUPINGS,
    IN_ORDER,
    MULTIPLICITY_MODES,
    ORDERING_MODES,
    SINGLE,
    CompletionMode,
    FeedbackChannel,
    FeedbackGrouping,
    MultiplicityMode,
    OrderingMode,
)
from saealib.core.contracts.observation import (
    OBSERVATION_SOURCES,
    TRUE,
    ObservationSource,
)
from saealib.core.contracts.observations import ObservationBatch
from saealib.core.contracts.proposals import FeedbackRequirement, ProposalId
from saealib.exceptions import ValidationError

__all__ = ["FeedbackBatch", "FeedbackContract"]


def _non_negative_int(value: object, name: str) -> int:
    if isinstance(value, (bool, np.bool_)) or not isinstance(value, (int, np.integer)):
        raise ValidationError(f"{name} must be a non-negative integer")
    value = int(value)
    if value < 0 or value > np.iinfo(np.int64).max:
        raise ValidationError(f"{name} must be a non-negative int64 integer")
    return value


[docs] @dataclass(frozen=True, kw_only=True) class FeedbackBatch: """Observations delivered for one proposal.""" proposal_id: ProposalId observations: ObservationBatch channel: FeedbackChannel final: bool sequence: int def __post_init__(self) -> None: object.__setattr__( self, "proposal_id", _non_negative_int(self.proposal_id, "proposal_id") ) if not isinstance(self.observations, ObservationBatch): raise ValidationError("observations must be an ObservationBatch") if not FEEDBACK_CHANNELS.contains(self.channel): raise ValidationError(f"unknown feedback channel: {self.channel!r}") if not isinstance(self.final, bool): raise ValidationError("final must be a boolean") object.__setattr__( self, "sequence", _non_negative_int(self.sequence, "sequence") )
[docs] @dataclass(frozen=True, kw_only=True) class FeedbackContract: """Declare the feedback deliveries a component can consume.""" accepted_channels: frozenset[FeedbackChannel] accepted_sources: frozenset[ObservationSource] = frozenset({TRUE}) completion: CompletionMode = COMPLETE_BATCH ordering: OrderingMode = IN_ORDER multiplicity: MultiplicityMode = SINGLE grouping: FeedbackGrouping = BY_PROPOSAL def __post_init__(self) -> None: try: channels = frozenset(self.accepted_channels) except TypeError as exc: raise ValidationError( "accepted_channels must be an iterable of registered channels" ) from exc if not channels or any( not FEEDBACK_CHANNELS.contains(value) for value in channels ): raise ValidationError("accepted_channels must contain registered channels") try: sources = frozenset(self.accepted_sources) except TypeError as exc: raise ValidationError( "accepted_sources must be an iterable of registered sources" ) from exc if not sources or any( not OBSERVATION_SOURCES.contains(value) for value in sources ): raise ValidationError("accepted_sources must contain registered sources") if not COMPLETION_MODES.contains(self.completion): raise ValidationError(f"unknown completion mode: {self.completion!r}") if not ORDERING_MODES.contains(self.ordering): raise ValidationError(f"unknown ordering mode: {self.ordering!r}") if not MULTIPLICITY_MODES.contains(self.multiplicity): raise ValidationError(f"unknown multiplicity mode: {self.multiplicity!r}") if not FEEDBACK_GROUPINGS.contains(self.grouping): raise ValidationError(f"unknown feedback grouping: {self.grouping!r}") object.__setattr__(self, "accepted_channels", channels) object.__setattr__(self, "accepted_sources", sources)
[docs] def contains_requirement(self, requirement: FeedbackRequirement) -> bool: """Return whether a requirement is no wider than this contract.""" if not isinstance(requirement, FeedbackRequirement): raise TypeError("requirement must be a FeedbackRequirement") if requirement.completion == COMPLETE_BATCH: completion_contained = True else: completion_contained = self.completion != COMPLETE_BATCH return completion_contained and all( quantity.sources <= self.accepted_sources for quantity in requirement.quantities )
[docs] def contains(self, requirement: FeedbackRequirement) -> bool: """Alias for :meth:`contains_requirement`.""" return self.contains_requirement(requirement)