PowerSystemModeler

explain here what the PowerSystemModeler is and why

Supported Modelers

  • PSS®E35: Industry-standard commercial power system analysis
  • PyPSA: Open-source Python-based optimization framework

API Reference

PowerSystemModeler

Bases: ABC

Abstract base class for power system modeling backends.

Defines standardized interface for PSS®E, PyPSA, and other power system tools in WEC-GRID framework. Provides grid analysis, WEC integration, and time-series simulation capabilities through common API.

Parameters:

Name Type Description Default
engine Any

WEC-GRID Engine with case_file, time, and wec_farms attributes.

required

Attributes:

Name Type Description
engine

Reference to simulation engine.

grid GridState

Time-series data for buses, generators, lines, loads.

sbase float

System base power [MVA].

Example

from wecgrid.modelers import PSSEModeler, PyPSAModeler psse_model = PSSEModeler(engine) pypsa_model = PyPSAModeler(engine)

Notes
  • Abstract class - use concrete implementations (PSSEModeler, PyPSAModeler)
  • Grid state data follows standardized schema for cross-platform comparison
  • All abstract methods must be implemented by subclasses
Source code in src/wecgrid/modelers/power_system/base.py
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
class PowerSystemModeler(ABC):
    """Abstract base class for power system modeling backends.

    Defines standardized interface for PSS®E, PyPSA, and other power system tools
    in WEC-GRID framework. Provides grid analysis, WEC integration, and time-series
    simulation capabilities through common API.

    Args:
        engine: WEC-GRID Engine with case_file, time, and wec_farms attributes.

    Attributes:
        engine: Reference to simulation engine.
        grid (GridState): Time-series data for buses, generators, lines, loads.
        sbase (float, optional): System base power [MVA].

    Example:
        >>> from wecgrid.modelers import PSSEModeler, PyPSAModeler
        >>> psse_model = PSSEModeler(engine)
        >>> pypsa_model = PyPSAModeler(engine)

    Notes:
        - Abstract class - use concrete implementations (PSSEModeler, PyPSAModeler)
        - Grid state data follows standardized schema for cross-platform comparison
        - All abstract methods must be implemented by subclasses
    """

    def __init__(self, engine: Any):
        """Initialize PowerSystemModeler with simulation engine.

        Args:
            engine: WEC-GRID Engine with case_file, time, and wec_farms attributes.

        Note:
            Call init_api() after construction to initialize backend tool.
        """
        self.engine = engine
        self.grid = GridState()
        self.report = SolveReport()
        self.grid.case = engine.case_name
        self.report.case = engine.case_name

        self.sbase: Optional[float] = None

    def __repr__(self) -> str:
        """Return a formatted string representation of the PowerSystemModeler.

        Provides summary of modeler state, case information, and grid statistics.

        Returns:
            str: Multi-line string representation showing modeler configuration and status.

        Example:
            >>> print(modeler)
            PSSEModeler:
            ├─ Case: IEEE_30_bus.raw (100.0 MVA base)
            ├─ Grid Components: 30 buses, 6 generators, 21 loads, 41 lines
            ├─ Time Configuration: 2025-08-23 10:00:00 → 2025-08-23 12:00:00 (5 min steps)
            ├─ WEC Farms: 2 farms, 15 total devices
            └─ Status: ✓ Initialized, ✓ Power flow converged
        """
        # Get class name (e.g., "PSSEModeler", "PyPSAModeler")
        class_name = self.__class__.__name__

        # Case information
        case_name = getattr(self.engine, "case_name", "No case loaded")
        if hasattr(self.engine, "case_file") and self.engine.case_file:
            case_file = (
                str(self.engine.case_file).split("\\")[-1].split("/")[-1]
            )  # Get filename
            case_name = case_file

        sbase_info = f" ({self.sbase} MVA base)" if self.sbase else ""
        case_line = f"├─ Case: {case_name}{sbase_info}"

        # Grid component counts
        grid_line = (
            f"├─ Grid Components: {len(self.grid.bus)} buses, "
            f"{len(self.grid.gen)} generators, {len(self.grid.load)} loads, "
            f"{len(self.grid.line)} lines"
        )

        # Time configuration
        time_line = "├─ Time Configuration: Not configured"
        if hasattr(self.engine, "time") and self.engine.time:
            time_mgr = self.engine.time
            if hasattr(time_mgr, "start_time") and hasattr(time_mgr, "delta_time"):
                start = getattr(time_mgr, "start_time", "Unknown")
                end = getattr(time_mgr, "sim_stop", "Unknown")
                delta = getattr(time_mgr, "delta_time", "Unknown")

                if start != "Unknown" and end != "Unknown":
                    time_line = (
                        f"├─ Time Configuration: {start}{end} ({delta} min steps)"
                    )
                elif start != "Unknown":
                    time_line = (
                        f"├─ Time Configuration: Starting {start} ({delta} min steps)"
                    )

        # WEC farm information
        wec_line = "├─ WEC Farms: None"
        if hasattr(self.engine, "wec_farms") and self.engine.wec_farms:
            num_farms = len(self.engine.wec_farms)
            total_devices = sum(
                len(farm.devices) for farm in self.engine.wec_farms.values()
            )
            wec_line = f"├─ WEC Farms: {num_farms} farms, {total_devices} total devices"

        # Status indicators (this would be implemented by subclasses with more specific info)
        status_line = "└─ Status: ⚠ Not initialized"

        return (
            f"{class_name}:\n"
            f"{case_line}\n"
            f"{grid_line}\n"
            f"{time_line}\n"
            f"{wec_line}\n"
            f"{status_line}"
        )

    @abstractmethod
    def init_api(self) -> bool:
        """Initialize backend power system tool and load case file.

        Returns:
            bool: True if initialization successful, False otherwise.

        Raises:
            ImportError: If backend tool not found or configured.
            ValueError: If case file invalid or cannot be loaded.

        Notes:
            Implementation should:

            - Initialize backend API/environment
            - Load case file (.sav, .raw, etc.)
            - Set system base MVA (self.sbase)
            - Perform initial power flow solution
            - Take initial grid state snapshot

        Example:
            >>> if modeler.init_api():
            ...     print("Backend initialized successfully")
        """
        pass

    @abstractmethod
    def solve_powerflow(self) -> bool:
        """Run power flow solution using backend solver.

        Returns:
            bool: True if power flow converged, False otherwise.

        Notes:
            Implementation should:

            - Call backend's power flow solver
            - Check convergence status
            - Handle solver-specific parameters
            - Suppress verbose output if needed

        Example:
            >>> if modeler.solve_powerflow():
            ...     print("Power flow converged")
        """
        pass

    @abstractmethod
    def add_wec_farm(self, farm: WECFarm) -> bool:
        """Add WEC farm to power system model.

        Args:
            farm (WECFarm): WEC farm with connection details and power characteristics.

        Returns:
            bool: True if farm added successfully, False otherwise.

        Raises:
            ValueError: If WEC farm parameters invalid.

        Notes:
            Implementation should:

            - Create new bus for WEC connection
            - Add WEC generator with power characteristics
            - Create transmission line to existing grid
            - Update grid state after modifications
            - Solve power flow to validate changes

        Example:
            >>> if modeler.add_wec_farm(wec_farm):
            ...     print("WEC farm added successfully")
        """
        pass

    @abstractmethod
    def simulate(self, load_curve: Optional[pd.DataFrame] = None) -> bool:
        """Run time-series simulation with WEC and load updates.

        Args:
            load_curve (pd.DataFrame, optional): Load values for each bus at each snapshot.
                Index: snapshots, columns: bus IDs. If None, loads remain constant.

        Returns:
            bool: True if simulation completes successfully, False otherwise.

        Raises:
            Exception: If error updating components or solving power flow.

        Notes:
            Implementation should:

            - Iterate through all time snapshots from engine.time
            - Update WEC generator power outputs [MW] from farm data
            - Update bus loads [MW] if load_curve provided
            - Solve power flow at each time step
            - Capture grid state snapshots for analysis
            - Handle convergence failures gracefully

        Example:
            >>> # Constant loads
            >>> modeler.simulate()
            >>>
            >>> # Time-varying loads
            >>> modeler.simulate(load_curve=load_df)
        """
        pass

    @abstractmethod
    def take_snapshot(self, timestamp: datetime) -> None:
        """Capture current grid state at specified timestamp.

        Args:
            timestamp (datetime): Timestamp for the snapshot.

        Notes:
            Implementation should:

            - Extract bus data: voltages [p.u.], [degrees], power [MW], [MVAr]
            - Extract generator data: power outputs [MW], [MVAr], status
            - Extract line data: power flows [MW], [MVAr], loading [%]
            - Extract load data: power consumption [MW], [MVAr]
            - Convert to standardized WEC-GRID schema
            - Store in self.grid with timestamp indexing

        Example:
            >>> modeler.take_snapshot(datetime.now())
        """
        pass

    # Convenience accessors
    @property
    def bus(self) -> Optional[pd.DataFrame]:
        """Current bus state with columns: bus, bus_name, type, p, q, v_mag, angle_deg, base.

        Returns:
            pd.DataFrame: Bus state data [p.u. on system MVA base] or None if no snapshots.
        """
        return self.grid.bus

    @property
    def gen(self) -> Optional[pd.DataFrame]:
        """Current generator state with columns: gen, bus, p, q, base, status.

        Returns:
            pd.DataFrame: Generator state data [p.u. on generator MVA base] or None if no snapshots.
        """
        return self.grid.gen

    @property
    def load(self) -> Optional[pd.DataFrame]:
        """Current load state with columns: load, bus, p, q, base, status.

        Returns:
            pd.DataFrame: Load state data [p.u. on system MVA base] or None if no snapshots.
        """
        return self.grid.load

    @property
    def line(self) -> Optional[pd.DataFrame]:
        """Current line state with columns: line, ibus, jbus, line_pct, status.

        Returns:
            pd.DataFrame: Line state data [line_pct as % of thermal rating] or None if no snapshots.
        """
        return self.grid.line

    @property
    def bus_t(self) -> Dict[str, pd.DataFrame]:
        """Time-series bus data for all snapshots.

        Returns:
            Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: bus state DataFrames.
        """
        return self.grid.bus_t

    @property
    def gen_t(self) -> Dict[str, pd.DataFrame]:
        """Time-series generator data for all snapshots.

        Returns:
            Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: generator state DataFrames.
        """
        return self.grid.gen_t

    @property
    def load_t(self) -> Dict[str, pd.DataFrame]:
        """Time-series load data for all snapshots.

        Returns:
            Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: load state DataFrames.
        """
        return self.grid.load_t

    @property
    def line_t(self) -> Dict[str, pd.DataFrame]:
        """Time-series line data for all snapshots.

        Returns:
            Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: line state DataFrames.
        """
        return self.grid.line_t

