Example #1
0
def oneLineGridlab(temp_dir):
    '''
	Create a one-line diagram of the input GLM and return a PNG of it.

	Form parameters:
	:param glm: a GLM file.
	:param useLatLons: 'True' if the diagram should be drawn with coordinate values taken from within the GLM, 'False' if the diagram should be drawn
		with artificial coordinates using Graphviz NEATO.

	Details:
	:OMF fuction: omf.feeder.latLonNxGraph().
	:run-time: about 1 to 30 seconds.
	'''
    glm_path = os.path.join(temp_dir, 'in.glm')
    feed = feeder.parse(glm_path)
    graph = feeder.treeToNxGraph(feed)
    neatoLayout = True if request.form.get('useLatLons') == 'False' else False
    # Clear old plots.
    plt.clf()
    plt.close()
    # Plot new plot.
    feeder.latLonNxGraph(graph,
                         labels=False,
                         neatoLayout=neatoLayout,
                         showPlot=False)
    plt.savefig(os.path.join(temp_dir, filenames["ongl"]))
Example #2
0
def genDiagram(outputDir, feederJson):
    # Load required data.
    tree = feederJson.get("tree", {})
    links = feederJson.get("links", {})
    # Generate lat/lons from nodes and links structures.
    for link in links:
        for typeLink in link.keys():
            if typeLink in ['source', 'target']:
                for key in link[typeLink].keys():
                    if key in ['x', 'y']:
                        objName = link[typeLink]['name']
                        for x in tree:
                            leaf = tree[x]
                            if leaf.get('name', '') == objName:
                                if key == 'x':
                                    leaf['latitude'] = link[typeLink][key]
                                else:
                                    leaf['longitude'] = link[typeLink][key]
    # Remove even more things (no lat, lon or from = node without a position).
    for key in tree.keys():
        aLat = tree[key].get('latitude')
        aLon = tree[key].get('longitude')
        aFrom = tree[key].get('from')
        if aLat is None and aLon is None and aFrom is None:
            tree.pop(key)
    # Create and save the graphic.
    nxG = feeder.treeToNxGraph(tree)
    feeder.latLonNxGraph(
        nxG)  # This function creates a .plt reference which can be saved here.
    plt.savefig(pJoin(outputDir, "feederChart.png"), dpi=800, pad_inches=0.0)
Example #3
0
File: geo.py Project: Billjutt/omf
def hullOfOmd(pathToOmdFile, conversion=False):
    '''Convex hull of an omd in the form of a geojson dictionary with a single ploygon.'''
    if not conversion:
        with open(pathToOmdFile) as inFile:
            tree = json.load(inFile)['tree']
        nxG = feeder.treeToNxGraph(tree)
        nxG = graphValidator(pathToOmdFile, nxG)
    #use conversion for testing other feeders
    if conversion:
        nxG = convertOmd(pathToOmdFile)
    points = np.array([
        nxG.nodes[nodewithPosition]['pos']
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    ])
    hull = ConvexHull(points)
    polygon = points[hull.vertices].tolist()
    for point in polygon:
        point.reverse()
    #Add first node to beginning to comply with geoJSON standard
    polygon.append(polygon[0])
    #Create dict and bump to json file
    geoJsonDict = {
        "type":
        "FeatureCollection",
        "features": [{
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [polygon]
            }
        }]
    }
    return geoJsonDict
Example #4
0
File: geo.py Project: Billjutt/omf
def omdGeoJson(pathToOmdFile, conversion=False):
    '''Create a geojson standards compliant file (https://tools.ietf.org/html/rfc7946) from an omd.'''
    with open(pathToOmdFile) as inFile:
        tree = json.load(inFile)['tree']
    nxG = feeder.treeToNxGraph(tree)
    #use conversion for testing other feeders
    nxG = graphValidator(pathToOmdFile, nxG)
    if conversion:
        nxG = convertOmd(pathToOmdFile)
    geoJsonDict = {"type": "FeatureCollection", "features": []}
    #Add nodes to geoJSON
    node_positions = {
        nodewithPosition: nxG.nodes[nodewithPosition]['pos']
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    }
    node_types = {
        nodewithType: nxG.nodes[nodewithType]['type']
        for nodewithType in nx.get_node_attributes(nxG, 'type')
    }
    for node in node_positions:
        geoJsonDict['features'].append({
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates":
                [node_positions[node][1], node_positions[node][0]]
            },
            "properties": {
                "name": node,
                #"pointType": node_types[node],
                #"pointColor": _obToCol(node_types[node])
            }
        })
    #Add edges to geoJSON
    edge_types = {
        edge: nxG[edge[0]][edge[1]]['type']
        for edge in nx.get_edge_attributes(nxG, 'type')
    }
    edge_phases = {
        edge: nxG[edge[0]][edge[1]]['phases']
        for edge in nx.get_edge_attributes(nxG, 'phases')
    }
    for edge in nx.edges(nxG):
        geoJsonDict['features'].append({
            "type": "Feature",
            "geometry": {
                "type":
                "LineString",
                "coordinates":
                [[node_positions[edge[0]][1], node_positions[edge[0]][0]],
                 [node_positions[edge[1]][1], node_positions[edge[1]][0]]]
            },
            "properties": {
                #"phase": edge_phases[edge],
                #"edgeType": edge_types[edge],
                #"edgeColor":_obToCol(edge_types[edge])
            }
        })
    return geoJsonDict
Example #5
0
File: geo.py Project: Billjutt/omf
def shortestPathOmd(pathToOmdFile, sourceObjectName, targetObjectName):
    '''Get the shortest path between two points on a feeder'''
    with open(pathToOmdFile) as inFile:
        tree = json.load(inFile)['tree']
    nxG = feeder.treeToNxGraph(tree)
    nxG = graphValidator(pathToOmdFile, nxG)
    tracePath = nx.bidirectional_shortest_path(nxG, sourceObjectName,
                                               targetObjectName)
    return tracePath
Example #6
0
def milsoftToGridlabTests(keepFiles=False):
	openPrefix = '../uploads/'
	outPrefix = './milToGridlabTests/'
	import os, json, traceback, shutil
	from omf.solvers import gridlabd
	from matplotlib import pyplot as plt
	from milToGridlab import convert
	import omf.feeder as feeder
	try:
		os.mkdir(outPrefix)
	except:
		pass # Directory already there.
	exceptionCount = 0
	# testFiles = [('INEC-RENOIR.std','INEC.seq'), ('INEC-GRAHAM.std','INEC.seq'),
	#   ('Olin-Barre.std','Olin.seq'), ('Olin-Brown.std','Olin.seq'),
	#   ('ABEC-FRANK.std','ABEC.seq'), ('ABEC-COLUMBIA.std','ABEC.seq'),('OMF_Norfork1.std', 'OMF_Norfork1.seq')]
	testFiles = [('Olin-Brown.std', 'Olin.seq')]
	testAttachments = {'schedules.glm':''}
	# testAttachments = {'schedules.glm':'', 'climate.tmy2':open('./data/Climate/KY-LEXINGTON.tmy2','r').read()}
	for stdString, seqString in testFiles:
		try:
			# Convert the std+seq.
			with open(openPrefix + stdString,'r') as stdFile, open(openPrefix + seqString,'r') as seqFile:
				outGlm,x,y = convert(stdFile.read(),seqFile.read())
			with open(outPrefix + stdString.replace('.std','.glm'),'w') as outFile:
				outFile.write(feeder.sortedWrite(outGlm))
			print 'WROTE GLM FOR', stdString
			try:
				# Draw the GLM.
				myGraph = feeder.treeToNxGraph(outGlm)
				feeder.latLonNxGraph(myGraph, neatoLayout=False)
				plt.savefig(outPrefix + stdString.replace('.std','.png'))
				print 'DREW GLM OF', stdString
			except:
				exceptionCount += 1
				print 'FAILED DRAWING', stdString
			try:
				# Run powerflow on the GLM. HACK:blank attachments for now.
				output = gridlabd.runInFilesystem(outGlm, attachments=testAttachments, keepFiles=False)
				with open(outPrefix + stdString.replace('.std','.json'),'w') as outFile:
					json.dump(output, outFile, indent=4)
				print 'RAN GRIDLAB ON', stdString
			except:
				exceptionCount += 1
				print 'POWERFLOW FAILED', stdString
		except:
			print 'FAILED CONVERTING', stdString
			exceptionCount += 1
			traceback.print_exc()
	if not keepFiles:
		shutil.rmtree(outPrefix)
	return exceptionCount
Example #7
0
def insert_coordinates(tree):
    # type: (dict) -> None
    """Insert additional latitude and longitude data into the dictionary."""
    print("Force laying out the graph...")
    # Use graphviz to lay out the graph.
    inGraph = feeder.treeToNxGraph(tree)
    # HACK: work on a new graph without attributes because graphViz tries to read attrs.
    cleanG = nx.Graph(inGraph.edges())
    # HACK2: might miss nodes without edges without the following.
    cleanG.add_nodes_from(inGraph)
    pos = nx.nx_agraph.graphviz_layout(cleanG, prog='neato')
    # # Charting the feeder in matplotlib:
    # feeder.latLonNxGraph(inGraph, labels=False, neatoLayout=True, showPlot=True)
    # Insert the latlons.
    for key in tree:
        obName = tree[key].get('name', '')
        thisPos = pos.get(obName, None)
        if thisPos != None:
            tree[key]['longitude'] = thisPos[0]
            tree[key]['latitude'] = thisPos[1]
Example #8
0
def voltPlot(omd, workDir=None, neatoLayout=False):
    ''' Draw a color-coded map of the voltage drop on a feeder.
	Returns a matplotlib object. '''
    tree = omd.get('tree', {})
    # # Get rid of schedules and climate:
    for key in tree.keys():
        if tree[key].get("argument",
                         "") == "\"schedules.glm\"" or tree[key].get(
                             "tmyfile", "") != "":
            del tree[key]
    # Make sure we have a voltDump:
    def safeInt(x):
        try:
            return int(x)
        except:
            return 0

    biggestKey = max([safeInt(x) for x in tree.keys()])
    tree[str(biggestKey * 10)] = {
        "object": "voltdump",
        "filename": "voltDump.csv"
    }
    # Run Gridlab.
    if not workDir:
        workDir = tempfile.mkdtemp()
        print "gridlabD runInFilesystem with no specified workDir. Working in", workDir
    gridlabOut = gridlabd.runInFilesystem(tree,
                                          attachments=omd.get(
                                              'attachments', {}),
                                          workDir=workDir)
    with open(pJoin(workDir, 'voltDump.csv'), 'r') as dumpFile:
        reader = csv.reader(dumpFile)
        reader.next()  # Burn the header.
        keys = reader.next()
        voltTable = []
        for row in reader:
            rowDict = {}
            for pos, key in enumerate(keys):
                rowDict[key] = row[pos]
            voltTable.append(rowDict)
    # Calculate average node voltage deviation. First, helper functions.
    def pythag(x, y):
        ''' For right triangle with sides a and b, return the hypotenuse. '''
        return math.sqrt(x**2 + y**2)

    def digits(x):
        ''' Returns number of digits before the decimal in the float x. '''
        return math.ceil(math.log10(x + 1))

    def avg(l):
        ''' Average of a list of ints or floats. '''
        return sum(l) / len(l)

    # Detect the feeder nominal voltage:
    for key in tree:
        ob = tree[key]
        if type(ob) == dict and ob.get('bustype', '') == 'SWING':
            feedVoltage = float(ob.get('nominal_voltage', 1))
    # Tot it all up.
    nodeVolts = {}
    for row in voltTable:
        allVolts = []
        for phase in ['A', 'B', 'C']:
            phaseVolt = pythag(float(row['volt' + phase + '_real']),
                               float(row['volt' + phase + '_imag']))
            if phaseVolt != 0.0:
                if digits(phaseVolt) > 3:
                    # Normalize to 120 V standard
                    phaseVolt = phaseVolt * (120 / feedVoltage)
                allVolts.append(phaseVolt)
        nodeVolts[row.get('node_name', '')] = avg(allVolts)
    # Color nodes by VOLTAGE.
    fGraph = feeder.treeToNxGraph(tree)
    voltChart = plt.figure(figsize=(15, 15))
    plt.axes(frameon=0)
    plt.axis('off')
    #set axes step equal
    voltChart.gca().set_aspect('equal')
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(fGraph.edges())
        cleanG.add_nodes_from(fGraph)
        positions = graphviz_layout(cleanG, prog='neato')
    else:
        positions = {n: fGraph.node[n].get('pos', (0, 0)) for n in fGraph}
    edgeIm = nx.draw_networkx_edges(fGraph, positions)
    nodeIm = nx.draw_networkx_nodes(
        fGraph,
        pos=positions,
        node_color=[nodeVolts.get(n, 0) for n in fGraph.nodes()],
        linewidths=0,
        node_size=30,
        cmap=plt.cm.jet)
    plt.sci(nodeIm)
    plt.clim(110, 130)
    plt.colorbar()
    return voltChart
