Skip to content

Module streamgen.parameter

⚙️ parameters are variables that change over time according to a schedule.

View Source
"""⚙️ parameters are variables that change over time according to a schedule."""

from __future__ import annotations

from collections.abc import Iterable

from copy import deepcopy

from itertools import cycle

from typing import Generic, Self, TypeAlias, TypedDict, TypeVar

from beartype import beartype

from streamgen.enums import (

    ParameterOutOfRangeStrategy,

    ParameterOutOfRangeStrategyLit,

)

from streamgen.exceptions import ParameterOutOfRangeError

T = TypeVar("T")

@beartype()

class Parameter(Generic[T]):

    """⚙️ parameters are variables that change over time according to a schedule.

    Args:

        name (str): variable name of the parameter. Defaults to "param".

        value (Generic[T] | None): the value of the parameter.

            if None and schedule is defined, use the first value of the schedule.

            Defaults to None.

        schedule (Iterable[T] | None): a schedule for the parameter.

            Defaults to None.

        parameter_out_of_range_strategy

            (ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit):

            strategy which defines what happens when calling `update` and there is

            no valid next value available. Defaults to `"hold"`, where the last

            valid value is held (not updated).

    """

    def __init__(  # noqa: D107

        self,

        name: str = "param",

        value: T | None = None,

        schedule: Iterable[T] | None = None,

        strategy: ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit = "hold",

    ) -> None:

        assert "." not in name, "`.` in parameter names are reserved for scopes."  # noqa: S101

        self.name = name

        self.value = value

        self.schedule = iter(schedule) if schedule is not None else None

        self.strategy = strategy

        if strategy == ParameterOutOfRangeStrategy.CYCLE:

            self.schedule = cycle(self.schedule)

        if self.value is None and self.schedule:

            self.value = self.update()

        self._initial_schedule = deepcopy(self.schedule)

        self._initial_value = self.value

    def update(self) -> T:

        """🆙 updates the value according to the schedule and strategy.

        Returns:

            T: updated value

        Raises:

            ParameterOutOfRangeError: when an `update` leads to an invalid value.

        """

        if self.schedule is None:

            return self.value

        try:

            self.value = next(self.schedule)

        except StopIteration as err:

            if self.strategy == ParameterOutOfRangeStrategy.RAISE_EXCEPTION:

                raise ParameterOutOfRangeError from err

        return self.value

    def __getitem__(self, idx: int) -> T:

        """🫱 gets the value after a certain number of update steps.

        This function resets the current schedule to its original schedule during construction.

        Returns:

            T: value after `idx` updates

        Raises:

            ParameterOutOfRangeError: when an `update` leads to an invalid value.

        """

        self.schedule = deepcopy(self._initial_schedule)

        self.value = self._initial_value

        for _ in range(idx):

            self.update()

        return self.value

    def __repr__(self) -> str:

        """🏷️ Returns the string representation `str(self)`.

        Returns:

            str: string representation of self

        """

        return f"{self.name}={self.value}"

    def __or__(self, param: Self) -> ParameterStore:

        """➕ combines two Parameters to a `ParameterStore` using `|`.

        Args:

            param (Parameter): another parameter

        Returns:

            ParameterStore: combined parameter store

        """  # noqa: RUF002

        return ParameterStore([self, param])

class ParameterDict(Generic[T], TypedDict, total=False):

    """📖 typed dictionary of `streamgen.parameter.Parameter`."""

    name: str | None

    value: T | None

    schedule: Iterable[T] | None

    strategy: ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit | None

ScopedParameterDict: TypeAlias = dict[str, ParameterDict | dict[str, ParameterDict]]

"""🔭📖 representation of multiple `streamgen.parameter.Parameter` as a dictionary.

The dictionary can be nested one level to create parameter scopes.

All top-level parameters are considered as having `scope=None`.

Examples:

    >>> params = {

            "var1": {

                "value": 1,

                "schedule": [2,3],

                "strategy": "cycle",

            },

            "var2": {

                "name": "var2", # can be present, but is not needed

                "schedule": [0.1, 0.2, 0.3],

            },

            "scope1": {

                "var1": { # var1 can be used again since its inside a scope

                    "value": 1,

                    "schedule": [2,3],

                    "strategy": "cycle",

                },

            },

        }

"""

# 🔄️ bottom-level import required to avoid circular dependency

from streamgen.parameter.store import ParameterStore  # noqa: E402

Sub-modules

Variables

T

Classes

Parameter

class Parameter(
    name: str = 'param',
    value: Optional[~T] = None,
    schedule: collections.abc.Iterable[~T] | None = None,
    strategy: Union[streamgen.enums.ParameterOutOfRangeStrategy, Literal['hold', 'cycle', 'raise exception']] = 'hold'
)

⚙️ parameters are variables that change over time according to a schedule.

Attributes

Name Type Description Default
name str variable name of the parameter. Defaults to "param". "param"
value Generic[T] None the value of the parameter.
if None and schedule is defined, use the first value of the schedule.
Defaults to None.
schedule Iterable[T] None a schedule for the parameter.
Defaults to None.
parameter_out_of_range_strategy ParameterOutOfRangeStrategy ParameterOutOfRangeStrategyLit strategy which defines what happens when calling update and there is
no valid next value available. Defaults to "hold", where the last
valid value is held (not updated).
View Source
@beartype()