bus property

Current bus state with columns: bus, bus_name, type, p, q, v_mag, angle_deg, base.

Returns:

Type Description
Optional[DataFrame]

pd.DataFrame: Bus state data [p.u. on system MVA base] or None if no snapshots.

bus_t property

Time-series bus data for all snapshots.

Returns:

Type Description
Dict[str, DataFrame]

Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: bus state DataFrames.

gen property

Current generator state with columns: gen, bus, p, q, base, status.

Returns:

Type Description
Optional[DataFrame]

pd.DataFrame: Generator state data [p.u. on generator MVA base] or None if no snapshots.

gen_t property

Time-series generator data for all snapshots.

Returns:

Type Description
Dict[str, DataFrame]

Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: generator state DataFrames.

line property

Current line state with columns: line, ibus, jbus, line_pct, status.

Returns:

Type Description
Optional[DataFrame]

pd.DataFrame: Line state data [line_pct as % of thermal rating] or None if no snapshots.

line_t property

Time-series line data for all snapshots.

Returns:

Type Description
Dict[str, DataFrame]

Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: line state DataFrames.

load property

Current load state with columns: load, bus, p, q, base, status.

Returns:

Type Description
Optional[DataFrame]

pd.DataFrame: Load state data [p.u. on system MVA base] or None if no snapshots.

