Source code for eclypse.policies.workload.diurnal_load
"""Diurnal load workload policy."""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING
from eclypse.policies.workload._helpers import apply_selected_asset_transform
if TYPE_CHECKING:
from eclypse.graph.asset_graph import AssetGraph
from eclypse.utils.types import UpdatePolicy
@dataclass(slots=True)
class DiurnalLoadPolicy:
"""Apply sinusoidal multiplicative load over a period."""
amplitude: float
period: int
baseline: float = 1.0
node_assets: str | list[str] | None = None
edge_assets: str | list[str] | None = None
step: int = 0
def __post_init__(self):
"""Validate the diurnal load configuration.
Args:
None.
Returns:
None.
"""
if self.period <= 0:
raise ValueError("period must be strictly positive.")
if self.node_assets is None and self.edge_assets is None:
raise ValueError(
"At least one of node_assets or edge_assets must be provided."
)
def __call__(self, graph: AssetGraph):
"""Apply one diurnal load step.
Args:
graph (AssetGraph): Asset graph to mutate.
Returns:
None.
"""
factor = self.baseline + (
self.amplitude * math.sin((2 * math.pi * self.step) / self.period)
)
for _, data in graph.nodes.data():
_scale_assets(data, self.node_assets, factor)
for _, _, data in graph.edges.data():
_scale_assets(data, self.edge_assets, factor)
self.step += 1
[docs]
def diurnal_load(
*,
amplitude: float,
period: int,
baseline: float = 1.0,
node_assets: str | list[str] | None = None,
edge_assets: str | list[str] | None = None,
) -> UpdatePolicy:
"""Apply sinusoidal multiplicative load over a period.
Args:
amplitude (float): Peak sinusoidal multiplier offset.
period (int): Number of calls in one cycle.
baseline (float): Base multiplier around which the load oscillates.
node_assets (str | list[str] | None): Optional node asset key selector.
edge_assets (str | list[str] | None): Optional edge asset key selector.
Returns:
Stateful policy that applies diurnal load.
"""
return DiurnalLoadPolicy(
amplitude=amplitude,
period=period,
baseline=baseline,
node_assets=node_assets,
edge_assets=edge_assets,
)
def _scale_assets(data, assets, factor):
"""Scale selected assets inside one asset mapping.
Args:
data (dict[str, object]): Asset mapping to mutate.
assets (str | list[str] | None): Optional asset selector.
factor (float): Multiplicative factor to apply.
Returns:
None.
"""
apply_selected_asset_transform(
data,
assets,
transform=lambda _key, current: current * factor,
)