def addSingleBattery(battDict): #Check to see if node where battery is going to be added exists feederTree = battDict['feederDict'] nodeOK = 0 for key1 in feederTree: for key2 in feederTree[key1]: if feederTree[key1][key2] == battDict['battNode'] and key2=='name': if 'object' in feederTree[key1] and (feederTree[key1]['object'] == 'meter' or feederTree[key1]['object'] == 'node'): #Attaching to a normal meter meterType = 'meter' phases = str( feederTree[key1]['phases']) elif 'object' in feederTree[key1] and (feederTree[key1]['object'] == 'triplex_meter' or feederTree[key1]['object'] == 'triplex_node'): meterType = 'triplex_meter' phases = str(feederTree[key1]['phases']) targetKey = key1 #Need this to find the nominal voltage of the node where the meter is being added. nodeOK = 1 #Find available key value maxKey = feeder.getMaxKey(feederTree) if nodeOK == 0: raise Exception('Battery not able to be added at node {}; node does not exist.'.format(battDict['battNode'])) else: #Getting parameters needed to add components meterNominalV = feederTree[targetKey]['nominal_voltage'] #Add new meter, inverter, and battery to feeder dictionary meterName = str(battDict['battNode'])+'_meter' feederTree[str(maxKey + 1)]={'phases': phases, 'name': meterName, 'parent':str(battDict['battNode']), 'object': meterType, 'nominal_voltage':str(meterNominalV)} invName = str(battDict['battNode'])+'_inv' feederTree[str(maxKey + 2)]={'name':invName, 'parent': meterName, 'object': 'inverter', 'inverter_type':'FOUR_QUADRANT', 'four_quadrant_control_mode': battDict['invControl'].upper(), 'generator_mode':'CONSTANT_PQ', 'generator_status':'ONLINE', 'sense_object': str(battDict['controlSenseNode']), 'charge_lockout_time': str(battDict['invChargeLockoutTime']), 'discharge_lockout_time':str(battDict['invDischargeLockoutTime']), 'rated_power':str(battDict['invPowerRatingkW']) + ' kW', 'inverter_efficiency':str(battDict['invEfficiency']), 'charge_on_threshold':str(battDict['invChargeOnThresholdkW']) + ' kW', 'charge_off_threshold': str(battDict['invChargeOffThresholdkW']) + ' kW', 'discharge_on_threshold':str(battDict['invDischargeOnThresholdkW']) + ' kW', 'discharge_off_threshold':str(battDict['invDischargeOffThresholdkW']) + ' kW', 'max_discharge_rate':str(battDict['invMaxChargeRatekW']) + ' kW', 'max_charge_rate':str(battDict['invMaxDischargeRatekW']) + ' kW'} batteryName = str(battDict['battNode'])+'_battery' feederTree[str(maxKey + 3)]={'name': batteryName, 'parent': invName, 'object': 'battery', 'use_internal_battery_model':'true', 'battery_type': str(battDict['battType']), 'battery_capacity': str(battDict['battEnergyRatingkWh']) + ' kWh', 'base_efficiency': str(battDict['battEfficiency']), 'state_of_charge': str(battDict['battSOCpu']), 'generator_mode':'SUPPLY_DRIVEN'} return feederTree
def omfCalibrate(workDir, feederPath, scadaPath): '''calibrates a feeder and saves the calibrated tree at a location''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) scadaSubPower = _processScadaData(workDir,scadaPath) # Force FBS powerflow, because NR fails a lot. for key in tree: if tree[key].get("module","").lower() == "powerflow": tree[key] = {"module":"powerflow","solver_method":"FBS"} # Attach player. classOb = {"class":"player", "variable_names":["value"], "variable_types":["double"]} playerOb = {"object":"player", "property":"value", "name":"scadaLoads", "file":"subScada.player", "loop":"0"} maxKey = feeder.getMaxKey(tree) tree[maxKey+1] = classOb tree[maxKey+2] = playerOb # Make loads reference player. loadTemplate = {"object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3"} for key in tree: ob = tree[key] if ob.get("object","") == "triplex_node" and ob.get("power_12","") != "": newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") oldPow = ob.get("power_12","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb # Search for the substation regulator and attach a recorder there. for key in tree: if tree[key].get('bustype','').lower() == 'swing': swingName = tree[key].get('name') for key in tree: if tree[key].get('object','') == 'regulator' and tree[key].get('from','') == swingName: regIndex = key SUB_REG_NAME = tree[key]['name'] recOb = {"object": "recorder", "parent": SUB_REG_NAME, "property": "power_in.real,power_in.imag", "file": "caliSub.csv", "interval": "900"} tree[maxKey + 3] = recOb HOURS = 100 feeder.adjustTime(tree, HOURS, "hours", "2011-01-01") # Run Gridlabd. output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=workDir) # Calculate scaling constant. outRealPow = output["caliSub.csv"]["power_in.real"] outImagPower = output["caliSub.csv"]["power_in.imag"] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPow, outImagPower)] # HACK: ignore first time step in output and input because GLD sometimes breaks the first step. SCAL_CONST = sum(scadaSubPower[1:HOURS])/sum(outAppPowerKw[1:HOURS]) # Rewrite the subScada.player file so all the power values are multiplied by the SCAL_CONSTANT. newPlayData = [] with open(pJoin(workDir, "subScada.player"), "r") as playerFile: for line in playerFile: (key,val) = line.split(',') newPlayData.append(str(key) + ',' + str(float(val)*SCAL_CONST) + "\n") with open(pJoin(workDir, "subScadaCalibrated.player"), "w") as playerFile: for row in newPlayData: playerFile.write(row) # Test by running a glm with subScadaCalibrated.player and caliSub.csv2. tree[maxKey+2]["file"] = "subScadaCalibrated.player" tree[maxKey + 3]["file"] = "caliSubCheck.csv" secondOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=workDir) plt.plot(outAppPowerKw[1:HOURS], label="initialGuess") plt.plot(scadaSubPower[1:HOURS], label="scadaSubPower") secondAppKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(secondOutput["caliSubCheck.csv"]["power_in.real"], secondOutput["caliSubCheck.csv"]["power_in.imag"])] plt.plot(secondAppKw[1:HOURS], label="finalGuess") plt.legend(loc=3) plt.savefig(pJoin(workDir,"caliCheckPlot.png")) # Write the final output. with open(pJoin(workDir,"calibratedFeeder.json"),"w") as outJson: playerString = open(pJoin(workDir,"subScadaCalibrated.player")).read() feederJson["attachments"]["subScadaCalibrated.player"] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return
def omfCalibrate(workDir, feederPath, scadaPath): '''calibrates a feeder and saves the calibrated tree at a location''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) scadaSubPower = _processScadaData(workDir, scadaPath) # Force FBS powerflow, because NR fails a lot. for key in tree: if tree[key].get("module", "").lower() == "powerflow": tree[key] = {"module": "powerflow", "solver_method": "FBS"} # Attach player. classOb = { "class": "player", "variable_names": ["value"], "variable_types": ["double"] } playerOb = { "object": "player", "property": "value", "name": "scadaLoads", "file": "subScada.player", "loop": "0" } maxKey = feeder.getMaxKey(tree) tree[maxKey + 1] = classOb tree[maxKey + 2] = playerOb # Make loads reference player. loadTemplate = { "object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3" } for key in tree: ob = tree[key] if ob.get("object", "") == "triplex_node" and ob.get("power_12", "") != "": newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage", "") newOb["latitude"] = ob.get("latitude", "0") newOb["longitude"] = ob.get("longitude", "0") oldPow = ob.get("power_12", "").replace("j", "d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb # Search for the substation regulator and attach a recorder there. for key in tree: if tree[key].get('bustype', '').lower() == 'swing': swingName = tree[key].get('name') for key in tree: if tree[key].get('object', '') == 'regulator' and tree[key].get( 'from', '') == swingName: regIndex = key SUB_REG_NAME = tree[key]['name'] recOb = { "object": "recorder", "parent": SUB_REG_NAME, "property": "power_in.real,power_in.imag", "file": "caliSub.csv", "interval": "900" } tree[maxKey + 3] = recOb HOURS = 100 feeder.adjustTime(tree, HOURS, "hours", "2011-01-01") # Run Gridlabd. output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=workDir) # Calculate scaling constant. outRealPow = output["caliSub.csv"]["power_in.real"] outImagPower = output["caliSub.csv"]["power_in.imag"] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5 / 1000 for x in zip(outRealPow, outImagPower)] # HACK: ignore first time step in output and input because GLD sometimes breaks the first step. SCAL_CONST = sum(scadaSubPower[1:HOURS]) / sum(outAppPowerKw[1:HOURS]) # Rewrite the subScada.player file so all the power values are multiplied by the SCAL_CONSTANT. newPlayData = [] with open(pJoin(workDir, "subScada.player"), "r") as playerFile: for line in playerFile: (key, val) = line.split(',') newPlayData.append( str(key) + ',' + str(float(val) * SCAL_CONST) + "\n") with open(pJoin(workDir, "subScadaCalibrated.player"), "w") as playerFile: for row in newPlayData: playerFile.write(row) # Test by running a glm with subScadaCalibrated.player and caliSub.csv2. tree[maxKey + 2]["file"] = "subScadaCalibrated.player" tree[maxKey + 3]["file"] = "caliSubCheck.csv" secondOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=workDir) plt.plot(outAppPowerKw[1:HOURS], label="initialGuess") plt.plot(scadaSubPower[1:HOURS], label="scadaSubPower") secondAppKw = [ (x[0]**2 + x[1]**2)**0.5 / 1000 for x in zip(secondOutput["caliSubCheck.csv"]["power_in.real"], secondOutput["caliSubCheck.csv"]["power_in.imag"]) ] plt.plot(secondAppKw[1:HOURS], label="finalGuess") plt.legend(loc=3) plt.savefig(pJoin(workDir, "caliCheckPlot.png")) # Write the final output. with open(pJoin(workDir, "calibratedFeeder.json"), "w") as outJson: playerString = open(pJoin(workDir, "subScadaCalibrated.player")).read() feederJson["attachments"]["subScadaCalibrated.player"] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return
def attachVolts(workDir, feederPath, voltVectorA, voltVectorB, voltVectorC, simStartDate, simLength, simLengthUnits): '''read voltage vectors of 3 different phases, run gridlabd, and attach output to the feeder.''' try: timeStamp = [simStartDate['Date']] for x in range (1, 8760): timeStamp.append(timeStamp[x-1] + dt.timedelta(hours=1)) firstDateTime = timeStamp[1] with open(pJoin(pJoin(workDir,"gridlabD"),"phaseAVoltage.player"),"w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f"%float(voltVectorA[x]))+"+0j" line = timestamp.strftime("%Y-%m-%d %H:%M:%S") + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(pJoin(pJoin(workDir,"gridlabD"),"phaseBVoltage.player"),"w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f"%float(voltVectorB[x]))+"-"+str("%0.4f"%float(random.uniform(6449,6460)))+"j" line = timestamp.strftime("%Y-%m-%d %H:%M:%S") + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(pJoin(pJoin(workDir,"gridlabD"),"phaseCVoltage.player"),"w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f"%float(voltVectorC[x]))+"+"+str("%0.4f"%float(random.uniform(6449,6460)))+"j" line = timestamp.strftime("%Y-%m-%d %H:%M:%S") + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) # Find swingNode name. for key in tree: if tree[key].get('bustype','').lower() == 'swing': swingName = tree[key].get('name') # Attach player. classOb = {'omftype':'class player','argument':'{double value;}'} voltageObA = {"object":"player", "property":"voltage_A", "file":"phaseAVoltage.player", "loop":"0", "parent":swingName} voltageObB = {"object":"player", "property":"voltage_B", "file":"phaseBVoltage.player", "loop":"0", "parent":swingName} voltageObC = {"object":"player", "property":"voltage_C", "file":"phaseCVoltage.player", "loop":"0", "parent":swingName} maxKey = feeder.getMaxKey(tree) voltplayerKeyA = maxKey + 2 voltplayerKeyB = maxKey + 3 voltplayerKeyC = maxKey + 4 tree[maxKey+1] = classOb tree[voltplayerKeyA] = voltageObA tree[voltplayerKeyB] = voltageObB tree[voltplayerKeyC] = voltageObC # Adjust time and run output. feeder.adjustTime(tree, simLength, simLengthUnits, firstDateTime.strftime("%Y-%m-%d %H:%M:%S")) output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin(workDir,"gridlabD")) # Write the output. with open(pJoin(workDir,"calibratedFeeder.omd"),"w") as outJson: playerStringA = open(pJoin(pJoin(workDir,"gridlabD"),"phaseAVoltage.player")).read() playerStringB = open(pJoin(pJoin(workDir,"gridlabD"),"phaseBVoltage.player")).read() playerStringC = open(pJoin(pJoin(workDir,"gridlabD"),"phaseCVoltage.player")).read() feederJson["attachments"]["phaseAVoltage.player"] = playerStringA feederJson["attachments"]["phaseBVoltage.player"] = playerStringB feederJson["attachments"]["phaseCVoltage.player"] = playerStringC feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return pJoin(workDir,"calibratedFeeder.omd"), True except: print "Failed to run gridlabD with voltage players." return "", False
def omfCalibrate(workDir, feederPath, scadaPath, simStartDate, simLength, simLengthUnits, solver="FBS", calibrateError=(0.05,5), trim=5): '''calibrates a feeder and saves the calibrated tree at a location. Note: feeders with cap banks should be calibrated with cap banks OPEN. We have seen cap banks throw off calibration.''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) simLength = simLength + trim # Process scada data. scadaSubPower = _processScadaData(pJoin(workDir,"gridlabD"),scadaPath, simStartDate, simLengthUnits) # Load specified solver. for key in tree: if tree[key].get("module","").lower() == "powerflow": tree[key] = {"module":"powerflow","solver_method":solver} # Attach player. classOb = {'omftype':'class player','argument':'{double value;}'} playerOb = {"object":"player", "property":"value", "name":"scadaLoads", "file":"subScada.player", "loop":"0"} maxKey = feeder.getMaxKey(tree) playerKey = maxKey + 2 tree[maxKey+1] = classOb tree[playerKey] = playerOb # Make loads reference player. loadTemplate = {"object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3"} loadTemplateR = {"object": "load", "impedance_pf_A": "0.98", "impedance_pf_B": "0.98", "impedance_pf_C": "0.98", "power_pf_A": "0.90", "power_pf_B": "0.90", "power_pf_C": "0.90", "impedance_fraction_A": "0.7", "impedance_fraction_B": "0.7", "impedance_fraction_C": "0.7", "power_fraction_A": "0.3", "power_fraction_B": "0.3", "power_fraction_C": "0.3"} for key in tree: ob = tree[key] if ob.get("object","") == "triplex_node" and ob.get("power_12","") != "": # Add to triplex_nodes. newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") oldPow = ob.get("power_12","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb elif ob.get("object","") == "load": # Add to residential_loads too. newOb = dict(loadTemplateR) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["load_class"] = ob.get("load_class", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") try: oldPow = ob.get("constant_power_A","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_A"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_B","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_B"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_C","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_C"] = "scadaLoads.value*" + str(pythagPower) except: pass tree[key] = newOb # Convert swing bus to a meter. for key in tree: if tree[key].get('bustype','').lower() == 'swing' and tree[key].get('object','') != 'meter': swingName = tree[key].get('name') regIndex = key tree[key]['object'] = 'meter' # Search for the substation meter and attach a recorder there. for key in tree: if tree[key].get('bustype','').lower() == 'swing': swingName = tree[key].get('name') recOb = {"object": "recorder", "parent": swingName, "property": "measured_real_power,measured_reactive_power,measured_power", "file": "caliSub.csv", "interval": "900"} outputRecorderKey = maxKey + 3 tree[outputRecorderKey] = recOb feeder.adjustTime(tree, simLength, simLengthUnits, simStartDate['Date'].strftime("%Y-%m-%d %H:%M:%S")) # Run Gridlabd, calculate scaling constant. def runPowerflowIter(tree,scadaSubPower): '''Runs powerflow once, then iterates.''' # Run initial powerflow to get power. print "Starting calibration." print "Goal of calibration: Error: %s, Iterations: <%s, trim: %s"%(calibrateError[0], calibrateError[1], trim) output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin(workDir,"gridlabD")) outRealPow = output["caliSub.csv"]["measured_real_power"][trim:simLength] outImagPower = output["caliSub.csv"]["measured_reactive_power"][trim:simLength] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPow, outImagPower)] lastFile = "subScada.player" nextFile = "subScadaCalibrated.player" nextPower = outAppPowerKw error = (sum(outRealPow)/1000-sum(scadaSubPower))/sum(scadaSubPower) iteration = 1 print "First error:", error while abs(error)>calibrateError[0] and iteration<calibrateError[1]: # Run calibration and iterate up to 5 times. SCAL_CONST = sum(scadaSubPower)/sum(nextPower) print "Calibrating & running again... Error: %s, Iteration: %s, SCAL_CONST: %s"%(str(round(abs(error*100),2)), str(iteration), round(SCAL_CONST,2)) newPlayData = [] with open(pJoin(pJoin(workDir,"gridlabD"), lastFile), "r") as playerFile: for line in playerFile: (key,val) = line.split(',') newPlayData.append(str(key) + ',' + str(float(val)*SCAL_CONST) + "\n") with open(pJoin(pJoin(workDir,"gridlabD"), nextFile), "w") as playerFile: for row in newPlayData: playerFile.write(row) tree[playerKey]["file"] = nextFile tree[outputRecorderKey]["file"] = "caliSubCheck.csv" nextOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin(workDir,"gridlabD")) outRealPowIter = nextOutput["caliSubCheck.csv"]["measured_real_power"][trim:simLength] outImagPowerIter = nextOutput["caliSubCheck.csv"]["measured_reactive_power"][trim:simLength] nextAppKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPowIter, outImagPowerIter)] lastFile = nextFile nextFile = "subScadaCalibrated"+str(iteration)+".player" nextPower = nextAppKw # Compute error and iterate. error = (sum(outRealPowIter)/1000-sum(scadaSubPower))/sum(scadaSubPower) iteration+=1 else: if iteration==1: outRealPowIter = outRealPow print "Calibration done: Error: %s, Iteration: %s, SCAL_CONST: %s"%(str(round(abs(error*100),2)), str(iteration), round(SCAL_CONST,2)) return outRealPow, outRealPowIter, lastFile, iteration outRealPow, outRealPowIter, lastFile, iteration = runPowerflowIter(tree,scadaSubPower[trim:simLength]) caliPowVectors = [[float(element) for element in scadaSubPower[trim:simLength]], [float(element)/1000 for element in outRealPow], [float(element)/1000 for element in outRealPowIter]] labels = ["scadaSubPower","initialGuess","finalGuess"] colors = ['red','lightblue','blue'] chartData = {"Title":"Substation Calibration Check (Iterated "+str(iteration+1)+"X)", "fileName":"caliCheckPlot", "colors":colors,"labels":labels, "timeZone":simStartDate['timeZone']} print "Len:", len(caliPowVectors[0]), len(caliPowVectors[1]), len(caliPowVectors[2]) plotLine(workDir, caliPowVectors, chartData, simStartDate['Date']+dt.timedelta(hours=trim), simLengthUnits) # Write the final output. with open(pJoin(workDir,"calibratedFeeder.omd"),"w") as outJson: playerString = open(pJoin(pJoin(workDir,"gridlabD"),lastFile)).read() feederJson["attachments"][lastFile] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return
from matplotlib import pyplot as plt import sys, json sys.path.append('../..') import feeder, weather from solvers.gridlabd import runInFilesystem # Make a weather file. weather.makeClimateCsv('2014-01-01', '2014-01-11', 'MSP', './mspWeather.csv') # Add stuff to the feeder. with open('./Orville Tree Pond Calibrated.json', 'r') as inFile: myFeed = json.load(inFile) myTree = myFeed['tree'] # HACK:doesn't matter if we import modules twice, so just stick everything on the end. oldMax = feeder.getMaxKey(myTree) myTree[oldMax + 1] = {'omftype': 'module', 'argument': 'tape'} myTree[oldMax + 2] = {'omftype': 'module', 'argument': 'climate'} myTree[oldMax + 3] = { 'object': 'csv_reader', 'name': 'weatherReader', 'filename': 'mspWeather.csv' } myTree[oldMax + 4] = { 'object': 'climate', 'name': 'exampleClimate', 'tmyfile': 'mspWeather.csv', 'reader': 'weatherReader' } # Add a few panels too to test. myTree[oldMax + 5] = {
def attachVolts(workDir, feederPath, voltVectorA, voltVectorB, voltVectorC, simStartDate, simLength, simLengthUnits): '''read voltage vectors of 3 different phases, run gridlabd, and attach output to the feeder.''' try: timeStamp = [simStartDate['Date']] for x in range(1, 8760): timeStamp.append(timeStamp[x - 1] + dt.timedelta(hours=1)) firstDateTime = timeStamp[1] with open(pJoin(pJoin(workDir, "gridlabD"), "phaseAVoltage.player"), "w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f" % float(voltVectorA[x])) + "+0j" line = timestamp.strftime( "%Y-%m-%d %H:%M:%S" ) + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(pJoin(pJoin(workDir, "gridlabD"), "phaseBVoltage.player"), "w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f" % float(voltVectorB[x])) + "-" + str( "%0.4f" % float(random.uniform(6449, 6460))) + "j" line = timestamp.strftime( "%Y-%m-%d %H:%M:%S" ) + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(pJoin(pJoin(workDir, "gridlabD"), "phaseCVoltage.player"), "w") as voltFile: for x in range(0, 8760): timestamp = timeStamp[x] voltage = str("%0.2f" % float(voltVectorC[x])) + "+" + str( "%0.4f" % float(random.uniform(6449, 6460))) + "j" line = timestamp.strftime( "%Y-%m-%d %H:%M:%S" ) + " " + simStartDate['timeZone'] + "," + str(voltage) + "\n" voltFile.write(line) with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) # Find swingNode name. for key in tree: if tree[key].get('bustype', '').lower() == 'swing': swingName = tree[key].get('name') # Attach player. classOb = {'omftype': 'class player', 'argument': '{double value;}'} voltageObA = { "object": "player", "property": "voltage_A", "file": "phaseAVoltage.player", "loop": "0", "parent": swingName } voltageObB = { "object": "player", "property": "voltage_B", "file": "phaseBVoltage.player", "loop": "0", "parent": swingName } voltageObC = { "object": "player", "property": "voltage_C", "file": "phaseCVoltage.player", "loop": "0", "parent": swingName } maxKey = feeder.getMaxKey(tree) voltplayerKeyA = maxKey + 2 voltplayerKeyB = maxKey + 3 voltplayerKeyC = maxKey + 4 tree[maxKey + 1] = classOb tree[voltplayerKeyA] = voltageObA tree[voltplayerKeyB] = voltageObB tree[voltplayerKeyC] = voltageObC # Adjust time and run output. feeder.adjustTime(tree, simLength, simLengthUnits, firstDateTime.strftime("%Y-%m-%d %H:%M:%S")) output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin(workDir, "gridlabD")) # Write the output. with open(pJoin(workDir, "calibratedFeeder.omd"), "w") as outJson: playerStringA = open( pJoin(pJoin(workDir, "gridlabD"), "phaseAVoltage.player")).read() playerStringB = open( pJoin(pJoin(workDir, "gridlabD"), "phaseBVoltage.player")).read() playerStringC = open( pJoin(pJoin(workDir, "gridlabD"), "phaseCVoltage.player")).read() feederJson["attachments"]["phaseAVoltage.player"] = playerStringA feederJson["attachments"]["phaseBVoltage.player"] = playerStringB feederJson["attachments"]["phaseCVoltage.player"] = playerStringC feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return pJoin(workDir, "calibratedFeeder.omd"), True except: print "Failed to run gridlabD with voltage players." return "", False
def omfCalibrate(workDir, feederPath, scadaPath, simStartDate, simLength, simLengthUnits, solver="FBS", calibrateError=(0.05, 5), trim=5): '''calibrates a feeder and saves the calibrated tree at a location. Note: feeders with cap banks should be calibrated with cap banks OPEN. We have seen cap banks throw off calibration.''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) simLength = simLength + trim # Process scada data. scadaSubPower = _processScadaData(pJoin(workDir, "gridlabD"), scadaPath, simStartDate, simLengthUnits) # Load specified solver. for key in tree: if tree[key].get("module", "").lower() == "powerflow": tree[key] = {"module": "powerflow", "solver_method": solver} # Attach player. classOb = {'omftype': 'class player', 'argument': '{double value;}'} playerOb = { "object": "player", "property": "value", "name": "scadaLoads", "file": "subScada.player", "loop": "0" } maxKey = feeder.getMaxKey(tree) playerKey = maxKey + 2 tree[maxKey + 1] = classOb tree[playerKey] = playerOb # Make loads reference player. loadTemplate = { "object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3" } loadTemplateR = { "object": "load", "impedance_pf_A": "0.98", "impedance_pf_B": "0.98", "impedance_pf_C": "0.98", "power_pf_A": "0.90", "power_pf_B": "0.90", "power_pf_C": "0.90", "impedance_fraction_A": "0.7", "impedance_fraction_B": "0.7", "impedance_fraction_C": "0.7", "power_fraction_A": "0.3", "power_fraction_B": "0.3", "power_fraction_C": "0.3" } for key in tree: ob = tree[key] if ob.get("object", "") in ("triplex_node", "triplex_load") and ( ob.get("power_12") or ob.get("base_power_12")): # Add to triplex_nodes. newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage", "") newOb["latitude"] = ob.get("latitude", "0") newOb["longitude"] = ob.get("longitude", "0") oldPow = ob.get("power_12", "").replace("j", "d") if not oldPow: oldPow = ob.get("base_power_12") if "scadaloads.value*" in oldPow: oldPow = oldPow[17:] pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb elif ob.get("object", "") == "load": # Add to residential_loads too. newOb = dict(loadTemplateR) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["load_class"] = ob.get("load_class", "") newOb["nominal_voltage"] = ob.get("nominal_voltage", "") newOb["latitude"] = ob.get("latitude", "0") newOb["longitude"] = ob.get("longitude", "0") try: oldPow = ob.get("constant_power_A", "").replace("j", "d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_A"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_B", "").replace("j", "d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_B"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_C", "").replace("j", "d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_C"] = "scadaLoads.value*" + str(pythagPower) except: pass tree[key] = newOb # Convert swing bus to a meter. for key in tree: if tree[key].get('bustype', '').lower() == 'swing' and tree[key].get( 'object', '') != 'meter': swingName = tree[key].get('name') regIndex = key tree[key]['object'] = 'meter' # Search for the substation meter and attach a recorder there. for key in tree: if tree[key].get('bustype', '').lower() == 'swing': swingName = tree[key].get('name') recOb = { "object": "recorder", "parent": swingName, "property": "measured_real_power,measured_reactive_power,measured_power", "file": "caliSub.csv", "interval": "3600" } outputRecorderKey = maxKey + 3 tree[outputRecorderKey] = recOb feeder.adjustTime(tree, simLength, simLengthUnits, simStartDate['Date'].strftime("%Y-%m-%d %H:%M:%S")) # Run Gridlabd, calculate scaling constant. def runPowerflowIter(tree, scadaSubPower): '''Runs powerflow once, then iterates.''' # Run initial powerflow to get power. print "Starting calibration." print "Goal of calibration: Error: %s, Iterations: <%s, trim: %s" % ( calibrateError[0], calibrateError[1], trim) output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin(workDir, "gridlabD")) outRealPow = output["caliSub.csv"]["measured_real_power"][ trim:simLength] outImagPower = output["caliSub.csv"]["measured_reactive_power"][ trim:simLength] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5 / 1000 for x in zip(outRealPow, outImagPower)] lastFile = "subScada.player" nextFile = "subScadaCalibrated.player" nextPower = outAppPowerKw error = (sum(outRealPow) / 1000 - sum(scadaSubPower)) / sum(scadaSubPower) iteration = 1 print "First error:", error while abs(error) > calibrateError[0] and iteration < calibrateError[1]: # Run calibration and iterate up to 5 times. SCAL_CONST = sum(scadaSubPower) / sum(nextPower) print "Calibrating & running again... Error: %s, Iteration: %s, SCAL_CONST: %s" % ( str(round(abs(error * 100), 6)), str(iteration), round(SCAL_CONST, 6)) newPlayData = [] with open(pJoin(pJoin(workDir, "gridlabD"), lastFile), "r") as playerFile: for line in playerFile: (key, val) = line.split(',') newPlayData.append( str(key) + ',' + str(float(val) * SCAL_CONST) + "\n") with open(pJoin(pJoin(workDir, "gridlabD"), nextFile), "w") as playerFile: for row in newPlayData: playerFile.write(row) tree[playerKey]["file"] = nextFile tree[outputRecorderKey]["file"] = "caliSubCheck.csv" nextOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=pJoin( workDir, "gridlabD")) outRealPowIter = nextOutput["caliSubCheck.csv"][ "measured_real_power"][trim:simLength] outImagPowerIter = nextOutput["caliSubCheck.csv"][ "measured_reactive_power"][trim:simLength] nextAppKw = [(x[0]**2 + x[1]**2)**0.5 / 1000 for x in zip(outRealPowIter, outImagPowerIter)] lastFile = nextFile nextFile = "subScadaCalibrated" + str(iteration) + ".player" nextPower = nextAppKw # Compute error and iterate. error = (sum(outRealPowIter) / 1000 - sum(scadaSubPower)) / sum(scadaSubPower) iteration += 1 else: if iteration == 1: outRealPowIter = outRealPow SCAL_CONST = 1.0 print "Calibration done: Error: %s, Iteration: %s, SCAL_CONST: %s" % ( str(round(abs(error * 100), 2)), str(iteration), round(SCAL_CONST, 2)) return outRealPow, outRealPowIter, lastFile, iteration outRealPow, outRealPowIter, lastFile, iteration = runPowerflowIter( tree, scadaSubPower[trim:simLength]) caliPowVectors = [[ float(element) for element in scadaSubPower[trim:simLength] ], [float(element) / 1000 for element in outRealPow], [float(element) / 1000 for element in outRealPowIter]] labels = ["scadaSubPower", "initialGuess", "finalGuess"] colors = ['red', 'lightblue', 'blue'] chartData = { "Title": "Substation Calibration Check (Iterated " + str(iteration + 1) + "X)", "fileName": "caliCheckPlot", "colors": colors, "labels": labels, "timeZone": simStartDate['timeZone'] } # Trimming vectors to make them all the same length as the smallest vector minCaliPowVecLen = min(len(caliPowVectors[0]), len(caliPowVectors[1]), len(caliPowVectors[2])) caliPowVectors[0] = caliPowVectors[0][:minCaliPowVecLen] caliPowVectors[1] = caliPowVectors[1][:minCaliPowVecLen] caliPowVectors[2] = caliPowVectors[2][:minCaliPowVecLen] print "Len:", len(caliPowVectors[0]), len(caliPowVectors[1]), len( caliPowVectors[2]) plotLine(workDir, caliPowVectors, chartData, simStartDate['Date'] + dt.timedelta(hours=trim), simLengthUnits) # Write the final output. with open(pJoin(workDir, "calibratedFeeder.omd"), "w") as outJson: playerString = open(pJoin(pJoin(workDir, "gridlabD"), lastFile)).read() feederJson["attachments"][lastFile] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return
def heavyProcessing(modelDir, inputDict): ''' Run the model in its directory. WARNING: GRIDLAB CAN TAKE HOURS TO COMPLETE. ''' print "STARTING TO RUN", modelDir beginTime = datetime.datetime.now() # Get feeder name and data in. try: os.mkdir(pJoin(modelDir, 'gldContainer')) except: pass feederDir, feederName = inputDict["feederName"].split("___") shutil.copy( pJoin(__metaModel__._omfDir, "data", "Feeder", feederDir, feederName + ".json"), pJoin(modelDir, "feeder.json")) shutil.copy( pJoin(__metaModel__._omfDir, "data", "Climate", inputDict["climateName"] + ".tmy2"), pJoin(modelDir, "gldContainer", "climate.tmy2")) try: startTime = datetime.datetime.now() feederJson = json.load(open(pJoin(modelDir, "feeder.json"))) tree = feederJson["tree"] # Set up GLM with correct time and recorders: feeder.attachRecorders(tree, "Regulator", "object", "regulator") feeder.attachRecorders(tree, "Capacitor", "object", "capacitor") feeder.attachRecorders(tree, "Inverter", "object", "inverter") feeder.attachRecorders(tree, "Windmill", "object", "windturb_dg") feeder.attachRecorders(tree, "CollectorVoltage", None, None) feeder.attachRecorders(tree, "Climate", "object", "climate") feeder.attachRecorders(tree, "OverheadLosses", None, None) feeder.attachRecorders(tree, "UndergroundLosses", None, None) feeder.attachRecorders(tree, "TriplexLosses", None, None) feeder.attachRecorders(tree, "TransformerLosses", None, None) feeder.groupSwingKids(tree) # Attach recorders for system voltage map: stub = { 'object': 'group_recorder', 'group': '"class=node"', 'property': 'voltage_A', 'interval': 3600, 'file': 'aVoltDump.csv' } for phase in ['A', 'B', 'C']: copyStub = dict(stub) copyStub['property'] = 'voltage_' + phase copyStub['file'] = phase.lower() + 'VoltDump.csv' tree[feeder.getMaxKey(tree) + 1] = copyStub feeder.adjustTime(tree=tree, simLength=float(inputDict["simLength"]), simLengthUnits=inputDict["simLengthUnits"], simStartDate=inputDict["simStartDate"]) # RUN GRIDLABD IN FILESYSTEM (EXPENSIVE!) rawOut = gridlabd.runInFilesystem( tree, attachments=feederJson["attachments"], keepFiles=True, workDir=pJoin(modelDir, 'gldContainer')) cleanOut = {} # Std Err and Std Out cleanOut['stderr'] = rawOut['stderr'] cleanOut['stdout'] = rawOut['stdout'] # Time Stamps for key in rawOut: if '# timestamp' in rawOut[key]: cleanOut['timeStamps'] = rawOut[key]['# timestamp'] break elif '# property.. timestamp' in rawOut[key]: cleanOut['timeStamps'] = rawOut[key]['# property.. timestamp'] else: cleanOut['timeStamps'] = [] # Day/Month Aggregation Setup: stamps = cleanOut.get('timeStamps', []) level = inputDict.get('simLengthUnits', 'hours') # Climate for key in rawOut: if key.startswith('Climate_') and key.endswith('.csv'): cleanOut['climate'] = {} cleanOut['climate']['Rain Fall (in/h)'] = hdmAgg( rawOut[key].get('rainfall'), sum, level) cleanOut['climate']['Wind Speed (m/s)'] = hdmAgg( rawOut[key].get('wind_speed'), avg, level) cleanOut['climate']['Temperature (F)'] = hdmAgg( rawOut[key].get('temperature'), max, level) cleanOut['climate']['Snow Depth (in)'] = hdmAgg( rawOut[key].get('snowdepth'), max, level) cleanOut['climate']['Direct Normal (W/sf)'] = hdmAgg( rawOut[key].get('solar_direct'), sum, level) #cleanOut['climate']['Global Horizontal (W/sf)'] = hdmAgg(rawOut[key].get('solar_global'), sum, level) climateWbySFList = hdmAgg(rawOut[key].get('solar_global'), sum, level) #converting W/sf to W/sm climateWbySMList = [x * 10.76392 for x in climateWbySFList] cleanOut['climate'][ 'Global Horizontal (W/sm)'] = climateWbySMList # Voltage Band if 'VoltageJiggle.csv' in rawOut: cleanOut['allMeterVoltages'] = {} cleanOut['allMeterVoltages']['Min'] = hdmAgg([ float(i / 2) for i in rawOut['VoltageJiggle.csv']['min(voltage_12.mag)'] ], min, level) cleanOut['allMeterVoltages']['Mean'] = hdmAgg([ float(i / 2) for i in rawOut['VoltageJiggle.csv']['mean(voltage_12.mag)'] ], avg, level) cleanOut['allMeterVoltages']['StdDev'] = hdmAgg([ float(i / 2) for i in rawOut['VoltageJiggle.csv']['std(voltage_12.mag)'] ], avg, level) cleanOut['allMeterVoltages']['Max'] = hdmAgg([ float(i / 2) for i in rawOut['VoltageJiggle.csv']['max(voltage_12.mag)'] ], max, level) # Power Consumption cleanOut['Consumption'] = {} # Set default value to be 0, avoiding missing value when computing Loads cleanOut['Consumption']['Power'] = [0] * int(inputDict["simLength"]) cleanOut['Consumption']['Losses'] = [0] * int(inputDict["simLength"]) cleanOut['Consumption']['DG'] = [0] * int(inputDict["simLength"]) for key in rawOut: if key.startswith('SwingKids_') and key.endswith('.csv'): oneSwingPower = hdmAgg( vecPyth(rawOut[key]['sum(power_in.real)'], rawOut[key]['sum(power_in.imag)']), avg, level) if 'Power' not in cleanOut['Consumption']: cleanOut['Consumption']['Power'] = oneSwingPower else: cleanOut['Consumption']['Power'] = vecSum( oneSwingPower, cleanOut['Consumption']['Power']) elif key.startswith('Inverter_') and key.endswith('.csv'): realA = rawOut[key]['power_A.real'] realB = rawOut[key]['power_B.real'] realC = rawOut[key]['power_C.real'] imagA = rawOut[key]['power_A.imag'] imagB = rawOut[key]['power_B.imag'] imagC = rawOut[key]['power_C.imag'] oneDgPower = hdmAgg( vecSum(vecPyth(realA, imagA), vecPyth(realB, imagB), vecPyth(realC, imagC)), avg, level) if 'DG' not in cleanOut['Consumption']: cleanOut['Consumption']['DG'] = oneDgPower else: cleanOut['Consumption']['DG'] = vecSum( oneDgPower, cleanOut['Consumption']['DG']) elif key.startswith('Windmill_') and key.endswith('.csv'): vrA = rawOut[key]['voltage_A.real'] vrB = rawOut[key]['voltage_B.real'] vrC = rawOut[key]['voltage_C.real'] viA = rawOut[key]['voltage_A.imag'] viB = rawOut[key]['voltage_B.imag'] viC = rawOut[key]['voltage_C.imag'] crB = rawOut[key]['current_B.real'] crA = rawOut[key]['current_A.real'] crC = rawOut[key]['current_C.real'] ciA = rawOut[key]['current_A.imag'] ciB = rawOut[key]['current_B.imag'] ciC = rawOut[key]['current_C.imag'] powerA = vecProd(vecPyth(vrA, viA), vecPyth(crA, ciA)) powerB = vecProd(vecPyth(vrB, viB), vecPyth(crB, ciB)) powerC = vecProd(vecPyth(vrC, viC), vecPyth(crC, ciC)) oneDgPower = hdmAgg(vecSum(powerA, powerB, powerC), avg, level) if 'DG' not in cleanOut['Consumption']: cleanOut['Consumption']['DG'] = oneDgPower else: cleanOut['Consumption']['DG'] = vecSum( oneDgPower, cleanOut['Consumption']['DG']) elif key in [ 'OverheadLosses.csv', 'UndergroundLosses.csv', 'TriplexLosses.csv', 'TransformerLosses.csv' ]: realA = rawOut[key]['sum(power_losses_A.real)'] imagA = rawOut[key]['sum(power_losses_A.imag)'] realB = rawOut[key]['sum(power_losses_B.real)'] imagB = rawOut[key]['sum(power_losses_B.imag)'] realC = rawOut[key]['sum(power_losses_C.real)'] imagC = rawOut[key]['sum(power_losses_C.imag)'] oneLoss = hdmAgg( vecSum(vecPyth(realA, imagA), vecPyth(realB, imagB), vecPyth(realC, imagC)), avg, level) if 'Losses' not in cleanOut['Consumption']: cleanOut['Consumption']['Losses'] = oneLoss else: cleanOut['Consumption']['Losses'] = vecSum( oneLoss, cleanOut['Consumption']['Losses']) elif key.startswith('Regulator_') and key.endswith('.csv'): #split function to strip off .csv from filename and user rest of the file name as key. for example- Regulator_VR10.csv -> key would be Regulator_VR10 regName = "" regName = key newkey = regName.split(".")[0] cleanOut[newkey] = {} cleanOut[newkey]['RegTapA'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapB'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapC'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapA'] = rawOut[key]['tap_A'] cleanOut[newkey]['RegTapB'] = rawOut[key]['tap_B'] cleanOut[newkey]['RegTapC'] = rawOut[key]['tap_C'] cleanOut[newkey]['RegPhases'] = rawOut[key]['phases'][0] elif key.startswith('Capacitor_') and key.endswith('.csv'): capName = "" capName = key newkey = capName.split(".")[0] cleanOut[newkey] = {} cleanOut[newkey]['Cap1A'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1B'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1C'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1A'] = rawOut[key]['switchA'] cleanOut[newkey]['Cap1B'] = rawOut[key]['switchB'] cleanOut[newkey]['Cap1C'] = rawOut[key]['switchC'] cleanOut[newkey]['CapPhases'] = rawOut[key]['phases'][0] # What percentage of our keys have lat lon data? latKeys = [ tree[key]['latitude'] for key in tree if 'latitude' in tree[key] ] latPerc = 1.0 * len(latKeys) / len(tree) if latPerc < 0.25: doNeato = True else: doNeato = False # Generate the frames for the system voltage map time traveling chart. genTime = generateVoltChart(tree, rawOut, modelDir, neatoLayout=doNeato) cleanOut['genTime'] = genTime # Aggregate up the timestamps: if level == 'days': cleanOut['timeStamps'] = aggSeries(stamps, stamps, lambda x: x[0][0:10], 'days') elif level == 'months': cleanOut['timeStamps'] = aggSeries(stamps, stamps, lambda x: x[0][0:7], 'months') # Write the output. with open(pJoin(modelDir, "allOutputData.json"), "w") as outFile: json.dump(cleanOut, outFile, indent=4) # 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) # Clean up the PID file. os.remove(pJoin(modelDir, "gldContainer", "PID.txt")) print "DONE RUNNING", modelDir except Exception as e: print "MODEL CRASHED", e # Cancel to get rid of extra background processes. try: os.remove(pJoin(modelDir, 'PPID.txt')) except: pass thisErr = traceback.format_exc() inputDict['stderr'] = thisErr with open(os.path.join(modelDir, 'stderr.txt'), 'w') as errorFile: errorFile.write(thisErr) # Dump input with error included. with open(pJoin(modelDir, "allInputData.json"), "w") as inFile: json.dump(inputDict, inFile, indent=4) finishTime = datetime.datetime.now() inputDict["runTime"] = str( datetime.timedelta(seconds=int((finishTime - beginTime).total_seconds()))) with open(pJoin(modelDir, "allInputData.json"), "w") as inFile: json.dump(inputDict, inFile, indent=4) try: os.remove(pJoin(modelDir, "PPID.txt")) except: pass
from matplotlib import pyplot as plt import sys, json sys.path.append('../..') import feeder, weather from solvers.gridlabd import runInFilesystem # Make a weather file. weather.makeClimateCsv('2014-01-01', '2014-01-11', 'MSP', './mspWeather.csv') # Add stuff to the feeder. with open('./Orville Tree Pond Calibrated.json','r') as inFile: myFeed = json.load(inFile) myTree = myFeed['tree'] # HACK:doesn't matter if we import modules twice, so just stick everything on the end. oldMax = feeder.getMaxKey(myTree) myTree[oldMax + 1] = {'omftype':'module', 'argument':'tape'} myTree[oldMax + 2] = {'omftype':'module', 'argument':'climate'} myTree[oldMax + 3] = {'object':'csv_reader', 'name':'weatherReader', 'filename':'mspWeather.csv'} myTree[oldMax + 4] = {'object':'climate', 'name':'exampleClimate', 'tmyfile':'mspWeather.csv', 'reader':'weatherReader'} # Add a few panels too to test. myTree[oldMax + 5] = {'name':'solEngInverter', 'parent':'node19 23CC 01001447022_A', 'generator_status':'ONLINE', 'inverter_type':'PWM', 'object':'inverter', 'generator_mode':'CONSTANT_PF' } myTree[oldMax + 6] = {'generator_mode':'SUPPLY_DRIVEN', 'name':'solar172879', 'parent':'solEngInverter', 'area':'30000 sf',
def omfCalibrate(workDir, feederPath, scadaPath, simStartDate, simLength, calibrateError=0.05): '''calibrates a feeder and saves the calibrated tree at a location''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) # Process scada data. gridlabdDir = pJoin(workDir,"gridlabD") scadaSubPower = _processScadaData(gridlabdDir,scadaPath, simStartDate) # Force FBS powerflow, because NR fails a lot. for key in tree: if tree[key].get("module","").lower() == "powerflow": tree[key] = {"module":"powerflow","solver_method":"FBS"} # Attach player. classOb = {'omftype':'class player','argument':'{double value;}'} playerOb = {"object":"player", "property":"value", "name":"scadaLoads", "file":"subScada.player", "loop":"0"} maxKey = feeder.getMaxKey(tree) playerKey = maxKey + 2 tree[maxKey+1] = classOb tree[playerKey] = playerOb # Make loads reference player. loadTemplate = {"object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3"} loadTemplateR = {"object": "load", "impedance_pf_A": "0.98", "impedance_pf_B": "0.98", "impedance_pf_C": "0.98", "power_pf_A": "0.90", "power_pf_B": "0.90", "power_pf_C": "0.90", "impedance_fraction_A": "0.7", "impedance_fraction_B": "0.7", "impedance_fraction_C": "0.7", "power_fraction_A": "0.3", "power_fraction_B": "0.3", "power_fraction_C": "0.3"} for key in tree: ob = tree[key] if ob.get("object","") == "triplex_node" and ob.get("power_12","") != "": # Add to triplex_nodes. newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") oldPow = ob.get("power_12","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb elif ob.get("object","") == "load": # Add to residential_loads too. newOb = dict(loadTemplateR) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["load_class"] = ob.get("load_class", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") try: oldPow = ob.get("constant_power_A","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_A"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_B","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_B"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_C","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_C"] = "scadaLoads.value*" + str(pythagPower) except: pass tree[key] = newOb # Convert swing bus to a meter. for key in tree: if tree[key].get('bustype','').lower() == 'swing' and tree[key].get('object','') != 'meter': swingName = tree[key].get('name') regIndex = key tree[key]['object'] = 'meter' # Search for the substation meter and attach a recorder there. for key in tree: if tree[key].get('bustype','').lower() == 'swing': swingName = tree[key].get('name') recOb = {"object": "recorder", "parent": swingName, "property": "measured_real_power,measured_reactive_power,measured_power", "file": "caliSub.csv", "interval": "900"} outputRecorderKey = maxKey + 3 tree[outputRecorderKey] = recOb feeder.adjustTime(tree, simLength, "hours", simStartDate['Date'].strftime("%Y-%m-%d %H:%M:%S")) # Run Gridlabd, calculate scaling constant. def runPowerflowIter(tree,scadaSubPower): '''Runs powerflow once, then iterates.''' # Run initial powerflow to get power. print "Running initial calibration powerflow." output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=gridlabdDir) outRealPow = output["caliSub.csv"]["measured_real_power"] outImagPower = output["caliSub.csv"]["measured_reactive_power"] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPow, outImagPower)] lastFile = "subScada.player" nextFile = "subScadaCalibrated.player" nextPower = outAppPowerKw error = (sum(outRealPow[1:simLength])/1000-sum(scadaSubPower[1:simLength]))/sum(scadaSubPower[1:simLength]) iteration = 1 while abs(error)>calibrateError and iteration<5: # Run calibration and iterate up to 5 times. SCAL_CONST = sum(scadaSubPower[1:simLength])/sum(nextPower[1:simLength]) print "Calibrating loads, running powerflow again. Our SCAL_CONST is: ", SCAL_CONST newPlayData = [] with open(pJoin(gridlabdDir, lastFile), "r") as playerFile: for line in playerFile: (key,val) = line.split(',') newPlayData.append(str(key) + ',' + str(float(val)*SCAL_CONST) + "\n") with open(pJoin(gridlabdDir, nextFile), "w") as playerFile: for row in newPlayData: playerFile.write(row) tree[playerKey]["file"] = nextFile tree[outputRecorderKey]["file"] = "caliSubCheck.csv" nextOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=gridlabdDir) outRealPowIter = nextOutput["caliSubCheck.csv"]["measured_real_power"] outImagPowerIter = nextOutput["caliSubCheck.csv"]["measured_reactive_power"] nextAppKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPowIter, outImagPowerIter)] lastFile = nextFile nextFile = "subScadaCalibrated"+str(iteration)+".player" nextPower = nextAppKw # Compute error and iterate. error = (sum(outRealPowIter[1:simLength])/1000-sum(scadaSubPower[1:simLength]))/sum(scadaSubPower[1:simLength]) iteration+=1 print "Error:", abs(error*100), "% Iteration:", iteration return outRealPow, outRealPowIter, lastFile, iteration outRealPow, outRealPowIter, lastFile, iteration = runPowerflowIter(tree,scadaSubPower) caliPowVectors = [[float(element) for element in scadaSubPower[1:simLength]], [float(element)/1000 for element in outRealPow[1:simLength]], [float(element)/1000 for element in outRealPowIter[1:simLength]]] labels = ["scadaSubPower","initialGuess","finalGuess"] colors = ['red','lightblue','blue'] chartData = {"Title":"Substation Calibration Check (Iterated "+str(iteration+1)+"X)", "fileName":"caliCheckPlot", "colors":colors,"labels":labels, "timeZone":simStartDate['timeZone']} plotLine(workDir, caliPowVectors, chartData, simStartDate['Date']+dt.timedelta(hours=1), 'hours') # Write the final output. with open(pJoin(workDir,"calibratedFeeder.json"),"w") as outJson: playerString = open(pJoin(gridlabdDir,lastFile)).read() feederJson["attachments"][lastFile] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return
def heavyProcessing(modelDir, inputDict): ''' Run the model in its directory. WARNING: GRIDLAB CAN TAKE HOURS TO COMPLETE. ''' print "STARTING TO RUN", modelDir beginTime = datetime.datetime.now() # Get feeder name and data in. try: os.mkdir(pJoin(modelDir,'gldContainer')) except: pass try: feederName = inputDict["feederName1"] inputDict["climateName"], latforpvwatts = zipCodeToClimateName(inputDict["zipCode"]) shutil.copy(pJoin(__metaModel__._omfDir, "data", "Climate", inputDict["climateName"] + ".tmy2"), pJoin(modelDir, "gldContainer", "climate.tmy2")) startTime = datetime.datetime.now() feederJson = json.load(open(pJoin(modelDir, feederName+'.omd'))) tree = feederJson["tree"] # Set up GLM with correct time and recorders: feeder.attachRecorders(tree, "Regulator", "object", "regulator") feeder.attachRecorders(tree, "Capacitor", "object", "capacitor") feeder.attachRecorders(tree, "Inverter", "object", "inverter") feeder.attachRecorders(tree, "Windmill", "object", "windturb_dg") feeder.attachRecorders(tree, "CollectorVoltage", None, None) feeder.attachRecorders(tree, "Climate", "object", "climate") feeder.attachRecorders(tree, "OverheadLosses", None, None) feeder.attachRecorders(tree, "UndergroundLosses", None, None) feeder.attachRecorders(tree, "TriplexLosses", None, None) feeder.attachRecorders(tree, "TransformerLosses", None, None) feeder.groupSwingKids(tree) # Attach recorders for system voltage map: stub = {'object':'group_recorder', 'group':'"class=node"', 'property':'voltage_A', 'interval':3600, 'file':'aVoltDump.csv'} for phase in ['A','B','C']: copyStub = dict(stub) copyStub['property'] = 'voltage_' + phase copyStub['file'] = phase.lower() + 'VoltDump.csv' tree[feeder.getMaxKey(tree) + 1] = copyStub feeder.adjustTime(tree=tree, simLength=float(inputDict["simLength"]), simLengthUnits=inputDict["simLengthUnits"], simStartDate=inputDict["simStartDate"]) # RUN GRIDLABD IN FILESYSTEM (EXPENSIVE!) rawOut = gridlabd.runInFilesystem(tree, attachments=feederJson["attachments"], keepFiles=True, workDir=pJoin(modelDir,'gldContainer')) cleanOut = {} # Std Err and Std Out cleanOut['stderr'] = rawOut['stderr'] cleanOut['stdout'] = rawOut['stdout'] # Time Stamps for key in rawOut: if '# timestamp' in rawOut[key]: cleanOut['timeStamps'] = rawOut[key]['# timestamp'] break elif '# property.. timestamp' in rawOut[key]: cleanOut['timeStamps'] = rawOut[key]['# property.. timestamp'] else: cleanOut['timeStamps'] = [] # Day/Month Aggregation Setup: stamps = cleanOut.get('timeStamps',[]) level = inputDict.get('simLengthUnits','hours') # Climate for key in rawOut: if key.startswith('Climate_') and key.endswith('.csv'): cleanOut['climate'] = {} cleanOut['climate']['Rain Fall (in/h)'] = hdmAgg(rawOut[key].get('rainfall'), sum, level) cleanOut['climate']['Wind Speed (m/s)'] = hdmAgg(rawOut[key].get('wind_speed'), avg, level) cleanOut['climate']['Temperature (F)'] = hdmAgg(rawOut[key].get('temperature'), max, level) cleanOut['climate']['Snow Depth (in)'] = hdmAgg(rawOut[key].get('snowdepth'), max, level) cleanOut['climate']['Direct Normal (W/sf)'] = hdmAgg(rawOut[key].get('solar_direct'), sum, level) #cleanOut['climate']['Global Horizontal (W/sf)'] = hdmAgg(rawOut[key].get('solar_global'), sum, level) climateWbySFList= hdmAgg(rawOut[key].get('solar_global'), sum, level) #converting W/sf to W/sm climateWbySMList= [x*10.76392 for x in climateWbySFList] cleanOut['climate']['Global Horizontal (W/sm)']=climateWbySMList # Voltage Band if 'VoltageJiggle.csv' in rawOut: cleanOut['allMeterVoltages'] = {} cleanOut['allMeterVoltages']['Min'] = hdmAgg([float(i / 2) for i in rawOut['VoltageJiggle.csv']['min(voltage_12.mag)']], min, level) cleanOut['allMeterVoltages']['Mean'] = hdmAgg([float(i / 2) for i in rawOut['VoltageJiggle.csv']['mean(voltage_12.mag)']], avg, level) cleanOut['allMeterVoltages']['StdDev'] = hdmAgg([float(i / 2) for i in rawOut['VoltageJiggle.csv']['std(voltage_12.mag)']], avg, level) cleanOut['allMeterVoltages']['Max'] = hdmAgg([float(i / 2) for i in rawOut['VoltageJiggle.csv']['max(voltage_12.mag)']], max, level) # Power Consumption cleanOut['Consumption'] = {} # Set default value to be 0, avoiding missing value when computing Loads cleanOut['Consumption']['Power'] = [0] * int(inputDict["simLength"]) cleanOut['Consumption']['Losses'] = [0] * int(inputDict["simLength"]) cleanOut['Consumption']['DG'] = [0] * int(inputDict["simLength"]) for key in rawOut: if key.startswith('SwingKids_') and key.endswith('.csv'): oneSwingPower = hdmAgg(vecPyth(rawOut[key]['sum(power_in.real)'],rawOut[key]['sum(power_in.imag)']), avg, level) if 'Power' not in cleanOut['Consumption']: cleanOut['Consumption']['Power'] = oneSwingPower else: cleanOut['Consumption']['Power'] = vecSum(oneSwingPower,cleanOut['Consumption']['Power']) elif key.startswith('Inverter_') and key.endswith('.csv'): realA = rawOut[key]['power_A.real'] realB = rawOut[key]['power_B.real'] realC = rawOut[key]['power_C.real'] imagA = rawOut[key]['power_A.imag'] imagB = rawOut[key]['power_B.imag'] imagC = rawOut[key]['power_C.imag'] oneDgPower = hdmAgg(vecSum(vecPyth(realA,imagA),vecPyth(realB,imagB),vecPyth(realC,imagC)), avg, level) if 'DG' not in cleanOut['Consumption']: cleanOut['Consumption']['DG'] = oneDgPower else: cleanOut['Consumption']['DG'] = vecSum(oneDgPower,cleanOut['Consumption']['DG']) elif key.startswith('Windmill_') and key.endswith('.csv'): vrA = rawOut[key]['voltage_A.real'] vrB = rawOut[key]['voltage_B.real'] vrC = rawOut[key]['voltage_C.real'] viA = rawOut[key]['voltage_A.imag'] viB = rawOut[key]['voltage_B.imag'] viC = rawOut[key]['voltage_C.imag'] crB = rawOut[key]['current_B.real'] crA = rawOut[key]['current_A.real'] crC = rawOut[key]['current_C.real'] ciA = rawOut[key]['current_A.imag'] ciB = rawOut[key]['current_B.imag'] ciC = rawOut[key]['current_C.imag'] powerA = vecProd(vecPyth(vrA,viA),vecPyth(crA,ciA)) powerB = vecProd(vecPyth(vrB,viB),vecPyth(crB,ciB)) powerC = vecProd(vecPyth(vrC,viC),vecPyth(crC,ciC)) oneDgPower = hdmAgg(vecSum(powerA,powerB,powerC), avg, level) if 'DG' not in cleanOut['Consumption']: cleanOut['Consumption']['DG'] = oneDgPower else: cleanOut['Consumption']['DG'] = vecSum(oneDgPower,cleanOut['Consumption']['DG']) elif key in ['OverheadLosses.csv', 'UndergroundLosses.csv', 'TriplexLosses.csv', 'TransformerLosses.csv']: realA = rawOut[key]['sum(power_losses_A.real)'] imagA = rawOut[key]['sum(power_losses_A.imag)'] realB = rawOut[key]['sum(power_losses_B.real)'] imagB = rawOut[key]['sum(power_losses_B.imag)'] realC = rawOut[key]['sum(power_losses_C.real)'] imagC = rawOut[key]['sum(power_losses_C.imag)'] oneLoss = hdmAgg(vecSum(vecPyth(realA,imagA),vecPyth(realB,imagB),vecPyth(realC,imagC)), avg, level) if 'Losses' not in cleanOut['Consumption']: cleanOut['Consumption']['Losses'] = oneLoss else: cleanOut['Consumption']['Losses'] = vecSum(oneLoss,cleanOut['Consumption']['Losses']) elif key.startswith('Regulator_') and key.endswith('.csv'): #split function to strip off .csv from filename and user rest of the file name as key. for example- Regulator_VR10.csv -> key would be Regulator_VR10 regName="" regName = key newkey=regName.split(".")[0] cleanOut[newkey] ={} cleanOut[newkey]['RegTapA'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapB'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapC'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['RegTapA'] = rawOut[key]['tap_A'] cleanOut[newkey]['RegTapB'] = rawOut[key]['tap_B'] cleanOut[newkey]['RegTapC'] = rawOut[key]['tap_C'] cleanOut[newkey]['RegPhases'] = rawOut[key]['phases'][0] elif key.startswith('Capacitor_') and key.endswith('.csv'): capName="" capName = key newkey=capName.split(".")[0] cleanOut[newkey] ={} cleanOut[newkey]['Cap1A'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1B'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1C'] = [0] * int(inputDict["simLength"]) cleanOut[newkey]['Cap1A'] = rawOut[key]['switchA'] cleanOut[newkey]['Cap1B'] = rawOut[key]['switchB'] cleanOut[newkey]['Cap1C'] = rawOut[key]['switchC'] cleanOut[newkey]['CapPhases'] = rawOut[key]['phases'][0] # What percentage of our keys have lat lon data? latKeys = [tree[key]['latitude'] for key in tree if 'latitude' in tree[key]] latPerc = 1.0*len(latKeys)/len(tree) if latPerc < 0.25: doNeato = True else: doNeato = False # Generate the frames for the system voltage map time traveling chart. genTime = generateVoltChart(tree, rawOut, modelDir, neatoLayout=doNeato) cleanOut['genTime'] = genTime # Aggregate up the timestamps: if level=='days': cleanOut['timeStamps'] = aggSeries(stamps, stamps, lambda x:x[0][0:10], 'days') elif level=='months': cleanOut['timeStamps'] = aggSeries(stamps, stamps, lambda x:x[0][0:7], 'months') # Write the output. with open(pJoin(modelDir, "allOutputData.json"),"w") as outFile: json.dump(cleanOut, outFile, indent=4) # 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) # Clean up the PID file. os.remove(pJoin(modelDir, "gldContainer", "PID.txt")) 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) finishTime = datetime.datetime.now() inputDict["runTime"] = str(datetime.timedelta(seconds = int((finishTime - beginTime).total_seconds()))) with open(pJoin(modelDir, "allInputData.json"),"w") as inFile: json.dump(inputDict, inFile, indent = 4) try: os.remove(pJoin(modelDir,"PPID.txt")) except: pass
def addBattery(battDict): #Validating input dictionary keys validKeys= ['feederDict', #REQUIRED 'battNode', #REQUIRED 'battEnergyRatingkWh', #REQUIRED 'invControl', #REQUIRED 'battEfficiency', #optional 'battType', #optional 'battSOCpu', #optional 'invPowerRatingkW', #REQUIRED 'invChargeLockoutTime', #optional 'invDischargeLockoutTime', #optional 'invEfficiency', #optional 'invChargeOnThresholdkW', #REQUIRED for LOAD_FOLLOWING control mode if and only if controlSenseAveragePowerkW not specified 'invChargeOffThresholdkW', #REQUIRED for LOAD_FOLLOWING control mode if and only if controlSenseAveragePowerkW not specified 'invDischargeOffThresholdkW', #REQUIRED for LOAD_FOLLOWING control mode if and only if controlSenseAveragePowerkW not specified 'invDischargeOnThresholdkW', #REQUIRED for LOAD_FOLLOWING control mode if and only if controlSenseAveragePowerkW not specified 'invMaxDischargeRatekW', #optional 'invMaxChargeRatekW', #optional 'controlSenseNode', #REQUIRED for LOAD_FOLLOWING 'controlSenseAveragePowerkW', #REQUIRED for LOAD_FOLLOWING control mode if and only if invCharge/DischargeOn/OffThresholdKW not specified 'constantPOut', #REQUIRED for CONSTANT_PQ control mode 'constantQout'] #REQUIRED for CONSTANT_PQ control mode for key1 in battDict: if (key1 not in validKeys) and (key1 != 'feederDict'): raise KeyError ('{} found in input parameter dictionary and is not a valid parameter.'.format(key1)) #Validating string parameter values if 'invControl' in battDict: isinstance(battDict['invControl'], basestring) else: raise Exception('Required parameter inverter control mode (\'invControl\') is undefined. ') if 'battType' in battDict: isinstance(battDict['battType'], basestring) else: battDict['battType'] = 'LI_ION' warnings.warn ('Battery type (\'battType\') is undefined, setting to \'LI_ION\'.') #Validating numeric parameter values if 'battEnergyRatingkWh' in battDict: if not (isinstance(battDict['battEnergyRatingkWh'], int) or isinstance(battDict['battEnergyRatingkWh'], float)) : raise TypeError('Parameter \'battEnergyRatingkWh\' is \'{}\', must be a numeric value.'.format(battDict['battEnergyRatingkWh'])) else: raise Exception('Required parameter battery power rating (\'battEnergyRatingkWh\') undefined.') if 'battEfficiency' in battDict: if not (isinstance(battDict['battEfficiency'], int) or isinstance(battDict['battEfficiency'], float)) : raise TypeError('Parameter \'battEfficiency\' is \'{}\', must be a numeric value.'.format(battDict['battEfficiency'])) else: battDict['battEfficiency'] = 0.85 warnings.warn('Battery efficiency (\'battEfficiency\') undefined, setting to 0.85 (85%).') if 'battSOCpu' in battDict: if not (isinstance(battDict['battSOCpu'], int) or isinstance(battDict['battSOCpu'], float)) : raise TypeError('Parameter \'battSOCpu\' is \'{}\', must be a numeric value.'.format(battDict['battSOCpu'])) else: battDict['battSOCpu'] = 1 warnings.warn('Battery initial state-of-charge (\'battSOCpu\') undefined, setting to 1pu (full).') if 'invPowerRatingkW' in battDict: if not (isinstance(battDict['invPowerRatingkW'], int) or isinstance(battDict['invPowerRatingkW'], float)) : raise TypeError('Parameter \'invPowerRatingkW\' is \'{}\', must be a numeric value.'.format(battDict['invPowerRatingkW'])) else: raise Exception('Required parameter inverter power rating (\'invPowerRatingkW\') undefined.') if 'invChargeLockoutTime' in battDict: if not (isinstance(battDict['invChargeLockoutTime'], int) or isinstance(battDict['invChargeLockoutTime'], float)) : raise TypeError('Parameter \'invChargeLockoutTime\' is \'{}\', must be a numeric value.'.format(battDict['invChargeLockoutTime'])) else: battDict['invChargeLockoutTime'] = 5 warnings.warn('Inverter charge lockout time (\'invChargeLockoutTime\') undefined, setting to 5 seconds.') if 'invDischargeLockoutTime' in battDict: if not (isinstance(battDict['invDischargeLockoutTime'], int) or isinstance(battDict['invDischargeLockoutTime'], float)) : raise TypeError('Parameter \'invDischargeLockoutTime\' is \'{}\', must be a numeric type.'.format(battDict['invDischargeLockoutTime'])) else: battDict['invDischargeLockoutTime'] = 5 warnings.warn('Inverter discharge lockout time (\'invDischargeLockoutTime\') undefined, setting to 5 seconds.') if 'invEfficiency' in battDict: if not (isinstance(battDict['invEfficiency'], int) or isinstance(battDict['invEfficiency'], float)) : raise TypeError('Parameter \'invEfficiency\' is \'{}\', must be a numeric value.'.format(battDict['invEfficiency'])) else: battDict['invEfficiency'] = 0.95 warnings.warn('Inverter efficiency (\'invEfficiency\') undefined, setting to 0.95 (95%).' ) if 'invMaxDischargeRatekW' in battDict: if not (isinstance(battDict['invMaxDischargeRatekW'], int) or isinstance(battDict['invMaxDischargeRatekW'], float)) : raise TypeError('Parameter \'invMaxDischargeRatekW\' is \'{}\', must be a numeric value.'.format(battDict['invMaxDischargeRatekW'])) else: battDict['invMaxDischargeRatekW'] = battDict['invPowerRatingkW'] warnings.warn('Inverter maximum discharge rate (\'invMaxDischargeRatekW\') undefined, setting to inverter power rating of {}kW.'.format(battDict['invPowerRatingkW'])) if 'invMaxChargeRatekW' in battDict: if not (isinstance(battDict['invMaxChargeRatekW'], int) or isinstance(battDict['invMaxChargeRatekW'], float)) : raise TypeError('Parameter \'invMaxChargeRatekW\' is \'{}\', must be a numeric value.'.format(battDict['invMaxChargeRatekW'])) else: battDict['invMaxChargeRatekW'] = battDict['invPowerRatingkW'] warnings.warn('Inverter maximum charge rate (\'invMaxChargeRatekW\') undefined, setting to inverter power rating of {}kW.'.format(battDict['invPowerRatingkW'])) if battDict['invControl'].upper() == 'CONSTANT_PQ': if 'constantPOut' in battDict: if not (isinstance(battDict['constantPOut'], int) or isinstance(battDict['constantPOut'], float)) : raise TypeError('Parameter \'constantPOut\' is \'{}\', must be a numeric value.'.format(battDict['constantPOut'])) else: raise Exception('Required parameter constant real power out (\'constantPOut\') is undefined. ') if 'constantQOUT' in battDict: if not (isinstance(battDict['constantQOut'], int) or isinstance(battDict['constantQOut'], float)) : raise TypeError('Parameter \'constantQOut\' is \'{}\', must be a numeric value.'.format(battDict['constantQOut'])) else: raise Exception('Required parameter constant reactive power out (\'constantQOut\') is undefined. ') elif battDict['invControl'].upper() == 'LOAD_FOLLOWING': if 'controlSenseAveragePowerkW' in battDict: if('invChargeOffThresholdkW' in battDict or 'invDischargeOffThresholdkW' in battDict or 'invChargeOnThresholdkW' in battDict or 'invDischargeOnThresholdkW' in battDict): raise Exception('Conflicting inverter settings detected. Only either \'controlSenseAveragePowerkW\' or the set of \'invChargeOffThresholdkW\', \'invChargeOnThresholdkW\', \'invDischargeOffThresholdkW\', \'invDischargeOnThresholdkW\' can be defined.') if not (isinstance(battDict['controlSenseAveragePowerkW'], int) or isinstance(battDict['controlSenseAveragePowerkW'], float)) : raise TypeError('Parameter \'controlSenseAveragePowerkW\' is \'{}\', must be a numeric type.'.format(battDict['controlSenseAveragePowerkW'])) print 'Setting inverter hysteris to default levels based on value of \'invChargeOnThresholdkW\'.' battDict['invChargeOffThresholdkW'] = battDict['controlSenseAveragePowerkW'] * 0.95 battDict['invDischargeOffThresholdkW'] = battDict['controlSenseAveragePowerkW'] * 1.05 battDict['invChargeOnThresholdkW'] = battDict['controlSenseAveragePowerkW'] * 0.7 battDict['invDischargeOnThresholdkW'] = battDict['controlSenseAveragePowerkW'] * 1.3 elif('invChargeOffThresholdkW' in battDict and 'invDischargeOffThresholdkW' in battDict and 'invChargeOnThresholdkW' in battDict and 'invDischargeOnThresholdkW' in battDict): if 'controlSenseAveragePowerkW' in battDict: raise Exception('Conflicting inverter settings detected. Only either \'controlSenseAveragePowerkW\' or the set of \'invChargeOffThresholdkW\', \'invChargeOnThresholdkW\', \'invDischargeOffThresholdkW\', \'invDischargeOnThresholdkW\' can be defined.') else: raise Exception('Either \'controlSenseAveragePowerkW\' or the set of \'invChargeOffThresholdkW\', \'invChargeOnThresholdkW\', \'invDischargeOffThresholdkW\', \'invDischargeOnThresholdkW\' can and must be defined.') else: raise ValueError('{} is an unsupported value for \'invControl\'. Valid values are \'CONSTAN_PQ\' or \'LOAD_FOLLOWING\'.'.format(battDict['invControl'])) #Adding in generators module declaration maxKey = feeder.getMaxKey(battDict['feederDict']) battDict['feederDict'][str(maxKey + 1)]={'omftype': 'module', 'argument': 'generators'} battNodeList = battDict['battNode'] for index in range(len(battNodeList)): battDict['battNode'] = battNodeList[index] battDict['feederDict'] = addSingleBattery(battDict) return battDict['feederDict']
def omfCalibrate(workDir, feederPath, scadaPath, simStartDate, simLength): '''calibrates a feeder and saves the calibrated tree at a location''' with open(feederPath, "r") as jsonIn: feederJson = json.load(jsonIn) tree = feederJson.get("tree", {}) # Process scada data. gridlabdDir = pJoin(workDir,"gridlabD") scadaSubPower = _processScadaData(gridlabdDir,scadaPath, simStartDate) # Force FBS powerflow, because NR fails a lot. for key in tree: if tree[key].get("module","").lower() == "powerflow": tree[key] = {"module":"powerflow","solver_method":"FBS"} # Attach player. classOb = {'omftype':'class player','argument':'{double value;}'} playerOb = {"object":"player", "property":"value", "name":"scadaLoads", "file":"subScada.player", "loop":"0"} maxKey = feeder.getMaxKey(tree) playerKey = maxKey + 2 tree[maxKey+1] = classOb tree[playerKey] = playerOb # Make loads reference player. loadTemplate = {"object": "triplex_load", "power_pf_12": "0.95", "impedance_pf_12": "0.98", "power_pf_12": "0.90", "impedance_fraction_12": "0.7", "power_fraction_12": "0.3"} loadTemplateR = {"object": "load", "impedance_pf_A": "0.98", "impedance_pf_B": "0.98", "impedance_pf_C": "0.98", "power_pf_A": "0.90", "power_pf_B": "0.90", "power_pf_C": "0.90", "impedance_fraction_A": "0.7", "impedance_fraction_B": "0.7", "impedance_fraction_C": "0.7", "power_fraction_A": "0.3", "power_fraction_B": "0.3", "power_fraction_C": "0.3"} for key in tree: ob = tree[key] if ob.get("object","") == "triplex_node" and ob.get("power_12","") != "": # Add to triplex_nodes. newOb = dict(loadTemplate) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") oldPow = ob.get("power_12","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_12"] = "scadaLoads.value*" + str(pythagPower) tree[key] = newOb elif ob.get("object","") == "load": # Add to residential_loads too. newOb = dict(loadTemplateR) newOb["name"] = ob.get("name", "") newOb["parent"] = ob.get("parent", "") newOb["phases"] = ob.get("phases", "") newOb["load_class"] = ob.get("load_class", "") newOb["nominal_voltage"] = ob.get("nominal_voltage","") newOb["latitude"] = ob.get("latitude","0") newOb["longitude"] = ob.get("longitude","0") try: oldPow = ob.get("constant_power_A","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_A"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_B","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_B"] = "scadaLoads.value*" + str(pythagPower) except: pass try: oldPow = ob.get("constant_power_C","").replace("j","d") pythagPower = gridlabd._strClean(oldPow) newOb["base_power_C"] = "scadaLoads.value*" + str(pythagPower) except: pass tree[key] = newOb # Convert swing bus to a meter. for key in tree: if tree[key].get('bustype','').lower() == 'swing' and tree[key].get('object','') != 'meter': swingName = tree[key].get('name') regIndex = key tree[key]['object'] = 'meter' # Search for the substation meter and attach a recorder there. for key in tree: if tree[key].get('bustype','').lower() == 'swing': swingName = tree[key].get('name') recOb = {"object": "recorder", "parent": swingName, "property": "measured_real_power,measured_reactive_power,measured_power", "file": "caliSub.csv", "interval": "900"} outputRecorderKey = maxKey + 3 tree[outputRecorderKey] = recOb feeder.adjustTime(tree, simLength, "hours", simStartDate['Date'].strftime("%Y-%m-%d %H:%M:%S")) # Run Gridlabd, calculate scaling constant. def runPowerflowIter(tree,scadaSubPower, iterationTimes): '''Runs powerflow once, then iterates.''' print "Running calibration powerflow #1." output = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=gridlabdDir) outRealPow = output["caliSub.csv"]["measured_real_power"] outImagPower = output["caliSub.csv"]["measured_reactive_power"] outAppPowerKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPow, outImagPower)] lastFile = "subScada.player" nextFile = "subScadaCalibrated.player" nextPower = outAppPowerKw for i in range(1, iterationTimes+1): SCAL_CONST = sum(scadaSubPower[1:simLength])/sum(nextPower[1:simLength]) print "Running calibration powerflow (iteration", str(i+1), "of", iterationTimes+1,") (SCAL_CONST: ", SCAL_CONST,")" newPlayData = [] with open(pJoin(gridlabdDir, lastFile), "r") as playerFile: for line in playerFile: (key,val) = line.split(',') newPlayData.append(str(key) + ',' + str(float(val)*SCAL_CONST) + "\n") with open(pJoin(gridlabdDir, nextFile), "w") as playerFile: for row in newPlayData: playerFile.write(row) tree[playerKey]["file"] = nextFile tree[outputRecorderKey]["file"] = "caliSubCheck.csv" nextOutput = gridlabd.runInFilesystem(tree, keepFiles=True, workDir=gridlabdDir) outRealPow2nd = nextOutput["caliSubCheck.csv"]["measured_real_power"] outImagPower2nd = nextOutput["caliSubCheck.csv"]["measured_reactive_power"] nextAppKw = [(x[0]**2 + x[1]**2)**0.5/1000 for x in zip(outRealPow2nd, outImagPower2nd)] lastFile = nextFile nextFile = "subScadaCalibrated"+str(i)+".player" nextPower = outAppPowerKw return outRealPow, outRealPow2nd, lastFile iterationTimes = 1 outRealPow, outRealPow2nd, lastFile = runPowerflowIter(tree,scadaSubPower,iterationTimes) caliPowVectors = [[float(element) for element in scadaSubPower[1:simLength]], [float(element)/1000 for element in outRealPow[1:simLength]], [float(element)/1000 for element in outRealPow2nd[1:simLength]]] labels = ["scadaSubPower","initialGuess","finalGuess"] colors = ['red','lightblue','blue'] chartData = {"Title":"Substation Calibration Check (Iterated "+str(iterationTimes+1)+"X)", "fileName":"caliCheckPlot", "colors":colors,"labels":labels, "timeZone":simStartDate['timeZone']} plotLine(workDir, caliPowVectors, chartData, simStartDate['Date']+dt.timedelta(hours=1), 'hours') # Write the final output. with open(pJoin(workDir,"calibratedFeeder.json"),"w") as outJson: playerString = open(pJoin(gridlabdDir,lastFile)).read() feederJson["attachments"][lastFile] = playerString feederJson["tree"] = tree json.dump(feederJson, outJson, indent=4) return