load_t property

Time-series load data for all snapshots.

Returns:

Type Description
Dict[str, DataFrame]

Dict[str, pd.DataFrame]: Keys: timestamp strings, Values: load state DataFrames.

add_wec_farm(farm) abstractmethod

Add WEC farm to power system model.

Parameters:

Name Type Description Default
farm WECFarm

WEC farm with connection details and power characteristics.

required

Returns:

Name Type Description
bool bool

True if farm added successfully, False otherwise.

Raises:

Type Description
ValueError

If WEC farm parameters invalid.

Notes

Implementation should:

  • Create new bus for WEC connection
  • Add WEC generator with power characteristics
  • Create transmission line to existing grid
  • Update grid state after modifications
  • Solve power flow to validate changes
Example

if modeler.add_wec_farm(wec_farm): ... print("WEC farm added successfully")

Source code in src/wecgrid/modelers/power_system/base.py
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
@abstractmethod
def add_wec_farm(self, farm: WECFarm) -> bool:
    """Add WEC farm to power system model.

    Args:
        farm (WECFarm): WEC farm with connection details and power characteristics.

    Returns:
        bool: True if farm added successfully, False otherwise.

    Raises:
        ValueError: If WEC farm parameters invalid.

    Notes:
        Implementation should:

        - Create new bus for WEC connection
        - Add WEC generator with power characteristics
        - Create transmission line to existing grid
        - Update grid state after modifications
        - Solve power flow to validate changes

    Example:
        >>> if modeler.add_wec_farm(wec_farm):
        ...     print("WEC farm added successfully")
    """
    pass

init_api() abstractmethod

Initialize backend power system tool and load case file.

Returns:

Name Type Description
bool bool

True if initialization successful, False otherwise.

Raises:

Type Description
ImportError

If backend tool not found or configured.

ValueError

If case file invalid or cannot be loaded.

Notes

Implementation should:

  • Initialize backend API/environment
  • Load case file (.sav, .raw, etc.)
  • Set system base MVA (self.sbase)
  • Perform initial power flow solution
  • Take initial grid state snapshot
Example

if modeler.init_api(): ... print("Backend initialized successfully")

