Skip to content

Commit

Permalink
plots: Add electric grid graph layout plot
Browse files Browse the repository at this point in the history
  • Loading branch information
stroitzsch committed Oct 18, 2023
1 parent f72d2b1 commit 5b608a9
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 15 deletions.
11 changes: 3 additions & 8 deletions examples/development/entrypoint_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,7 @@ def main():
results_raw = mesmo.api.run_nominal_operation_problem(
scenario_name, results_path=results_path, store_results=False, recreate_database=False
)
results = results_raw.get_run_results()

# # Roundtrip save/load to/from JSON, just for demonstration
# print("Writing results to file")
# results.to_json_file(results_path / "results.json")
# print("Loading results from file")
# results = mesmo.data_models.RunResults.from_json_file(results_path / "results.json")
results = results_raw.get_run_results(scenario_name=scenario_name)

# Write results to compressed JSON
print("Writing results to file")
Expand All @@ -32,7 +26,7 @@ def main():
mesmo.config.base_path / "results" / "results.json.bz2", decompress=True
)

# Sample plotting to file, just for demonstration
# Sample plotting to file
plots.plot_to_file(plots.der_active_power_time_series, results=results, results_path=results_path)
plots.plot_to_file(plots.der_reactive_power_time_series, results=results, results_path=results_path)
plots.plot_to_file(plots.der_apparent_power_time_series, results=results, results_path=results_path)
Expand All @@ -41,6 +35,7 @@ def main():
plots.plot_to_file(plots.der_aggregated_apparent_power_time_series, results=results, results_path=results_path)
plots.plot_to_file(plots.node_voltage_per_unit_time_series, results=results, results_path=results_path)
plots.plot_to_file(plots.node_aggregated_voltage_per_unit_time_series, results=results, results_path=results_path)
plots.plot_to_file(plots.electric_grid_asset_layout, results=results, results_path=results_path)

# Sample JSON return
print(plots.plot_to_json(plots.der_active_power_time_series, results=results))
Expand Down
1 change: 1 addition & 0 deletions mesmo/data_models/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class ThermalGridDLMPRunResults(base_model.BaseModel):


