-
Notifications
You must be signed in to change notification settings - Fork 39
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
Resourcegraph #223
Changes from all commits
8fd8424
4238876
afdf3e5
1a2177e
ec33cd8
f704483
56d49a2
f8dac41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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: | ||
""" | ||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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. | ||
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, you're right, I'm sorry. I saw |
||
|
||
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__': | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.