Source code in src/wecgrid/modelers/power_system/base.py
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
@abstractmethod
def init_api(self) -> bool:
    """Initialize backend power system tool and load case file.

    Returns:
        bool: True if initialization successful, False otherwise.

    Raises:
        ImportError: If backend tool not found or configured.
        ValueError: If case file invalid or cannot be loaded.

    Notes:
        Implementation should:

        - Initialize backend API/environment
        - Load case file (.sav, .raw, etc.)
        - Set system base MVA (self.sbase)
        - Perform initial power flow solution
        - Take initial grid state snapshot

    Example:
        >>> if modeler.init_api():
        ...     print("Backend initialized successfully")
    """
    pass

simulate(load_curve=None) abstractmethod

Run time-series simulation with WEC and load updates.

Parameters:

Name Type Description Default
load_curve DataFrame

Load values for each bus at each snapshot. Index: snapshots, columns: bus IDs. If None, loads remain constant.

None

Returns:

Name Type Description
bool bool

True if simulation completes successfully, False otherwise.

Raises:

Type Description
Exception

If error updating components or solving power flow.

Notes

Implementation should:

  • Iterate through all time snapshots from engine.time
  • Update WEC generator power outputs [MW] from farm data
  • Update bus loads [MW] if load_curve provided
  • Solve power flow at each time step
  • Capture grid state snapshots for analysis
  • Handle convergence failures gracefully
Example

Constant loads

modeler.simulate()

Time-varying loads

modeler.simulate(load_curve=load_df)

Source code in src/wecgrid/modelers/power_system/base.py
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
@abstractmethod
def simulate(self, load_curve: Optional[pd.DataFrame] = None) -> bool:
    """Run time-series simulation with WEC and load updates.

    Args:
        load_curve (pd.DataFrame, optional): Load values for each bus at each snapshot.
            Index: snapshots, columns: bus IDs. If None, loads remain constant.

    Returns:
        bool: True if simulation completes successfully, False otherwise.

    Raises:
        Exception: If error updating components or solving power flow.

    Notes:
        Implementation should:

        - Iterate through all time snapshots from engine.time
        - Update WEC generator power outputs [MW] from farm data
        - Update bus loads [MW] if load_curve provided
        - Solve power flow at each time step
        - Capture grid state snapshots for analysis
        - Handle convergence failures gracefully

    Example:
        >>> # Constant loads
        >>> modeler.simulate()
        >>>
        >>> # Time-varying loads
        >>> modeler.simulate(load_curve=load_df)
    """
    pass

solve_powerflow() abstractmethod

Run power flow solution using backend solver.

Returns:

Name Type Description
bool bool

True if power flow converged, False otherwise.

Notes

Implementation should:

  • Call backend's power flow solver
  • Check convergence status
  • Handle solver-specific parameters
  • Suppress verbose output if needed
Example

if modeler.solve_powerflow(): ... print("Power flow converged")

Source code in src/wecgrid/modelers/power_system/base.py
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
@abstractmethod
def solve_powerflow(self) -> bool:
    """Run power flow solution using backend solver.

    Returns:
        bool: True if power flow converged, False otherwise.

    Notes:
        Implementation should:

        - Call backend's power flow solver
        - Check convergence status
        - Handle solver-specific parameters
        - Suppress verbose output if needed

    Example:
        >>> if modeler.solve_powerflow():
        ...     print("Power flow converged")
    """
    pass

take_snapshot(timestamp) abstractmethod

Capture current grid state at specified timestamp.

Parameters:

Name Type Description Default
timestamp datetime

Timestamp for the snapshot.

required
Notes

Implementation should:

  • Extract bus data: voltages [p.u.], [degrees], power [MW], [MVAr]
  • Extract generator data: power outputs [MW], [MVAr], status
  • Extract line data: power flows [MW], [MVAr], loading [%]
  • Extract load data: power consumption [MW], [MVAr]
  • Convert to standardized WEC-GRID schema
  • Store in self.grid with timestamp indexing
Example

modeler.take_snapshot(datetime.now())

Source code in src/wecgrid/modelers/power_system/base.py
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
@abstractmethod
def take_snapshot(self, timestamp: datetime) -> None:
    """Capture current grid state at specified timestamp.

    Args:
        timestamp (datetime): Timestamp for the snapshot.

    Notes:
        Implementation should:

        - Extract bus data: voltages [p.u.], [degrees], power [MW], [MVAr]
        - Extract generator data: power outputs [MW], [MVAr], status
        - Extract line data: power flows [MW], [MVAr], loading [%]
        - Extract load data: power consumption [MW], [MVAr]
        - Convert to standardized WEC-GRID schema
        - Store in self.grid with timestamp indexing

    Example:
        >>> modeler.take_snapshot(datetime.now())
    """
    pass