def convertTests(): ''' Test convert every windmil feeder we have (in uploads). Return number of exceptions we hit. ''' exceptionCount = 0 testFiles = [('OrvilleTreePond.std', 'OrvilleTreePond.seq')] # ,('OlinBarre.std','OlinBarre.seq'),('OlinBeckenham.std','OlinBeckenham.seq'), ('AutocliAlberich.std','AutocliAlberich.seq') for stdString, seqString in testFiles: try: # Convert the std+seq. with open(stdString, 'r') as stdFile, open(seqString, 'r') as seqFile: outGlm, x, y = milToGridlab.convert(stdFile.read(), seqFile.read()) with open(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(stdString.replace('.std', '.png')) print 'DREW GLM OF', stdString except: exceptionCount += 1 print 'FAILED DRAWING', stdString try: # Run powerflow on the GLM. output = gridlabd.runInFilesystem(outGlm, keepFiles=False) with open(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() print exceptionCount
def _tests(): pathPrefix = '../../uploads/' 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') ] for stdPath, seqPath in testFiles: try: # Conver the std+seq. with open(pathPrefix + stdPath, 'r') as stdFile, open(pathPrefix + seqPath, 'r') as seqFile: outGlm, x, y = m2g.convert(stdFile.read(), seqFile.read()) with open(stdPath.replace('.std', '.glm'), 'w') as outFile: outFile.write(feeder.sortedWrite(outGlm)) print 'WROTE GLM FOR', stdPath try: # Draw the GLM. myGraph = feeder.treeToNxGraph(outGlm) feeder.latLonNxGraph(myGraph, neatoLayout=False) plt.savefig(stdPath.replace('.std', '.png')) except: print 'FAILED DRAWING', stdPath try: # Run powerflow on the GLM. output = gridlabd.runInFilesystem(outGlm, keepFiles=False) with open(stdPath.replace('.std', '.json'), 'w') as outFile: json.dump(output, outFile, indent=4) except: print 'POWERFLOW FAILED', stdPath except: print 'FAILED CONVERTING', stdPath traceback.print_exc() print os.listdir('.')
def convertTests(): ''' Test convert every windmil feeder we have (in uploads). Return number of exceptions we hit. ''' exceptionCount = 0 testFiles = [('OrvilleTreePond.std','OrvilleTreePond.seq')] # ,('OlinBarre.std','OlinBarre.seq'),('OlinBeckenham.std','OlinBeckenham.seq'), ('AutocliAlberich.std','AutocliAlberich.seq') for stdString, seqString in testFiles: try: # Convert the std+seq. with open(stdString,'r') as stdFile, open(seqString,'r') as seqFile: outGlm,x,y = milToGridlab.convert(stdFile.read(),seqFile.read()) with open(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(stdString.replace('.std','.png')) print 'DREW GLM OF', stdString except: exceptionCount += 1 print 'FAILED DRAWING', stdString try: # Run powerflow on the GLM. output = gridlabd.runInFilesystem(outGlm, keepFiles=False) with open(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() print exceptionCount
def runForeground(modelDir, inputDict): ''' Run the model in the foreground. WARNING: can take about a minute. ''' # Global vars, and load data from the model directory. print "STARTING TO RUN", modelDir try: startTime = datetime.datetime.now() if not os.path.isdir(modelDir): os.makedirs(modelDir) inputDict["created"] = str(startTime) feederName = inputDict.get('feederName1','feeder1') feederPath = pJoin(modelDir,feederName+'.omd') feederJson = json.load(open(feederPath)) tree = feederJson.get("tree",{}) attachments = feederJson.get("attachments",{}) allOutput = {} ''' Run CVR analysis. ''' # Reformate monthData and rates. rates = {k:float(inputDict[k]) for k in ["capitalCost", "omCost", "wholesaleEnergyCostPerKwh", "retailEnergyCostPerKwh", "peakDemandCostSpringPerKw", "peakDemandCostSummerPerKw", "peakDemandCostFallPerKw", "peakDemandCostWinterPerKw"]} # print "RATES", rates 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}) # for row in monthData: # print row # 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")) allOutput["histPeak"] = d1 allOutput["histAverage"] = d2 allOutput["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: allOutput["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")) allOutput["monthDataMat"] = dictalToMatrix(monthData) allOutput["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")) allOutput["energyReductionDollars"] = d1 allOutput["lossReductionDollars"] = d2 allOutput["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")) allOutput["annualSave"] = [annualSave(x) for x in range(31)] # Update the runTime in the input file. endTime = datetime.datetime.now() inputDict["runTime"] = str(datetime.timedelta(seconds=int((endTime - startTime).total_seconds()))) with open(pJoin(modelDir,"allInputData.json"),"w") as inFile: json.dump(inputDict, inFile, indent=4) # Write output file. with open(pJoin(modelDir,"allOutputData.json"),"w") as outFile: json.dump(allOutput, outFile, indent=4) # For autotest, there won't be such file. try: os.remove(pJoin(modelDir, "PPID.txt")) except: pass print "DONE RUNNING", modelDir except Exception as e: # If input range wasn't valid delete output, write error to disk. cancel(modelDir) thisErr = traceback.format_exc() print 'ERROR IN MODEL', modelDir, thisErr inputDict['stderr'] = thisErr with open(os.path.join(modelDir,'stderr.txt'),'w') as errorFile: errorFile.write(thisErr) with open(pJoin(modelDir,"allInputData.json"),"w") as inFile: json.dump(inputDict, inFile, indent=4)
def runForeground(modelDir, inputDict): ''' Run the model in the foreground. WARNING: can take about a minute. ''' # Global vars, and load data from the model directory. print "STARTING TO RUN", modelDir try: startTime = datetime.datetime.now() if not os.path.isdir(modelDir): os.makedirs(modelDir) inputDict["created"] = str(startTime) feederPath = pJoin(__metaModel__._omfDir,"data", "Feeder", inputDict["feederName"].split("___")[0], inputDict["feederName"].split("___")[1]+'.json') feederJson = json.load(open(feederPath)) tree = feederJson.get("tree",{}) attachments = feederJson.get("attachments",{}) allOutput = {} ''' Run CVR analysis. ''' # Reformate monthData and rates. rates = {k:float(inputDict[k]) for k in ["capitalCost", "omCost", "wholesaleEnergyCostPerKwh", "retailEnergyCostPerKwh", "peakDemandCostSpringPerKw", "peakDemandCostSummerPerKw", "peakDemandCostFallPerKw", "peakDemandCostWinterPerKw"]} # print "RATES", rates 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}) # for row in monthData: # print row # 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")) allOutput["histPeak"] = d1 allOutput["histAverage"] = d2 allOutput["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: allOutput["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")) allOutput["monthDataMat"] = dictalToMatrix(monthData) allOutput["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")) allOutput["energyReductionDollars"] = d1 allOutput["lossReductionDollars"] = d2 allOutput["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")) allOutput["annualSave"] = [annualSave(x) for x in range(31)] # Update the runTime in the input file. endTime = datetime.datetime.now() inputDict["runTime"] = str(datetime.timedelta(seconds=int((endTime - startTime).total_seconds()))) with open(pJoin(modelDir,"allInputData.json"),"w") as inFile: json.dump(inputDict, inFile, indent=4) # Write output file. with open(pJoin(modelDir,"allOutputData.json"),"w") as outFile: json.dump(allOutput, outFile, indent=4) # For autotest, there won't be such file. try: os.remove(pJoin(modelDir, "PPID.txt")) except: pass print "DONE RUNNING", modelDir except Exception as e: # If input range wasn't valid delete output, write error to disk. cancel(modelDir) thisErr = traceback.format_exc() print 'ERROR IN MODEL', modelDir, thisErr inputDict['stderr'] = thisErr with open(os.path.join(modelDir,'stderr.txt'),'w') as errorFile: errorFile.write(thisErr) with open(pJoin(modelDir,"allInputData.json"),"w") as inFile: json.dump(inputDict, inFile, indent=4)
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=(10,10)) plt.axes(frameon = 0) plt.axis('off') 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
def runAnalysis(tree, monthData, rates): ''' Run CVR analysis. ''' # Graph the SCADA data. fig = plt.figure(figsize=(17,5)) 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)) plt.bar(ticks,d1,color='gray') plt.bar(ticks,d2,color='dimgray') plt.xticks([t+0.5 for t in ticks],indices) plt.ylabel('Mean and peak historical power consumptions (kW)') # Graph feeder. myGraph = feeder.treeToNxGraph(tree) feeder.latLonNxGraph(myGraph, neatoLayout=False) # 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'] for key in tree: if tree[key].get('name','') == regConfName: regConfIndex = key # Set substation regulator to manual operation. baselineTap = 3 # 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':'0.5', 'impedance_fraction_12':'0.5', 'power_pf_12':'0.9', #TODO: we can probably get this PF data from the Milsoft loads. 'impedance_pf_12':'0.97', '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. # TODO: 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, keepFiles=False) 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'] * (rates['wholesaleEnergyCostPerKwh'] - rates['retailEnergyCostPerKwh']) row['peakReductionDollars'] = row['peakReduction'] * rates['peakDemandCost' + row['season'] + 'PerKw'] row['lossReductionDollars'] = row['lossReduction'] * rates['wholesaleEnergyCostPerKwh'] # Pretty output def plotTable(inData): fig = plt.figure(figsize=(20,10)) plt.axis('off') plt.tight_layout() plt.table(cellText=[row[1:] for row in inData[1:]], loc = 'center', rowLabels = [row[0] for row in 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)) # Monetary results. plotTable(dictalToMatrix(monthData)) # Graph the money data. fig = plt.figure(figsize=(17,10)) 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)) plt.bar(ticks,d1,color='red') plt.bar(ticks,d2,color='green') plt.bar(ticks,d3,color='blue',yerr=d2) plt.xticks([t+0.5 for t in ticks],indices) plt.ylabel('Utility Savings ($)') # Graph the cumulative savings. fig = plt.figure(figsize=(17,8)) 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')
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 = nx.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=(10, 10)) plt.axes(frameon=0) plt.axis('off') voltChart.subplots_adjust(left=0.03, bottom=0.03, right=0.97, top=0.97, wspace=None, hspace=None) 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
def _tests(Network, Equipment, keepFiles=True): import os, json, traceback, shutil from solvers import gridlabd from matplotlib import pyplot as plt import feeder exceptionCount = 0 try: #db_network = os.path.abspath('./uploads/IEEE13.mdb') #db_equipment = os.path.abspath('./uploads/IEEE13.mdb') prefix = str(Path("testPEC.py").resolve()).strip('scratch\cymeToGridlabTests\testPEC.py') + "\uploads\\" db_network = "C" + prefix + Network db_equipment = "C" + prefix + Equipment id_feeder = '650' conductors = prefix + "conductor_data.csv" #print "dbnet", db_network #print "eqnet", db_equipment #print "conductors", conductors #cyme_base, x, y = convertCymeModel(db_network, db_equipment, id_feeder, conductors) cyme_base, x, y = convertCymeModel(str(db_network), str(db_equipment), test=True, type=2, feeder_id='CV160') feeder.attachRecorders(cyme_base, "TriplexLosses", None, None) feeder.attachRecorders(cyme_base, "TransformerLosses", None, None) glmString = feeder.sortedWrite(cyme_base) feederglm = "C:\Users\Asus\Documents\GitHub\omf\omf\uploads\PEC.glm" #print "feeederglm", feederglm gfile = open(feederglm, 'w') gfile.write(glmString) gfile.close() #print 'WROTE GLM FOR' outPrefix = "C:\Users\Asus\Documents\GitHub\omf\omf\scratch\cymeToGridlabTests\\" try: os.mkdir(outPrefix) except: pass # Directory already there. '''Attempt to graph''' try: # Draw the GLM. print "trying to graph" myGraph = feeder.treeToNxGraph(cyme_base) feeder.latLonNxGraph(myGraph, neatoLayout=False) plt.savefig(outPrefix + "PEC.png") print "outprefix", outPrefix + "PEC.png" print 'DREW GLM OF' except: exceptionCount += 1 print 'FAILED DRAWING' try: # Run powerflow on the GLM. output = gridlabd.runInFilesystem(glmString, keepFiles=False) with open(outPrefix + "PEC.JSON",'w') as outFile: json.dump(output, outFile, indent=4) print 'RAN GRIDLAB ON\n' except: exceptionCount += 1 print 'POWERFLOW FAILED' except: print 'FAILED CONVERTING' exceptionCount += 1 traceback.print_exc() if not keepFiles: shutil.rmtree(outPrefix) return exceptionCount '''db_network = os.path.abspath('./uploads/PasoRobles11cymsectiondevice[device]['phases']08.mdb')
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=(10, 10)) plt.axes(frameon=0) plt.axis('off') 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
def _tests(Network, Equipment, keepFiles=True): import os, json, traceback, shutil from solvers import gridlabd from matplotlib import pyplot as plt import feeder exceptionCount = 0 try: #db_network = os.path.abspath('./uploads/IEEE13.mdb') #db_equipment = os.path.abspath('./uploads/IEEE13.mdb') prefix = str(Path("testPEC.py").resolve()).strip( 'scratch\cymeToGridlabTests\testPEC.py') + "\uploads\\" db_network = "C" + prefix + Network db_equipment = "C" + prefix + Equipment id_feeder = '650' conductors = prefix + "conductor_data.csv" #print "dbnet", db_network #print "eqnet", db_equipment #print "conductors", conductors #cyme_base, x, y = convertCymeModel(db_network, db_equipment, id_feeder, conductors) cyme_base, x, y = convertCymeModel(str(db_network), str(db_equipment), test=True, type=2, feeder_id='CV160') feeder.attachRecorders(cyme_base, "TriplexLosses", None, None) feeder.attachRecorders(cyme_base, "TransformerLosses", None, None) glmString = feeder.sortedWrite(cyme_base) feederglm = "C:\Users\Asus\Documents\GitHub\omf\omf\uploads\PEC.glm" #print "feeederglm", feederglm gfile = open(feederglm, 'w') gfile.write(glmString) gfile.close() #print 'WROTE GLM FOR' outPrefix = "C:\Users\Asus\Documents\GitHub\omf\omf\scratch\cymeToGridlabTests\\" try: os.mkdir(outPrefix) except: pass # Directory already there. '''Attempt to graph''' try: # Draw the GLM. print "trying to graph" myGraph = feeder.treeToNxGraph(cyme_base) feeder.latLonNxGraph(myGraph, neatoLayout=False) plt.savefig(outPrefix + "PEC.png") print "outprefix", outPrefix + "PEC.png" print 'DREW GLM OF' except: exceptionCount += 1 print 'FAILED DRAWING' try: # Run powerflow on the GLM. output = gridlabd.runInFilesystem(glmString, keepFiles=False) with open(outPrefix + "PEC.JSON", 'w') as outFile: json.dump(output, outFile, indent=4) print 'RAN GRIDLAB ON\n' except: exceptionCount += 1 print 'POWERFLOW FAILED' except: print 'FAILED CONVERTING' exceptionCount += 1 traceback.print_exc() if not keepFiles: shutil.rmtree(outPrefix) return exceptionCount '''db_network = os.path.abspath('./uploads/PasoRobles11cymsectiondevice[device]['phases']08.mdb')
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 = nx.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=(10,10)) plt.axes(frameon = 0) plt.axis('off') voltChart.subplots_adjust(left=0.03, bottom=0.03, right=0.97, top=0.97, wspace=None, hspace=None) 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
def generateVoltChart(tree, rawOut, modelDir, neatoLayout=True): ''' Map the voltages on a feeder over time using a set of PNGs.''' # Make the subfolder we need. try: shutil.rmtree(pJoin(modelDir, 'pngs')) except: pass try: os.mkdir(pJoin(modelDir, 'pngs')) except: pass # We need to timestamp images 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 = nx.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. for step, stamp in enumerate(rawOut['aVoltDump.csv']['# timestamp']): # Build voltage map. nodeVolts = {} 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[nodeName] = avg(allVolts) # Apply voltage map and chart it. voltChart = plt.figure(figsize=(10,10)) plt.axes(frameon = 0) plt.axis('off') 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.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(stamp) voltChart.savefig(pJoin(modelDir,'pngs','volts' + str(step).zfill(3) + "-" + genTime + '.png')) # Reclaim memory by closing, deleting and garbage collecting the last chart. voltChart.clf() plt.close() del voltChart gc.collect() return genTime