1- """Top-level orchestration for WEC-Grid simulations .
1+ """Simulation orchestration for WEC-Grid.
22
3- Defines the :class:`Engine` that coordinates WEC farms, power system modelers,
4- database access, and visualization utilities. The module links PSS®E and
5- PyPSA backends, manages time through :class:`WECGridTime`, and integrates
6- WEC-Sim for device-level modeling.
3+ Provides the :class:`Engine` entry point that wires together WEC farms,
4+ power-system modelers (PSS®E, PyPSA), time management, a results database,
5+ and plotting utilities.
76"""
87
98# Standard library
2221
2322
2423class Engine :
25- """Main orchestrator for WEC-Grid simulations.
24+ """Top-level controller for WEC-Grid simulations.
2625
27- Coordinates Wave Energy Converter (WEC) farm integration with PSS®E
28- and PyPSA power system modeling backends. Manages simulation
29- workflows, time synchronization, and visualization for grid studies.
26+ Coordinates WEC farm integration with PSS®E and/or PyPSA, manages
27+ simulation time, and provides access to the database and plotting.
3028
3129 Attributes:
32- case_file (Optional[str]): Path to the power system case file
33- (RAW format).
34- case_name (Optional[str]): Human-readable identifier for the
35- loaded case.
36- time (WECGridTime): Manages simulation time and snapshots.
37- psse (Optional[PSSEModeler]): PSS®E simulation backend.
38- pypsa (Optional[PyPSAModeler]): PyPSA simulation backend.
39- wec_farms (List[WECFarm]): Collection of WEC farms in the
40- simulation.
41- database (WECGridDB): Interface for reading/writing simulation
42- data.
43- plot (WECGridPlot): Visualization and plotting utilities.
44- wecsim (WECSimRunner): WEC-Sim integration for device-level
45- hydrodynamic modeling.
46- sbase (Optional[float]): System base power in MVA.
30+ case_file: Path to the input case file (RAW/SAV).
31+ case_name: Human-readable case identifier.
32+ time: Time manager (:class:`WECGridTime`).
33+ psse: PSS®E modeler instance or ``None``.
34+ pypsa: PyPSA modeler instance or ``None``.
35+ wec_farms: WEC farms added to the simulation.
36+ database: Database interface (:class:`WECGridDB`).
37+ plot: Plotting utilities (:class:`WECGridPlot`).
38+ wecsim: WEC-Sim runner (:class:`WECSimRunner`).
39+ sbase: System base power [MVA] once a modeler is initialized.
4740 """
4841
4942 def __init__ (self ):
@@ -66,12 +59,7 @@ def __init__(self):
6659
6760
6861 def __repr__ (self ) -> str :
69- """Return a string representation of the engine state.
70-
71- Returns:
72- str: Tree-style summary of loaded case, modelers, farms,
73- and system base power (MVA).
74- """
62+ """Return a compact tree-style summary of the engine state."""
7563 return (
7664 f"Engine:\n "
7765 f"├─ Case: { self .case_name } \n "
@@ -84,34 +72,27 @@ def __repr__(self) -> str:
8472 )
8573
8674 def case (self , case_file : str ):
87- """Specify the power system case file for subsequent loading .
75+ """Set the power- system case file to load later .
8876
8977 Args:
90- case_file (str): Path or identifier for a PSS®E RAW case file.
91- Full paths, bundled cases like ``IEEE_30_bus``, or filenames
92- such as ``IEEE_39_bus.RAW`` are supported.
93-
94- Notes:
95- This method only stores the file path and a human-friendly name.
96- It does not check whether the file exists or is valid.
97- Only PSS®E RAW (``.RAW``) format is supported.
78+ case_file: Path or identifier for a case file (RAW/SAV).
79+
80+ Note:
81+ This only records the path and a friendly name; it does not
82+ validate file existence or format.
9883 """
9984
10085 self .case_file = str (case_file )
10186 self .case_name = Path (case_file ).stem .replace ("_" , " " ).replace ("-" , " " )
10287
10388 def load (self , software : List [str ]) -> None :
104- """Initialize power system simulation backends .
89+ """Initialize one or more modelers .
10590
10691 Args:
107- software (List[str]): List of backends to initialize.
108- Supported values are ``"psse"`` and ``"pypsa"``.
92+ software: Iterable of backends to initialize ("psse", "pypsa").
10993
11094 Raises:
111- ValueError: If no case file has been set or if an invalid
112- backend name is provided.
113- RuntimeError: If backend initialization fails (e.g. missing
114- license or API issue).
95+ ValueError: If no case is set or an unsupported backend is given.
11596 """
11697 if self .case_file is None :
11798 raise ValueError (
@@ -144,27 +125,21 @@ def apply_wec(
144125 wec_sim_id : int = 1 ,
145126 bus_location : int = 1 ,
146127 connecting_bus : int = 1 , # todo this should default to swing bus
147- scaling_factor : int = 1 , # used for scaling wec power output
128+ scaling_factor : float = 1.0 , # used for scaling WEC power output
148129 ) -> None :
149- """Add a Wave Energy Converter ( WEC) farm to the power system .
130+ """Add a WEC farm connected at a bus .
150131
151132 Args:
152- farm_name (str): Human-readable WEC farm identifier.
153- size (int, optional): Number of WEC devices in the farm.
154- Defaults to ``1``.
155- wec_sim_id (int, optional): Database simulation ID for WEC data.
156- Defaults to ``1``.
157- bus_location (int, optional): Grid bus for WEC connection.
158- Defaults to ``1``.
159- connecting_bus (int, optional): Network topology connection bus.
160- Defaults to ``1``.
161- scaling_factor (int, optional): Multiplier applied to WEC power
162- output (unitless). Defaults to ``1``.
163-
164- Notes:
165- - Farm power scales linearly with device count.
166- - WEC data is sourced from the database using ``wec_sim_id``.
167- - Generator IDs are auto-assigned sequentially based on farm order.
133+ farm_name: Farm identifier.
134+ size: Number of identical devices in the farm.
135+ wec_sim_id: WEC-Sim run ID in the database.
136+ bus_location: Bus number to connect the WEC farm.
137+ connecting_bus: Existing bus to which the new bus/branch ties.
138+ scaling_factor: Linear multiplier applied to device power.
139+
140+ Note:
141+ Power scales with ``size`` and ``scaling_factor``. Modelers are
142+ updated if already loaded.
168143 """
169144 wec_farm : WECFarm = WECFarm (
170145 farm_name = farm_name ,
@@ -200,45 +175,27 @@ def generate_load_curves(
200175 evening_peak_hour : float = 18.0 ,
201176 morning_sigma_h : float = 2.0 ,
202177 evening_sigma_h : float = 3.0 ,
203- amplitude : float = 0.05 , # ±30 % swing around mean
178+ amplitude : float = 0.05 , # ±5 % swing around mean
204179 min_multiplier : float = 0.50 , # floor/ceiling clamp
205180 amp_overrides : Optional [Dict [int , float ]] = None ,
206181 ) -> pd .DataFrame :
207- """Generate realistic, time-varying load profiles.
208-
209- Produces bus-specific demand time series with a double-peak
210- (morning/evening) daily pattern. Profiles scale base case loads
211- with configurable timing and variability.
182+ """Create simple double-peak daily load multipliers (per-unit).
212183
213184 Args:
214- morning_peak_hour (float, optional): Morning demand peak time
215- (hours). Defaults to ``8.0``.
216- evening_peak_hour (float, optional): Evening demand peak time
217- (hours). Defaults to ``18.0``.
218- morning_sigma_h (float, optional): Width of the morning peak
219- (hours). Defaults to ``2.0``.
220- evening_sigma_h (float, optional): Width of the evening peak
221- (hours). Defaults to ``3.0``.
222- amplitude (float, optional): Maximum variation around base load.
223- Defaults to ``0.05`` (±5%).
224- min_multiplier (float, optional): Minimum scaling factor for load.
225- Defaults to ``0.50``.
226- amp_overrides (Dict[int, float], optional): Per-bus amplitude
227- overrides. Keys are bus numbers.
185+ morning_peak_hour: Morning peak time [h].
186+ evening_peak_hour: Evening peak time [h].
187+ morning_sigma_h: Morning peak width [h].
188+ evening_sigma_h: Evening peak width [h].
189+ amplitude: Max variation (e.g., 0.05 for ±5%).
190+ min_multiplier: Floor for the multiplier.
191+ amp_overrides: Optional per-bus amplitude overrides.
228192
229193 Returns:
230- pd.DataFrame: Time-indexed load profiles in MW.
231- - Index: simulation snapshots
232- - Columns: bus numbers
233- - Values: active power demand
194+ DataFrame indexed by simulation snapshots with bus-number columns
195+ and per-unit load values as entries.
234196
235197 Raises:
236- ValueError: If no power system modeler is loaded.
237-
238- Notes:
239- - Short simulations (<6h) produce flat load profiles.
240- - PSS®E base loads use system MVA base.
241- - PyPSA base loads are aggregated per bus.
198+ ValueError: If no modeler is loaded to provide base loads.
242199 """
243200 if self .psse is None and self .pypsa is None :
244201 raise ValueError (
@@ -320,28 +277,12 @@ def g(h, mu, sig):
320277 def simulate (
321278 self , num_steps : Optional [int ] = None , load_curve : bool = False , strict_convergence : bool = False
322279 ) -> None :
323- """Run time-series power system simulations .
280+ """Run a time-series simulation across loaded modelers .
324281
325282 Args:
326- num_steps (int | None, optional): Number of simulation steps.
327- If ``None``, the maximum length available is used, constrained
328- by WEC data if present.
329- load_curve (bool, optional): Whether to enable time-varying load
330- profiles. Defaults to ``False``.
331- strict_convergence (bool, optional): Whether to stop on the first
332- convergence failure. Defaults to ``False``.
333-
334- Raises:
335- ValueError: If no power system modelers are loaded.
336-
337- Notes:
338- - All backends use identical time snapshots for comparison.
339- - WEC data length limits simulation duration.
340- - Load curves use reduced amplitude (10%) for realism.
341- - Results are available through ``engine.psse.grid`` and
342- ``engine.pypsa.grid``.
343- - ``strict_convergence=True`` enforces classical power system
344- analysis behavior.
283+ num_steps: Optional number of steps to run; defaults to available data.
284+ load_curve: If True, apply synthetic per-unit load multipliers.
285+ strict_convergence: Reserved for future use.
345286 """
346287
347288 # show that if different farms have different wec durations this logic fails
@@ -373,4 +314,3 @@ def simulate(
373314 for modeler in [self .psse , self .pypsa ]:
374315 if modeler is not None :
375316 modeler .simulate (load_curve = load_curve_df )
376- # todo if plot then plot
0 commit comments