class RunResults(base_model.BaseModel):
scenario_name: str
electric_grid_model_index: Optional[model_index.ElectricGridModelIndex]
thermal_grid_model_index: Optional[model_index.ThermalGridModelIndex]
der_model_set_index: model_index.DERModelSetIndex
Expand Down
1 change: 1 addition & 0 deletions mesmo/plots/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Plotting function collection."""

from .graph import electric_grid_asset_layout
from .plots import plot_to_figure, plot_to_file, plot_to_json
from .time_series import (
der_active_power_time_series,
Expand Down
1 change: 1 addition & 0 deletions mesmo/plots/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ class ValueLabels(str, enum.Enum):
REACTIVE_POWER = "Reactive power"
APPARENT_POWER = "Apparent power"
VOLTAGE = "Voltage"
ASSETS = "Assets"
181 changes: 181 additions & 0 deletions mesmo/plots/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"""Graph-based plotting functions."""

import networkx as nx
import pandas as pd
import plotly.graph_objects as go
from multimethod import multimethod
from netgraph import Graph

import mesmo.data_interface
from mesmo import data_models
from mesmo.plots import constants


class ElectricGridGraph(nx.DiGraph):
"""Electric grid graph object."""

line_edges: dict[str, tuple[str, str]]
transformer_edges: dict[str, tuple[str, str]]
node_positions: dict[str, tuple[float, float]]

@multimethod
def __init__(self, scenario_name: str):
# Obtain electric grid data.
electric_grid_data = mesmo.data_interface.ElectricGridData(scenario_name)

self.__init__(electric_grid_data)

@multimethod
def __init__(self, electric_grid_data: mesmo.data_interface.ElectricGridData):
# Create graph
super().__init__()
self.add_nodes_from(electric_grid_data.electric_grid_nodes.loc[:, "node_name"].tolist(), layer=0)
self.add_edges_from(
electric_grid_data.electric_grid_lines.loc[:, ["node_1_name", "node_2_name"]].itertuples(index=False)
)
self.add_edges_from(
electric_grid_data.electric_grid_transformers.loc[:, ["node_1_name", "node_2_name"]].itertuples(index=False)
)

# Obtain edges indexed by line name
self.line_edges = pd.Series(
electric_grid_data.electric_grid_lines.loc[:, ["node_1_name", "node_2_name"]].itertuples(index=False),
index=electric_grid_data.electric_grid_lines.loc[:, "line_name"],
).to_dict()

# Obtain edges indexed by transformer name
self.transformer_edges = pd.Series(
electric_grid_data.electric_grid_transformers.loc[:, ["node_1_name", "node_2_name"]].itertuples(
index=False
),
index=electric_grid_data.electric_grid_transformers.loc[:, "transformer_name"],
).to_dict()

# Apply graph layout for node positions
graph_layout = Graph(self, node_layout="dot")
self.node_positions = graph_layout.node_positions


def electric_grid_asset_layout(figure: go.Figure, results: data_models.RunResults) -> go.Figure:
graph = ElectricGridGraph(results.scenario_name)

# Plot lines
line_edge_x = []
line_edge_y = []
line_edge_x_label = []
line_edge_y_label = []
line_name = []
for name, edge in graph.line_edges.items():
x0, y0 = graph.node_positions[edge[0]]
x1, y1 = graph.node_positions[edge[1]]
line_edge_x.append(x0)
line_edge_x.append(x1)
line_edge_x.append(None)
line_edge_y.append(y0)
line_edge_y.append(y1)
line_edge_y.append(None)
line_edge_x_label.append((x0 + x1) / 2)
line_edge_y_label.append((y0 + y1) / 2)
line_name.append(name)
figure.add_trace(
go.Scatter(
x=line_edge_x,
y=line_edge_y,
line=go.scatter.Line(width=2, color="grey"),
mode="lines",
name="lines",
)
)
figure.add_trace(
go.Scatter(
x=line_edge_x_label,
y=line_edge_y_label,
hoverinfo="text",
hovertext=line_name,
mode="markers",
showlegend=False,
marker=go.scatter.Marker(opacity=0),
)
)

# Plot transformers
transformer_edge_x = []
transformer_edge_y = []
transformer_edge_x_label = []
transformer_edge_y_label = []
transformer_name = []
for name, edge in graph.transformer_edges.items():
x0, y0 = graph.node_positions[edge[0]]
x1, y1 = graph.node_positions[edge[1]]
transformer_edge_x.append(x0)
transformer_edge_x.append(x1)
transformer_edge_x.append(None)
transformer_edge_y.append(y0)
transformer_edge_y.append(y1)
transformer_edge_y.append(None)
transformer_edge_x_label.append((x0 + x1) / 2)
transformer_edge_y_label.append((y0 + y1) / 2)
transformer_name.append(name)
figure.add_trace(
go.Scatter(
x=transformer_edge_x,
y=transformer_edge_y,
line=go.scatter.Line(width=2, color="black", dash="dot"),
mode="lines",
name="transformers",
)
)
figure.add_trace(
go.Scatter(
x=transformer_edge_x_label,
y=transformer_edge_y_label,
hoverinfo="text",
hovertext=transformer_name,
mode="markers",
showlegend=False,
marker=go.scatter.Marker(opacity=0),
)
)

node_x = []
node_y = []
node_name = []
for node in graph.nodes():
x, y = graph.node_positions[node]
node_x.append(x)
node_y.append(y)
node_name.append(node)
figure.add_trace(
go.Scatter(
x=node_x,
y=node_y,
mode="markers",
hoverinfo="text",
hovertext=node_name,
marker=go.scatter.Marker(
# showscale=True,
# colorscale options
#'Greys' | 'YlGnBu' | 'Greens' | 'YlOrRd' | 'Bluered' | 'RdBu' |
#'Reds' | 'Blues' | 'Picnic' | 'Rainbow' | 'Portland' | 'Jet' |
#'Hot' | 'Blackbody' | 'Earth' | 'Electric' | 'Viridis' |
# colorscale="YlGnBu",
# reversescale=True,
color="teal",
size=10,
# colorbar=dict(thickness=15, title="Node Connections", xanchor="left", titleside="right"),
# line_width=2,
),
name="nodes",
)
)

title = "Electric grid asset layout"
legend_title = constants.ValueLabels.ASSETS

figure.update_layout(
title=title,
xaxis=go.layout.XAxis(showgrid=False, visible=False),
yaxis=go.layout.YAxis(showgrid=False, visible=False),
legend=go.layout.Legend(title=legend_title, x=0.99, xanchor="auto", y=0.01, yanchor="auto"),
)
return figure
3 changes: 2 additions & 1 deletion mesmo/problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class Results(

price_data: mesmo.data_interface.PriceData

def get_run_results(self) -> mesmo.data_models.RunResults:
def get_run_results(self, scenario_name: str) -> mesmo.data_models.RunResults:
return mesmo.data_models.RunResults(
scenario_name=scenario_name,
electric_grid_model_index=getattr(self, "electric_grid_model_index", None),
thermal_grid_model_index=getattr(self, "thermal_grid_model_index", None),
der_model_set_index=getattr(self, "der_model_set_index", None),
Expand Down
86 changes: 80 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ requests = "^2.31.0" # For HiGHS installation.
scipy = "^1.11.3"
tqdm = "^4.66.1"
pandera = "^0.17.2"
netgraph = "^4.12.11"

[tool.poetry.group.test.dependencies]
coverage = { extras = ["toml"], version = "^7.3.2" }
Expand Down

0 comments on commit 5b608a9

Please sign in to comment.