Skip to content

Commit

Permalink
Added downline load hosting capacity. Included scratch work
Browse files Browse the repository at this point in the history
  • Loading branch information
jenny-nyx committed Apr 4, 2024
1 parent b2ad2c3 commit 02c9a2b
Show file tree
Hide file tree
Showing 8 changed files with 1,562 additions and 128 deletions.
34 changes: 34 additions & 0 deletions omf/models/hostingCapacity.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@
</label>
<input type="text" id="traditionalHCMaxTestkw" name="traditionalHCMaxTestkw" value="{{allInputDataDict.traditionalHCMaxTestkw}}" pattern="^[0-9]*$"/>
</div>
<div class="wideInput">
<p class="inputSectionHeader">Downline Load Hosting Capacity</p>
</div>
<hr>
<div class="shortInput">
<label class="tooltip">Apply Downline Hosting Capacity Algorithm with above circuit.</label>
<select id="runDownlineAlgorithm" name="runDownlineAlgorithm" value="{{allInputDataDict.runDownlineAlgorithm}}" required>
<option value="off" {% if allInputDataDict.runDownlineAlgorithm == 'off' %}selected{% endif %}>Off</option>
<option value="on" {% if allInputDataDict.runDownlineAlgorithm == 'on' %}selected{% endif %}>On</option>
</select>
</div>
{{ omfModelButtons }}
</form>
</div>
Expand Down Expand Up @@ -178,6 +189,29 @@
</table>
</div>
{% endif %}
{% if allInputDataDict['runDownlineAlgorithm'] == 'on' %}
<p class="reportTitle">Downline Load Hosting Capacity Runtime ( H:M:S:MS )</p>
<div id="traditionalRunTime" class="tightContent">
<span style="border: 1px solid grey; padding: 3px;"> {{ allOutputDataDict['downline_runtime'] }} </span>
</div>
<p class="reportTitle">Downline Load Hosting Capacity Full Data Table</p>
<div id="downlineHostingCapacityTable" class="tightContent" style="height:300px; overflow-y: scroll;">
<table style="width:100%">
<tr>
{% for header in allOutputDataDict["downline_tableHeadings"] %}
<th>{{ header }}</th>
{% endfor %}
</tr>
{% for values in allOutputDataDict["downline_tableValues"] %}
<tr>
{% for val in values %}
<td>{{ val }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{{ rawOutputFiles }}
</div>
{% endif %}
Expand Down
137 changes: 132 additions & 5 deletions omf/models/hostingCapacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import numpy as np
from pathlib import Path
import time
import networkx as nx
import matplotlib.pyplot as plt


# OMF imports
import omf
Expand Down Expand Up @@ -47,17 +50,140 @@ def createColorCSV(modelDir, df):
new_df = df[['bus','max_kw']]
new_df.to_csv(Path(modelDir, 'color_by.csv'), index=False)

def my_GetCoords(dssFilePath):
'''Takes in an OpenDSS circuit definition file and outputs the bus coordinates as a dataframe.'''
dssFileLoc = os.path.dirname(dssFilePath)
curr_dir = os.getcwd()
opendss.runDSS(dssFilePath)
opendss.runDssCommand(f'export buscoords "{curr_dir}/coords.csv"')
coords = pd.read_csv(curr_dir + '/coords.csv', header=None)
# JENNY - Deleted Radius and everything after.
coords.columns = ['Element', 'X', 'Y']
return coords

def omd_to_nx( dssFilePath, tree=None ):
''' Combines dss_to_networkX and opendss.networkPlot together.
Creates a networkx directed graph from a dss files. If a tree is provided, build graph from that instead of the file.
Creates a .png picture of the graph.
Adds data to certain DSS node types ( loads )
args:
filepath (str of file name):- dss file path
tree (list): None - tree representation of dss file
output_name (str):- name of png
show_labels (bool): true - show node label names
node_size (int): 300 - size of node circles in png
font_size (int): 8 - font size for png labels
return:
A networkx graph of the circuit
'''
if tree == None:
tree = opendss.dssConvert.dssToTree( dssFilePath )

G = nx.DiGraph()
pos = {}

setbusxyList = [x for x in tree if '!CMD' in x and x['!CMD'] == 'setbusxy']
x_coords = [x['x'] for x in setbusxyList if 'x' in x]
y_coords = [x['y'] for x in setbusxyList if 'y' in x]
bus_names = [x['bus'] for x in setbusxyList if 'bus' in x]
for bus, x, y in zip( bus_names, x_coords, y_coords):
G.add_node(bus, pos=(x, y))
pos[bus] = (x, y)

lines = [x for x in tree if x.get('object', 'N/A').startswith('line.')]
bus1_lines = [x.split('.')[0] for x in [x['bus1'] for x in lines if 'bus1' in x]]
bus2_lines = [x.split('.')[0] for x in [x['bus2'] for x in lines if 'bus2' in x]]
edges = []
for bus1, bus2 in zip( bus1_lines, bus2_lines):
edges.append( (bus1, bus2) )
G.add_edges_from(edges)

# Need edges from bus --- trasnformr info ---> load
transformers = [x for x in tree if x.get('object', 'N/A').startswith('transformer.')]
transformer_bus_names = [x['buses'] for x in transformers if 'buses' in x]
bus_to_transformer_pairs = {}
for transformer_bus in transformer_bus_names:
strip_paren = transformer_bus.strip('[]')
split_buses = strip_paren.split(',')
bus = split_buses[0].split('.')[0]
transformer_name = split_buses[1].split('.')[0]
bus_to_transformer_pairs[transformer_name] = bus

loads = [x for x in tree if x.get('object', 'N/A').startswith('load.')] # This is an orderedDict
load_names = [x['object'].split('.')[1] for x in loads if 'object' in x and x['object'].startswith('load.')]
load_transformer_name = [x.split('.')[0] for x in [x['bus1'] for x in loads if 'bus1' in x]]

# Connects loads to buses via transformers
labels = {}
for load_name, load_transformer in zip(load_names, load_transformer_name):
# Add edge from bus to load, with transformer name as an attribute
bus = bus_to_transformer_pairs[load_transformer]
G.add_edge(bus, load_name, transformer=load_transformer)
pos[load_name] = pos[load_transformer]
labels[load_name] = load_name
labels[ bus ] = bus

# TEMP: Remove transformer nodes added from coordinates. Transformer data is edges, not nodes.
for transformer_name in load_transformer_name:
if transformer_name in G.nodes:
G.remove_node( transformer_name )
pos.pop( transformer_name )

load_kw = [x['kw'] for x in loads if 'kw' in x]
for load, kw in zip( load_names, load_kw ):
G.nodes[load]['kw'] = kw

return [G, pos, labels]

def work(modelDir, inputDict):
outData = {}

if inputDict['runAmiAlgorithm'] == 'on':
run_ami_algorithm(modelDir, inputDict, outData)
if inputDict.get('optionalCircuitFile', outData) == 'on':
run_traditional_algorithm(modelDir, inputDict, outData)
if inputDict.get('runDownlineAlgorithm') == 'on':
run_downline_load_algorithm( modelDir, inputDict, outData)
outData['stdout'] = "Success"
outData['stderr'] = ""
return outData

def run_downline_load_algorithm( modelDir, inputDict, outData):
feederName = [x for x in os.listdir(modelDir) if x.endswith('.omd') and x[:-4] == inputDict['feederName1'] ][0]
inputDict['feederName1'] = feederName[:-4]
path_to_omd = Path(modelDir, feederName)
tree = opendss.dssConvert.omdToTree(path_to_omd)
opendss.dssConvert.treeToDss(tree, Path(modelDir, 'downlineLoad.dss'))
downline_start_time = time.time()
graphData = omd_to_nx( os.path.join( modelDir, 'downlineLoad.dss') )
graph = graphData[0]
buses = opendss.get_all_buses( os.path.join( modelDir, 'downlineLoad.dss') )
buses_output = {}
kwFromGraph = nx.get_node_attributes(graph, 'kw')
for bus in buses:
if bus in graph.nodes:
kwSum = 0
get_dependents = sorted(nx.descendants(graph, bus))
for dependent in get_dependents:
if dependent in kwFromGraph.keys():
kwSum += float(kwFromGraph[dependent])
buses_output[bus] = kwSum
downline_output = pd.DataFrame(list(buses_output.items()), columns=['busname', 'kw'] )
downline_end_time = time.time()
sorted_downlineDF = downline_output.sort_values(by='busname')

buses_to_remove = ['eq_source_bus', 'bus_xfmr']
indexes = []
for bus in buses_to_remove:
indexes.append( sorted_downlineDF[sorted_downlineDF['busname'] == bus].index )
for i in indexes:
sorted_downlineDF = sorted_downlineDF.drop(i)
outData['downline_tableHeadings'] = downline_output.columns.values.tolist()
outData['downline_tableValues'] = (list(sorted_downlineDF.itertuples(index=False, name=None)))
outData['downline_runtime'] = convert_seconds_to_hms_ms( downline_end_time - downline_start_time )

def run_ami_algorithm(modelDir, inputDict, outData):
# mohca data-driven hosting capacity
inputPath = Path(modelDir, inputDict['AMIDataFileName'])
Expand Down Expand Up @@ -99,20 +225,17 @@ def run_ami_algorithm(modelDir, inputDict, outData):
outData['AMI_tableValues'] = ( list(AMI_results.sort_values( by="max_cap_allowed_kW", ascending=False, ignore_index=True ).itertuples(index=False, name=None)) )
outData['AMI_runtime'] = convert_seconds_to_hms_ms( AMI_end_time - AMI_start_time )


def run_traditional_algorithm(modelDir, inputDict, outData):
# traditional hosting capacity if they uploaded an omd circuit file and chose to use it.
feederName = [x for x in os.listdir(modelDir) if x.endswith('.omd') and x[:-4] == inputDict['feederName1'] ][0]
inputDict['feederName1'] = feederName[:-4]
path_to_omd = Path(modelDir, feederName)
tree = opendss.dssConvert.omdToTree(path_to_omd)
opendss.dssConvert.treeToDss(tree, Path(modelDir, 'circuit.dss'))
curr_dir = os.getcwd()
traditional_start_time = time.time()
traditionalHCResults = opendss.hosting_capacity_all(Path(modelDir, 'circuit.dss'), int(inputDict["traditionalHCMaxTestkw"]))
traditional_end_time = time.time()
# - opendss.hosting_capacity_all() changes the cwd, so change it back so other code isn't affected
os.chdir(curr_dir)
tradHCDF = pd.DataFrame(traditionalHCResults)
tradHCDF['plot_color'] = tradHCDF.apply ( lambda row: bar_chart_coloring(row), axis=1 )
# Plotly has its own colors - need to map the "name" of given colors to theirs
Expand Down Expand Up @@ -147,10 +270,13 @@ def run_traditional_algorithm(modelDir, inputDict, outData):
with open(new_path, 'w+') as out_file:
json.dump(omd, out_file, indent=4)
omf.geo.map_omd(new_path, modelDir, open_browser=False )

sorted_tradHCDF = tradHCDF.sort_values(by='bus')

outData['traditionalHCMap'] = open(Path(modelDir, "geoJson_offline.html"), 'r' ).read()
outData['traditionalGraphData'] = json.dumps(traditionalHCFigure, cls=py.utils.PlotlyJSONEncoder )
outData['traditionalHCTableHeadings'] = tradHCDF.columns.values.tolist()
outData['traditionalHCTableValues'] = (list(tradHCDF.itertuples(index=False, name=None)))
outData['traditionalHCTableValues'] = (list(sorted_tradHCDF.itertuples(index=False, name=None)))
outData['traditionalRuntime'] = convert_seconds_to_hms_ms( traditional_end_time - traditional_start_time )
outData['traditionalHCResults'] = traditionalHCResults

Expand All @@ -171,7 +297,8 @@ def new(modelDir):
"feederName1": 'iowa240.clean.dss',
"optionalCircuitFile": 'on',
"traditionalHCMaxTestkw": 50000,
"runAmiAlgorithm": 'on'
"runAmiAlgorithm": 'on',
"runDownlineAlgorithm": 'on'
}
creationCode = __neoMetaModel__.new(modelDir, defaultInputs)
try:
Expand Down
Binary file removed omf/scratch/hostingcapacity/directedNetworkPlot.png
Binary file not shown.
Loading

0 comments on commit 02c9a2b

Please sign in to comment.