Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resourcegraph #223

Merged
merged 8 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dependencies.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<dependencies>
<main>
<networkx/>
<dill>0.3.5</dill>
<dispatches source='pip' optional='True'>1.0</dispatches>
<pyomo source='forge'/>
Expand Down
133 changes: 133 additions & 0 deletions src/NetworkPlot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2020, Battelle Energy Alliance, LLC
# ALL RIGHTS RESERVED
"""
This module defines logic to create a resource utilization network
graph for HERON Simulations.
"""
import networkx as nx
import matplotlib as mpl
mpl.use('Agg') # Prevents the module from blocking while plotting
import matplotlib.pyplot as plt


class NetworkPlot:
"""
Represents a network graph visualization of the resources found
in a HERON system.
"""

def __init__(self, components: list) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you added annotations to method definitions (-> and the like). It is PEP compliant (3107) and I think it adds some valuable information, especially to developers at a quick glance. I haven't seen these annotations elsewhere in HERON, should we standardize this practice going forward? What I mean is, should we include these annotations going forward or remove them? I would vote for including them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. I like having the annotations and the more detailed we can be with them, the more easy our code would be to understand. Perhaps maybe a soft encouragement to use them going forward? We could easily add a pylint type checking test to our Precheck on CIVET if we wanted to make it a hard standard. @PaulTalbot-INL @joshua-cogliati-inl do you have any specific ideas or reservations about this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not ready to codify it as required practice, but I certainly am happy to see it added where possible. If forced into a decision, I would prefer to include them, but it will take time and opportunity to convert existing code to this standard. I would also like to re-think the RAVEN approach to docstrings at the same time, since this info is largely included in the type hints, but that represents a significant change for a lot of code, possibly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with optionally adding typing annotations. We should add checking them with mypy or pyright or similar before making them required.

"""
Initialize the network plot.
@ In, components, list, the components defined by the input file.
@ Out, None
"""
self._components = components
self._resources = set()
self._producers_and_consumers = set()
self._capacities = {}
self._edges = []
self._graph = nx.DiGraph()
self._find_nodes_and_edges()
self._plot_graph()
self._build_table()

def _find_nodes_and_edges(self) -> None:
"""
Iterate over the components to determine nodes and their
associated directional edges.
@ In, None
@ Out, None
"""
for c in self._components:
self._producers_and_consumers.add(c.name)

rsc_in = c.get_inputs()
for ri in rsc_in:
self._resources.add(ri)
self._graph.add_edge(ri, c.name)
self._edges.append((ri, c.name))

rsc_out = c.get_outputs()
for ro in rsc_out:
self._resources.add(ro)
self._graph.add_edge(c.name, ro)
self._edges.append((c.name, ro))

def _build_table(self) -> None:
"""
Table should have two major sections: economic info and optimization parameters

Economic info:
- Cash flows (just the names?)
- Lifetime?

Optimization settings:
- dispatch (fixed, independent, dependent)
- optimized, swept, or fixed?
- capacity (optimization bounds, sweep values, or fixed value)

@ In, None
@ Out, None
"""
col_labels = ['Dispatchable?', 'Governed?']
cell_text = []
row_labels = []

for c in self._components:
row_labels.append(c.name)
cell_text.append([c.is_dispatchable(), c.is_governed()])

plt.table(cell_text, rowLabels=row_labels, colLabels=col_labels, loc='bottom')

def _plot_graph(self) -> None:
"""
Plots and formats the graph
@ In, None
@ Out, None
"""
tech_options = { # TODO make this something that can be done dynamically
"node_size": 1000,
"node_color": "#FCEDDA",
"edgecolors": "#FCEDDA",
"linewidths": 1
}

resrc_options = {
"node_size": 1500,
"node_color": "#EE4E34",
"edgecolors": "#EE4E34",
"linewidths": 1
}

label_options = {
"font_size": 8,
"font_weight": "normal",
"font_color": "black",
}

edge_options = {
'edge_color': 'black',
"width": 1,
'arrows': True,
'arrowsize': 20
}

fig, ax = plt.subplots(figsize=(7,7))
pos = nx.spring_layout(self._graph)

nx.draw_networkx_nodes(self._graph, pos, nodelist=list(self._resources), **resrc_options)
nx.draw_networkx_nodes(self._graph, pos, nodelist=list(self._producers_and_consumers), **tech_options)
nx.draw_networkx_labels(self._graph, pos, **label_options)
nx.draw_networkx_edges(self._graph, pos, node_size=1500, **edge_options)

ax.axis('off')
fig.set_facecolor('darkgrey')

def save(self, filename: str) -> None:
"""
Save resource graph to file
@ In, filename, str, path to file
@ Out, None
"""
plt.savefig(filename)
21 changes: 18 additions & 3 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from HERON.src.base import Base
from HERON.src.Moped import MOPED
from HERON.src.Herd import HERD
from HERON.src.NetworkPlot import NetworkPlot

from ravenframework.MessageHandler import MessageHandler

Expand All @@ -45,7 +46,7 @@ def __init__(self):
'suppressErrs': False,})
self.messageHandler = messageHandler

def read_input(self, name):
def read_input(self, name: str) -> None:
"""
Loads data from input
@ In, name, str, name of file to read from
Expand All @@ -68,7 +69,7 @@ def __repr__(self):
"""
return '<HERON Simulation>'

def print_me(self, tabs=0, tab=' '):
def print_me(self, tabs=0, tab=' ') -> None:
"""
Prints info about self.
@ In, tabs, int, number of tabs to insert
Expand All @@ -86,6 +87,19 @@ def print_me(self, tabs=0, tab=' '):
for source in self._sources:
source.print_me(tabs=tabs+1, tab=tab)

def plot_resource_graph(self) -> None:
"""
Plots the resource graph of the HERON simulation using components
from the input file.

@ In, None
@ Out, None
"""
if self._case.debug['enabled']: # TODO do this every time?
graph = NetworkPlot(self._components)
img_path = os.path.join(self._input_dir, 'network.png')
graph.save(img_path)

def create_raven_workflow(self, case=None):
"""
Loads, modifies, and writes a RAVEN template workflow based on the Case.
Expand Down Expand Up @@ -154,13 +168,14 @@ def main():
sim.read_input(args.xml_input_file) # TODO expand to use arguments?
# print details
sim.print_me()
sim.plot_resource_graph()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be fine initially, but I think this network graph should be something we do at the HERON compilation stage instead of during dispatch optimization. How hard would it be to move?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is creating the plot during HERON compilation. If debug mode is enabled it will create the plot when running heron heron_input.xml

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right, I'm sorry. I saw sim.print_me() and immediately thought of the pyomo model. I should have looked more closely! I'm still on travel but can review this tomorrow or Friday probably.


if sim._case._workflow == 'standard':
sim.create_raven_workflow()
elif sim._case._workflow == 'MOPED':
sim.run_moped_workflow()
elif sim._case._workflow == 'DISPATCHES':
sim.run_dispatches_workflow()
# TODO someday? sim.run()


if __name__ == '__main__':
Expand Down
4 changes: 4 additions & 0 deletions tests/integration_tests/mechanics/debug_mode/tests
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
type = Exists
output = 'Debug_Run_o/dispatch_id0_y10_c0.png Debug_Run_o/dispatch_id0_y11_c0.png Debug_Run_o/dispatch_id1_y10_c0.png Debug_Run_o/dispatch_id1_y11_c0.png'
[../]
[./debug_plot]
type = Exists
output = 'network.png'
[../]
[../]
[]

Expand Down