class Parameter(Generic[T]):

    """⚙️ parameters are variables that change over time according to a schedule.

    Args:

        name (str): variable name of the parameter. Defaults to "param".

        value (Generic[T] | None): the value of the parameter.

            if None and schedule is defined, use the first value of the schedule.

            Defaults to None.

        schedule (Iterable[T] | None): a schedule for the parameter.

            Defaults to None.

        parameter_out_of_range_strategy

            (ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit):

            strategy which defines what happens when calling `update` and there is

            no valid next value available. Defaults to `"hold"`, where the last

            valid value is held (not updated).

    """

    def __init__(  # noqa: D107

        self,

        name: str = "param",

        value: T | None = None,

        schedule: Iterable[T] | None = None,

        strategy: ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit = "hold",

    ) -> None:

        assert "." not in name, "`.` in parameter names are reserved for scopes."  # noqa: S101

        self.name = name

        self.value = value

        self.schedule = iter(schedule) if schedule is not None else None

        self.strategy = strategy

        if strategy == ParameterOutOfRangeStrategy.CYCLE:

            self.schedule = cycle(self.schedule)

        if self.value is None and self.schedule:

            self.value = self.update()

        self._initial_schedule = deepcopy(self.schedule)

        self._initial_value = self.value

    def update(self) -> T:

        """🆙 updates the value according to the schedule and strategy.

        Returns:

            T: updated value

        Raises:

            ParameterOutOfRangeError: when an `update` leads to an invalid value.

        """

        if self.schedule is None:

            return self.value

        try:

            self.value = next(self.schedule)

        except StopIteration as err:

            if self.strategy == ParameterOutOfRangeStrategy.RAISE_EXCEPTION:

                raise ParameterOutOfRangeError from err

        return self.value

    def __getitem__(self, idx: int) -> T:

        """🫱 gets the value after a certain number of update steps.

        This function resets the current schedule to its original schedule during construction.

        Returns:

            T: value after `idx` updates

        Raises:

            ParameterOutOfRangeError: when an `update` leads to an invalid value.

        """

        self.schedule = deepcopy(self._initial_schedule)

        self.value = self._initial_value

        for _ in range(idx):

            self.update()

        return self.value

    def __repr__(self) -> str:

        """🏷️ Returns the string representation `str(self)`.

        Returns:

            str: string representation of self

        """

        return f"{self.name}={self.value}"

    def __or__(self, param: Self) -> ParameterStore:

        """➕ combines two Parameters to a `ParameterStore` using `|`.

        Args:

            param (Parameter): another parameter

        Returns:

            ParameterStore: combined parameter store

        """  # noqa: RUF002

        return ParameterStore([self, param])

Ancestors (in MRO)

  • typing.Generic

Methods

update

def update(
    self
) -> ~T

🆙 updates the value according to the schedule and strategy.

Returns:

Type Description
T updated value

Raises:

Type Description
ParameterOutOfRangeError when an update leads to an invalid value.
View Source
    def update(self) -> T:

        """🆙 updates the value according to the schedule and strategy.

        Returns:

            T: updated value

        Raises:

            ParameterOutOfRangeError: when an `update` leads to an invalid value.

        """

        if self.schedule is None:

            return self.value

        try:

            self.value = next(self.schedule)

        except StopIteration as err:

            if self.strategy == ParameterOutOfRangeStrategy.RAISE_EXCEPTION:

                raise ParameterOutOfRangeError from err

        return self.value

ParameterDict

class ParameterDict(
    /,
    *args,
    **kwargs
)

📖 typed dictionary of streamgen.parameter.Parameter.

View Source
class ParameterDict(Generic[T], TypedDict, total=False):

    """📖 typed dictionary of `streamgen.parameter.Parameter`."""

    name: str | None

    value: T | None

    schedule: Iterable[T] | None

    strategy: ParameterOutOfRangeStrategy | ParameterOutOfRangeStrategyLit | None

Ancestors (in MRO)

  • typing.Generic
  • builtins.dict

Methods

clear

def clear(
    ...
)

D.clear() -> None. Remove all items from D.

copy

def copy(
    ...
)

D.copy() -> a shallow copy of D

fromkeys

def fromkeys(
    iterable,
    value=None,
    /
)

Create a new dictionary with keys from iterable and values set to value.

get

def get(
    self,
    key,
    default=None,
    /
)

Return the value for key if key is in the dictionary, else default.

items

def items(
    ...
)

D.items() -> a set-like object providing a view on D's items

keys

def keys(
    ...
)

D.keys() -> a set-like object providing a view on D's keys

pop

def pop(
    ...
)

D.pop(k[,d]) -> v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem

def popitem(
    self,
    /
)

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault

def setdefault(
    self,
    key,
    default=None,
    /
)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update

def update(
    ...
)

D.update([E, ]**F) -> None. Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values

def values(
    ...
)

D.values() -> an object providing a view on D's values