BaseSystem Architecture#
Overview#
The BaseSystem abstraction provides a unified interface for reading system files from different simulation platforms (Sienna, ReEDS, Plexos, etc.). This abstraction enables:
Palette auto-generation: Automatically create palette configurations from system metadata
Platform independence: Write code that works across different simulation platforms
Consistent data access: Standardized API for accessing generator categories, classifications, and metadata
Extensibility: Easy to add support for new simulation platforms
Architecture Diagram#
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (PaletteGenerator, CLI commands, plotting functions) │
└────────────────────────────┬────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────────────────────────────┐
│ BaseSystem (Abstract) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ - get_system_info() -> SystemInfo │ │
│ │ - list_generator_categories() -> List[GeneratorCategory]│
│ │ - list_load_categories() -> List[LoadCategory] │ │
│ │ - get_generator_data() -> DataFrame │ │
│ │ - get_load_data() -> DataFrame │ │
│ │ - get_vre_categories() -> List[str] │ │
│ │ - get_storage_categories() -> List[str] │ │
│ │ - validate() -> List[str] │ │
│ └───────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
│ implements
▼
┌──────────────────┬──────────────────┬──────────────────────┐
│ │ │ │
│ SiennaSystem │ ReedsSystem │ PlexosSystem │
│ (Implemented) │ (Future) │ (Future) │
│ │ │ │
└──────────────────┴──────────────────┴──────────────────────┘
Core Components#
1. BaseSystem Abstract Class#
Location: gat/datahelpers/base_system.py
The abstract base class defines the interface that all system parsers must implement:
class BaseSystem(ABC):
"""Abstract base class for system file parsers."""
@abstractmethod
def get_system_info(self) -> SystemInfo:
"""Get basic system information."""
pass
@abstractmethod
def list_generator_categories(self) -> List[GeneratorCategory]:
"""List all generator categories in the system."""
pass
@abstractmethod
def list_load_categories(self) -> List[LoadCategory]:
"""List all load categories in the system."""
pass
@abstractmethod
def get_generator_data(self, category: Optional[str] = None) -> pd.DataFrame:
"""Get detailed generator data."""
pass
@abstractmethod
def get_load_data(self, category: Optional[str] = None) -> pd.DataFrame:
"""Get detailed load data."""
pass
2. Data Models#
SystemInfo#
Basic metadata about a power system:
class SystemInfo(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
base_power: Optional[float] = None # MVA base
data_format_version: Optional[str] = None
num_generators: Optional[int] = None
num_buses: Optional[int] = None
num_loads: Optional[int] = None
GeneratorCategory#
Information about a generator category/type:
class GeneratorCategory(BaseModel):
name: str # Category name
display_name: Optional[str] = None # Human-readable name
fuel_type: Optional[str] = None # Fuel type
prime_mover: Optional[str] = None # Prime mover type
technology: Optional[str] = None # Technology classification
count: int = 0 # Number of generators
total_capacity: Optional[float] = None # Total capacity in MW
is_vre: bool = False # Variable Renewable Energy
is_storage: bool = False # Storage flag
is_curtailable: bool = False # Can be curtailed
LoadCategory#
Information about load categories:
class LoadCategory(BaseModel):
name: str
display_name: Optional[str] = None
is_flexible: bool = False # Flexible/dispatchable
is_storage_charging: bool = False # Storage charging load
count: int = 0
total_demand: Optional[float] = None # Total demand in MW
3. SiennaSystem Implementation#
Location: gat/datahelpers/sienna_system.py
Concrete implementation for Sienna/PowerSystems.jl JSON files:
Key Features:
Parses Sienna JSON system files
Extracts generator metadata (fuel type, prime mover, technology)
Builds categories from component types and attributes
Identifies VRE technologies (Solar, Wind, etc.)
Identifies storage technologies (Battery, Pumped Hydro)
Returns standardized DataFrames for generator and load data
Category Extraction Logic:
Group generators by component type + fuel + prime mover
Calculate total capacity per category
Classify as VRE based on technology patterns
Classify as storage based on component type
Mark curtailable resources (typically VRE)
Example:
system = SiennaSystem("path/to/system.json")
# Get system info
info = system.get_system_info()
print(f"Generators: {info.num_generators}")
# List categories
categories = system.list_generator_categories()
for cat in categories:
print(f"{cat.name}: {cat.count} units, {cat.total_capacity:.1f} MW")
# Get detailed data
df = system.get_generator_data()
print(df[['name', 'category', 'capacity', 'is_vre']])
Design Principles#
1. Separation of Concerns#
BaseSystem: Defines the interface and common functionality
Concrete implementations: Handle platform-specific file formats
PaletteGenerator: Uses BaseSystem to generate palettes, platform-agnostic
CLI/Application: Interacts only with the abstraction
2. Platform Independence#
Code that uses BaseSystem works with any implementation:
def analyze_system(system: BaseSystem):
"""Works with any BaseSystem implementation."""
info = system.get_system_info()
categories = system.list_generator_categories()
vre = system.get_vre_categories()
return info, categories, vre
# Works with Sienna
sienna_sys = SiennaSystem("system.json")
analyze_system(sienna_sys)
# Will work with ReEDS (future)
reeds_sys = ReedsSystem("reeds_output/")
analyze_system(reeds_sys)
3. Standardized Output#
All implementations return the same data structures:
SystemInfofor metadataGeneratorCategoryfor category informationpd.DataFramefor detailed data with standard columns
4. Extensibility#
Adding support for a new platform:
Create new class inheriting from
BaseSystemImplement required abstract methods
Handle platform-specific file formats internally
Return standardized data structures
No changes needed to consuming code
Comparison with SiennaSystemParser#
The new BaseSystem abstraction improves upon SiennaSystemParser:
Aspect |
SiennaSystemParser |
BaseSystem + SiennaSystem |
|---|---|---|
Scope |
Sienna-only |
Multi-platform |
Interface |
Sienna-specific methods |
Standardized abstract interface |
Categories |
Limited category extraction |
Rich category metadata with classifications |
Type hints |
Partial |
Complete with Pydantic models |
Documentation |
Basic |
Comprehensive with examples |
Validation |
Minimal |
Built-in validation with warnings |
Testing |
Limited |
Comprehensive test coverage |
Use case |
Direct data access |
Palette generation, analysis, visualization |
Migration Path:
SiennaSystemParser is retained for backward compatibility. New code should use SiennaSystem.
Usage Patterns#
Pattern 1: System Inspection#
from gat.datahelpers.sienna_system import SiennaSystem
system = SiennaSystem("system.json")
# Basic info
info = system.get_system_info()
print(f"System: {info.name}")
print(f"Generators: {info.num_generators}")
# Categories
for cat in system.list_generator_categories():
print(f"{cat.name}: {cat.total_capacity:.1f} MW")
if cat.is_vre:
print(" → VRE technology")
Pattern 2: Palette Generation#
from gat.datahelpers.sienna_system import SiennaSystem
from gat.palette_generator import PaletteGenerator
system = SiennaSystem("system.json")
generator = PaletteGenerator(system)
palette = generator.generate(
name="My Palette",
simulation_type="sienna"
)
Pattern 3: Data Analysis#
from gat.datahelpers.sienna_system import SiennaSystem
system = SiennaSystem("system.json")
# Get all generators as DataFrame
df = system.get_generator_data()
# Analyze VRE capacity
vre_df = df[df['is_vre']]
vre_capacity = vre_df['capacity'].sum()
print(f"Total VRE capacity: {vre_capacity:.1f} MW")
# Analyze by fuel type
capacity_by_fuel = df.groupby('fuel_type')['capacity'].sum()
print(capacity_by_fuel)
Pattern 4: Validation#
from gat.datahelpers.sienna_system import SiennaSystem
system = SiennaSystem("system.json")
warnings = system.validate()
if warnings:
print("System validation warnings:")
for warning in warnings:
print(f" ⚠ {warning}")
else:
print("✓ System validation passed")
Future Extensions#
Support for Additional Platforms#
ReedsSystem (Planned)#
class ReedsSystem(BaseSystem):
"""Parser for ReEDS output files."""
def __init__(self, reeds_path: str, solve_year: Optional[int] = None):
self.reeds_path = Path(reeds_path)
self.solve_year = solve_year
# Load ReEDS data from CSV files
PlexosSystem (Planned)#
class PlexosSystem(BaseSystem):
"""Parser for Plexos solution files."""
def __init__(self, solution_path: str):
self.solution_path = Path(solution_path)
# Load Plexos XML/database
Additional Methods#
Potential additions to BaseSystem:
get_bus_data(): Bus/node informationget_branch_data(): Transmission line dataget_zone_data(): Zone/region aggregationsget_time_series_metadata(): Available time seriesexport_to_format(): Convert between formats
Testing Strategy#
Unit Tests#
Test each implementation independently:
def test_sienna_system_info():
system = SiennaSystem("test_system.json")
info = system.get_system_info()
assert info.num_generators > 0
assert info.base_power is not None
def test_generator_categories():
system = SiennaSystem("test_system.json")
categories = system.list_generator_categories()
assert len(categories) > 0
assert all(cat.count > 0 for cat in categories)
Integration Tests#
Test with PaletteGenerator:
def test_palette_generation():
system = SiennaSystem("test_system.json")
generator = PaletteGenerator(system)
palette = generator.generate(name="Test")
assert len(palette.display_categories) > 0
Fixture-Based Testing#
Use synthetic system files for testing:
@pytest.fixture
def sample_system():
"""Create minimal valid system for testing."""
system_data = {
"data_format_version": "3.0.0",
"data": {
"base_power": 100.0,
"components": [
# Test generators
]
}
}
# Save to temp file and return path
Best Practices#
For Consumers#
Use the abstraction: Write code against
BaseSystem, not concrete implementationsHandle validation warnings: Check
validate()outputCheck for missing data: Not all fields are available in all platforms
Use type hints: Leverage Pydantic models for validation
Test with multiple platforms: Ensure code works across implementations