Example #9
0
def generateVoltChart(tree, rawOut, modelDir, neatoLayout=True):
	''' Map the voltages on a feeder over time using a movie.'''
	# We need to timestamp frames with the system clock to make sure the browser caches them appropriately.
	genTime = str(datetime.datetime.now()).replace(':','.')
	# Detect the feeder nominal voltage:
	for key in tree:
		ob = tree[key]
		if type(ob)==dict and ob.get('bustype','')=='SWING':
			feedVoltage = float(ob.get('nominal_voltage',1))
	# Make a graph object.
	fGraph = feeder.treeToNxGraph(tree)
	if neatoLayout:
		# HACK: work on a new graph without attributes because graphViz tries to read attrs.
		cleanG = nx.Graph(fGraph.edges())
		cleanG.add_nodes_from(fGraph)
		# was formerly : positions = nx.graphviz_layout(cleanG, prog='neato') but this threw an error
		positions = nx.nx_agraph.graphviz_layout(cleanG, prog='neato')
	else:
		rawPositions = {n:fGraph.node[n].get('pos',(0,0)) for n in fGraph}
		#HACK: the import code reverses the y coords.
		def yFlip(pair):
			try: return (pair[0], -1.0*pair[1])
			except: return (0,0)
		positions = {k:yFlip(rawPositions[k]) for k in rawPositions}
	# Plot all time steps.
	nodeVolts = {}
	for step, stamp in enumerate(rawOut['aVoltDump.csv']['# timestamp']):
		# Build voltage map.
		nodeVolts[step] = {}
		for nodeName in [x for x in rawOut.get('aVoltDump.csv',{}).keys() + rawOut.get('1nVoltDump.csv',{}).keys() + rawOut.get('1mVoltDump.csv',{}).keys() if x != '# timestamp']:
			allVolts = []
			for phase in ['a','b','c','1n','2n','1m','2m']:
				try:
					voltStep = rawOut[phase + 'VoltDump.csv'][nodeName][step]
				except:
					continue # the nodeName doesn't have the phase we're looking for.
				# HACK: Gridlab complex number format sometimes uses i, sometimes j, sometimes d. WTF?
				if type(voltStep) is str:
					voltStep = voltStep.replace('i','j')
				v = complex(voltStep)
				phaseVolt = abs(v)
				if phaseVolt != 0.0:
					if _digits(phaseVolt)>3:
						# Normalize to 120 V standard
						phaseVolt = phaseVolt*(120/feedVoltage)
					allVolts.append(phaseVolt)
			# HACK: Take average of all phases to collapse dimensionality.
			nodeVolts[step][nodeName] = avg(allVolts)
	# Line current calculations
	lineCurrents = {}
	if os.path.exists(pJoin(modelDir,'OH_line_current_phaseA.csv')):
		for step, stamp in enumerate(rawOut['OH_line_current_phaseA.csv']['# timestamp']):
			lineCurrents[step] = {} 
			currentArray = []
			# Finding currents of all phases on the line
			for key in [x for x in rawOut.get('OH_line_current_phaseA.csv',{}).keys() if x != '# timestamp']:
				currA = rawOut['OH_line_current_phaseA.csv'][key][step]
				currB = rawOut['OH_line_current_phaseB.csv'][key][step]
				currC = rawOut['OH_line_current_phaseC.csv'][key][step]
				flowDir = rawOut['OH_line_flow_direc.csv'][key][step]
				lineRating = rawOut['OH_line_cont_rating.csv'][key][step]
				if 'R' in flowDir:
					direction = -1
				else :
					direction = 1
				if type(currA) is str: 
					currA = stringToMag(currA)
					currB = stringToMag(currB)
					currC = stringToMag(currC)
					maxCurrent = max(abs(currA),abs(currB),abs(currC))
					directedCurrent = float(maxCurrent/lineRating * direction)
				for objt in tree:
					if 'name' in tree[objt].keys():
						if tree[objt]['name'] == str(int(key)):
							keyTup = (tree[objt]['to'],tree[objt]['from'])
				lineCurrents[step][keyTup] = directedCurrent
	# Underground Lines
	if os.path.exists(pJoin(modelDir,'UG_line_current_phaseA.csv')):
		for step, stamp in enumerate(rawOut['UG_line_current_phaseA.csv']['# timestamp']):
			currentArray = []
			# Finding currents of all phases on the line
			for key in [x for x in rawOut.get('UG_line_current_phaseA.csv',{}).keys() if x != '# timestamp']:
				currA = rawOut['UG_line_current_phaseA.csv'][key][step]
				currB = rawOut['UG_line_current_phaseB.csv'][key][step]
				currC = rawOut['UG_line_current_phaseC.csv'][key][step]
				flowDir = rawOut['UG_line_flow_direc.csv'][key][step]
				lineRating = rawOut['UG_line_cont_rating.csv'][key][step]
				if 'R' in flowDir:
					direction = -1
				else :
					direction = 1
				if type(currA) is str: 
					currA = stringToMag(currA)
					currB = stringToMag(currB)
					currC = stringToMag(currC)
					maxCurrent = max(abs(currA),abs(currB),abs(currC))
					directedCurrent = float(maxCurrent/lineRating * direction)
				for objt in tree:
					if 'name' in tree[objt].keys():
						if tree[objt]['name'] == str(int(key)):
							keyTup = (tree[objt]['to'],tree[objt]['from'])
				lineCurrents[step][keyTup] = directedCurrent
		for step in lineCurrents:
			for edge in fGraph.edges():
				if edge not in lineCurrents[step].keys():
					lineCurrents[step][edge] = 0
	# Draw animation.
	voltChart = plt.figure(figsize=(15,15))
	plt.axes(frameon = 0)
	plt.axis('off')
	#set axes step equal
	voltChart.gca().set_aspect('equal')
	custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list('custColMap',[(0.0,'blue'),(0.25,'darkgray'),(0.75,'darkgray'),(1.0,'yellow')])
	custom_cm.set_under(color='black')
	current_cm = matplotlib.colors.LinearSegmentedColormap.from_list('custColMap',[(0.0,'green'),(0.999999,'green'),(1.0,'red')])
	# current_cm = matplotlib.colors.LinearSegmentedColormap.from_list('custColMap',[(-1.0,'green'),(0.0, 'gray'),(1.0,'red'),(1.0,'red')])
	# use edge color to set color and dashness of overloaded/negative currents
	if len(lineCurrents)>0:
		edgeIm = nx.draw_networkx_edges(fGraph, 
			pos = positions,
			edge_color = [lineCurrents[0].get(n,0) for n in fGraph.edges()],
			edge_cmap = current_cm)
	else:	
		edgeIm = nx.draw_networkx_edges(fGraph, positions)
	nodeIm = nx.draw_networkx_nodes(fGraph,
		pos = positions,
		node_color = [nodeVolts[0].get(n,0) for n in fGraph.nodes()],
		linewidths = 0,
		node_size = 30,
		cmap = custom_cm)
	plt.sci(nodeIm)
	plt.clim(110,130)
	plt.colorbar()
	plt.title(rawOut['aVoltDump.csv']['# timestamp'][0])
	def update(step):
		nodeColors = np.array([nodeVolts[step].get(n,0) for n in fGraph.nodes()])
		if len(lineCurrents)>0:
			edgeColors = np.array([lineCurrents[step].get(n,0) for n in fGraph.edges()])
			edgeIm.set_array(edgeColors)
		plt.title(rawOut['aVoltDump.csv']['# timestamp'][step])
		nodeIm.set_array(nodeColors)
		return nodeColors,
	mapTimestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
	anim = FuncAnimation(voltChart, update, frames=len(rawOut['aVoltDump.csv']['# timestamp']), interval=200, blit=False)
	anim.save(pJoin(modelDir,'voltageChart_'+ mapTimestamp +'.mp4'), codec='h264', extra_args=['-pix_fmt', 'yuv420p'])
	# Reclaim memory by closing, deleting and garbage collecting the last chart.
	voltChart.clf()
	plt.close()
	del voltChart
	gc.collect()
	return genTime, mapTimestamp
Example #10
0
File: geo.py Project: Billjutt/omf
def rasterTilesFromOmd(pathToOmdFile, outputPath, conversion=False):
    '''Save raster tiles of omd to serve from zoom/x/y directory'''
    if not conversion:
        with open(pathToOmdFile) as inFile:
            tree = json.load(inFile)['tree']
        #networkx graph to work with
        nxG = feeder.treeToNxGraph(tree)
        nxG = graphValidator(pathToOmdFile, nxG)
    #use conversion for testing other feeders
    if conversion:
        nxG = convertOmd(pathToOmdFile)
    #Lat/lon min/max for caluclating tile coverage later
    latitude_min = min([
        nxG.nodes[nodewithPosition]['pos'][0]
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    ])
    longitude_min = min([
        nxG.nodes[nodewithPosition]['pos'][1]
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    ])
    latitude_max = max([
        nxG.nodes[nodewithPosition]['pos'][0]
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    ])
    longitude_max = max([
        nxG.nodes[nodewithPosition]['pos'][1]
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    ])
    #Set the plot settings
    plt.switch_backend('Agg')
    fig = plt.figure(frameon=False, figsize=[2.56, 2.56])
    ax = fig.add_axes([0, 0, 1, 1])
    ax.axis('off')
    #Create the default tile
    if not os.path.exists(outputPath):
        os.makedirs(outputPath)
    plt.savefig(pJoin(outputPath, 'default.png'),
                frameon=False,
                pad_inches=0,
                bbox='tight')
    #map latlon to projection
    epsg3857 = Proj(init='epsg:3857')
    wgs84 = Proj(init='EPSG:4326')
    node_positions = {
        nodewithPosition: nxG.nodes[nodewithPosition]['pos']
        for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
    }
    for point in node_positions:
        node_positions[point] = transform(wgs84, epsg3857,
                                          node_positions[point][1],
                                          node_positions[point][0])
    #Go through each zoom level and create tiles for each area covering the feeder
    nx.draw_networkx(nxG,
                     pos=node_positions,
                     nodelist=list(node_positions.keys()),
                     with_labels=False,
                     node_size=2,
                     edge_size=1)
    for zoomLevel in range(0, 19):
        #Boundaries covering the omd locations for the current zoom level
        upperRightTile = tileXY(latitude_max, longitude_max, zoomLevel)
        lowerLeftTile = tileXY(latitude_min, longitude_min, zoomLevel)
        firstTileEdges = tileEdges(upperRightTile[0], upperRightTile[1],
                                   zoomLevel)
        lastTileEdges = tileEdges(lowerLeftTile[0], lowerLeftTile[1],
                                  zoomLevel)
        #Map omd for each x/y tile area
        for tileX in range(lowerLeftTile[0], upperRightTile[0] + 1):
            for tileY in range(upperRightTile[1], lowerLeftTile[1] + 1):
                currentTileEdges = tileEdges(tileX, tileY, zoomLevel)
                southWest = transform(wgs84, epsg3857, currentTileEdges[1],
                                      currentTileEdges[0])
                northEast = transform(wgs84, epsg3857, currentTileEdges[3],
                                      currentTileEdges[2])
                # S,W,N,E
                plt.ylim(top=northEast[1], bottom=southWest[1])
                plt.xlim(southWest[0], northEast[0])
                #create directory for tile
                savePath = pJoin(outputPath, str(zoomLevel), str(tileX))
                if not os.path.exists(savePath):
                    os.makedirs(savePath)
                plt.savefig(pJoin(savePath, '%s.png' % str(tileY)),
                            frameon=False,
                            pad_inches=0,
                            bbox='tight')
