Source code for eclypse.workflow.trigger.cascade

"""Module for CascadeTrigger class.

A cascade trigger allows an EclypseEvent to be triggered based on the state of
another event in the simulation workflow.

Available cascade triggers include:
- CascadeTrigger: Fires when a specific event occurs.
- PeriodicCascadeTrigger: Fires at regular intervals based on another event.
- ScheduledCascadeTrigger: Fires at specific times based on another event.
"""

from __future__ import annotations

import os
import random
from typing import (
    TYPE_CHECKING,
)

from eclypse.utils.constants import RND_SEED
from eclypse.workflow.trigger.trigger import Trigger

if TYPE_CHECKING:
    from eclypse.workflow.event.event import EclypseEvent


[docs] class CascadeTrigger(Trigger): """A trigger that fires based on the state of another event."""
[docs] def __init__( self, trigger_event: str, ): """Initialize the cascade trigger. Args: trigger_event (str): The name of the event that can trigger this cascade. """ self.trigger_event = trigger_event
[docs] def trigger(self, trigger_event: EclypseEvent | None = None) -> bool: """Check if the trigger should fire based on its condition.""" return trigger_event is not None and trigger_event.name == self.trigger_event
def __repr__(self) -> str: """Return a string representation of the cascade trigger.""" return f"CascadeTrigger(trigger_event={self.trigger_event})"
[docs] class PeriodicCascadeTrigger(CascadeTrigger): """A trigger that fires based on the state of another event at regular intervals."""
[docs] def __init__( self, trigger_event: str, every_n_triggers: int = 1, ): """Initialize the cascade trigger. Args: trigger_event (str): The name of the event that can trigger this cascade. every_n_triggers (int): The number of calls to the triggering event required to trigger this cascade. Defaults to 1. """ super().__init__(trigger_event) self.every_n_triggers = every_n_triggers
[docs] def trigger(self, trigger_event: EclypseEvent | None = None) -> bool: """Check if the trigger should fire based on its condition.""" return ( super().trigger(trigger_event) and trigger_event.n_triggers % self.every_n_triggers == 0 # type: ignore[union-attr] )
def __repr__(self) -> str: """Return a string representation of the cascade trigger.""" return ( f"PeriodicCascadeTrigger(trigger_event={self.trigger_event}, " f"every_n_triggers={self.every_n_triggers})" )
[docs] class ScheduledCascadeTrigger(CascadeTrigger): """A trigger that fires based on the state of another event at scheduled times."""
[docs] def __init__( self, trigger_event: str, scheduled_times: list[int], ): """Initialize the cascade trigger. Args: trigger_event (str): The name of the event that can trigger this cascade. scheduled_times (list[int]): A list of scheduled times \ (in number of triggers) when the trigger should fire. Raises: ValueError: If scheduled_times is empty. """ super().__init__(trigger_event) if not scheduled_times: raise ValueError("'scheduled_times' cannot be empty!") self.scheduled_times = sorted(scheduled_times)
[docs] def trigger(self, trigger_event: EclypseEvent | None = None) -> bool: """Check if the trigger should fire based on its condition.""" if ( super().trigger(trigger_event) and self.scheduled_times and self.scheduled_times[0] == trigger_event.n_triggers # type: ignore[union-attr] ): self.scheduled_times.pop(0) return True return False
def __repr__(self) -> str: """Return a string representation of the cascade trigger.""" return ( f"ScheduledCascadeTrigger(trigger_event={self.trigger_event}, " f"scheduled_times={self.scheduled_times})" )
[docs] class RandomCascadeTrigger(CascadeTrigger): """A trigger that fires based on the state of another event at random intervals."""
[docs] def __init__( self, trigger_event: str, probability: float = 0.5, seed: int | None = None, ): """Initialize the random cascade trigger. Args: trigger_event (str): The name of the event that can trigger this cascade. probability (float): The probability of the trigger firing when the triggering event occurs. Defaults to 0.5. seed (int | None): An optional seed for the random number generator. Defaults to None. """ super().__init__(trigger_event) self.probability = probability self.seed = seed self.rnd = None
[docs] def prepare(self): """Initialize the random number generator.""" self.seed = int(os.getenv(RND_SEED)) if self.seed is None else self.seed self.rnd = random.Random(self.seed)
[docs] def trigger(self, trigger_event: EclypseEvent | None = None) -> bool: """Check if the trigger should fire based on its condition.""" if self.rnd is None: raise RuntimeError( "Trigger not initialised. Call prepare() before trigger()." ) return super().trigger(trigger_event) and self.rnd.random() < self.probability
def __repr__(self) -> str: """Return a string representation of the random cascade trigger.""" return ( f"RandomCascadeTrigger(trigger_event={self.trigger_event}, " f"probability={self.probability})" )