Navigating SMS++ Block Structures¶
This notebook demonstrates how to navigate and inspect the hierarchical block structure of SMS++ models. SMS++ uses a nested block architecture where each block can contain dimensions, variables, attributes, and sub-blocks. Understanding this structure is essential for working with complex SMS++ optimization models.
Loading an SMS++ Network¶
Let's start by loading a sample SMS++ network from a NetCDF file and exploring its structure.
from pysmspp import SMSNetwork
import os
# Load a sample network - adjust path as needed
network_path = "../../test/test_data/microgrid_ALLbutStore_1N.nc4"
if os.path.exists(network_path):
net = SMSNetwork(network_path)
print("Network loaded successfully!")
else:
print(f"Network file not found at {network_path}")
print("Creating a simple example network instead...")
net = SMSNetwork()
net.block_type = "ExampleNetwork"
Network loaded successfully!
Visualizing the Block Tree¶
The print_tree() method provides a visual representation of the block hierarchy.
This is particularly useful for understanding complex nested structures.
# Print the basic block structure
net.print_tree()
SMSNetwork
└── Block_0 [UCBlock]
├── UnitBlock_0 [ThermalUnitBlock]
├── UnitBlock_1 [IntermittentUnitBlock]
├── UnitBlock_2 [IntermittentUnitBlock]
├── UnitBlock_3 [BatteryUnitBlock]
└── UnitBlock_4 [HydroUnitBlock]
Exploring Block Details¶
The print_tree() method accepts several options to display additional information:
show_dimensions=True: Display dimension informationshow_variables=True: Display variable informationshow_attributes=True: Display attribute information
# Show the tree with dimensions
print("=== Tree with Dimensions ===")
net.print_tree(show_dimensions=True)
=== Tree with Dimensions ===
SMSNetwork
└── Block_0 [UCBlock]
Dimensions (5): TimeHorizon=24, NumberUnits=5, NumberElectricalGenerators=6, NumberNodes=1, NumberLines=0
├── UnitBlock_0 [ThermalUnitBlock]
├── UnitBlock_1 [IntermittentUnitBlock]
├── UnitBlock_2 [IntermittentUnitBlock]
├── UnitBlock_3 [BatteryUnitBlock]
└── UnitBlock_4 [HydroUnitBlock]
Dimensions (3): NumberReservoirs=1, NumberArcs=2, TotalNumberPieces=2
# Show the tree with variables
print("\n=== Tree with Variables ===")
net.print_tree(show_variables=True)
=== Tree with Variables ===
SMSNetwork
└── Block_0 [UCBlock]
Variables (1): ActivePowerDemand
├── UnitBlock_0 [ThermalUnitBlock]
│ Variables (10): MinPower, MaxPower, StartUpCost, LinearTerm, ConstantTerm, ... (10 total)
├── UnitBlock_1 [IntermittentUnitBlock]
│ Variables (2): MinPower, MaxPower
├── UnitBlock_2 [IntermittentUnitBlock]
│ Variables (2): MinPower, MaxPower
├── UnitBlock_3 [BatteryUnitBlock]
│ Variables (7): MinPower, MaxPower, MinStorage, MaxStorage, InitialStorage, ... (7 total)
└── UnitBlock_4 [HydroUnitBlock]
Variables (13): StartArc, EndArc, MaxPower, MinPower, MinFlow, ... (13 total)
# Show complete tree with all details
print("\n=== Complete Tree ===")
net.print_tree(show_dimensions=True, show_variables=True, show_attributes=True)
=== Complete Tree ===
SMSNetwork
Attributes (1): SMS++_file_type=1
└── Block_0 [UCBlock]
Dimensions (5): TimeHorizon=24, NumberUnits=5, NumberElectricalGenerators=6, NumberNodes=1, NumberLines=0
Variables (1): ActivePowerDemand
├── UnitBlock_0 [ThermalUnitBlock]
│ Variables (10): MinPower, MaxPower, StartUpCost, LinearTerm, ConstantTerm, ... (10 total)
│ Attributes (1): id=0
├── UnitBlock_1 [IntermittentUnitBlock]
│ Variables (2): MinPower, MaxPower
│ Attributes (1): id=1
├── UnitBlock_2 [IntermittentUnitBlock]
│ Variables (2): MinPower, MaxPower
│ Attributes (1): id=2
├── UnitBlock_3 [BatteryUnitBlock]
│ Variables (7): MinPower, MaxPower, MinStorage, MaxStorage, InitialStorage, ... (7 total)
│ Attributes (1): id=3
└── UnitBlock_4 [HydroUnitBlock]
Dimensions (3): NumberReservoirs=1, NumberArcs=2, TotalNumberPieces=2
Variables (13): StartArc, EndArc, MaxPower, MinPower, MinFlow, ... (13 total)
Attributes (1): id=4
Navigating Blocks Programmatically¶
Beyond visualization, you can navigate blocks programmatically to access specific elements.
# Access top-level blocks
print("Top-level blocks:")
for block_name in net.blocks.keys():
print(f" - {block_name}")
Top-level blocks: - Block_0
# Navigate to a specific block and inspect it
if net.blocks:
first_block_name = list(net.blocks.keys())[0]
first_block = net.blocks[first_block_name]
print(f"\nInspecting block: {first_block_name}")
print(f"Block type: {first_block.block_type}")
print(f"Number of dimensions: {len(first_block.dimensions)}")
print(f"Number of variables: {len(first_block.variables)}")
print(f"Number of sub-blocks: {len(first_block.blocks)}")
# Print tree for this specific block
print(f"\nTree structure of {first_block_name}:")
first_block.print_tree()
Inspecting block: Block_0 Block type: UCBlock Number of dimensions: 5 Number of variables: 1 Number of sub-blocks: 5 Tree structure of Block_0: UCBlock [UCBlock] ├── UnitBlock_0 [ThermalUnitBlock] ├── UnitBlock_1 [IntermittentUnitBlock] ├── UnitBlock_2 [IntermittentUnitBlock] ├── UnitBlock_3 [BatteryUnitBlock] └── UnitBlock_4 [HydroUnitBlock]
Accessing Dimensions, Variables, and Attributes¶
Each block provides dictionaries to access its components.
# Example: Accessing dimensions
if net.blocks and list(net.blocks.values())[0].dimensions:
block = list(net.blocks.values())[0]
print("Dimensions:")
for dim_name, dim_value in block.dimensions.items():
print(f" {dim_name} = {dim_value}")
Dimensions: TimeHorizon = 24 NumberUnits = 5 NumberElectricalGenerators = 6 NumberNodes = 1 NumberLines = 0
# Example: Accessing variables
if net.blocks and list(net.blocks.values())[0].variables:
block = list(net.blocks.values())[0]
print("\nVariables:")
for var_name, var_obj in list(block.variables.items())[:5]: # Show first 5
print(f" {var_name}: {type(var_obj).__name__}")
Variables: ActivePowerDemand: Variable
Traversing the Block Hierarchy¶
Here's a function to recursively traverse all blocks in the network.
def traverse_blocks(block, name="root", level=0):
"""Recursively traverse and print block information."""
indent = " " * level
block_type = (
block.block_type
if hasattr(block, "block_type") and block.block_type
else "Unknown"
)
print(f"{indent}{name} [{block_type}]")
print(
f"{indent} Dimensions: {len(block.dimensions)}, Variables: {len(block.variables)}, Sub-blocks: {len(block.blocks)}"
)
# Recursively process sub-blocks
for sub_name, sub_block in block.blocks.items():
traverse_blocks(sub_block, sub_name, level + 1)
# Traverse the network
print("\nBlock Hierarchy:")
traverse_blocks(net, "Network")
Block Hierarchy:
Network [Unknown]
Dimensions: 0, Variables: 0, Sub-blocks: 1
Block_0 [UCBlock]
Dimensions: 5, Variables: 1, Sub-blocks: 5
UnitBlock_0 [ThermalUnitBlock]
Dimensions: 0, Variables: 10, Sub-blocks: 0
UnitBlock_1 [IntermittentUnitBlock]
Dimensions: 0, Variables: 2, Sub-blocks: 0
UnitBlock_2 [IntermittentUnitBlock]
Dimensions: 0, Variables: 2, Sub-blocks: 0
UnitBlock_3 [BatteryUnitBlock]
Dimensions: 0, Variables: 7, Sub-blocks: 0
UnitBlock_4 [HydroUnitBlock]
Dimensions: 3, Variables: 13, Sub-blocks: 0
Summary¶
This notebook demonstrated:
- Loading SMS++ networks from files
- Visualizing block structure with
print_tree() - Navigating blocks programmatically
- Accessing dimensions, variables, and attributes
- Traversing the entire block hierarchy
The print_tree() utility is particularly useful for:
- Understanding complex model structures
- Debugging model construction
- Documenting model architecture
- Exploring unfamiliar SMS++ networks