Example #11
0
File: geo.py Project: Billjutt/omf
def simplifiedOmdShape(pathToOmdFile, conversion=False):
    '''Use kmeans clustering to create simplified geojson object with convex hull and connected clusters from an omd.'''
    if not conversion:
        with open(pathToOmdFile) as inFile:
            tree = json.load(inFile)['tree']
        nxG = feeder.treeToNxGraph(tree)
        nxG = graphValidator(pathToOmdFile, nxG)
        simplifiedGeoDict = hullOfOmd(pathToOmdFile)
    #use conversion for testing other feeders
    if conversion:
        nxG = convertOmd(pathToOmdFile)
        simplifiedGeoDict = hullOfOmd(pathToOmdFile, conversion=True)

    #Kmeans clustering function
    numpyGraph = np.array([[
        node,
        float(nxG.nodes[node]['pos'][0]),
        float(nxG.nodes[node]['pos'][1])
    ] for node in nx.get_node_attributes(nxG, 'pos')],
                          dtype=object)
    Kmean = KMeans(n_clusters=20)
    Kmean.fit(numpyGraph[:, 1:3])

    #Set up new graph with cluster centers as nodes to use in output
    centerNodes = Kmean.cluster_centers_
    clusterDict = {
        i: numpyGraph[np.where(Kmean.labels_ == i)]
        for i in range(Kmean.n_clusters)
    }
    simplifiedGraph = nx.Graph()
    for centerNode in clusterDict:
        currentClusterGroup = clusterDict[centerNode]
        simplifiedGraph.add_node('centroid' + str(centerNode),
                                 type='centroid',
                                 pos=(centerNodes[centerNode][0],
                                      centerNodes[centerNode][1]),
                                 clusterSize=np.ma.size(currentClusterGroup,
                                                        axis=0),
                                 lineCount=0)

    #Create edges between cluster centers
    for centerNode in clusterDict:
        currentClusterGroup = clusterDict[centerNode]
        nxG.add_node('centroid' + str(centerNode),
                     type='centroid',
                     pos=(centerNodes[centerNode][0],
                          centerNodes[centerNode][1]))
        intraClusterLines = 0
        for i in currentClusterGroup:
            currentNode = i[0]
            neighbors = nx.neighbors(nxG, currentNode)
            for neighbor in neighbors:
                #connect centroids
                if nxG.nodes[neighbor]['type'] == 'centroid':
                    if ('centroid' + str(centerNode),
                            neighbor) not in nx.edges(simplifiedGraph):
                        simplifiedGraph.add_edge('centroid' + str(centerNode),
                                                 neighbor,
                                                 type='centroidConnector',
                                                 lineCount=1)
                    else:
                        simplifiedGraph[
                            'centroid' +
                            str(centerNode)][neighbor]['lineCount'] += 1
                #connect centroid to nodes in other clusters, which is replaced in subsequent loops
                elif neighbor not in currentClusterGroup[:, 0]:
                    nxG.add_edge('centroid' + str(centerNode),
                                 neighbor,
                                 type='centroidConnector')
                else:
                    simplifiedGraph.nodes['centroid' +
                                          str(centerNode)]['lineCount'] += 1
            if currentNode in simplifiedGraph:
                simplifiedGraph.remove_node(currentNode)

    #Add nodes and edges to dict with convex hull
    for node in simplifiedGraph.nodes:
        simplifiedGeoDict['features'].append({
            "type": "Feature",
            "geometry": {
                "type":
                "Point",
                "coordinates": [
                    simplifiedGraph.nodes[node]['pos'][1],
                    simplifiedGraph.nodes[node]['pos'][0]
                ]
            },
            "properties": {
                "name": node,
                "pointType": simplifiedGraph.nodes[node]['type'],
                "lineCount": simplifiedGraph.nodes[node]['lineCount']
            }
        })
    #Add edges to dict
    for edge in nx.edges(simplifiedGraph):
        simplifiedGeoDict['features'].append({
            "type": "Feature",
            "geometry": {
                "type":
                "LineString",
                "coordinates": [[
                    simplifiedGraph.nodes[edge[0]]['pos'][1],
                    simplifiedGraph.nodes[edge[0]]['pos'][0]
                ],
                                [
                                    simplifiedGraph.nodes[edge[1]]['pos'][1],
                                    simplifiedGraph.nodes[edge[1]]['pos'][0]
                                ]]
            },
            "properties": {
                "lineCount": simplifiedGraph[edge[0]][edge[1]]['lineCount'],
                "edgeType": simplifiedGraph[edge[0]][edge[1]]['type']
            }
        })
    return simplifiedGeoDict
    '''
Example #12
0
def viz(pathToOmdOrGlm, forceLayout=False, outputPath=None):
    ''' Vizualize a distribution system.'''
    # HACK: make sure we have our homebrew binaries available.
    os.environ['PATH'] += os.pathsep + '/usr/local/bin'
    # Load in the feeder.
    with open(pathToOmdOrGlm, 'r') as feedFile:
        if pathToOmdOrGlm.endswith('.omd'):
            thisFeed = {
                'tree': json.load(feedFile)['tree']
            }  # TODO: later bring back attachments.
        elif pathToOmdOrGlm.endswith('.glm'):
            thisFeed = {'tree': feeder.parse(pathToOmdOrGlm, filePath=True)}
        tree = thisFeed['tree']
    # If there is zero lat/lon info, do force layout by default.
    latLonCount = 0
    for key in tree:
        for subKey in ['latitude', 'longitude']:
            if subKey in tree[key]:
                latLonCount += 1
    if latLonCount == 0:
        forceLayout = True
    # Force layout of feeders with no lat/lon information so we can actually see what's there.
    if forceLayout:
        print "Force laying out the graph..."
        # Use graphviz to lay out the graph.
        inGraph = feeder.treeToNxGraph(tree)
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(inGraph.edges())
        # HACK2: might miss nodes without edges without the following.
        cleanG.add_nodes_from(inGraph)
        pos = nx.nx_agraph.graphviz_layout(cleanG, prog='neato')
        # # Charting the feeder in matplotlib:
        # feeder.latLonNxGraph(inGraph, labels=False, neatoLayout=True, showPlot=True)
        # Insert the latlons.
        for key in tree:
            obName = tree[key].get('name', '')
            thisPos = pos.get(obName, None)
            if thisPos != None:
                tree[key]['longitude'] = thisPos[0]
                tree[key]['latitude'] = thisPos[1]
    # Set up temp directory and copy the feeder and viewer in to it.
    if outputPath == None:
        tempDir = tempfile.mkdtemp()
    else:
        tempDir = outputPath
    #HACK: make sure we get the required files from the right place.
    SOURCE_DIR = os.path.dirname(__file__) + '/'
    shutil.copy(SOURCE_DIR + '/distNetViz.html', tempDir + '/viewer.html')
    shutil.copy(SOURCE_DIR + '/svg-pan-zoom.js', tempDir + '/svg-pan-zoom.js')
    # Grab the library we need.
    with open(SOURCE_DIR + 'svg-pan-zoom.js', 'r') as pzFile:
        pzData = pzFile.read()
    # Rewrite the load lines in viewer.html
    # Note: you can't juse open the file in r+ mode because, based on the way the file is mapped to memory, you can only overwrite a line with another of exactly the same length.
    for line in fileinput.input(tempDir + '/viewer.html', inplace=1):
        if line.lstrip().startswith("<script id='feederLoadScript''>"):
            print ""  # Remove the existing load.
        elif line.lstrip().startswith("<script id='feederInsert'>"):
            print "<script id='feederInsert'>\ntestFeeder=" + json.dumps(
                thisFeed, indent=4)  # load up the new feeder.
        elif line.lstrip().startswith("<script id='panZoomInsert'>"):
            print "<script id='panZoomInsert'>\n" + pzData  # load up the new feeder.
        else:
            print line.rstrip()
    # os.system('open -a "Google Chrome" ' + '"file://' + tempDir + '/viewer.html"')
    webbrowser.open_new("file://" + tempDir + '/viewer.html')
Example #13
0
def generateVoltChart(tree, rawOut, modelDir, neatoLayout=True):
	''' Map the voltages on a feeder over time using a movie.'''
	# We need to timestamp frames with the system clock to make sure the browser caches them appropriately.
	genTime = str(datetime.datetime.now()).replace(':','.')
	# Detect the feeder nominal voltage:
	for key in tree:
		ob = tree[key]
		if type(ob)==dict and ob.get('bustype','')=='SWING':
			feedVoltage = float(ob.get('nominal_voltage',1))
	# Make a graph object.
	fGraph = feeder.treeToNxGraph(tree)
	if neatoLayout:
		# HACK: work on a new graph without attributes because graphViz tries to read attrs.
		cleanG = nx.Graph(fGraph.edges())
		cleanG.add_nodes_from(fGraph)
		# was formerly : positions = nx.graphviz_layout(cleanG, prog='neato') but this threw an error
		positions = graphviz_layout(cleanG, prog='neato')
	else:
		rawPositions = {n:fGraph.node[n].get('pos',(0,0)) for n in fGraph}
		#HACK: the import code reverses the y coords.
		def yFlip(pair):
			try: return (pair[0], -1.0*pair[1])
			except: return (0,0)
		positions = {k:yFlip(rawPositions[k]) for k in rawPositions}
	# Plot all time steps.
	nodeVolts = {}
	for step, stamp in enumerate(rawOut['aVoltDump.csv']['# timestamp']):
		# Build voltage map.
		nodeVolts[step] = {}
		for nodeName in [x for x in rawOut['aVoltDump.csv'].keys() if x != '# timestamp']:
			allVolts = []
			for phase in ['a','b','c']:
				voltStep = rawOut[phase + 'VoltDump.csv'][nodeName][step]
				# HACK: Gridlab complex number format sometimes uses i, sometimes j, sometimes d. WTF?
				if type(voltStep) is str: voltStep = voltStep.replace('i','j')
				v = complex(voltStep)
				phaseVolt = abs(v)
				if phaseVolt != 0.0:
					if _digits(phaseVolt)>3:
						# Normalize to 120 V standard
						phaseVolt = phaseVolt*(120/feedVoltage)
					allVolts.append(phaseVolt)
			# HACK: Take average of all phases to collapse dimensionality.
			nodeVolts[step][nodeName] = avg(allVolts)
	# Draw animation.
	voltChart = plt.figure(figsize=(15,15))
	plt.axes(frameon = 0)
	plt.axis('off')
	#set axes step equal
	voltChart.gca().set_aspect('equal')
	custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list('custColMap',[(0.0,'blue'),(0.25,'darkgray'),(0.75,'darkgray'),(1.0,'yellow')])
	edgeIm = nx.draw_networkx_edges(fGraph, positions)
	nodeIm = nx.draw_networkx_nodes(fGraph,
		pos = positions,
		node_color = [nodeVolts[0].get(n,0) for n in fGraph.nodes()],
		linewidths = 0,
		node_size = 30,
		cmap = custom_cm)
	plt.sci(nodeIm)
	plt.clim(110,130)
	plt.colorbar()
	plt.title(rawOut['aVoltDump.csv']['# timestamp'][0])
	def update(step):
		nodeColors = np.array([nodeVolts[step].get(n,0) for n in fGraph.nodes()])
		plt.title(rawOut['aVoltDump.csv']['# timestamp'][step])
		nodeIm.set_array(nodeColors)
		return nodeColors,
	anim = FuncAnimation(voltChart, update, frames=len(rawOut['aVoltDump.csv']['# timestamp']), interval=200, blit=False)
	anim.save(pJoin(modelDir,'voltageChart.mp4'), codec='h264', extra_args=['-pix_fmt', 'yuv420p'])
	# Reclaim memory by closing, deleting and garbage collecting the last chart.
	voltChart.clf()
	plt.close()
	del voltChart
	gc.collect()
	return genTime
Example #14
0
def generateVoltChart(tree, rawOut, modelDir, neatoLayout=True):
	''' Map the voltages on a feeder over time using a movie.'''
	# We need to timestamp frames with the system clock to make sure the browser caches them appropriately.
	genTime = str(datetime.datetime.now()).replace(':','.')
	# Detect the feeder nominal voltage:
	for key in tree:
		ob = tree[key]
		if type(ob)==dict and ob.get('bustype','')=='SWING':
			feedVoltage = float(ob.get('nominal_voltage',1))
	# Make a graph object.
	fGraph = feeder.treeToNxGraph(tree)
	if neatoLayout:
		# HACK: work on a new graph without attributes because graphViz tries to read attrs.
		cleanG = nx.Graph(fGraph.edges())
		cleanG.add_nodes_from(fGraph)
		positions = graphviz_layout(cleanG, prog='neato')
	else:
		rawPositions = {n:fGraph.node[n].get('pos',(0,0)) for n in fGraph}
		#HACK: the import code reverses the y coords.
		def yFlip(pair):
			try: return (pair[0], -1.0*pair[1])
			except: return (0,0)
		positions = {k:yFlip(rawPositions[k]) for k in rawPositions}
	# Plot all time steps.
	nodeVolts = {}
	for step, stamp in enumerate(rawOut['aVoltDump.csv']['# timestamp']):
		# Build voltage map.
		nodeVolts[step] = {}
		for nodeName in [x for x in rawOut['aVoltDump.csv'].keys() if x != '# timestamp']:
			allVolts = []
			for phase in ['a','b','c']:
				voltStep = rawOut[phase + 'VoltDump.csv'][nodeName][step]
				# HACK: Gridlab complex number format sometimes uses i, sometimes j, sometimes d. WTF?
				if type(voltStep) is str: voltStep = voltStep.replace('i','j')
				v = complex(voltStep)
				phaseVolt = abs(v)
				if phaseVolt != 0.0:
					if _digits(phaseVolt)>3:
						# Normalize to 120 V standard
						phaseVolt = phaseVolt*(120/feedVoltage)
					allVolts.append(phaseVolt)
			# HACK: Take average of all phases to collapse dimensionality.
			nodeVolts[step][nodeName] = avg(allVolts)
	# Draw animation.
	voltChart = plt.figure(figsize=(15,15))
	plt.axes(frameon = 0)
	plt.axis('off')
	#set axes step equal
	voltChart.gca().set_aspect('equal')
	custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list('custColMap',[(0.0,'blue'),(0.25,'darkgray'),(0.75,'darkgray'),(1.0,'yellow')])
	edgeIm = nx.draw_networkx_edges(fGraph, positions)
	nodeIm = nx.draw_networkx_nodes(fGraph,
		pos = positions,
		node_color = [nodeVolts[0].get(n,0) for n in fGraph.nodes()],
		linewidths = 0,
		node_size = 30,
		cmap = custom_cm)
	plt.sci(nodeIm)
	plt.clim(110,130)
	plt.colorbar()
	plt.title(rawOut['aVoltDump.csv']['# timestamp'][0])
	def update(step):
		nodeColors = np.array([nodeVolts[step].get(n,0) for n in fGraph.nodes()])
		plt.title(rawOut['aVoltDump.csv']['# timestamp'][step])
		nodeIm.set_array(nodeColors)
		return nodeColors,
	anim = FuncAnimation(voltChart, update, frames=len(rawOut['aVoltDump.csv']['# timestamp']), interval=200, blit=False)
	anim.save(pJoin(modelDir,'voltageChart.mp4'), codec='h264', extra_args=['-pix_fmt', 'yuv420p'])
	# Reclaim memory by closing, deleting and garbage collecting the last chart.
	voltChart.clf()
	plt.close()
	del voltChart
	gc.collect()
	return genTime
Example #15
0
def drawPlot(path,
             workDir=None,
             neatoLayout=False,
             edgeLabs=None,
             nodeLabs=None,
             edgeCol=None,
             nodeCol=None,
             faultLoc=None,
             faultType=None,
             customColormap=False,
             scaleMin=None,
             scaleMax=None,
             rezSqIn=400,
             simTime='2000-01-01 0:00:00',
             loadLoc=None):
    ''' Draw a color-coded map of the voltage drop on a feeder.
	path is the full path to the GridLAB-D .glm file or OMF .omd file.
	workDir is where GridLAB-D will run, if it's None then a temp dir is used.
	neatoLayout=True means the circuit is displayed using a force-layout approach.
	edgeCol property must be either 'Current', 'Power', 'Rating', 'PercentOfRating', or None
	nodeCol property must be either 'Voltage', 'VoltageImbalance', 'perUnitVoltage', 'perUnit120Voltage', or None
	edgeLabs and nodeLabs properties must be either 'Name', 'Value', or None
	edgeCol and nodeCol can be set to false to avoid coloring edges or nodes
	customColormap=True means use a one that is nicely scaled to perunit values highlighting extremes.
	faultType and faultLoc are the type of fault and the name of the line that it occurs on.
	Returns a matplotlib object.'''
    # Be quiet matplotlib:
    # warnings.filterwarnings("ignore")
    if path.endswith('.glm'):
        tree = feeder.parse(path)
        attachments = []
    elif path.endswith('.omd'):
        with open(path) as f:
            omd = json.load(f)
        tree = omd.get('tree', {})
        attachments = omd.get('attachments', [])
    else:
        raise Exception('Invalid input file type. We require a .glm or .omd.')
    #print path
    # add fault object to tree
    def safeInt(x):
        try:
            return int(x)
        except:
            return 0

    biggestKey = max([safeInt(x) for x in tree.keys()])
    # Add Reliability module
    tree[str(biggestKey * 10)] = {
        "module": "reliability",
        "maximum_event_length": "18000",
        "report_event_log": "true"
    }
    CLOCK_START = simTime
    dt_start = parser.parse(CLOCK_START)
    dt_end = dt_start + relativedelta(seconds=+20)
    CLOCK_END = str(dt_end)
    CLOCK_RANGE = CLOCK_START + ',' + CLOCK_END
    if faultType != None:
        # Add eventgen object (the fault)
        tree[str(biggestKey * 10 + 1)] = {
            "object": "eventgen",
            "name": "ManualEventGen",
            "parent": "RelMetrics",
            "fault_type": faultType,
            "manual_outages": faultLoc + ',' + CLOCK_RANGE
        }  # TODO: change CLOCK_RANGE to read the actual start and stop time, not just hard-coded
        # Add fault_check object
        tree[str(biggestKey * 10 + 2)] = {
            "object": "fault_check",
            "name": "test_fault",
            "check_mode": "ONCHANGE",
            "eventgen_object": "ManualEventGen",
            "output_filename": "Fault_check_out.txt"
        }
        # Add reliabilty metrics object
        tree[str(biggestKey * 10 + 3)] = {
            "object": "metrics",
            "name": "RelMetrics",
            "report_file": "Metrics_Output.csv",
            "module_metrics_object": "PwrMetrics",
            "metrics_of_interest": '"SAIFI,SAIDI,CAIDI,ASAI,MAIFI"',
            "customer_group": '"groupid=METERTEST"',
            "metric_interval": "5 h",
            "report_interval": "5 h"
        }
        # Add power_metrics object
        tree[str(biggestKey * 10 + 4)] = {
            "object": "power_metrics",
            "name": "PwrMetrics",
            "base_time_value": "1 h"
        }

        # HACK: set groupid for all meters so outage stats are collected.
        noMeters = True
        for key in tree:
            if tree[key].get('object', '') in ['meter', 'triplex_meter']:
                tree[key]['groupid'] = "METERTEST"
                noMeters = False
        if noMeters:
            raise Exception(
                "No meters detected on the circuit. Please add at least one meter to allow for collection of outage statistics."
            )
    for key in tree:
        if 'clock' in tree[key]:
            tree[key]['starttime'] = "'" + CLOCK_START + "'"
            tree[key]['stoptime'] = "'" + CLOCK_END + "'"

    # dictionary to hold info on lines present in glm
    edge_bools = dict.fromkeys([
        'underground_line', 'overhead_line', 'triplex_line', 'transformer',
        'regulator', 'fuse', 'switch'
    ], False)
    # Map to speed up name lookups.
    nameToIndex = {tree[key].get('name', ''): key for key in tree.keys()}
    # Get rid of schedules and climate and check for all edge types:
    for key in list(tree.keys()):
        obtype = tree[key].get("object", "")
        if obtype == 'underground_line':
            edge_bools['underground_line'] = True
        elif obtype == 'overhead_line':
            edge_bools['overhead_line'] = True
        elif obtype == 'triplex_line':
            edge_bools['triplex_line'] = True
        elif obtype == 'transformer':
            edge_bools['transformer'] = True
        elif obtype == 'regulator':
            edge_bools['regulator'] = True
        elif obtype == 'fuse':
            edge_bools['fuse'] = True
        elif obtype == 'switch':
            edge_bools['switch'] = True
        if tree[key].get("argument",
                         "") == "\"schedules.glm\"" or tree[key].get(
                             "tmyfile", "") != "":
            del tree[key]
    # Make sure we have a voltage dump and current dump:
    tree[str(biggestKey * 10 + 5)] = {
        "object": "voltdump",
        "filename": "voltDump.csv"
    }
    tree[str(biggestKey * 10 + 6)] = {
        "object": "currdump",
        "filename": "currDump.csv"
    }
    # Line rating dumps
    tree[feeder.getMaxKey(tree) + 1] = {'module': 'tape'}
    for key in edge_bools.keys():
        if edge_bools[key]:
            tree[feeder.getMaxKey(tree) + 1] = {
                'object': 'group_recorder',
                'group': '"class=' + key + '"',
                'property': 'continuous_rating',
                'file': key + '_cont_rating.csv'
            }
    #Record initial status readout of each fuse/recloser/switch/sectionalizer before running
    # Reminder: fuse objects have 'phase_X_status' instead of 'phase_X_state'
    protDevices = dict.fromkeys(
        ['fuse', 'recloser', 'switch', 'sectionalizer'], False)
    #dictionary of protective device initial states for each phase
    protDevInitStatus = {}
    #dictionary of protective devices final states for each phase after running Gridlab-D
    protDevFinalStatus = {}
    #dictionary of protective device types to help the testing and debugging process
    protDevTypes = {}
    protDevOpModes = {}
    for key in tree:
        obj = tree[key]
        obType = obj.get('object')
        if obType in protDevices.keys():
            obName = obj.get('name', '')
            protDevTypes[obName] = obType
            if obType != 'fuse':
                protDevOpModes[obName] = obj.get('operating_mode',
                                                 'INDIVIDUAL')
            protDevices[obType] = True
            protDevInitStatus[obName] = {}
            protDevFinalStatus[obName] = {}
            for phase in ['A', 'B', 'C']:
                if obType != 'fuse':
                    phaseState = obj.get('phase_' + phase + '_state', 'CLOSED')
                else:
                    phaseState = obj.get('phase_' + phase + '_status', 'GOOD')
                if phase in obj.get('phases', ''):
                    protDevInitStatus[obName][phase] = phaseState
    #print protDevInitStatus

    #Create a recorder for protective device states
    for key in protDevices.keys():
        if protDevices[key]:
            for phase in ['A', 'B', 'C']:
                if key != 'fuse':
                    tree[feeder.getMaxKey(tree) + 1] = {
                        'object': 'group_recorder',
                        'group': '"class=' + key + '"',
                        'property': 'phase_' + phase + '_state',
                        'file': key + '_phase_' + phase + '_state.csv'
                    }
                else:
                    tree[feeder.getMaxKey(tree) + 1] = {
                        'object': 'group_recorder',
                        'group': '"class=' + key + '"',
                        'property': 'phase_' + phase + '_status',
                        'file': key + '_phase_' + phase + '_state.csv'
                    }

    # Run Gridlab.
    if not workDir:
        workDir = tempfile.mkdtemp()
        print('@@@@@@', workDir)
    # for i in range(6):
    # 	gridlabOut = gridlabd.runInFilesystem(tree, attachments=attachments, workDir=workDir)
    # 	#HACK: workaround for shoddy macOS gridlabd build.
    # 	if 'error when setting parent' not in gridlabOut.get('stderr','OOPS'):
    # 		break
    gridlabOut = gridlabd.runInFilesystem(tree,
                                          attachments=attachments,
                                          workDir=workDir)

    #Record final status readout of each fuse/recloser/switch/sectionalizer after running
    try:
        for key in protDevices.keys():
            if protDevices[key]:
                for phase in ['A', 'B', 'C']:
                    with open(pJoin(workDir,
                                    key + '_phase_' + phase + '_state.csv'),
                              newline='') as statusFile:
                        reader = csv.reader(statusFile)
                        # loop past the header,
                        keys = []
                        vals = []
                        for row in reader:
                            if '# timestamp' in row:
                                keys = row
                                i = keys.index('# timestamp')
                                keys.pop(i)
                                vals = next(reader)
                                vals.pop(i)
                        for pos, key2 in enumerate(keys):
                            protDevFinalStatus[key2][phase] = vals[pos]
    except:
        pass
    #print protDevFinalStatus
    #compare initial and final states of protective devices
    #quick compare to see if they are equal
    #print cmp(protDevInitStatus, protDevFinalStatus)
    #find which values changed
    changedStates = {}
    #read voltDump values into a dictionary.
    try:
        with open(pJoin(workDir, 'voltDump.csv'), newline='') as dumpFile:
            reader = csv.reader(dumpFile)
            next(reader)  # Burn the header.
            keys = next(reader)
            voltTable = []
            for row in reader:
                rowDict = {}
                for pos, key in enumerate(keys):
                    rowDict[key] = row[pos]
                voltTable.append(rowDict)
    except:
        raise Exception(
            'GridLAB-D failed to run with the following errors:\n' +
            gridlabOut['stderr'])
    # read currDump values into a dictionary
    with open(pJoin(workDir, 'currDump.csv'), newline='') as currDumpFile:
        reader = csv.reader(currDumpFile)
        next(reader)  # Burn the header.
        keys = next(reader)
        currTable = []
        for row in reader:
            rowDict = {}
            for pos, key in enumerate(keys):
                rowDict[key] = row[pos]
            currTable.append(rowDict)
    # read line rating values into a single dictionary
    lineRatings = {}
    rating_in_VA = []
    for key1 in edge_bools.keys():
        if edge_bools[key1]:
            with open(pJoin(workDir, key1 + '_cont_rating.csv'),
                      newline='') as ratingFile:
                reader = csv.reader(ratingFile)
                # loop past the header,
                keys = []
                vals = []
                for row in reader:
                    if '# timestamp' in row:
                        keys = row
                        i = keys.index('# timestamp')
                        keys.pop(i)
                        vals = next(reader)
                        vals.pop(i)
                for pos, key2 in enumerate(keys):
                    lineRatings[key2] = abs(float(vals[pos]))
    #edgeTupleRatings = lineRatings copy with to-from tuple as keys for labeling
    edgeTupleRatings = {}
    for edge in lineRatings:
        for obj in tree.values():
            if obj.get('name', '').replace('"', '') == edge:
                nodeFrom = obj.get('from')
                nodeTo = obj.get('to')
                coord = (nodeFrom, nodeTo)
                ratingVal = lineRatings.get(edge)
                edgeTupleRatings[coord] = ratingVal
    # Calculate average node voltage deviation. First, helper functions.
    def digits(x):
        ''' Returns number of digits before the decimal in the float x. '''
        return math.ceil(math.log10(x + 1))

    def avg(l):
        ''' Average of a list of ints or floats. '''
        # HACK: add a small value to the denominator to avoid divide by zero for out of service locations (i.e. zero voltage).
        return sum(l) / (len(l) + 0.00000000000000001)

    # Detect the feeder nominal voltage:
    for key in tree:
        ob = tree[key]
        if type(ob) == dict and ob.get('bustype', '') == 'SWING':
            feedVoltage = float(ob.get('nominal_voltage', 1))
    # Tot it all up.
    nodeVolts = {}
    nodeVoltsPU = {}
    nodeVoltsPU120 = {}
    voltImbalances = {}
    for row in voltTable:
        allVolts = []
        allVoltsPU = []
        allDiffs = []
        nodeName = row.get('node_name', '')
        for phase in ['A', 'B', 'C']:
            realVolt = abs(float(row['volt' + phase + '_real']))
            imagVolt = abs(float(row['volt' + phase + '_imag']))
            phaseVolt = math.sqrt((realVolt**2) + (imagVolt**2))
            if phaseVolt != 0.0:
                treeKey = nameToIndex.get(nodeName, 0)
                nodeObj = tree.get(treeKey, {})
                try:
                    nominal_voltage = float(nodeObj['nominal_voltage'])
                except:
                    nominal_voltage = feedVoltage
                allVolts.append(phaseVolt)
                normVolt = (phaseVolt / nominal_voltage)
                allVoltsPU.append(normVolt)
        avgVolts = avg(allVolts)
        avgVoltsPU = avg(allVoltsPU)
        avgVoltsPU120 = 120 * avgVoltsPU
        nodeVolts[nodeName] = float("{0:.2f}".format(avgVolts))
        nodeVoltsPU[nodeName] = float("{0:.2f}".format(avgVoltsPU))
        nodeVoltsPU120[nodeName] = float("{0:.2f}".format(avgVoltsPU120))
        if len(allVolts) == 3:
            voltA = allVolts.pop()
            voltB = allVolts.pop()
            voltC = allVolts.pop()
            allDiffs.append(abs(float(voltA - voltB)))
            allDiffs.append(abs(float(voltA - voltC)))
            allDiffs.append(abs(float(voltB - voltC)))
            maxDiff = max(allDiffs)
            voltImbal = maxDiff / avgVolts
            voltImbalances[nodeName] = float("{0:.2f}".format(voltImbal))
        # Use float("{0:.2f}".format(avg(allVolts))) if displaying the node labels
    nodeLoadNames = {}
    nodeNames = {}
    for key in nodeVolts.keys():
        nodeNames[key] = key
        if key == loadLoc:
            nodeLoadNames[key] = "LOAD: " + key
    # find edge currents by parsing currdump
    edgeCurrentSum = {}
    edgeCurrentMax = {}
    for row in currTable:
        allCurr = []
        for phase in ['A', 'B', 'C']:
            realCurr = abs(float(row['curr' + phase + '_real']))
            imagCurr = abs(float(row['curr' + phase + '_imag']))
            phaseCurr = math.sqrt((realCurr**2) + (imagCurr**2))
            allCurr.append(phaseCurr)
        edgeCurrentSum[row.get('link_name', '')] = sum(allCurr)
        edgeCurrentMax[row.get('link_name', '')] = max(allCurr)
    # When just showing current as labels, use sum of the three lines' current values, when showing the per unit values (current/rating), use the max of the three
    #edgeTupleCurrents = edgeCurrents copy with to-from tuple as keys for labeling
    edgeTupleCurrents = {}
    #edgeValsPU = values normalized per unit by line ratings
    edgeValsPU = {}
    #edgeTupleValsPU = edgeValsPU copy with to-from tuple as keys for labeling
    edgeTupleValsPU = {}
    #edgeTuplePower = dict with to-from tuples as keys and sim power as values for debugging
    edgeTuplePower = {}
    #edgeTupleNames = dict with to-from tuples as keys and names as values for debugging
    edgeTupleNames = {}
    #edgeTupleFaultNames = dict with to-from tuples as keys and the name of the Fault as the only value
    edgeTupleFaultNames = {}
    #edgeTupleProtDevs = dict with to-from tuples as keys and the initial of the type of protective device as the value
    edgeTupleProtDevs = {}
    #linePhases = dictionary containing the number of phases on each line for line-width purposes
    linePhases = {}
    edgePower = {}
    for edge in edgeCurrentSum:
        for obj in tree.values():
            obname = obj.get('name', '').replace('"', '')
            if obname == edge:
                objType = obj.get('object')
                nodeFrom = obj.get('from')
                nodeTo = obj.get('to')
                coord = (nodeFrom, nodeTo)
                currVal = edgeCurrentSum.get(edge)
                voltVal = avg([nodeVolts.get(nodeFrom), nodeVolts.get(nodeTo)])
                power = (currVal * voltVal) / 1000
                lineRating = lineRatings.get(edge, 10.0**9)
                edgePerUnitVal = (edgeCurrentMax.get(edge)) / lineRating
                edgeTupleCurrents[coord] = "{0:.2f}".format(currVal)
                edgeTuplePower[coord] = "{0:.2f}".format(power)
                edgePower[edge] = power
                edgeValsPU[edge] = edgePerUnitVal
                edgeTupleValsPU[coord] = "{0:.2f}".format(edgePerUnitVal)
                edgeTupleNames[coord] = edge
                if faultLoc == edge:
                    edgeTupleFaultNames[coord] = "FAULT: " + edge
                phaseStr = obj.get('phases', '').replace('"', '').replace(
                    'N', '').replace('S', '')
                numPhases = len(phaseStr)
                if (numPhases < 1) or (numPhases > 3):
                    numPhases = 1
                linePhases[edge] = numPhases
                protDevLabel = ""
                protDevBlownStr = ""
                if objType in protDevices.keys():
                    for phase in protDevFinalStatus[obname].keys():
                        if objType == 'fuse':
                            if protDevFinalStatus[obname][phase] == "BLOWN":
                                protDevBlownStr = "!"
                        else:
                            if protDevFinalStatus[obname][phase] == "OPEN":
                                protDevBlownStr = "!"
                if objType == 'fuse':
                    protDevLabel = 'F'
                elif objType == 'switch':
                    protDevLabel = 'S'
                elif objType == 'recloser':
                    protDevLabel = 'R'
                elif objType == 'sectionalizer':
                    protDevLabel = 'X'
                edgeTupleProtDevs[coord] = protDevLabel + protDevBlownStr
    #define which dict will be used for edge line color
    edgeColors = edgeValsPU
    #define which dict will be used for edge label
    edgeLabels = edgeTupleValsPU
    # Build the graph.
    fGraph = feeder.treeToNxGraph(tree)
    # TODO: consider whether we can set figsize dynamically.
    wlVal = int(math.sqrt(float(rezSqIn)))
    voltChart = plt.figure(figsize=(wlVal, wlVal))
    plt.axes(frameon=0)
    plt.axis('off')
    voltChart.gca().set_aspect('equal')
    plt.tight_layout()
    #set axes step equal
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(fGraph.edges())
        cleanG.add_nodes_from(fGraph)
        positions = graphviz_layout(cleanG, prog='neato')
    else:
        remove_nodes = [
            n for n in fGraph if fGraph.nodes[n].get('pos', (0, 0)) == (0, 0)
        ]
        fGraph.remove_nodes_from(remove_nodes)
        positions = {n: fGraph.nodes[n].get('pos', (0, 0)) for n in fGraph}
    # Need to get edge names from pairs of connected node names.
    edgeNames = []
    for e in fGraph.edges():
        edgeNames.append((fGraph.edges[e].get('name',
                                              'BLANK')).replace('"', ''))
    #create custom colormap
    if customColormap:
        if scaleMin != None and scaleMax != None:
            scaleDif = scaleMax - scaleMin
            custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list(
                'custColMap', [(scaleMin, 'blue'),
                               (scaleMin + (0.12 * scaleDif), 'darkgray'),
                               (scaleMin + (0.56 * scaleDif), 'darkgray'),
                               (scaleMin + (0.8 * scaleDif), 'red')])
            vmin = scaleMin
            vmax = scaleMax
        else:
            custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list(
                'custColMap', [(0.0, 'blue'), (0.15, 'darkgray'),
                               (0.7, 'darkgray'), (1.0, 'red')])
            vmin = 0
            vmax = 1.25
        custom_cm.set_under(color='black')
    else:
        custom_cm = plt.cm.get_cmap('viridis')
        if scaleMin != None and scaleMax != None:
            vmin = scaleMin
            vmax = scaleMax
        else:
            vmin = None
            vmax = None
    drawColorbar = False
    emptyColors = {}
    #draw edges with or without colors
    if edgeCol != None:
        drawColorbar = True
        if edgeCol == "Current":
            edgeList = [edgeCurrentSum.get(n, 1) for n in edgeNames]
            drawColorbar = True
        elif edgeCol == "Power":
            edgeList = [edgePower.get(n, 1) for n in edgeNames]
            drawColorbar = True
        elif edgeCol == "Rating":
            edgeList = [lineRatings.get(n, 10.0**9) for n in edgeNames]
            drawColorbar = True
        elif edgeCol == "PercentOfRating":
            edgeList = [edgeValsPU.get(n, .5) for n in edgeNames]
            drawColorbar = True
        else:
            edgeList = [emptyColors.get(n, .6) for n in edgeNames]
            print(
                "WARNING: edgeCol property must be 'Current', 'Power', 'Rating', 'PercentOfRating', or None"
            )
    else:
        edgeList = [emptyColors.get(n, .6) for n in edgeNames]
    edgeIm = nx.draw_networkx_edges(
        fGraph,
        pos=positions,
        edge_color=edgeList,
        width=[linePhases.get(n, 1) for n in edgeNames],
        edge_cmap=custom_cm)
    #draw edge labels
    if edgeLabs != None:
        if edgeLabs == "Name":
            edgeLabels = edgeTupleNames
        elif edgeLabs == "Fault":
            edgeLabels = edgeTupleFaultNames
        elif edgeLabs == "Value":
            if edgeCol == "Current":
                edgeLabels = edgeTupleCurrents
            elif edgeCol == "Power":
                edgeLabels = edgeTuplePower
            elif edgeCol == "Rating":
                edgeLabels = edgeTupleRatings
            elif edgeCol == "PercentOfRating":
                edgeLabels = edgeTupleValsPU
            else:
                edgeLabels = None
                print(
                    "WARNING: edgeCol property cannot be set to None when edgeLabs property is set to 'Value'"
                )
        elif edgeLabs == "ProtDevs":
            edgeLabels = edgeTupleProtDevs
        else:
            edgeLabs = None
            print(
                "WARNING: edgeLabs property must be either 'Name', 'Value', or None"
            )
    if edgeLabs != None:
        edgeLabelsIm = nx.draw_networkx_edge_labels(fGraph,
                                                    pos=positions,
                                                    edge_labels=edgeLabels,
                                                    font_size=8)
    # draw nodes with or without color
    if nodeCol != None:
        if nodeCol == "Voltage":
            nodeList = [nodeVolts.get(n, 1) for n in fGraph.nodes()]
            drawColorbar = True
        elif nodeCol == "VoltageImbalance":
            nodeList = [voltImbalances.get(n, 1) for n in fGraph.nodes()]
            drawColorbar = True
        elif nodeCol == "perUnitVoltage":
            nodeList = [nodeVoltsPU.get(n, .5) for n in fGraph.nodes()]
            drawColorbar = True
        elif nodeCol == "perUnit120Voltage":
            nodeList = [nodeVoltsPU120.get(n, 120) for n in fGraph.nodes()]
            drawColorbar = True
        else:
            nodeList = [emptyColors.get(n, 1) for n in fGraph.nodes()]
            print(
                "WARNING: nodeCol property must be 'Voltage', 'VoltageImbalance', 'perUnitVoltage', 'perUnit120Voltage', or None"
            )
    else:
        nodeList = [emptyColors.get(n, .6) for n in fGraph.nodes()]

    nodeIm = nx.draw_networkx_nodes(fGraph,
                                    pos=positions,
                                    node_color=nodeList,
                                    linewidths=0,
                                    node_size=30,
                                    vmin=vmin,
                                    vmax=vmax,
                                    cmap=custom_cm)
    #draw node labels
    nodeLabels = {}
    if nodeLabs != None:
        if nodeLabs == "Name":
            nodeLabels = nodeNames
        elif nodeLabs == "Value":
            if nodeCol == "Voltage":
                nodeLabels = nodeVolts
            elif nodeCol == "VoltageImbalance":
                nodeLabels = voltImbalances
            elif nodeCol == "perUnitVoltage":
                nodeLabels = nodeVoltsPU
            elif nodeCol == "perUnit120Voltage":
                nodeLabels = nodeVoltsPU120
            else:
                nodeLabels = None
                print(
                    "WARNING: nodeCol property cannot be set to None when nodeLabs property is set to 'Value'"
                )
        #HACK: add hidden node label option for displaying specified load name
        elif nodeLabs == "Load":
            nodeLabels = nodeLoadNames
        else:
            nodeLabs = None
            print(
                "WARNING: nodeLabs property must be either 'Name', 'Value', or None"
            )
    if nodeLabs != None:
        nodeLabelsIm = nx.draw_networkx_labels(fGraph,
                                               pos=positions,
                                               labels=nodeLabels,
                                               font_size=8)
    plt.sci(nodeIm)
    # plt.clim(110,130)
    if drawColorbar:
        plt.colorbar()
    return voltChart
Example #16
0
def work(modelDir, inputDict):
	''' Run the model in the foreground. WARNING: can take about a minute. '''
	# Global vars, and load data from the model directory.
	feederName = [x for x in os.listdir(modelDir) if x.endswith('.omd')][0][:-4]
	inputDict["feederName1"] = feederName
	feederPath = pJoin(modelDir,feederName+'.omd')
	feederJson = json.load(open(feederPath))
	tree = feederJson.get("tree",{})
	attachments = feederJson.get("attachments",{})
	outData = {}
	''' Run CVR analysis. '''
	# Reformate monthData and rates.
	rates = {k:float(inputDict[k]) for k in ['capitalCost', 'omCost', 'wholesaleEnergyCostPerKwh',
		'retailEnergyCostPerKwh', 'peakDemandCostSpringPerKw', 'peakDemandCostSummerPerKw',
		'peakDemandCostFallPerKw', 'peakDemandCostWinterPerKw']}
	monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
		'September', 'October', 'November', 'December']
	monthToSeason = {'January':'Winter','February':'Winter','March':'Spring','April':'Spring',
		'May':'Spring','June':'Summer','July':'Summer','August':'Summer',
		'September':'Fall','October':'Fall','November':'Fall','December':'Winter'}
	monthData = []
	for i, x in enumerate(monthNames):
		monShort = x[0:3].lower()
		season = monthToSeason[x]
		histAvg = float(inputDict.get(monShort + "Avg", 0))
		histPeak = float(inputDict.get(monShort + "Peak", 0))
		monthData.append({"monthId":i, "monthName":x, "histAverage":histAvg,
			"histPeak":histPeak, "season":season})
	# Graph the SCADA data.
	fig = plt.figure(figsize=(10,6))
	indices = [r['monthName'] for r in monthData]
	d1 = [r['histPeak']/(10**3) for r in monthData]
	d2 = [r['histAverage']/(10**3) for r in monthData]
	ticks = range(len(d1))
	bar_peak = plt.bar(ticks,d1,color='gray')
	bar_avg = plt.bar(ticks,d2,color='dimgray')
	plt.legend([bar_peak[0],bar_avg[0]],['histPeak','histAverage'],bbox_to_anchor=(0., 1.015, 1., .102), loc=3,
	   ncol=2, mode="expand", borderaxespad=0.1)
	plt.xticks([t+0.5 for t in ticks],indices)
	plt.ylabel('Mean and peak historical power consumptions (kW)')
	fig.autofmt_xdate()
	plt.savefig(pJoin(modelDir,"scadaChart.png"))
	outData["histPeak"] = d1
	outData["histAverage"] = d2
	outData["monthName"] = [name[0:3] for name in monthNames]
	# Graph feeder.
	fig = plt.figure(figsize=(10,10))
	myGraph = feeder.treeToNxGraph(tree)
	feeder.latLonNxGraph(myGraph, neatoLayout=False)
	plt.savefig(pJoin(modelDir,"feederChart.png"))
	with open(pJoin(modelDir,"feederChart.png"),"rb") as inFile:
		outData["feederChart"] = inFile.read().encode("base64")
	# Get the load levels we need to test.
	allLoadLevels = [x.get('histPeak',0) for x in monthData] + [y.get('histAverage',0) for y in monthData]
	maxLev = _roundOne(max(allLoadLevels),'up')
	minLev = _roundOne(min(allLoadLevels),'down')
	tenLoadLevels = range(int(minLev),int(maxLev),int((maxLev-minLev)/10))
	# Gather variables from the feeder.
	for key in tree.keys():
		# Set clock to single timestep.
		if tree[key].get('clock','') == 'clock':
			tree[key] = {"timezone":"PST+8PDT",
				"stoptime":"'2013-01-01 00:00:00'",
				"starttime":"'2013-01-01 00:00:00'",
				"clock":"clock"}
		# Save swing node index.
		if tree[key].get('bustype','').lower() == 'swing':
			swingIndex = key
			swingName = tree[key].get('name')
		# Remove all includes.
		if tree[key].get('omftype','') == '#include':
			del key
	# Find the substation regulator and config.
	for key in tree:
		if tree[key].get('object','') == 'regulator' and tree[key].get('from','') == swingName:
			regIndex = key
			regConfName = tree[key]['configuration']
	if not regConfName: regConfName = False
	for key in tree:
		if tree[key].get('name','') == regConfName:
			regConfIndex = key
	# Set substation regulator to manual operation.
	baselineTap = int(inputDict.get("baselineTap")) # GLOBAL VARIABLE FOR DEFAULT TAP POSITION
	tree[regConfIndex] = {
		'name':tree[regConfIndex]['name'],
		'object':'regulator_configuration',
		'connect_type':'1',
		'raise_taps':'10',
		'lower_taps':'10',
		'CT_phase':'ABC',
		'PT_phase':'ABC',
		'regulation':'0.10', #Yo, 0.10 means at tap_pos 10 we're 10% above 120V.
		'Control':'MANUAL',
		'control_level':'INDIVIDUAL',
		'Type':'A',
		'tap_pos_A':str(baselineTap),
		'tap_pos_B':str(baselineTap),
		'tap_pos_C':str(baselineTap) }
	# Attach recorders relevant to CVR.
	recorders = [
		{'object': 'collector',
		'file': 'ZlossesTransformer.csv',
		'group': 'class=transformer',
		'limit': '0',
		'property': 'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'},
		{'object': 'collector',
		'file': 'ZlossesUnderground.csv',
		'group': 'class=underground_line',
		'limit': '0',
		'property': 'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'},
		{'object': 'collector',
		'file': 'ZlossesOverhead.csv',
		'group': 'class=overhead_line',
		'limit': '0',
		'property': 'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'},
		{'object': 'recorder',
		'file': 'Zregulator.csv',
		'limit': '0',
		'parent': tree[regIndex]['name'],
		'property': 'tap_A,tap_B,tap_C,power_in.real,power_in.imag'},
		{'object': 'collector',
		'file': 'ZvoltageJiggle.csv',
		'group': 'class=triplex_meter',
		'limit': '0',
		'property': 'min(voltage_12.mag),mean(voltage_12.mag),max(voltage_12.mag),std(voltage_12.mag)'},
		{'object': 'recorder',
		'file': 'ZsubstationTop.csv',
		'limit': '0',
		'parent': tree[swingIndex]['name'],
		'property': 'voltage_A,voltage_B,voltage_C'},
		{'object': 'recorder',
		'file': 'ZsubstationBottom.csv',
		'limit': '0',
		'parent': tree[regIndex]['to'],
		'property': 'voltage_A,voltage_B,voltage_C'} ]
	biggest = 1 + max([int(k) for k in tree.keys()])
	for index, rec in enumerate(recorders):
		tree[biggest + index] = rec
	# Change constant PF loads to ZIP loads. (See evernote for rationale about 50/50 power/impedance mix.)
	blankZipModel = {'object':'triplex_load',
		'name':'NAMEVARIABLE',
		'base_power_12':'POWERVARIABLE',
		'power_fraction_12': str(inputDict.get("p_percent")),
		'impedance_fraction_12': str(inputDict.get("z_percent")),
		'current_fraction_12': str(inputDict.get("i_percent")),
		'power_pf_12': str(inputDict.get("power_factor")), #MAYBEFIX: we can probably get this PF data from the Milsoft loads.
		'impedance_pf_12':str(inputDict.get("power_factor")),
		'current_pf_12':str(inputDict.get("power_factor")),
		'nominal_voltage':'120',
		'phases':'PHASESVARIABLE',
		'parent':'PARENTVARIABLE' }
	def powerClean(powerStr):
		''' take 3339.39+1052.29j to 3339.39 '''
		return powerStr[0:powerStr.find('+')]
	for key in tree:
		if tree[key].get('object','') == 'triplex_node':
			# Get existing variables.
			name = tree[key].get('name','')
			power = tree[key].get('power_12','')
			parent = tree[key].get('parent','')
			phases = tree[key].get('phases','')
			# Replace object and reintroduce variables.
			tree[key] = copy(blankZipModel)
			tree[key]['name'] = name
			tree[key]['base_power_12'] = powerClean(power)
			tree[key]['parent'] = parent
			tree[key]['phases'] = phases
	# Function to determine how low we can tap down in the CVR case:
	def loweringPotential(baseLine):
		''' Given a baseline end of line voltage, how many more percent can we shave off the substation voltage? '''
		''' testsWePass = [122.0,118.0,200.0,110.0] '''
		lower = int(math.floor((baseLine/114.0-1)*100)) - 1
		# If lower is negative, we can't return it because we'd be undervolting beyond what baseline already was!
		if lower < 0:
			return baselineTap
		else:
			return baselineTap - lower
	# Run all the powerflows.
	powerflows = []
	for doingCvr in [False, True]:
		# For each load level in the tenLoadLevels, run a powerflow with the load objects scaled to the level.
		for desiredLoad in tenLoadLevels:
			# Find the total load that was defined in Milsoft:
			loadList = []
			for key in tree:
				if tree[key].get('object','') == 'triplex_load':
					loadList.append(tree[key].get('base_power_12',''))
			totalLoad = sum([float(x) for x in loadList])
			# Rescale each triplex load:
			for key in tree:
				if tree[key].get('object','') == 'triplex_load':
					currentPow = float(tree[key]['base_power_12'])
					ratio = desiredLoad/totalLoad
					tree[key]['base_power_12'] = str(currentPow*ratio)
			# If we're doing CVR then lower the voltage.
			if doingCvr:
				# Find the minimum voltage we can tap down to:
				newTapPos = baselineTap
				for row in powerflows:
					if row.get('loadLevel','') == desiredLoad:
						newTapPos = loweringPotential(row.get('lowVoltage',114))
				# Tap it down to there.
				# MAYBEFIX: do each phase separately because that's how it's done in the field... Oof.
				tree[regConfIndex]['tap_pos_A'] = str(newTapPos)
				tree[regConfIndex]['tap_pos_B'] = str(newTapPos)
				tree[regConfIndex]['tap_pos_C'] = str(newTapPos)
			# Run the model through gridlab and put outputs in the table.
			output = gridlabd.runInFilesystem(tree, attachments=attachments,
				keepFiles=True, workDir=modelDir)
			os.remove(pJoin(modelDir,"PID.txt"))
			p = output['Zregulator.csv']['power_in.real'][0]
			q = output['Zregulator.csv']['power_in.imag'][0]
			s = math.sqrt(p**2+q**2)
			lossTotal = 0.0
			for device in ['ZlossesOverhead.csv','ZlossesTransformer.csv','ZlossesUnderground.csv']:
				for letter in ['A','B','C']:
					r = output[device]['sum(power_losses_' + letter + '.real)'][0]
					i = output[device]['sum(power_losses_' + letter + '.imag)'][0]
					lossTotal += math.sqrt(r**2 + i**2)
			## Entire output:
			powerflows.append({
				'doingCvr':doingCvr,
				'loadLevel':desiredLoad,
				'realPower':p,
				'powerFactor':p/s,
				'losses':lossTotal,
				'subVoltage': (
					output['ZsubstationBottom.csv']['voltage_A'][0] +
					output['ZsubstationBottom.csv']['voltage_B'][0] +
					output['ZsubstationBottom.csv']['voltage_C'][0] )/3/60,
				'lowVoltage':output['ZvoltageJiggle.csv']['min(voltage_12.mag)'][0]/2,
				'highVoltage':output['ZvoltageJiggle.csv']['max(voltage_12.mag)'][0]/2 })
	# For a given load level, find two points to interpolate on.
	def getInterpPoints(t):
		''' Find the two points we can interpolate from. '''
		''' tests pass on [tenLoadLevels[0],tenLoadLevels[5]+499,tenLoadLevels[-1]-988] '''
		loc = sorted(tenLoadLevels + [t]).index(t)
		if loc==0:
			return (tenLoadLevels[0],tenLoadLevels[1])
		elif loc>len(tenLoadLevels)-2:
			return (tenLoadLevels[-2],tenLoadLevels[-1])
		else:
			return (tenLoadLevels[loc-1],tenLoadLevels[loc+1])
	# Calculate peak reduction.
	for row in monthData:
		peak = row['histPeak']
		peakPoints = getInterpPoints(peak)
		peakTopBase = [x for x in powerflows if x.get('loadLevel','') == peakPoints[-1] and x.get('doingCvr','') == False][0]
		peakTopCvr = [x for x in powerflows if x.get('loadLevel','') == peakPoints[-1] and x.get('doingCvr','') == True][0]
		peakBottomBase = [x for x in powerflows if x.get('loadLevel','') == peakPoints[0] and x.get('doingCvr','') == False][0]
		peakBottomCvr = [x for x in powerflows if x.get('loadLevel','') == peakPoints[0] and x.get('doingCvr','') == True][0]
		# Linear interpolation so we aren't running umpteen million loadflows.
		x = (peakPoints[0],peakPoints[1])
		y = (peakTopBase['realPower'] - peakTopCvr['realPower'],
			 peakBottomBase['realPower'] - peakBottomCvr['realPower'])
		peakRed = y[0] + (y[1] - y[0]) * (peak - x[0]) / (x[1] - x[0])
		row['peakReduction'] = peakRed
	# Calculate energy reduction and loss reduction based on average load.
	for row in monthData:
		avgEnergy = row['histAverage']
		energyPoints = getInterpPoints(avgEnergy)
		avgTopBase = [x for x in powerflows if x.get('loadLevel','') == energyPoints[-1] and x.get('doingCvr','') == False][0]
		avgTopCvr = [x for x in powerflows if x.get('loadLevel','') == energyPoints[-1] and x.get('doingCvr','') == True][0]
		avgBottomBase = [x for x in powerflows if x.get('loadLevel','') == energyPoints[0] and x.get('doingCvr','') == False][0]
		avgBottomCvr = [x for x in powerflows if x.get('loadLevel','') == energyPoints[0] and x.get('doingCvr','') == True][0]
		# Linear interpolation so we aren't running umpteen million loadflows.
		x = (energyPoints[0], energyPoints[1])
		y = (avgTopBase['realPower'] - avgTopCvr['realPower'],
			avgBottomBase['realPower'] - avgBottomCvr['realPower'])
		energyRed = y[0] + (y[1] - y[0]) * (avgEnergy - x[0]) / (x[1] - x[0])
		row['energyReduction'] = energyRed
		lossY = (avgTopBase['losses'] - avgTopCvr['losses'],
			avgBottomBase['losses'] - avgBottomCvr['losses'])
		lossRed = lossY[0] + (lossY[1] - lossY[0]) * (avgEnergy - x[0]) / (x[1] - x[0])
		row['lossReduction'] = lossRed
	# Multiply by dollars.
	for row in monthData:
		row['energyReductionDollars'] = row['energyReduction']/1000 * (rates['wholesaleEnergyCostPerKwh'] - rates['retailEnergyCostPerKwh'])
		row['peakReductionDollars'] = row['peakReduction']/1000 * rates['peakDemandCost' + row['season'] + 'PerKw']
		row['lossReductionDollars'] = row['lossReduction']/1000 * rates['wholesaleEnergyCostPerKwh']
	# Pretty output
	def plotTable(inData):
		fig = plt.figure(figsize=(10,5))
		plt.axis('off')
		plt.tight_layout()
		plt.table(cellText=[row for row in inData[1:]],
			loc = 'center',
			rowLabels = range(len(inData)-1),
			colLabels = inData[0])
	def dictalToMatrix(dictList):
		''' Take our dictal format to a matrix. '''
		matrix = [dictList[0].keys()]
		for row in dictList:
			matrix.append(row.values())
		return matrix
	# Powerflow results.
	plotTable(dictalToMatrix(powerflows))
	plt.savefig(pJoin(modelDir,"powerflowTable.png"))
	# Monetary results.
	## To print partial money table
	monthDataMat = dictalToMatrix(monthData)
	dimX = len(monthDataMat)
	dimY = len(monthDataMat[0])
	monthDataPart = []
	for k in range (0,dimX):
		monthDatatemp = []
		for m in range (4,dimY):
			monthDatatemp.append(monthDataMat[k][m])
		monthDataPart.append(monthDatatemp)
	plotTable(monthDataPart)
	plt.savefig(pJoin(modelDir,"moneyTable.png"))
	outData["monthDataMat"] = dictalToMatrix(monthData)
	outData["monthDataPart"] = monthDataPart
	# Graph the money data.
	fig = plt.figure(figsize=(10,8))
	indices = [r['monthName'] for r in monthData]
	d1 = [r['energyReductionDollars'] for r in monthData]
	d2 = [r['lossReductionDollars'] for r in monthData]
	d3 = [r['peakReductionDollars'] for r in monthData]
	ticks = range(len(d1))
	bar_erd = plt.bar(ticks,d1,color='red')
	bar_lrd = plt.bar(ticks,d2,color='green')
	bar_prd = plt.bar(ticks,d3,color='blue',yerr=d2)
	plt.legend([bar_prd[0], bar_lrd[0], bar_erd[0]], ['peakReductionDollars','lossReductionDollars','energyReductionDollars'],bbox_to_anchor=(0., 1.015, 1., .102), loc=3,
	   ncol=2, mode="expand", borderaxespad=0.1)
	plt.xticks([t+0.5 for t in ticks],indices)
	plt.ylabel('Utility Savings ($)')
	plt.tight_layout(5.5,1.3,1.2)
	fig.autofmt_xdate()
	plt.savefig(pJoin(modelDir,"spendChart.png"))
	outData["energyReductionDollars"] = d1
	outData["lossReductionDollars"] = d2
	outData["peakReductionDollars"] = d3
	# Graph the cumulative savings.
	fig = plt.figure(figsize=(10,5))
	annualSavings = sum(d1) + sum(d2) + sum(d3)
	annualSave = lambda x:(annualSavings - rates['omCost']) * x - rates['capitalCost']
	simplePayback = rates['capitalCost']/(annualSavings - rates['omCost'])
	plt.xlabel('Year After Installation')
	plt.xlim(0,30)
	plt.ylabel('Cumulative Savings ($)')
	plt.plot([0 for x in range(31)],c='gray')
	plt.axvline(x=simplePayback, ymin=0, ymax=1, c='gray', linestyle='--')
	plt.plot([annualSave(x) for x in range(31)], c='green')
	plt.savefig(pJoin(modelDir,"savingsChart.png"))
	outData["annualSave"] = [annualSave(x) for x in range(31)]
	# For autotest, there won't be such file.
	return outData
Example #17
0
def drawPlot(tree,
             nodeDict=None,
             edgeDict=None,
             edgeLabsDict=None,
             displayLabs=False,
             customColormap=False,
             perUnitScale=False,
             rezSqIn=400,
             neatoLayout=False,
             nodeFlagBounds=[-float('inf'), float('inf')],
             defaultNodeVal=1):
    ''' Draw a color-coded map of the voltage drop on a feeder.
	path is the full path to the GridLAB-D .glm file or OMF .omd file.
	workDir is where GridLAB-D will run, if it's None then a temp dir is used.
	neatoLayout=True means the circuit is displayed using a force-layout approach.
	edgeLabs property must be either 'Name', 'Current', 'Power', 'Rating', 'PercentOfRating', or None
	nodeLabs property must be either 'Name', 'Voltage', 'VoltageImbalance', or None
	edgeCol and nodeCol can be set to false to avoid coloring edges or nodes
	customColormap=True means use a one that is nicely scaled to perunit values highlighting extremes.
	Returns a matplotlib object.'''
    # Be quiet matplotlib:
    # warnings.filterwarnings('ignore')

    # Build the graph.
    fGraph = feeder.treeToNxGraph(tree)
    # TODO: consider whether we can set figsize dynamically.
    wlVal = int(math.sqrt(float(rezSqIn)))

    chart = plt.figure(figsize=(wlVal, wlVal))
    plt.axes(frameon=0)
    plt.axis('off')
    chart.gca().set_aspect('equal')
    plt.tight_layout()

    # Need to get edge names from pairs of connected node names.
    edgeNames = []
    for e in fGraph.edges():
        edgeNames.append((fGraph.edges[e].get('name',
                                              'BLANK')).replace('"', ''))

    #set axes step equal
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(fGraph.edges())
        cleanG.add_nodes_from(fGraph)
        positions = graphviz_layout(cleanG, prog='neato')
    else:
        positions = {
            n: fGraph.nodes[n].get('pos', (0, 0))[::-1]
            for n in fGraph
        }

    #create custom colormap
    if customColormap:
        custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list(
            'custColMap', [(0.0, 'blue'), (0.15, 'darkgray'),
                           (0.7, 'darkgray'), (1.0, 'red')])
        custom_cm.set_under(color='black')
    else:
        custom_cm = plt.cm.get_cmap('viridis')

    drawColorbar = False
    emptyColors = {}

    #draw edges with or without colors
    if edgeDict != None:
        drawColorbar = True
        edgeList = [edgeDict.get(n, 1) for n in edgeNames]
    else:
        edgeList = [emptyColors.get(n, .6) for n in edgeNames]

    edgeIm = nx.draw_networkx_edges(fGraph,
                                    pos=positions,
                                    edge_color=edgeList,
                                    width=1,
                                    edge_cmap=custom_cm)

    #draw edge labels
    if displayLabs:
        edgeLabelsIm = nx.draw_networkx_edge_labels(fGraph,
                                                    pos=positions,
                                                    edge_labels=edgeLabsDict,
                                                    font_size=8)

    # draw nodes with or without color
    if nodeDict != None:
        nodeList = [nodeDict.get(n, defaultNodeVal) for n in fGraph.nodes()]
        drawColorbar = True
    else:
        nodeList = [emptyColors.get(n, .6) for n in fGraph.nodes()]

    if perUnitScale:
        vmin = 0
        vmax = 1.25
    else:
        vmin = None
        vmax = None

    edgecolors = ['None'] * len(nodeList)
    for i in range(len(nodeList)):
        if nodeList[i] < nodeFlagBounds[0]:
            edgecolors[i] = '#ffa500'
        if nodeList[i] > nodeFlagBounds[1]:
            edgecolors[i] = 'r'
    nodeIm = nx.draw_networkx_nodes(fGraph,
                                    pos=positions,
                                    node_color=nodeList,
                                    edgecolors=edgecolors,
                                    linewidths=2,
                                    node_size=30,
                                    vmin=vmin,
                                    vmax=vmax,
                                    cmap=custom_cm)

    #draw node labels
    if displayLabs and nodeDict != None:
        nodeLabelsIm = nx.draw_networkx_labels(fGraph,
                                               pos=positions,
                                               labels=nodeDict,
                                               font_size=8)

    plt.sci(nodeIm)
    # plt.clim(110,130)
    if drawColorbar:
        plt.colorbar()
    return chart
Example #18
0
def voltPlot(tree, workDir=None, neatoLayout=False):
    """ Draw a color-coded map of the voltage drop on a feeder.
	Returns a matplotlib object. """
    # Get rid of schedules and climate:
    for key in tree.keys():
        if tree[key].get("argument", "") == '"schedules.glm"' or tree[key].get("tmyfile", "") != "":
            del tree[key]
            # Make sure we have a voltDump:

    def safeInt(x):
        try:
            return int(x)
        except:
            return 0

    biggestKey = max([safeInt(x) for x in tree.keys()])
    tree[str(biggestKey * 10)] = {"object": "voltdump", "filename": "voltDump.csv"}
    # Run Gridlab.
    if not workDir:
        workDir = tempfile.mkdtemp()
        print "gridlabD runInFilesystem with no specified workDir. Working in", workDir
    gridlabOut = gridlabd.runInFilesystem(tree, attachments=[], workDir=workDir)
    with open(pJoin(workDir, "voltDump.csv"), "r") as dumpFile:
        reader = csv.reader(dumpFile)
        reader.next()  # Burn the header.
        keys = reader.next()
        voltTable = []
        for row in reader:
            rowDict = {}
            for pos, key in enumerate(keys):
                rowDict[key] = row[pos]
            voltTable.append(rowDict)
            # Calculate average node voltage deviation. First, helper functions.

    def pythag(x, y):
        """ For right triangle with sides a and b, return the hypotenuse. """
        return math.sqrt(x ** 2 + y ** 2)

    def digits(x):
        """ Returns number of digits before the decimal in the float x. """
        return math.ceil(math.log10(x + 1))

    def avg(l):
        """ Average of a list of ints or floats. """
        return sum(l) / len(l)
        # Detect the feeder nominal voltage:

    for key in tree:
        ob = tree[key]
        if type(ob) == dict and ob.get("bustype", "") == "SWING":
            feedVoltage = float(ob.get("nominal_voltage", 1))
            # Tot it all up.
    nodeVolts = {}
    for row in voltTable:
        allVolts = []
        for phase in ["A", "B", "C"]:
            phaseVolt = pythag(float(row["volt" + phase + "_real"]), float(row["volt" + phase + "_imag"]))
            if phaseVolt != 0.0:
                if digits(phaseVolt) > 3:
                    # Normalize to 120 V standard
                    phaseVolt = phaseVolt * (120 / feedVoltage)
                allVolts.append(phaseVolt)
        nodeVolts[row.get("node_name", "")] = avg(allVolts)
        # Color nodes by VOLTAGE.
    fGraph = feeder.treeToNxGraph(tree)
    voltChart = plt.figure(figsize=(15, 15))
    plt.axes(frameon=0)
    plt.axis("off")
    # set axes step equal
    voltChart.gca().set_aspect("equal")
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(fGraph.edges())
        cleanG.add_nodes_from(fGraph)
        positions = nx.graphviz_layout(cleanG, prog="neato")
    else:
        positions = {n: fGraph.node[n].get("pos", (0, 0)) for n in fGraph}
    edgeIm = nx.draw_networkx_edges(fGraph, positions)
    nodeIm = nx.draw_networkx_nodes(
        fGraph,
        pos=positions,
        node_color=[nodeVolts.get(n, 0) for n in fGraph.nodes()],
        linewidths=0,
        node_size=30,
        cmap=plt.cm.jet,
    )
    plt.sci(nodeIm)
    plt.clim(110, 130)
    plt.colorbar()
    return voltChart
Example #19
0
import omf.feeder as feeder, json
import os, networkx as nx
from os.path import join as pJoin
from networkx.drawing.nx_agraph import graphviz_layout

_myDir = os.path.dirname(os.path.abspath(__file__))
IN_PATH_OMD = pJoin(_myDir,'superModel Tomorrow.omd')
OUT_PATH_OMD = pJoin(_myDir,'superModel Tomorrow with latlons.omd')

with open(IN_PATH_OMD,'r') as jsonFile:
	omd = json.load(jsonFile)
	tree = omd['tree']

# Use graphviz to lay out the graph.
inGraph = feeder.treeToNxGraph(tree)
# HACK: work on a new graph without attributes because graphViz tries to read attrs.
cleanG = nx.Graph(inGraph.edges())
# HACK2: might miss nodes without edges without the following.
cleanG.add_nodes_from(inGraph)
pos = graphviz_layout(cleanG, prog='neato')
# # Charting the feeder in matplotlib:
# feeder.latLonNxGraph(inGraph, labels=False, neatoLayout=True, showPlot=True)
# Insert the latlons.
for key in tree:
	obName = tree[key].get('name','')
	thisPos = pos.get(obName, None)
	if thisPos != None:
		tree[key]['longitude'] = thisPos[0]
		tree[key]['latitude'] = thisPos[1]

with open(OUT_PATH_OMD,'w') as outFile:
Example #20
0
def voltPlot(omd, workDir=None, neatoLayout=False):
    ''' Draw a color-coded map of the voltage drop on a feeder.
	Returns a matplotlib object. '''
    tree = omd.get('tree', {})
    # # Get rid of schedules and climate:
    for key in tree.keys():
        if tree[key].get("argument",
                         "") == "\"schedules.glm\"" or tree[key].get(
                             "tmyfile", "") != "":
            del tree[key]
    # Map to speed up name lookups.
    nameToIndex = {tree[key].get('name', ''): key for key in tree.keys()}

    # Make sure we have a voltDump:
    def safeInt(x):
        try:
            return int(x)
        except:
            return 0

    biggestKey = max([safeInt(x) for x in tree.keys()])
    tree[str(biggestKey * 10)] = {
        "object": "voltdump",
        "filename": "voltDump.csv"
    }
    # Run Gridlab.
    if not workDir:
        workDir = tempfile.mkdtemp()
    gridlabOut = omf.solvers.gridlabd_gridballast.runInFilesystem(
        tree, attachments=omd.get('attachments', {}), workDir=workDir)
    with open(pJoin(workDir, 'voltDump.csv'), 'r') as dumpFile:
        reader = csv.reader(dumpFile)
        reader.next()  # Burn the header.
        keys = reader.next()
        voltTable = []
        for row in reader:
            rowDict = {}
            for pos, key in enumerate(keys):
                rowDict[key] = row[pos]
            voltTable.append(rowDict)
    # Calculate average node voltage deviation. First, helper functions.
    def digits(x):
        ''' Returns number of digits before the decimal in the float x. '''
        return math.ceil(math.log10(x + 1))

    def avg(l):
        ''' Average of a list of ints or floats. '''
        return sum(l) / len(l)

    # Use the swing bus voltage as a reasonable default voltage.
    for key in tree:
        ob = tree[key]
        if type(ob) == dict and ob.get('bustype', '') == 'SWING':
            swingVoltage = float(ob.get('nominal_voltage', 1))
    # Tot it all up.
    nodeVolts = {}
    for row in voltTable:
        allVolts = []
        for phase in ['A', 'B', 'C']:
            realV = float(row['volt' + phase + '_real'])
            imagV = float(row['volt' + phase + '_imag'])
            phaseVolt = math.hypot(realV, imagV)
            if phaseVolt != 0.0:
                if digits(phaseVolt) > 3:
                    nodeName = row.get('node_name', '')
                    treeKey = nameToIndex.get(nodeName, 0)
                    nodeObj = tree.get(treeKey, {})
                    try:
                        nominal_voltage = float(nodeObj['nominal_voltage'])
                    except:
                        nominal_voltage = swingVoltage
                    # Normalize to 120 V standard
                    phaseVolt = phaseVolt * (120 / nominal_voltage)
                allVolts.append(phaseVolt)
        # Hack: average across phases.
        nodeVolts[row.get('node_name', '')] = avg(allVolts)
    # Color nodes by VOLTAGE.
    fGraph = feeder.treeToNxGraph(tree)
    voltChart = plt.figure(figsize=(20, 20))
    plt.axes(frameon=0)
    plt.axis('off')
    plt.tight_layout()
    #set axes step equal
    voltChart.gca().set_aspect('equal')
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(fGraph.edges())
        cleanG.add_nodes_from(fGraph)
        positions = graphviz_layout(cleanG, prog='neato')
    else:
        positions = {n: fGraph.node[n].get('pos', (0, 0)) for n in fGraph}
    edgeIm = nx.draw_networkx_edges(fGraph, positions)
    nodeIm = nx.draw_networkx_nodes(
        fGraph,
        pos=positions,
        node_color=[nodeVolts.get(n, 0) for n in fGraph.nodes()],
        linewidths=0,
        node_size=30,
        cmap=plt.cm.viridis)
    plt.sci(nodeIm)
    plt.clim(110, 130)
    plt.colorbar(orientation='horizontal', fraction=0.05)
    return voltChart
Example #21
0
def genDiagram(outputDir, feederJson, damageDict, critLoads, damagedLoads, edgeLabelsToAdd, generatorList):
	# print damageDict
	warnings.filterwarnings("ignore")
	# Load required data.
	tree = feederJson.get("tree",{})
	links = feederJson.get("links",{})
	# Generate lat/lons from nodes and links structures.
	for link in links:
		for typeLink in link.keys():
			if typeLink in ['source', 'target']:
				for key in link[typeLink].keys():
					if key in ['x', 'y']:
						objName = link[typeLink]['name']
						for x in tree:
							leaf = tree[x]
							if leaf.get('name','')==objName:
								if key=='x': leaf['latitude'] = link[typeLink][key]
								else: leaf['longitude'] = link[typeLink][key]
	# Remove even more things (no lat, lon or from = node without a position).
	for key in tree.keys():
		aLat = tree[key].get('latitude')
		aLon = tree[key].get('longitude')
		aFrom = tree[key].get('from')
		if aLat is None and aLon is None and aFrom is None:
			 tree.pop(key)
	# Create and save the graphic.
	inGraph = feeder.treeToNxGraph(tree)
	#feeder.latLonNxGraph(nxG) # This function creates a .plt reference which can be saved here.
	labels=True
	neatoLayout=False 
	showPlot=False
	plt.axis('off')
	plt.tight_layout()
	plt.gca().invert_yaxis()
	plt.gca().set_aspect('equal')
	# Layout the graph via GraphViz neato. Handy if there's no lat/lon data.
	if neatoLayout:
		# HACK: work on a new graph without attributes because graphViz tries to read attrs.
		cleanG = nx.Graph(inGraph.edges())
		# HACK2: might miss nodes without edges without the following.
		cleanG.add_nodes_from(inGraph)
		pos = nx.nx_agraph.graphviz_layout(cleanG, prog='neato')
	else:
		pos = {n:inGraph.node[n].get('pos',(0,0)) for n in inGraph}
	# Rescale using the magic number.
	for k in pos:
		newPos = (pos[k][0]/HACK_SCALING_CONSTANT, pos[k][1]/HACK_SCALING_CONSTANT)
		pos[k] = newPos
	# Draw all the edges
	selected_labels = {}
	for e in inGraph.edges():
		edgeName = inGraph.edge[e[0]][e[1]].get('name')
		if edgeName in edgeLabelsToAdd.keys():
			selected_labels[e] = edgeLabelsToAdd[edgeName]
		edgeColor = 'black'
		if edgeName in damageDict:
			if damageDict[edgeName] == 1:
				edgeColor = 'yellow'
			if damageDict[edgeName] == 2:
				edgeColor = 'orange'
			if damageDict[edgeName] >= 3:
				edgeColor = 'red'
		eType = inGraph.edge[e[0]][e[1]].get('type','underground_line')
		ePhases = inGraph.edge[e[0]][e[1]].get('phases',1)
		standArgs = {'edgelist':[e],
					 'edge_color':edgeColor,
					 'width':2,
					 'style':{'parentChild':'dotted','underground_line':'dashed'}.get(eType,'solid') }
		if ePhases==3:
			standArgs.update({'width':5})
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
			standArgs.update({'width':3,'edge_color':'gainsboro'})
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
			standArgs.update({'width':1,'edge_color':edgeColor})
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
		if ePhases==2:
			standArgs.update({'width':3})
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
			standArgs.update({'width':1,'edge_color':'gainsboro'})
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
		else:
			nx.draw_networkx_edges(inGraph,pos,**standArgs)
	# Get swing buses.
	green_list = []
	for node in tree:
		if 'bustype' in tree[node] and tree[node]['bustype'] == 'SWING':
			green_list.append(tree[node]['name'])
	isFirst = {'green': False, 'red': False, 'blue': False, 'grey': False, 'dimgrey': False}
	nodeLabels = {'green': 'Swing Bus', 'red': 'Critical Load', 'blue': 'Load', 'dimgrey':'New Generator', 'grey': 'Other'}
	# Draw nodes and optional labels.
	for key in pos.keys():
		isLoad = key[2:6]
		nodeColor = 'grey'
		nodeLabel = 'Other'
		if key in green_list:
			nodeColor = 'green'
		elif key in critLoads:
			nodeColor = 'red'			
		elif isLoad == 'load':
			nodeColor = 'blue'
		elif key in generatorList and key not in green_list:
			nodeColor = 'dimgrey'
		kwargs = {
			'nodelist': [key],
			'node_color': nodeColor,
			'node_size': 16,
			'linewidths': 1.0
		}
		if not isFirst[nodeColor]:
			kwargs['label'] = nodeLabels[nodeColor]
			isFirst[nodeColor] = True
		node = nx.draw_networkx_nodes(inGraph, pos, **kwargs)
		if key in generatorList:
			node.set_edgecolor('black')
	if labels:
		nx.draw_networkx_labels(
			inGraph,
			pos,
			labels=damagedLoads,
			font_color='white',
			font_weight='bold',
			font_size=3
		)
		nx.draw_networkx_edge_labels(
			inGraph,
			pos,
			edge_labels=selected_labels,
			bbox={'alpha':0},
			font_color='red',
			font_size=4
		)
	# Hazard field.
	# xlim = plt.xlim(); ylim = plt.ylim() # capture network limits.
	# a = np.random.random((600, 600))
	# plt.imshow(a, cmap='Greys', interpolation='nearest', alpha=0.3)
	# plt.xlim(*xlim); plt.ylim(*ylim) # reset limits to be tight on network
	# Final showing or saving.
	plt.legend(loc='lower right')
	if showPlot: plt.show()
	plt.savefig(pJoin(outputDir,"feederChart.png"), dpi=800, pad_inches=0.0)
Example #22
0
def genDiagram(outputDir, feederJson, damageDict, critLoads, damagedLoads,
               edgeLabelsToAdd, generatorList):
    # print damageDict
    warnings.filterwarnings("ignore")
    # Load required data.
    tree = feederJson.get("tree", {})
    links = feederJson.get("links", {})
    # Generate lat/lons from nodes and links structures.
    for link in links:
        for typeLink in link.keys():
            if typeLink in ['source', 'target']:
                for key in link[typeLink].keys():
                    if key in ['x', 'y']:
                        objName = link[typeLink]['name']
                        for x in tree:
                            leaf = tree[x]
                            if leaf.get('name', '') == objName:
                                if key == 'x':
                                    leaf['latitude'] = link[typeLink][key]
                                else:
                                    leaf['longitude'] = link[typeLink][key]
    # Remove even more things (no lat, lon or from = node without a position).
    for key in tree.keys():
        aLat = tree[key].get('latitude')
        aLon = tree[key].get('longitude')
        aFrom = tree[key].get('from')
        if aLat is None and aLon is None and aFrom is None:
            tree.pop(key)
    # Create and save the graphic.
    inGraph = feeder.treeToNxGraph(tree)
    labels = True
    neatoLayout = False
    showPlot = False
    plt.axis('off')
    plt.tight_layout()
    plt.gca().invert_yaxis()
    plt.gca().set_aspect('equal')
    # Layout the graph via GraphViz neato. Handy if there's no lat/lon data.
    if neatoLayout:
        # HACK: work on a new graph without attributes because graphViz tries to read attrs.
        cleanG = nx.Graph(inGraph.edges())
        # HACK2: might miss nodes without edges without the following.
        cleanG.add_nodes_from(inGraph)
        pos = nx.nx_agraph.graphviz_layout(cleanG, prog='neato')
    else:
        pos = {n: inGraph.node[n].get('pos', (0, 0)) for n in inGraph}
    # Rescale using the magic number.
    for k in pos:
        newPos = (pos[k][0] / HACK_SCALING_CONSTANT,
                  pos[k][1] / HACK_SCALING_CONSTANT)
        pos[k] = newPos
    # Draw all the edges
    selected_labels = {}
    for e in inGraph.edges():
        edgeName = inGraph.edge[e[0]][e[1]].get('name')
        if edgeName in edgeLabelsToAdd.keys():
            selected_labels[e] = edgeLabelsToAdd[edgeName]
        edgeColor = 'black'
        if edgeName in damageDict:
            if damageDict[edgeName] == 1:
                edgeColor = 'yellow'
            if damageDict[edgeName] == 2:
                edgeColor = 'orange'
            if damageDict[edgeName] >= 3:
                edgeColor = 'red'
        eType = inGraph.edge[e[0]][e[1]].get('type', 'underground_line')
        ePhases = inGraph.edge[e[0]][e[1]].get('phases', 1)
        standArgs = {
            'edgelist': [e],
            'edge_color': edgeColor,
            'width': 2,
            'style': {
                'parentChild': 'dotted',
                'underground_line': 'dashed'
            }.get(eType, 'solid')
        }
        if ePhases == 3:
            standArgs.update({'width': 5})
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
            standArgs.update({'width': 3, 'edge_color': 'gainsboro'})
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
            standArgs.update({'width': 1, 'edge_color': edgeColor})
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
        if ePhases == 2:
            standArgs.update({'width': 3})
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
            standArgs.update({'width': 1, 'edge_color': 'gainsboro'})
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
        else:
            nx.draw_networkx_edges(inGraph, pos, **standArgs)
    # Get swing buses.
    green_list = []
    for node in tree:
        if 'bustype' in tree[node] and tree[node]['bustype'] == 'SWING':
            green_list.append(tree[node]['name'])
    isFirst = {
        'green': False,
        'red': False,
        'blue': False,
        'grey': False,
        'dimgrey': False
    }
    nodeLabels = {
        'green': 'Swing Bus',
        'red': 'Critical Load',
        'blue': 'Load',
        'dimgrey': 'New Generator',
        'grey': 'Other'
    }
    # Draw nodes and optional labels.
    for key in pos.keys():
        isLoad = key[2:6]
        nodeColor = 'grey'
        nodeLabel = 'Other'
        if key in green_list:
            nodeColor = 'green'
        elif key in critLoads:
            nodeColor = 'red'
        elif isLoad == 'load':
            nodeColor = 'blue'
        elif key in generatorList and key not in green_list:
            nodeColor = 'dimgrey'
        kwargs = {
            'nodelist': [key],
            'node_color': nodeColor,
            'node_size': 16,
            'linewidths': 1.0
        }
        if not isFirst[nodeColor]:
            kwargs['label'] = nodeLabels[nodeColor]
            isFirst[nodeColor] = True
        node = nx.draw_networkx_nodes(inGraph, pos, **kwargs)
        if key in generatorList:
            node.set_edgecolor('black')
    if labels:
        nx.draw_networkx_labels(inGraph,
                                pos,
                                labels=damagedLoads,
                                font_color='white',
                                font_weight='bold',
                                font_size=3)
        nx.draw_networkx_edge_labels(inGraph,
                                     pos,
                                     edge_labels=selected_labels,
                                     bbox={'alpha': 0},
                                     font_color='red',
                                     font_size=4)
    # Final showing or saving.
    fig = matplotlib.pyplot.gcf()
    fig.set_size_inches(9, 6)
    plt.legend(loc='lower right')
    if showPlot: plt.show()
    plt.savefig(pJoin(outputDir, "feederChart.png"), dpi=800, pad_inches=0.0)
Example #23
0
File: geo.py Project: Billjutt/omf
def mapOmd(pathToOmdFile,
           outputPath,
           fileFormat,
           openBrowser=False,
           conversion=False):
    '''
	Draw an omd on a map.
	
	fileFormat options: html or png
	Use html option to create a geojson file to be displayed with an interactive leaflet map.
	Use the png file format to create a static png image.
	By default the file(s) is saved to the outputPath, but setting openBrowser to True with open in a new browser window.
	'''
    if fileFormat == 'html':
        if not conversion:
            geoJsonDict = omdGeoJson(pathToOmdFile)
        #use conversion for testing other feeders
        if conversion:
            geoJsonDict = omdGeoJson(pathToOmdFile, conversion=True)
        if not os.path.exists(outputPath):
            os.makedirs(outputPath)
        shutil.copy(omf.omfDir + '/templates/geoJsonMap.html', outputPath)
        with open(pJoin(outputPath, 'geoJsonFeatures.js'), "w") as outFile:
            outFile.write("var geojson =")
            json.dump(geoJsonDict, outFile, indent=4)
        if openBrowser:
            openInBrowser(pJoin(outputPath, 'geoJsonMap.html'))
    elif fileFormat == 'png':
        if not conversion:
            with open(pathToOmdFile) as inFile:
                tree = json.load(inFile)['tree']
            nxG = feeder.treeToNxGraph(tree)
            nxG = graphValidator(pathToOmdFile, nxG)
        #use conversion for testing other feeders
        if conversion:
            nxG = convertOmd(pathToOmdFile)
        latitude_min = min([
            nxG.nodes[nodewithPosition]['pos'][0]
            for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
        ])
        longitude_min = min([
            nxG.nodes[nodewithPosition]['pos'][1]
            for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
        ])
        latitude_max = max([
            nxG.nodes[nodewithPosition]['pos'][0]
            for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
        ])
        longitude_max = max([
            nxG.nodes[nodewithPosition]['pos'][1]
            for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
        ])
        #Set the plot settings
        plt.switch_backend('Agg')
        fig = plt.figure(frameon=False, figsize=[10, 10])
        ax = fig.add_axes([0, 0, 1, 1])
        ax.axis('off')
        #map latlon to projection
        epsg3857 = Proj(init='epsg:3857')
        wgs84 = Proj(init='EPSG:4326')
        node_positions = {
            nodewithPosition: nxG.nodes[nodewithPosition]['pos']
            for nodewithPosition in nx.get_node_attributes(nxG, 'pos')
        }
        for point in node_positions:
            node_positions[point] = transform(wgs84, epsg3857,
                                              node_positions[point][1],
                                              node_positions[point][0])
        for zoomLevel in range(18, 19):
            numberofTiles = numTiles(zoomLevel)
            #Get bounding tiles and their lat/lon edges
            upperRightTile = tileXY(latitude_max, longitude_max, zoomLevel)
            lowerLeftTile = tileXY(latitude_min, longitude_min, zoomLevel)
            firstTileEdges = tileEdges(upperRightTile[0], upperRightTile[1],
                                       zoomLevel)
            lastTileEdges = tileEdges(lowerLeftTile[0], lowerLeftTile[1],
                                      zoomLevel)
            #Get N S E W boundaries for outer tiles in mercator projection x/y
            mainsouthWest = transform(wgs84, epsg3857, lastTileEdges[1],
                                      lastTileEdges[0])
            mainnorthEast = transform(wgs84, epsg3857, firstTileEdges[3],
                                      firstTileEdges[2])
            nx.draw_networkx(nxG,
                             pos=node_positions,
                             nodelist=list(node_positions.keys()),
                             with_labels=False,
                             node_size=2,
                             edge_size=1)
            for tileX in range(lowerLeftTile[0], upperRightTile[0] + 1):
                for tileY in range(upperRightTile[1], lowerLeftTile[1] + 1):
                    #Get section of tree that covers this tile
                    currentTileEdges = tileEdges(tileX, tileY, zoomLevel)
                    southWest = transform(wgs84, epsg3857, currentTileEdges[1],
                                          currentTileEdges[0])
                    northEast = transform(wgs84, epsg3857, currentTileEdges[3],
                                          currentTileEdges[2])
                    #Get map background from tile
                    url = 'https://a.tile.openstreetmap.org/%s/%s/%s.png' % (
                        zoomLevel, tileX, tileY)
                    # Spoof the User-Agent so we don't get 429
                    response = requests.request(
                        'GET',
                        url,
                        stream=True,
                        headers={
                            'User-Agent':
                            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0'
                        })
                    with tempfile.NamedTemporaryFile() as f:
                        f.write(response.raw.read())
                        img = plt.imread(f)
                    plt.imshow(img,
                               extent=(southWest[0], northEast[0],
                                       southWest[1], northEast[1]))
            plt.ylim(top=mainnorthEast[1], bottom=mainsouthWest[1])
            plt.xlim(mainsouthWest[0], mainnorthEast[0])
            if not os.path.exists(outputPath):
                os.makedirs(outputPath)
            plt.savefig(pJoin(outputPath, 'graphOnMap.png'),
                        frameon=False,
                        pad_inches=0,
                        bbox='tight')
            if openBrowser:
                openInBrowser(pJoin(outputPath, 'graphOnMap.png'))
Example #24
0
def work(modelDir, inputDict):
    ''' Run the model in the foreground. WARNING: can take about a minute. '''
    # Global vars, and load data from the model directory.
    feederName = [x for x in os.listdir(modelDir)
                  if x.endswith('.omd')][0][:-4]
    inputDict["feederName1"] = feederName
    feederPath = pJoin(modelDir, feederName + '.omd')
    feederJson = json.load(open(feederPath))
    tree = feederJson.get("tree", {})
    attachments = feederJson.get("attachments", {})
    outData = {}
    ''' Run CVR analysis. '''
    # Reformate monthData and rates.
    rates = {
        k: float(inputDict[k])
        for k in [
            "capitalCost", "omCost", "wholesaleEnergyCostPerKwh",
            "retailEnergyCostPerKwh", "peakDemandCostSpringPerKw",
            "peakDemandCostSummerPerKw", "peakDemandCostFallPerKw",
            "peakDemandCostWinterPerKw"
        ]
    }
    monthNames = [
        "January", "February", "March", "April", "May", "June", "July",
        "August", "September", "October", "November", "December"
    ]
    monthToSeason = {
        'January': 'Winter',
        'February': 'Winter',
        'March': 'Spring',
        'April': 'Spring',
        'May': 'Spring',
        'June': 'Summer',
        'July': 'Summer',
        'August': 'Summer',
        'September': 'Fall',
        'October': 'Fall',
        'November': 'Fall',
        'December': 'Winter'
    }
    monthData = []
    for i, x in enumerate(monthNames):
        monShort = x[0:3].lower()
        season = monthToSeason[x]
        histAvg = float(inputDict.get(monShort + "Avg", 0))
        histPeak = float(inputDict.get(monShort + "Peak", 0))
        monthData.append({
            "monthId": i,
            "monthName": x,
            "histAverage": histAvg,
            "histPeak": histPeak,
            "season": season
        })
    # Graph the SCADA data.
    fig = plt.figure(figsize=(10, 6))
    indices = [r['monthName'] for r in monthData]
    d1 = [r['histPeak'] / (10**3) for r in monthData]
    d2 = [r['histAverage'] / (10**3) for r in monthData]
    ticks = range(len(d1))
    bar_peak = plt.bar(ticks, d1, color='gray')
    bar_avg = plt.bar(ticks, d2, color='dimgray')
    plt.legend([bar_peak[0], bar_avg[0]], ['histPeak', 'histAverage'],
               bbox_to_anchor=(0., 1.015, 1., .102),
               loc=3,
               ncol=2,
               mode="expand",
               borderaxespad=0.1)
    plt.xticks([t + 0.5 for t in ticks], indices)
    plt.ylabel('Mean and peak historical power consumptions (kW)')
    fig.autofmt_xdate()
    plt.savefig(pJoin(modelDir, "scadaChart.png"))
    outData["histPeak"] = d1
    outData["histAverage"] = d2
    outData["monthName"] = [name[0:3] for name in monthNames]
    # Graph feeder.
    fig = plt.figure(figsize=(10, 10))
    myGraph = feeder.treeToNxGraph(tree)
    feeder.latLonNxGraph(myGraph, neatoLayout=False)
    plt.savefig(pJoin(modelDir, "feederChart.png"))
    with open(pJoin(modelDir, "feederChart.png"), "rb") as inFile:
        outData["feederChart"] = inFile.read().encode("base64")
    # Get the load levels we need to test.
    allLoadLevels = [x.get('histPeak', 0) for x in monthData
                     ] + [y.get('histAverage', 0) for y in monthData]
    maxLev = _roundOne(max(allLoadLevels), 'up')
    minLev = _roundOne(min(allLoadLevels), 'down')
    tenLoadLevels = range(int(minLev), int(maxLev), int(
        (maxLev - minLev) / 10))
    # Gather variables from the feeder.
    for key in tree.keys():
        # Set clock to single timestep.
        if tree[key].get('clock', '') == 'clock':
            tree[key] = {
                "timezone": "PST+8PDT",
                "stoptime": "'2013-01-01 00:00:00'",
                "starttime": "'2013-01-01 00:00:00'",
                "clock": "clock"
            }
        # Save swing node index.
        if tree[key].get('bustype', '').lower() == 'swing':
            swingIndex = key
            swingName = tree[key].get('name')
        # Remove all includes.
        if tree[key].get('omftype', '') == '#include':
            del key
    # Find the substation regulator and config.
    for key in tree:
        if tree[key].get('object', '') == 'regulator' and tree[key].get(
                'from', '') == swingName:
            regIndex = key
            regConfName = tree[key]['configuration']
    if not regConfName: regConfName = False
    for key in tree:
        if tree[key].get('name', '') == regConfName:
            regConfIndex = key
    # Set substation regulator to manual operation.
    baselineTap = int(inputDict.get(
        "baselineTap"))  # GLOBAL VARIABLE FOR DEFAULT TAP POSITION
    tree[regConfIndex] = {
        'name': tree[regConfIndex]['name'],
        'object': 'regulator_configuration',
        'connect_type': '1',
        'raise_taps': '10',
        'lower_taps': '10',
        'CT_phase': 'ABC',
        'PT_phase': 'ABC',
        'regulation':
        '0.10',  #Yo, 0.10 means at tap_pos 10 we're 10% above 120V.
        'Control': 'MANUAL',
        'control_level': 'INDIVIDUAL',
        'Type': 'A',
        'tap_pos_A': str(baselineTap),
        'tap_pos_B': str(baselineTap),
        'tap_pos_C': str(baselineTap)
    }
    # Attach recorders relevant to CVR.
    recorders = [{
        'object':
        'collector',
        'file':
        'ZlossesTransformer.csv',
        'group':
        'class=transformer',
        'limit':
        '0',
        'property':
        'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'
    }, {
        'object':
        'collector',
        'file':
        'ZlossesUnderground.csv',
        'group':
        'class=underground_line',
        'limit':
        '0',
        'property':
        'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'
    }, {
        'object':
        'collector',
        'file':
        'ZlossesOverhead.csv',
        'group':
        'class=overhead_line',
        'limit':
        '0',
        'property':
        'sum(power_losses_A.real),sum(power_losses_A.imag),sum(power_losses_B.real),sum(power_losses_B.imag),sum(power_losses_C.real),sum(power_losses_C.imag)'
    }, {
        'object': 'recorder',
        'file': 'Zregulator.csv',
        'limit': '0',
        'parent': tree[regIndex]['name'],
        'property': 'tap_A,tap_B,tap_C,power_in.real,power_in.imag'
    }, {
        'object':
        'collector',
        'file':
        'ZvoltageJiggle.csv',
        'group':
        'class=triplex_meter',
        'limit':
        '0',
        'property':
        'min(voltage_12.mag),mean(voltage_12.mag),max(voltage_12.mag),std(voltage_12.mag)'
    }, {
        'object': 'recorder',
        'file': 'ZsubstationTop.csv',
        'limit': '0',
        'parent': tree[swingIndex]['name'],
        'property': 'voltage_A,voltage_B,voltage_C'
    }, {
        'object': 'recorder',
        'file': 'ZsubstationBottom.csv',
        'limit': '0',
        'parent': tree[regIndex]['to'],
        'property': 'voltage_A,voltage_B,voltage_C'
    }]
    biggest = 1 + max([int(k) for k in tree.keys()])
    for index, rec in enumerate(recorders):
        tree[biggest + index] = rec
    # Change constant PF loads to ZIP loads. (See evernote for rationale about 50/50 power/impedance mix.)
    blankZipModel = {
        'object': 'triplex_load',
        'name': 'NAMEVARIABLE',
        'base_power_12': 'POWERVARIABLE',
        'power_fraction_12': str(inputDict.get("p_percent")),
        'impedance_fraction_12': str(inputDict.get("z_percent")),
        'current_fraction_12': str(inputDict.get("i_percent")),
        'power_pf_12': str(
            inputDict.get("power_factor")
        ),  #MAYBEFIX: we can probably get this PF data from the Milsoft loads.
        'impedance_pf_12': str(inputDict.get("power_factor")),
        'current_pf_12': str(inputDict.get("power_factor")),
        'nominal_voltage': '120',
        'phases': 'PHASESVARIABLE',
        'parent': 'PARENTVARIABLE'
    }

    def powerClean(powerStr):
        ''' take 3339.39+1052.29j to 3339.39 '''
        return powerStr[0:powerStr.find('+')]

    for key in tree:
        if tree[key].get('object', '') == 'triplex_node':
            # Get existing variables.
            name = tree[key].get('name', '')
            power = tree[key].get('power_12', '')
            parent = tree[key].get('parent', '')
            phases = tree[key].get('phases', '')
            # Replace object and reintroduce variables.
            tree[key] = copy(blankZipModel)
            tree[key]['name'] = name
            tree[key]['base_power_12'] = powerClean(power)
            tree[key]['parent'] = parent
            tree[key]['phases'] = phases
    # Function to determine how low we can tap down in the CVR case:
    def loweringPotential(baseLine):
        ''' Given a baseline end of line voltage, how many more percent can we shave off the substation voltage? '''
        ''' testsWePass = [122.0,118.0,200.0,110.0] '''
        lower = int(math.floor((baseLine / 114.0 - 1) * 100)) - 1
        # If lower is negative, we can't return it because we'd be undervolting beyond what baseline already was!
        if lower < 0:
            return baselineTap
        else:
            return baselineTap - lower

    # Run all the powerflows.
    powerflows = []
    for doingCvr in [False, True]:
        # For each load level in the tenLoadLevels, run a powerflow with the load objects scaled to the level.
        for desiredLoad in tenLoadLevels:
            # Find the total load that was defined in Milsoft:
            loadList = []
            for key in tree:
                if tree[key].get('object', '') == 'triplex_load':
                    loadList.append(tree[key].get('base_power_12', ''))
            totalLoad = sum([float(x) for x in loadList])
            # Rescale each triplex load:
            for key in tree:
                if tree[key].get('object', '') == 'triplex_load':
                    currentPow = float(tree[key]['base_power_12'])
                    ratio = desiredLoad / totalLoad
                    tree[key]['base_power_12'] = str(currentPow * ratio)
            # If we're doing CVR then lower the voltage.
            if doingCvr:
                # Find the minimum voltage we can tap down to:
                newTapPos = baselineTap
                for row in powerflows:
                    if row.get('loadLevel', '') == desiredLoad:
                        newTapPos = loweringPotential(
                            row.get('lowVoltage', 114))
                # Tap it down to there.
                # MAYBEFIX: do each phase separately because that's how it's done in the field... Oof.
                tree[regConfIndex]['tap_pos_A'] = str(newTapPos)
                tree[regConfIndex]['tap_pos_B'] = str(newTapPos)
                tree[regConfIndex]['tap_pos_C'] = str(newTapPos)
            # Run the model through gridlab and put outputs in the table.
            output = gridlabd.runInFilesystem(tree,
                                              attachments=attachments,
                                              keepFiles=True,
                                              workDir=modelDir)
            os.remove(pJoin(modelDir, "PID.txt"))
            p = output['Zregulator.csv']['power_in.real'][0]
            q = output['Zregulator.csv']['power_in.imag'][0]
            s = math.sqrt(p**2 + q**2)
            lossTotal = 0.0
            for device in [
                    'ZlossesOverhead.csv', 'ZlossesTransformer.csv',
                    'ZlossesUnderground.csv'
            ]:
                for letter in ['A', 'B', 'C']:
                    r = output[device]['sum(power_losses_' + letter +
                                       '.real)'][0]
                    i = output[device]['sum(power_losses_' + letter +
                                       '.imag)'][0]
                    lossTotal += math.sqrt(r**2 + i**2)
            ## Entire output:
            powerflows.append({
                'doingCvr':
                doingCvr,
                'loadLevel':
                desiredLoad,
                'realPower':
                p,
                'powerFactor':
                p / s,
                'losses':
                lossTotal,
                'subVoltage':
                (output['ZsubstationBottom.csv']['voltage_A'][0] +
                 output['ZsubstationBottom.csv']['voltage_B'][0] +
                 output['ZsubstationBottom.csv']['voltage_C'][0]) / 3 / 60,
                'lowVoltage':
                output['ZvoltageJiggle.csv']['min(voltage_12.mag)'][0] / 2,
                'highVoltage':
                output['ZvoltageJiggle.csv']['max(voltage_12.mag)'][0] / 2
            })
    # For a given load level, find two points to interpolate on.
    def getInterpPoints(t):
        ''' Find the two points we can interpolate from. '''
        ''' tests pass on [tenLoadLevels[0],tenLoadLevels[5]+499,tenLoadLevels[-1]-988] '''
        loc = sorted(tenLoadLevels + [t]).index(t)
        if loc == 0:
            return (tenLoadLevels[0], tenLoadLevels[1])
        elif loc > len(tenLoadLevels) - 2:
            return (tenLoadLevels[-2], tenLoadLevels[-1])
        else:
            return (tenLoadLevels[loc - 1], tenLoadLevels[loc + 1])

    # Calculate peak reduction.
    for row in monthData:
        peak = row['histPeak']
        peakPoints = getInterpPoints(peak)
        peakTopBase = [
            x for x in powerflows if x.get('loadLevel', '') == peakPoints[-1]
            and x.get('doingCvr', '') == False
        ][0]
        peakTopCvr = [
            x for x in powerflows if x.get('loadLevel', '') == peakPoints[-1]
            and x.get('doingCvr', '') == True
        ][0]
        peakBottomBase = [
            x for x in powerflows if x.get('loadLevel', '') == peakPoints[0]
            and x.get('doingCvr', '') == False
        ][0]
        peakBottomCvr = [
            x for x in powerflows if x.get('loadLevel', '') == peakPoints[0]
            and x.get('doingCvr', '') == True
        ][0]
        # Linear interpolation so we aren't running umpteen million loadflows.
        x = (peakPoints[0], peakPoints[1])
        y = (peakTopBase['realPower'] - peakTopCvr['realPower'],
             peakBottomBase['realPower'] - peakBottomCvr['realPower'])
        peakRed = y[0] + (y[1] - y[0]) * (peak - x[0]) / (x[1] - x[0])
        row['peakReduction'] = peakRed
    # Calculate energy reduction and loss reduction based on average load.
    for row in monthData:
        avgEnergy = row['histAverage']
        energyPoints = getInterpPoints(avgEnergy)
        avgTopBase = [
            x for x in powerflows if x.get('loadLevel', '') == energyPoints[-1]
            and x.get('doingCvr', '') == False
        ][0]
        avgTopCvr = [
            x for x in powerflows if x.get('loadLevel', '') == energyPoints[-1]
            and x.get('doingCvr', '') == True
        ][0]
        avgBottomBase = [
            x for x in powerflows if x.get('loadLevel', '') == energyPoints[0]
            and x.get('doingCvr', '') == False
        ][0]
        avgBottomCvr = [
            x for x in powerflows if x.get('loadLevel', '') == energyPoints[0]
            and x.get('doingCvr', '') == True
        ][0]
        # Linear interpolation so we aren't running umpteen million loadflows.
        x = (energyPoints[0], energyPoints[1])
        y = (avgTopBase['realPower'] - avgTopCvr['realPower'],
             avgBottomBase['realPower'] - avgBottomCvr['realPower'])
        energyRed = y[0] + (y[1] - y[0]) * (avgEnergy - x[0]) / (x[1] - x[0])
        row['energyReduction'] = energyRed
        lossY = (avgTopBase['losses'] - avgTopCvr['losses'],
                 avgBottomBase['losses'] - avgBottomCvr['losses'])
        lossRed = lossY[0] + (lossY[1] - lossY[0]) * (avgEnergy -
                                                      x[0]) / (x[1] - x[0])
        row['lossReduction'] = lossRed
    # Multiply by dollars.
    for row in monthData:
        row['energyReductionDollars'] = row['energyReduction'] / 1000 * (
            rates['wholesaleEnergyCostPerKwh'] -
            rates['retailEnergyCostPerKwh'])
        row['peakReductionDollars'] = row['peakReduction'] / 1000 * rates[
            'peakDemandCost' + row['season'] + 'PerKw']
        row['lossReductionDollars'] = row['lossReduction'] / 1000 * rates[
            'wholesaleEnergyCostPerKwh']
    # Pretty output
    def plotTable(inData):
        fig = plt.figure(figsize=(10, 5))
        plt.axis('off')
        plt.tight_layout()
        plt.table(cellText=[row for row in inData[1:]],
                  loc='center',
                  rowLabels=range(len(inData) - 1),
                  colLabels=inData[0])

    def dictalToMatrix(dictList):
        ''' Take our dictal format to a matrix. '''
        matrix = [dictList[0].keys()]
        for row in dictList:
            matrix.append(row.values())
        return matrix

    # Powerflow results.
    plotTable(dictalToMatrix(powerflows))
    plt.savefig(pJoin(modelDir, "powerflowTable.png"))
    # Monetary results.
    ## To print partial money table
    monthDataMat = dictalToMatrix(monthData)
    dimX = len(monthDataMat)
    dimY = len(monthDataMat[0])
    monthDataPart = []
    for k in range(0, dimX):
        monthDatatemp = []
        for m in range(4, dimY):
            monthDatatemp.append(monthDataMat[k][m])
        monthDataPart.append(monthDatatemp)
    plotTable(monthDataPart)
    plt.savefig(pJoin(modelDir, "moneyTable.png"))
    outData["monthDataMat"] = dictalToMatrix(monthData)
    outData["monthDataPart"] = monthDataPart
    # Graph the money data.
    fig = plt.figure(figsize=(10, 8))
    indices = [r['monthName'] for r in monthData]
    d1 = [r['energyReductionDollars'] for r in monthData]
    d2 = [r['lossReductionDollars'] for r in monthData]
    d3 = [r['peakReductionDollars'] for r in monthData]
    ticks = range(len(d1))
    bar_erd = plt.bar(ticks, d1, color='red')
    bar_lrd = plt.bar(ticks, d2, color='green')
    bar_prd = plt.bar(ticks, d3, color='blue', yerr=d2)
    plt.legend([bar_prd[0], bar_lrd[0], bar_erd[0]], [
        'peakReductionDollars', 'lossReductionDollars',
        'energyReductionDollars'
    ],
               bbox_to_anchor=(0., 1.015, 1., .102),
               loc=3,
               ncol=2,
               mode="expand",
               borderaxespad=0.1)
    plt.xticks([t + 0.5 for t in ticks], indices)
    plt.ylabel('Utility Savings ($)')
    plt.tight_layout(5.5, 1.3, 1.2)
    fig.autofmt_xdate()
    plt.savefig(pJoin(modelDir, "spendChart.png"))
    outData["energyReductionDollars"] = d1
    outData["lossReductionDollars"] = d2
    outData["peakReductionDollars"] = d3
    # Graph the cumulative savings.
    fig = plt.figure(figsize=(10, 5))
    annualSavings = sum(d1) + sum(d2) + sum(d3)
    annualSave = lambda x: (annualSavings - rates['omCost']) * x - rates[
        'capitalCost']
    simplePayback = rates['capitalCost'] / (annualSavings - rates['omCost'])
    plt.xlabel('Year After Installation')
    plt.xlim(0, 30)
    plt.ylabel('Cumulative Savings ($)')
    plt.plot([0 for x in range(31)], c='gray')
    plt.axvline(x=simplePayback, ymin=0, ymax=1, c='gray', linestyle='--')
    plt.plot([annualSave(x) for x in range(31)], c='green')
    plt.savefig(pJoin(modelDir, "savingsChart.png"))
    outData["annualSave"] = [annualSave(x) for x in range(31)]
    # For autotest, there won't be such file.
    return outData
Example #25
0
import omf.feeder as feeder, json
import os, networkx as nx
from os.path import join as pJoin
from networkx.drawing.nx_agraph import graphviz_layout

_myDir = os.path.dirname(os.path.abspath(__file__))
IN_PATH_OMD = pJoin(_myDir, 'superModel Tomorrow.omd')
OUT_PATH_OMD = pJoin(_myDir, 'superModel Tomorrow with latlons.omd')

with open(IN_PATH_OMD, 'r') as jsonFile:
    omd = json.load(jsonFile)
    tree = omd['tree']

# Use graphviz to lay out the graph.
inGraph = feeder.treeToNxGraph(tree)
# HACK: work on a new graph without attributes because graphViz tries to read attrs.
cleanG = nx.Graph(inGraph.edges())
# HACK2: might miss nodes without edges without the following.
cleanG.add_nodes_from(inGraph)
pos = graphviz_layout(cleanG, prog='neato')
# # Charting the feeder in matplotlib:
# feeder.latLonNxGraph(inGraph, labels=False, neatoLayout=True, showPlot=True)
# Insert the latlons.
for key in tree:
    obName = tree[key].get('name', '')
    thisPos = pos.get(obName, None)
    if thisPos != None:
        tree[key]['longitude'] = thisPos[0]
        tree[key]['latitude'] = thisPos[1]

with open(OUT_PATH_OMD, 'w') as outFile: