Source code for esapp.utils.contingency

"""
Contingency Builder Utilities
=============================

Provides fluent builder tools for constructing transient stability
contingency event sequences.

Classes
-------
ContingencyBuilder
    Fluent builder for TS contingencies with method chaining.
SimAction
    Enumeration of standard simulation action strings.
"""

from enum import Enum
from typing import List, Tuple, Union, Any

from pandas import DataFrame

__all__ = ['ContingencyBuilder', 'SimAction']


[docs] class SimAction(str, Enum): """Enumeration of standard simulation actions to prevent magic string errors.""" FAULT_3PB = "FAULT 3PB SOLID" CLEAR_FAULT = "CLEARFAULT" OPEN = "OPEN" CLOSE = "CLOSE"
[docs] class ContingencyBuilder: """ Fluent builder for Transient Stability (TS) contingencies. Constructs a timeline of events to be simulated using method chaining. Parameters ---------- name : str Unique name for the contingency. runtime : float, optional Simulation duration in seconds (default: 10.0). Attributes ---------- name : str The contingency name. runtime : float Simulation end time in seconds. Example ------- >>> builder = ContingencyBuilder("GenTrip", runtime=5.0) >>> builder.at(1.0).fault_bus("101").at(1.1).clear_fault("101") """ def __init__(self, name: str, runtime: float = 10.0): self.name = name self.runtime = runtime self._current_time: float = 0.0 self._events: List[Tuple[float, str, str, str]] = []
[docs] def at(self, t: float) -> 'ContingencyBuilder': """ Set the current time cursor for subsequent events. Parameters ---------- t : float Time in seconds (must be non-negative). Returns ------- ContingencyBuilder Self for method chaining. Raises ------ ValueError If time is negative. """ if t < 0: raise ValueError(f"Time cannot be negative: {t}") self._current_time = t return self
[docs] def add_event(self, obj_type: str, who: str, action: Union[str, SimAction]) -> 'ContingencyBuilder': """ Add a generic event at the current time cursor. Parameters ---------- obj_type : str PowerWorld object type (e.g., "Bus", "Gen", "Branch"). who : str Object identifier string. action : Union[str, SimAction] Action to perform (e.g., SimAction.OPEN or "OPEN"). Returns ------- ContingencyBuilder Self for method chaining. """ act_str = action.value if isinstance(action, SimAction) else str(action) self._events.append((self._current_time, obj_type, who, act_str)) return self
[docs] def fault_bus(self, bus: Any) -> 'ContingencyBuilder': """Apply a 3-phase solid fault to a bus at the current time.""" return self.add_event("Bus", str(bus), SimAction.FAULT_3PB)
[docs] def clear_fault(self, bus: Any) -> 'ContingencyBuilder': """Clear the fault at a bus at the current time.""" return self.add_event("Bus", str(bus), SimAction.CLEAR_FAULT)
[docs] def trip_gen(self, bus: Any, gid: str = "1") -> 'ContingencyBuilder': """Trip (open) a generator at the current time.""" return self.add_event("Gen", f"{bus} '{gid}'", SimAction.OPEN)
[docs] def trip_branch(self, f_bus: Any, t_bus: Any, ckt: str = "1") -> 'ContingencyBuilder': """Trip (open) a branch at the current time.""" return self.add_event("Branch", f"{f_bus} {t_bus} '{ckt}'", SimAction.OPEN)
[docs] def to_dataframes(self) -> Tuple[DataFrame, DataFrame]: """ Generates DataFrames matching the ESA GObject schemas. Returns: Tuple[DataFrame, DataFrame]: (Contingency Definition, Element Definitions) """ # 1. Contingency Header ctg_df = DataFrame({ 'TSCTGName': [self.name], 'StartTime': [0.0], 'EndTime': [self.runtime], 'CTGSkip': ['NO'] }) # 2. Elements if not self._events: return ctg_df, DataFrame() # Vectorized list creation is generally fast enough here ele_rows = [ { 'TSCTGName': self.name, 'TSEventString': f"{action} {obj_type} {who}", 'TSTimeInSeconds': t, 'WhoAmI': f"{obj_type} {who}", 'TSTimeInCycles': t * 60.0, } for t, obj_type, who, action in self._events ] return ctg_df, DataFrame(ele_rows)