def addScaledRandomHouses(inFeed): ''' Take a feeder, translate each triplex_node under a meter in to a scaled, semi-randomized house object. ''' houseArchetypes = _get_house_archetypes() childrenPath = os.path.join(omf.omfDir, 'static', 'testFiles', 'houseChildren.glm') childrenArchetypes = feeder.parse(childrenPath) tripNodeKeys = _get_by_key_val(inFeed, 'object', 'triplex_node', getAll=True) tripLoadKeys = [k for k in tripNodeKeys if 'parent' in inFeed[k]] maxKey = feeder.getMaxKey(inFeed) + 1 inFeed[maxKey] = {'omftype': 'module', 'argument': 'residential'} maxKey += 1 inFeed[maxKey] = {'omftype': '#include','argument': '\"schedulesResponsiveLoads.glm\"'} maxKey += 1 for tripKey in tripLoadKeys: tMeter = inFeed[_get_by_key_val(inFeed, 'name', inFeed[tripKey]['parent'])] tPower = complex(inFeed[tripKey]['power_12']).real newHouse = dict(random.choice(list(houseArchetypes.values()))) newHouse['name'] += '_' + str(tripKey) newHouse['parent'] = tMeter['name'] newHouse['schedule_skew'] = str(random.gauss(2000,500)) newHouse['floor_area'] = str(500.0 + 0.50*tPower) # Add 500 because very small floor_areas break GLD. newHouse['latitude'] = tMeter.get('latitude','0.0') newHouse['longitude'] = tMeter.get('longitude','0.0') inFeed[maxKey] = newHouse maxKey += 1 for childKey in childrenArchetypes: newChild = dict(childrenArchetypes[childKey]) newChild['name'] += '_' + str(tripKey) + '_' + str(childKey) newChild['parent'] = newHouse['name'] newChild['latitude'] = tMeter.get('latitude','0.0') newChild['longitude'] = tMeter.get('longitude','0.0') newChild['schedule_skew'] = str(random.gauss(8000,1000)) inFeed[maxKey] = newChild maxKey += 1 del inFeed[tripKey]
def oneLineGridlab(temp_dir): ''' Create a one-line diagram of the input GLM and return a PNG of it. Form parameters: :param glm: a GLM file. :param useLatLons: 'True' if the diagram should be drawn with coordinate values taken from within the GLM, 'False' if the diagram should be drawn with artificial coordinates using Graphviz NEATO. Details: :OMF fuction: omf.feeder.latLonNxGraph(). :run-time: about 1 to 30 seconds. ''' glm_path = os.path.join(temp_dir, 'in.glm') feed = feeder.parse(glm_path) graph = feeder.treeToNxGraph(feed) neatoLayout = True if request.form.get('useLatLons') == 'False' else False # Clear old plots. plt.clf() plt.close() # Plot new plot. feeder.latLonNxGraph(graph, labels=False, neatoLayout=neatoLayout, showPlot=False) plt.savefig(os.path.join(temp_dir, filenames["ongl"]))
def createNewGLM(glmFile): '''Takes a GLM file and adds a solar inverter/panel pair to each meter on GLM. Based on Taxonomic Feeders''' feed = feeder.parse('./Taxonomy_Feeders-master/'+glmFile) '''Following block provides automatic coversion for taxonmic feeders''' if 'timestamp' in feed[0]: feed[0]['starttime'] = feed[0].pop('timestamp') if 'omftype' in feed[1] and '#set' in feed[1]['omftype']: feed[1]['omftype'] = 'module' feed[1]['argument'] = 'tape;\nmodule generators' meter_list = [] for item in feed: if 'object' not in feed[item]: pass elif 'meter' in feed[item]['object']: meter_list.append(item) count = 1 for item in meter_list: addRandomSolar(feed, item, count) count += 1 output = open('./Taxonomy_Feeders-solar/'+'.'.join(glmFile.split('.')[:2])+'-solarAdd.glm', 'w') output.write(feeder.write(feed)) output.close()
def createNewGLM(glmFile): '''Takes a GLM file and adds a solar inverter/panel pair to each meter on GLM. Based on Taxonomic Feeders''' feed = feeder.parse('./Taxonomy_Feeders-master/' + glmFile) '''Following block provides automatic coversion for taxonmic feeders''' if 'timestamp' in feed[0]: feed[0]['starttime'] = feed[0].pop('timestamp') if 'omftype' in feed[1] and '#set' in feed[1]['omftype']: feed[1]['omftype'] = 'module' feed[1]['argument'] = 'tape;\nmodule generators' meter_list = [] for item in feed: if 'object' not in feed[item]: pass elif 'meter' in feed[item]['object']: meter_list.append(item) count = 1 for item in meter_list: addRandomSolar(feed, item, count) count += 1 output = open( './Taxonomy_Feeders-solar/' + '.'.join(glmFile.split('.')[:2]) + '-solarAdd.glm', 'w') output.write(feeder.write(feed)) output.close()
def _test9(): # test KillAllAtTime agent glmPath = omf.omfDir + '/scratch/CIGAR/test_smsSingle.glm' from omf import cyberAttack cosimProps = { 'port': '6267', 'hostname': 'localhost', 'glmPath': glmPath, 'startTime': '2000-01-01 00:00:00', 'endTime': '2000-01-05 00:00:00', 'stepSizeSeconds': 3600 } # Gather the things to attack from omf import feeder tree = feeder.parse(glmPath) namesOfInverters = [] for key in tree: ob = tree[key] if ob.get('object', '') == 'inverter' and 'name' in ob: namesOfInverters.append(ob['name']) # Set up the agents and the simulation agents = [] agents.append( cyberAttack.KillAllAtTime('KillAllInverters', '2000-01-01 12:00:00', 'MagnitudeOfKillInput', namesOfInverters)) print('Starting co-sim with the KillAllInverters agent.') coord = Coordinator(agents, cosimProps) print(coord.drawPrettyResults())
def _validate_oneLineGridlab(temp_dir): '''TODO''' glm_path = os.path.join(temp_dir, 'in.glm') request.files['glm'].save(glm_path) tree = feeder.parse(glm_path) if not distNetViz.contains_valid_coordinates( tree) and request.form['useLatLons'] == 'True': return ({ 'useLatLons': 'True' }, ("Since the submitted GLM contained no coordinates, or the coordinates could not be parsed as floats, " "'useLatLons' must be 'False' because artificial coordinates must be used to draw the GLM." )) return (None, None)
def glmToModel(glmPath, modelDir): ''' One shot model creation from glm. ''' tree = feeder.parse(glmPath) # Run powerflow. First name the folder for it. # Remove old copy of the model. shutil.rmtree(modelDir, ignore_errors=True) # Create the model directory. new(modelDir) # Create the .omd. os.remove(modelDir + '/Olin Barre Geo.omd') with open(modelDir + '/Olin Barre Geo.omd', 'w') as omdFile: omd = dict(feeder.newFeederWireframe) omd['tree'] = tree json.dump(omd, omdFile, indent=4)
def gridlabdToGfm(temp_dir): ''' Convert a GridLAB-D model (i.e. .glm file) into a LANL ANSI General Fragility Model and return the GFM model as JSON. Note that this is not the main fragility model for GRIP. Form parameters: :param glm: a GLM file. :param phase_variation: maximum phase unbalance allowed in the optimization model. :param chance_constraint: indicates the percent of damage scenarios where load constraints above must be met. :param critical_load_met: indicates the percent of critical load that must be met in each damage scenario. :param total_load_met: indicates the percent of non-critical load that must be met in each damage scenario. :param maxDGPerGenerator: the maximum DG capacity that a generator supports in MW. :param dgUnitCost: the cost of adding distributed generation to a load in $/MW. :param generatorCandidates: the IDs of nodes on the system where the user wants to consider adding distributed generation. At least one node is required. :type generatorCandidates: one long string delimited with commas. :param criticalLoads: the IDs of loads on the system that the user declares to be critical (must-run). :type criticalLoads: one long string delimited with commas. Details: :OMF function: omf.models.resilientDist.convertToGFM(). :run-time: a few seconds. ''' fName = 'in.glm' f = request.files['glm'] glmPath = os.path.join(temp_dir, fName) f.save(glmPath) gfmInputTemplate = { 'phase_variation': float(request.form.get('phase_variation', 0.15)), 'chance_constraint': float(request.form.get('chance_constraint', 1)), 'critical_load_met': float(request.form.get('critical_load_met', .98)), 'total_load_met': float(request.form.get('total_load_met', .9)), 'maxDGPerGenerator': float(request.form.get('max_dg_per_generator', 1)), 'dgUnitCost': float(request.form.get('dg_unit_cost', 1000000)), 'generatorCandidates': request.form.get('generator_candidates', ''), 'criticalLoads': request.form.get('critical_loads', '') } feederModel = { 'nodes': [], # Don't need these. 'tree': feeder.parse(glmPath) } gfmDict = resilientDist.convertToGFM(gfmInputTemplate, feederModel) with open(os.path.join(temp_dir, filenames["glgfm"]), 'w') as f: json.dump(gfmDict, f)
def glmForceLayout(temp_dir): ''' Inject artifical coordinates into a GridLAB-D .glm and return the .glm. Form parameters: :param glm: a GLM file Details: :OMF function: omf.distNetViz.insert_coordinates() :run-time: a few seconds ''' glm_path = os.path.join(temp_dir, 'in.glm') glm_file = request.files['glm'] glm_file.save(glm_path) tree = feeder.parse(glm_path) distNetViz.insert_coordinates(tree) with open(os.path.join(temp_dir, filenames['gfl']), 'w') as f: f.write(feeder.sortedWrite(tree))
def _testingPlot(): PREFIX = os.path.join(os.path.dirname(__file__), '../scratch/CIGAR/') #PREFIX = omf.omfDir + '/scratch/CIGAR/' FNAME = 'test_base_R4-25.00-1.glm_CLEAN.glm' # FNAME = 'test_Exercise_4_2_1.glm' # FNAME = 'test_ieee37node.glm' # FNAME = 'test_ieee123nodeBetter.glm' # FNAME = 'test_large-R5-35.00-1.glm_CLEAN.glm'c # FNAME = 'test_medium-R4-12.47-1.glm_CLEAN.glm' # FNAME = 'test_smsSingle.glm' tree = feeder.parse(PREFIX + FNAME) chart = drawPlot(tree, neatoLayout=True, perUnitScale=False, rezSqIn=400) #chart = drawPlot(PREFIX + FNAME, neatoLayout=True, edgeCol=True, nodeLabs="Voltage", edgeLabs="Current", perUnitScale=False, rezSqIn=400) chart.savefig(PREFIX + "YO_WHATS_GOING_ON.png") plt.show()
def gridlabRun(temp_dir): ''' Run a .glm through GridLAB-D and return the results as JSON. Form parameters: :param glm: a GLM file. Details: :OMF fuction: omf.solvers.gridlabd.runInFileSystem(). :run-time: up to a few hours. TODO: think about attachment support. ''' fName = 'in.glm' f = request.files['glm'] glmOnDisk = os.path.join(temp_dir, fName) f.save(glmOnDisk) feed = feeder.parse(glmOnDisk) outDict = gridlabd.runInFilesystem(feed, attachments=[], keepFiles=True, workDir=temp_dir, glmName='out.glm') with open(os.path.join(temp_dir, filenames["glrun"]), 'w') as f: json.dump(outDict, f)
def read_glm_files(directory): """ Read each .glm file and return a dictionary of dictionaries """ os.chdir(directory) return [feeder.parse(file_path) for file_path in glob.glob("*.glm")]
import omf.feeder as feeder from omf.solvers.gridlabd import runInFilesystem feed = feeder.parse('GC-12.47-1.glm') maxKey = feeder.getMaxKey(feed) print(feed[1]) feed[maxKey + 1] = { 'object': 'node', 'name': 'test_solar_node', 'phases': 'ABCN', 'nominal_voltage': '7200' } feed[maxKey + 2] = { 'object': 'underground_line', 'name': 'test_solar_line', 'phases': 'ABCN', 'from': 'test_solar_node', 'to': 'GC-12-47-1_node_26', 'length': '100', 'configuration': 'line_configuration:6' } feed[maxKey + 3] = { 'object': 'meter', 'name': 'test_solar_meter', 'parent': 'test_solar_node', 'phases': 'ABCN', 'nominal_voltage': '480' } feed[maxKey + 4] = {
'measured_current_1.real, ' + \ 'measured_current_2.real, ' + \ 'measured_current_N.real, ' + \ 'indiv_measured_power_1.real, ' + \ 'indiv_measured_power_2.real, ' + \ 'indiv_measured_power_N.real' METER_FILENAME = 'meter.csv' OUTPUT_FILENAME = 'faultyData.csv' # load circuit --------------------------------------------------------------------- # if circuit is defined in a glm file if CIRCUIT_PATH.endswith('.glm'): tree = feeder.parse(CIRCUIT_PATH) attachments = [] # if circuit is defined in a omd file elif CIRCUIT_PATH.endswith('.omd'): omd = json.load(open(CIRCUIT_PATH)) tree = omd.get('tree', {}) attachments = omd.get('attachments',[]) else: # incorrect file type raise Exception('Invalid input file type. We require a .glm or .omd.') # modify circuit ------------------------------------------------------------------- # assume circuit doesnt have a clock for keeping time or a tape module for recording
import omf.feeder as feeder from omf.solvers.gridlabd import runInFilesystem feed = feeder.parse('GC-12.47-1.glm') maxKey = feeder.getMaxKey(feed) print (feed[1]) feed[maxKey + 1] = { 'object': 'node', 'name': 'test_solar_node', 'phases': 'ABCN', 'nominal_voltage': '7200' } feed[maxKey + 2] = { 'object': 'underground_line', 'name': 'test_solar_line', 'phases': 'ABCN', 'from': 'test_solar_node', 'to': 'GC-12-47-1_node_26', 'length': '100', 'configuration': 'line_configuration:6' } feed[maxKey + 3] = { 'object': 'meter', 'name': 'test_solar_meter', 'parent': 'test_solar_node', 'phases': 'ABCN', 'nominal_voltage': '480' } feed[maxKey + 4] = { 'object': 'inverter', 'name': 'test_solar_inverter', 'parent': 'test_solar_meter', 'phases': 'AS', 'inverter_type': 'PWM', 'power_factor': '1.0', 'generator_status': 'ONLINE', 'generator_mode': 'CONSTANT_PF' } feed[maxKey + 5] = { 'object': 'solar', 'name': 'test_solar', 'parent': 'test_solar_inverter', 'area': '1000000 sf', 'generator_status': 'ONLINE', 'efficiency': '0.2', 'generator_mode': 'SUPPLY_DRIVEN', 'panel_type': 'SINGLE_CRYSTAL_SILICON' }
import io, os, json, tempfile from pathlib import Path import requests import pytest from flask import url_for, request import omf from omf import feeder from omf.solvers import gridlabd from omf.scratch.GRIP import grip # Place to hack on stuff. Not intended to be run with real tests via pytest if __name__ == '__main__': temp_dir = tempfile.mkdtemp() path = Path(omf.omfDir) / 'scratch/CIGAR/test_ieee123nodeBetter.glm' #path = Path(__file__).parent / 'test-files/ieee123_pole_vulnerability.glm' feed = feeder.parse(path) #import pdb; pdb.set_trace() outDict = gridlabd.runInFilesystem(feed, attachments=[], keepFiles=True, workDir=temp_dir, glmName='out.glm') print(outDict) @pytest.fixture(scope="module") # The client should only be created once def client(): # testing must be set to true on the Flask application grip.app.config['TESTING'] = True # create a test client with built-in Flask code client = grip.app.test_client()
def getRandomGridlabModelFromTemplate(templatePath, \ deleteEVCharger, waterHeaterType, coolingType, heatingType): # initialize variables toDelete = [] toInsert = [] # get gridlab model from template gridlabModel = feeder.parse(templatePath) # calculate random house sqft as gaussian and convert to percent of range # percent of range is used to scale unresponsive and responsive loads houseSqft = random.gauss(HOUSE_SQFT_MEAN, HOUSE_SQFT_STDDEV) normalizedHouseSqft = abs(houseSqft - HOUSE_SQFT_MEAN) / HOUSE_SQFT_STDDEV sqftAsPercent = norm.cdf(normalizedHouseSqft) * 100 # set simulation params appropriately for modelItemKey, modelItem in gridlabModel.items(): # skew all schedules by random gaussians if (modelItem.get('schedule_skew') != None): gridlabModel[modelItemKey]['schedule_skew'] = \ random.gauss(SCHEDULE_SKEW_MEAN,SCHEDULE_SKEW_STDDEV) # set all recoder intervals based on user provided timestep if (modelItem.get('object') == 'recorder'): gridlabModel[modelItemKey]['interval'] = TIMESTEP # make the water heater electric or gas elif (modelItem.get('object') == 'waterheater'): gridlabModel[modelItemKey]['heat_mode'] = waterHeaterType # set simulation climate randomly elif (modelItem.get('object') == 'climate'): # climateFile = glob(CLIMATE_FILES)[218] climateFile = random.choice(glob(CLIMATE_FILES)) gridlabModel[modelItemKey]['tmyfile'] = climateFile # set time between samples given user provided timestep elif (modelItem.get('argument') != None) and \ modelItem.get('argument').startswith('minimum_timestep='): gridlabModel[modelItemKey]['argument'] = \ 'minimum_timestep=' + str(TIMESTEP) # set clock given user provided start time, stop time, and timezone elif (modelItem.get('clock') != None): simStartTime = '\'' + SIM_START_TIME + '\'' simStopTime = '\'' + SIM_STOP_TIME + '\'' gridlabModel[modelItemKey]['starttime'] = simStartTime gridlabModel[modelItemKey]['stoptime'] = simStopTime gridlabModel[modelItemKey]['timezone'] = TIMEZONE # add random multipliers to house loads based on house sqft elif (modelItem.get('base_power') == 'responsive_loads') or \ (modelItem.get('base_power') == 'unresponsive_loads'): # add some noise to sqftAsPercentage to ensure # that the loads do not use the same multiplier sqftAsPercentWithNoise = sqftAsPercent + \ random.gauss(0, SQFT_AS_PERCENT_NOISE_STDDEV) if sqftAsPercentWithNoise < 0: sqftAsPercentWithNoise = 0 elif sqftAsPercentWithNoise > 100: sqftAsPercentWithNoise = 100 # scale loads multiplierRange = MAX_LOAD_MULTIPLIER - MIN_LOAD_MULTIPLIER multiplier = (sqftAsPercentWithNoise / 100) * multiplierRange multiplier = MIN_LOAD_MULTIPLIER + multiplier gridlabModel[modelItemKey]['base_power'] += '*' + str(multiplier) # remove the ev charger from house some of the time elif (modelItem.get('object') == 'evcharger_det'): # create recorder to save ev charger data evRecorder = { 'object': 'recorder', 'interval': TIMESTEP, 'property': 'power.real', 'line_units': 'NONE', 'file': 'out_ev_charger.csv', 'parent': 'ev_house0' } if deleteEVCharger: # delete charger and point recorder to # an empty meter that just outputs zeros toDelete.append(modelItemKey) evRecorder['parent'] = 'zero_meter' evRecorder['property'] = 'measured_power.real' # add recorder to insert queue toInsert.append(evRecorder) # set house params based on random house and # randomize heating and cooling setpoints, and # heating and cooling type elif (modelItem.get('object') == 'house'): # set params of template house based on random house house = loadModeling.get_random_new_house() house['floor_area'] = houseSqft for houseKey, houseVal in house.items(): if (houseKey != 'name') and (houseKey != 'parent'): gridlabModel[modelItemKey][houseKey] = houseVal # use user provided gaussian to set # house heating and cooling setpoints heatingSetpoint, coolingSetpoint = 0, 0 while (heatingSetpoint >= coolingSetpoint): heatingSetpoint = random.gauss(HEATING_SETPOINT_MEAN, HEATING_SETPOINT_STDDEV) coolingSetpoint = random.gauss(COOLING_SETPOINT_MEAN, COOLING_SETPOINT_STDDEV) gridlabModel[modelItemKey]['heating_setpoint'] = heatingSetpoint gridlabModel[modelItemKey]['cooling_setpoint'] = coolingSetpoint # set house heating and cooling type gridlabModel[modelItemKey]['heating_system_type'] = heatingType gridlabModel[modelItemKey]['cooling_system_type'] = coolingType # insert and delete the appropriate items for item in toInsert: feeder.insert(gridlabModel, item) for modelItemKey in toDelete: del gridlabModel[modelItemKey] # output model return gridlabModel
def drawPlot(path, workDir=None, neatoLayout=False, edgeLabs=None, nodeLabs=None, edgeCol=None, nodeCol=None, faultLoc=None, faultType=None, customColormap=False, scaleMin=None, scaleMax=None, rezSqIn=400, simTime='2000-01-01 0:00:00', loadLoc=None): ''' Draw a color-coded map of the voltage drop on a feeder. path is the full path to the GridLAB-D .glm file or OMF .omd file. workDir is where GridLAB-D will run, if it's None then a temp dir is used. neatoLayout=True means the circuit is displayed using a force-layout approach. edgeCol property must be either 'Current', 'Power', 'Rating', 'PercentOfRating', or None nodeCol property must be either 'Voltage', 'VoltageImbalance', 'perUnitVoltage', 'perUnit120Voltage', or None edgeLabs and nodeLabs properties must be either 'Name', 'Value', or None edgeCol and nodeCol can be set to false to avoid coloring edges or nodes customColormap=True means use a one that is nicely scaled to perunit values highlighting extremes. faultType and faultLoc are the type of fault and the name of the line that it occurs on. Returns a matplotlib object.''' # Be quiet matplotlib: # warnings.filterwarnings("ignore") if path.endswith('.glm'): tree = feeder.parse(path) attachments = [] elif path.endswith('.omd'): with open(path) as f: omd = json.load(f) tree = omd.get('tree', {}) attachments = omd.get('attachments', []) else: raise Exception('Invalid input file type. We require a .glm or .omd.') #print path # add fault object to tree def safeInt(x): try: return int(x) except: return 0 biggestKey = max([safeInt(x) for x in tree.keys()]) # Add Reliability module tree[str(biggestKey * 10)] = { "module": "reliability", "maximum_event_length": "18000", "report_event_log": "true" } CLOCK_START = simTime dt_start = parser.parse(CLOCK_START) dt_end = dt_start + relativedelta(seconds=+20) CLOCK_END = str(dt_end) CLOCK_RANGE = CLOCK_START + ',' + CLOCK_END if faultType != None: # Add eventgen object (the fault) tree[str(biggestKey * 10 + 1)] = { "object": "eventgen", "name": "ManualEventGen", "parent": "RelMetrics", "fault_type": faultType, "manual_outages": faultLoc + ',' + CLOCK_RANGE } # TODO: change CLOCK_RANGE to read the actual start and stop time, not just hard-coded # Add fault_check object tree[str(biggestKey * 10 + 2)] = { "object": "fault_check", "name": "test_fault", "check_mode": "ONCHANGE", "eventgen_object": "ManualEventGen", "output_filename": "Fault_check_out.txt" } # Add reliabilty metrics object tree[str(biggestKey * 10 + 3)] = { "object": "metrics", "name": "RelMetrics", "report_file": "Metrics_Output.csv", "module_metrics_object": "PwrMetrics", "metrics_of_interest": '"SAIFI,SAIDI,CAIDI,ASAI,MAIFI"', "customer_group": '"groupid=METERTEST"', "metric_interval": "5 h", "report_interval": "5 h" } # Add power_metrics object tree[str(biggestKey * 10 + 4)] = { "object": "power_metrics", "name": "PwrMetrics", "base_time_value": "1 h" } # HACK: set groupid for all meters so outage stats are collected. noMeters = True for key in tree: if tree[key].get('object', '') in ['meter', 'triplex_meter']: tree[key]['groupid'] = "METERTEST" noMeters = False if noMeters: raise Exception( "No meters detected on the circuit. Please add at least one meter to allow for collection of outage statistics." ) for key in tree: if 'clock' in tree[key]: tree[key]['starttime'] = "'" + CLOCK_START + "'" tree[key]['stoptime'] = "'" + CLOCK_END + "'" # dictionary to hold info on lines present in glm edge_bools = dict.fromkeys([ 'underground_line', 'overhead_line', 'triplex_line', 'transformer', 'regulator', 'fuse', 'switch' ], False) # Map to speed up name lookups. nameToIndex = {tree[key].get('name', ''): key for key in tree.keys()} # Get rid of schedules and climate and check for all edge types: for key in list(tree.keys()): obtype = tree[key].get("object", "") if obtype == 'underground_line': edge_bools['underground_line'] = True elif obtype == 'overhead_line': edge_bools['overhead_line'] = True elif obtype == 'triplex_line': edge_bools['triplex_line'] = True elif obtype == 'transformer': edge_bools['transformer'] = True elif obtype == 'regulator': edge_bools['regulator'] = True elif obtype == 'fuse': edge_bools['fuse'] = True elif obtype == 'switch': edge_bools['switch'] = True if tree[key].get("argument", "") == "\"schedules.glm\"" or tree[key].get( "tmyfile", "") != "": del tree[key] # Make sure we have a voltage dump and current dump: tree[str(biggestKey * 10 + 5)] = { "object": "voltdump", "filename": "voltDump.csv" } tree[str(biggestKey * 10 + 6)] = { "object": "currdump", "filename": "currDump.csv" } # Line rating dumps tree[feeder.getMaxKey(tree) + 1] = {'module': 'tape'} for key in edge_bools.keys(): if edge_bools[key]: tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=' + key + '"', 'property': 'continuous_rating', 'file': key + '_cont_rating.csv' } #Record initial status readout of each fuse/recloser/switch/sectionalizer before running # Reminder: fuse objects have 'phase_X_status' instead of 'phase_X_state' protDevices = dict.fromkeys( ['fuse', 'recloser', 'switch', 'sectionalizer'], False) #dictionary of protective device initial states for each phase protDevInitStatus = {} #dictionary of protective devices final states for each phase after running Gridlab-D protDevFinalStatus = {} #dictionary of protective device types to help the testing and debugging process protDevTypes = {} protDevOpModes = {} for key in tree: obj = tree[key] obType = obj.get('object') if obType in protDevices.keys(): obName = obj.get('name', '') protDevTypes[obName] = obType if obType != 'fuse': protDevOpModes[obName] = obj.get('operating_mode', 'INDIVIDUAL') protDevices[obType] = True protDevInitStatus[obName] = {} protDevFinalStatus[obName] = {} for phase in ['A', 'B', 'C']: if obType != 'fuse': phaseState = obj.get('phase_' + phase + '_state', 'CLOSED') else: phaseState = obj.get('phase_' + phase + '_status', 'GOOD') if phase in obj.get('phases', ''): protDevInitStatus[obName][phase] = phaseState #print protDevInitStatus #Create a recorder for protective device states for key in protDevices.keys(): if protDevices[key]: for phase in ['A', 'B', 'C']: if key != 'fuse': tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=' + key + '"', 'property': 'phase_' + phase + '_state', 'file': key + '_phase_' + phase + '_state.csv' } else: tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=' + key + '"', 'property': 'phase_' + phase + '_status', 'file': key + '_phase_' + phase + '_state.csv' } # Run Gridlab. if not workDir: workDir = tempfile.mkdtemp() print('@@@@@@', workDir) # for i in range(6): # gridlabOut = gridlabd.runInFilesystem(tree, attachments=attachments, workDir=workDir) # #HACK: workaround for shoddy macOS gridlabd build. # if 'error when setting parent' not in gridlabOut.get('stderr','OOPS'): # break gridlabOut = gridlabd.runInFilesystem(tree, attachments=attachments, workDir=workDir) #Record final status readout of each fuse/recloser/switch/sectionalizer after running try: for key in protDevices.keys(): if protDevices[key]: for phase in ['A', 'B', 'C']: with open(pJoin(workDir, key + '_phase_' + phase + '_state.csv'), newline='') as statusFile: reader = csv.reader(statusFile) # loop past the header, keys = [] vals = [] for row in reader: if '# timestamp' in row: keys = row i = keys.index('# timestamp') keys.pop(i) vals = next(reader) vals.pop(i) for pos, key2 in enumerate(keys): protDevFinalStatus[key2][phase] = vals[pos] except: pass #print protDevFinalStatus #compare initial and final states of protective devices #quick compare to see if they are equal #print cmp(protDevInitStatus, protDevFinalStatus) #find which values changed changedStates = {} #read voltDump values into a dictionary. try: with open(pJoin(workDir, 'voltDump.csv'), newline='') as dumpFile: reader = csv.reader(dumpFile) next(reader) # Burn the header. keys = next(reader) voltTable = [] for row in reader: rowDict = {} for pos, key in enumerate(keys): rowDict[key] = row[pos] voltTable.append(rowDict) except: raise Exception( 'GridLAB-D failed to run with the following errors:\n' + gridlabOut['stderr']) # read currDump values into a dictionary with open(pJoin(workDir, 'currDump.csv'), newline='') as currDumpFile: reader = csv.reader(currDumpFile) next(reader) # Burn the header. keys = next(reader) currTable = [] for row in reader: rowDict = {} for pos, key in enumerate(keys): rowDict[key] = row[pos] currTable.append(rowDict) # read line rating values into a single dictionary lineRatings = {} rating_in_VA = [] for key1 in edge_bools.keys(): if edge_bools[key1]: with open(pJoin(workDir, key1 + '_cont_rating.csv'), newline='') as ratingFile: reader = csv.reader(ratingFile) # loop past the header, keys = [] vals = [] for row in reader: if '# timestamp' in row: keys = row i = keys.index('# timestamp') keys.pop(i) vals = next(reader) vals.pop(i) for pos, key2 in enumerate(keys): lineRatings[key2] = abs(float(vals[pos])) #edgeTupleRatings = lineRatings copy with to-from tuple as keys for labeling edgeTupleRatings = {} for edge in lineRatings: for obj in tree.values(): if obj.get('name', '').replace('"', '') == edge: nodeFrom = obj.get('from') nodeTo = obj.get('to') coord = (nodeFrom, nodeTo) ratingVal = lineRatings.get(edge) edgeTupleRatings[coord] = ratingVal # Calculate average node voltage deviation. First, helper functions. def digits(x): ''' Returns number of digits before the decimal in the float x. ''' return math.ceil(math.log10(x + 1)) def avg(l): ''' Average of a list of ints or floats. ''' # HACK: add a small value to the denominator to avoid divide by zero for out of service locations (i.e. zero voltage). return sum(l) / (len(l) + 0.00000000000000001) # Detect the feeder nominal voltage: for key in tree: ob = tree[key] if type(ob) == dict and ob.get('bustype', '') == 'SWING': feedVoltage = float(ob.get('nominal_voltage', 1)) # Tot it all up. nodeVolts = {} nodeVoltsPU = {} nodeVoltsPU120 = {} voltImbalances = {} for row in voltTable: allVolts = [] allVoltsPU = [] allDiffs = [] nodeName = row.get('node_name', '') for phase in ['A', 'B', 'C']: realVolt = abs(float(row['volt' + phase + '_real'])) imagVolt = abs(float(row['volt' + phase + '_imag'])) phaseVolt = math.sqrt((realVolt**2) + (imagVolt**2)) if phaseVolt != 0.0: treeKey = nameToIndex.get(nodeName, 0) nodeObj = tree.get(treeKey, {}) try: nominal_voltage = float(nodeObj['nominal_voltage']) except: nominal_voltage = feedVoltage allVolts.append(phaseVolt) normVolt = (phaseVolt / nominal_voltage) allVoltsPU.append(normVolt) avgVolts = avg(allVolts) avgVoltsPU = avg(allVoltsPU) avgVoltsPU120 = 120 * avgVoltsPU nodeVolts[nodeName] = float("{0:.2f}".format(avgVolts)) nodeVoltsPU[nodeName] = float("{0:.2f}".format(avgVoltsPU)) nodeVoltsPU120[nodeName] = float("{0:.2f}".format(avgVoltsPU120)) if len(allVolts) == 3: voltA = allVolts.pop() voltB = allVolts.pop() voltC = allVolts.pop() allDiffs.append(abs(float(voltA - voltB))) allDiffs.append(abs(float(voltA - voltC))) allDiffs.append(abs(float(voltB - voltC))) maxDiff = max(allDiffs) voltImbal = maxDiff / avgVolts voltImbalances[nodeName] = float("{0:.2f}".format(voltImbal)) # Use float("{0:.2f}".format(avg(allVolts))) if displaying the node labels nodeLoadNames = {} nodeNames = {} for key in nodeVolts.keys(): nodeNames[key] = key if key == loadLoc: nodeLoadNames[key] = "LOAD: " + key # find edge currents by parsing currdump edgeCurrentSum = {} edgeCurrentMax = {} for row in currTable: allCurr = [] for phase in ['A', 'B', 'C']: realCurr = abs(float(row['curr' + phase + '_real'])) imagCurr = abs(float(row['curr' + phase + '_imag'])) phaseCurr = math.sqrt((realCurr**2) + (imagCurr**2)) allCurr.append(phaseCurr) edgeCurrentSum[row.get('link_name', '')] = sum(allCurr) edgeCurrentMax[row.get('link_name', '')] = max(allCurr) # When just showing current as labels, use sum of the three lines' current values, when showing the per unit values (current/rating), use the max of the three #edgeTupleCurrents = edgeCurrents copy with to-from tuple as keys for labeling edgeTupleCurrents = {} #edgeValsPU = values normalized per unit by line ratings edgeValsPU = {} #edgeTupleValsPU = edgeValsPU copy with to-from tuple as keys for labeling edgeTupleValsPU = {} #edgeTuplePower = dict with to-from tuples as keys and sim power as values for debugging edgeTuplePower = {} #edgeTupleNames = dict with to-from tuples as keys and names as values for debugging edgeTupleNames = {} #edgeTupleFaultNames = dict with to-from tuples as keys and the name of the Fault as the only value edgeTupleFaultNames = {} #edgeTupleProtDevs = dict with to-from tuples as keys and the initial of the type of protective device as the value edgeTupleProtDevs = {} #linePhases = dictionary containing the number of phases on each line for line-width purposes linePhases = {} edgePower = {} for edge in edgeCurrentSum: for obj in tree.values(): obname = obj.get('name', '').replace('"', '') if obname == edge: objType = obj.get('object') nodeFrom = obj.get('from') nodeTo = obj.get('to') coord = (nodeFrom, nodeTo) currVal = edgeCurrentSum.get(edge) voltVal = avg([nodeVolts.get(nodeFrom), nodeVolts.get(nodeTo)]) power = (currVal * voltVal) / 1000 lineRating = lineRatings.get(edge, 10.0**9) edgePerUnitVal = (edgeCurrentMax.get(edge)) / lineRating edgeTupleCurrents[coord] = "{0:.2f}".format(currVal) edgeTuplePower[coord] = "{0:.2f}".format(power) edgePower[edge] = power edgeValsPU[edge] = edgePerUnitVal edgeTupleValsPU[coord] = "{0:.2f}".format(edgePerUnitVal) edgeTupleNames[coord] = edge if faultLoc == edge: edgeTupleFaultNames[coord] = "FAULT: " + edge phaseStr = obj.get('phases', '').replace('"', '').replace( 'N', '').replace('S', '') numPhases = len(phaseStr) if (numPhases < 1) or (numPhases > 3): numPhases = 1 linePhases[edge] = numPhases protDevLabel = "" protDevBlownStr = "" if objType in protDevices.keys(): for phase in protDevFinalStatus[obname].keys(): if objType == 'fuse': if protDevFinalStatus[obname][phase] == "BLOWN": protDevBlownStr = "!" else: if protDevFinalStatus[obname][phase] == "OPEN": protDevBlownStr = "!" if objType == 'fuse': protDevLabel = 'F' elif objType == 'switch': protDevLabel = 'S' elif objType == 'recloser': protDevLabel = 'R' elif objType == 'sectionalizer': protDevLabel = 'X' edgeTupleProtDevs[coord] = protDevLabel + protDevBlownStr #define which dict will be used for edge line color edgeColors = edgeValsPU #define which dict will be used for edge label edgeLabels = edgeTupleValsPU # Build the graph. fGraph = feeder.treeToNxGraph(tree) # TODO: consider whether we can set figsize dynamically. wlVal = int(math.sqrt(float(rezSqIn))) voltChart = plt.figure(figsize=(wlVal, wlVal)) plt.axes(frameon=0) plt.axis('off') voltChart.gca().set_aspect('equal') plt.tight_layout() #set axes step equal if neatoLayout: # HACK: work on a new graph without attributes because graphViz tries to read attrs. cleanG = nx.Graph(fGraph.edges()) cleanG.add_nodes_from(fGraph) positions = graphviz_layout(cleanG, prog='neato') else: remove_nodes = [ n for n in fGraph if fGraph.nodes[n].get('pos', (0, 0)) == (0, 0) ] fGraph.remove_nodes_from(remove_nodes) positions = {n: fGraph.nodes[n].get('pos', (0, 0)) for n in fGraph} # Need to get edge names from pairs of connected node names. edgeNames = [] for e in fGraph.edges(): edgeNames.append((fGraph.edges[e].get('name', 'BLANK')).replace('"', '')) #create custom colormap if customColormap: if scaleMin != None and scaleMax != None: scaleDif = scaleMax - scaleMin custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list( 'custColMap', [(scaleMin, 'blue'), (scaleMin + (0.12 * scaleDif), 'darkgray'), (scaleMin + (0.56 * scaleDif), 'darkgray'), (scaleMin + (0.8 * scaleDif), 'red')]) vmin = scaleMin vmax = scaleMax else: custom_cm = matplotlib.colors.LinearSegmentedColormap.from_list( 'custColMap', [(0.0, 'blue'), (0.15, 'darkgray'), (0.7, 'darkgray'), (1.0, 'red')]) vmin = 0 vmax = 1.25 custom_cm.set_under(color='black') else: custom_cm = plt.cm.get_cmap('viridis') if scaleMin != None and scaleMax != None: vmin = scaleMin vmax = scaleMax else: vmin = None vmax = None drawColorbar = False emptyColors = {} #draw edges with or without colors if edgeCol != None: drawColorbar = True if edgeCol == "Current": edgeList = [edgeCurrentSum.get(n, 1) for n in edgeNames] drawColorbar = True elif edgeCol == "Power": edgeList = [edgePower.get(n, 1) for n in edgeNames] drawColorbar = True elif edgeCol == "Rating": edgeList = [lineRatings.get(n, 10.0**9) for n in edgeNames] drawColorbar = True elif edgeCol == "PercentOfRating": edgeList = [edgeValsPU.get(n, .5) for n in edgeNames] drawColorbar = True else: edgeList = [emptyColors.get(n, .6) for n in edgeNames] print( "WARNING: edgeCol property must be 'Current', 'Power', 'Rating', 'PercentOfRating', or None" ) else: edgeList = [emptyColors.get(n, .6) for n in edgeNames] edgeIm = nx.draw_networkx_edges( fGraph, pos=positions, edge_color=edgeList, width=[linePhases.get(n, 1) for n in edgeNames], edge_cmap=custom_cm) #draw edge labels if edgeLabs != None: if edgeLabs == "Name": edgeLabels = edgeTupleNames elif edgeLabs == "Fault": edgeLabels = edgeTupleFaultNames elif edgeLabs == "Value": if edgeCol == "Current": edgeLabels = edgeTupleCurrents elif edgeCol == "Power": edgeLabels = edgeTuplePower elif edgeCol == "Rating": edgeLabels = edgeTupleRatings elif edgeCol == "PercentOfRating": edgeLabels = edgeTupleValsPU else: edgeLabels = None print( "WARNING: edgeCol property cannot be set to None when edgeLabs property is set to 'Value'" ) elif edgeLabs == "ProtDevs": edgeLabels = edgeTupleProtDevs else: edgeLabs = None print( "WARNING: edgeLabs property must be either 'Name', 'Value', or None" ) if edgeLabs != None: edgeLabelsIm = nx.draw_networkx_edge_labels(fGraph, pos=positions, edge_labels=edgeLabels, font_size=8) # draw nodes with or without color if nodeCol != None: if nodeCol == "Voltage": nodeList = [nodeVolts.get(n, 1) for n in fGraph.nodes()] drawColorbar = True elif nodeCol == "VoltageImbalance": nodeList = [voltImbalances.get(n, 1) for n in fGraph.nodes()] drawColorbar = True elif nodeCol == "perUnitVoltage": nodeList = [nodeVoltsPU.get(n, .5) for n in fGraph.nodes()] drawColorbar = True elif nodeCol == "perUnit120Voltage": nodeList = [nodeVoltsPU120.get(n, 120) for n in fGraph.nodes()] drawColorbar = True else: nodeList = [emptyColors.get(n, 1) for n in fGraph.nodes()] print( "WARNING: nodeCol property must be 'Voltage', 'VoltageImbalance', 'perUnitVoltage', 'perUnit120Voltage', or None" ) else: nodeList = [emptyColors.get(n, .6) for n in fGraph.nodes()] nodeIm = nx.draw_networkx_nodes(fGraph, pos=positions, node_color=nodeList, linewidths=0, node_size=30, vmin=vmin, vmax=vmax, cmap=custom_cm) #draw node labels nodeLabels = {} if nodeLabs != None: if nodeLabs == "Name": nodeLabels = nodeNames elif nodeLabs == "Value": if nodeCol == "Voltage": nodeLabels = nodeVolts elif nodeCol == "VoltageImbalance": nodeLabels = voltImbalances elif nodeCol == "perUnitVoltage": nodeLabels = nodeVoltsPU elif nodeCol == "perUnit120Voltage": nodeLabels = nodeVoltsPU120 else: nodeLabels = None print( "WARNING: nodeCol property cannot be set to None when nodeLabs property is set to 'Value'" ) #HACK: add hidden node label option for displaying specified load name elif nodeLabs == "Load": nodeLabels = nodeLoadNames else: nodeLabs = None print( "WARNING: nodeLabs property must be either 'Name', 'Value', or None" ) if nodeLabs != None: nodeLabelsIm = nx.draw_networkx_labels(fGraph, pos=positions, labels=nodeLabels, font_size=8) plt.sci(nodeIm) # plt.clim(110,130) if drawColorbar: plt.colorbar() return voltChart
def viz(pathToOmdOrGlm, forceLayout=False, outputPath=None): ''' Vizualize a distribution system.''' # HACK: make sure we have our homebrew binaries available. os.environ['PATH'] += os.pathsep + '/usr/local/bin' # Load in the feeder. with open(pathToOmdOrGlm, 'r') as feedFile: if pathToOmdOrGlm.endswith('.omd'): thisFeed = { 'tree': json.load(feedFile)['tree'] } # TODO: later bring back attachments. elif pathToOmdOrGlm.endswith('.glm'): thisFeed = {'tree': feeder.parse(pathToOmdOrGlm, filePath=True)} tree = thisFeed['tree'] # If there is zero lat/lon info, do force layout by default. latLonCount = 0 for key in tree: for subKey in ['latitude', 'longitude']: if subKey in tree[key]: latLonCount += 1 if latLonCount == 0: forceLayout = True # Force layout of feeders with no lat/lon information so we can actually see what's there. if forceLayout: print "Force laying out the graph..." # Use graphviz to lay out the graph. inGraph = feeder.treeToNxGraph(tree) # HACK: work on a new graph without attributes because graphViz tries to read attrs. cleanG = nx.Graph(inGraph.edges()) # HACK2: might miss nodes without edges without the following. cleanG.add_nodes_from(inGraph) pos = nx.nx_agraph.graphviz_layout(cleanG, prog='neato') # # Charting the feeder in matplotlib: # feeder.latLonNxGraph(inGraph, labels=False, neatoLayout=True, showPlot=True) # Insert the latlons. for key in tree: obName = tree[key].get('name', '') thisPos = pos.get(obName, None) if thisPos != None: tree[key]['longitude'] = thisPos[0] tree[key]['latitude'] = thisPos[1] # Set up temp directory and copy the feeder and viewer in to it. if outputPath == None: tempDir = tempfile.mkdtemp() else: tempDir = outputPath #HACK: make sure we get the required files from the right place. SOURCE_DIR = os.path.dirname(__file__) + '/' shutil.copy(SOURCE_DIR + '/distNetViz.html', tempDir + '/viewer.html') shutil.copy(SOURCE_DIR + '/svg-pan-zoom.js', tempDir + '/svg-pan-zoom.js') # Grab the library we need. with open(SOURCE_DIR + 'svg-pan-zoom.js', 'r') as pzFile: pzData = pzFile.read() # Rewrite the load lines in viewer.html # Note: you can't juse open the file in r+ mode because, based on the way the file is mapped to memory, you can only overwrite a line with another of exactly the same length. for line in fileinput.input(tempDir + '/viewer.html', inplace=1): if line.lstrip().startswith("<script id='feederLoadScript''>"): print "" # Remove the existing load. elif line.lstrip().startswith("<script id='feederInsert'>"): print "<script id='feederInsert'>\ntestFeeder=" + json.dumps( thisFeed, indent=4) # load up the new feeder. elif line.lstrip().startswith("<script id='panZoomInsert'>"): print "<script id='panZoomInsert'>\n" + pzData # load up the new feeder. else: print line.rstrip() # os.system('open -a "Google Chrome" ' + '"file://' + tempDir + '/viewer.html"') webbrowser.open_new("file://" + tempDir + '/viewer.html')
def drawTable(path, workDir=None): #return self.log # warnings.filterwarnings("ignore") if path.endswith('.glm'): tree = feeder.parse(path) attachments = [] elif path.endswith('.omd'): with open(path) as f: omd = json.load(f) tree = omd.get('tree', {}) attachments = omd.get('attachments', []) else: raise Exception('Invalid input file type. We require a .glm or .omd.') # Reminder: fuse objects have 'phase_X_status' instead of 'phase_X_state' protDevices = dict.fromkeys( ['fuse', 'recloser', 'switch', 'sectionalizer'], False) #dictionary of protective device initial states for each phase protDevInitStatus = {} #dictionary of protective devices final states for each phase after running Gridlab-D protDevFinalStatus = {} #dictionary of protective device types to help the testing and debugging process protDevTypes = {} protDevOpModes = {} for key in tree: obj = tree[key] obType = obj.get('object') if obType in protDevices.keys(): obName = obj.get('name', '') protDevTypes[obName] = obType if obType != 'fuse': protDevOpModes[obName] = obj.get('operating_mode', 'INDIVIDUAL') protDevices[obType] = True protDevInitStatus[obName] = {} protDevFinalStatus[obName] = {} for phase in ['A', 'B', 'C']: if obType != 'fuse': phaseState = obj.get('phase_' + phase + '_state', 'CLOSED') else: phaseState = obj.get('phase_' + phase + '_status', 'GOOD') if phase in obj.get('phases', ''): protDevInitStatus[obName][phase] = phaseState for key in protDevices.keys(): if protDevices[key]: for phase in ['A', 'B', 'C']: with open(pJoin(workDir, key + '_phase_' + phase + '_state.csv'), newline='') as statusFile: reader = csv.reader(statusFile) # loop past the header, keys = [] vals = [] for row in reader: if '# timestamp' in row: keys = row i = keys.index('# timestamp') keys.pop(i) vals = next(reader) vals.pop(i) for pos, key2 in enumerate(keys): protDevFinalStatus[key2][phase] = vals[pos] html_str = """ <table cellpadding="0" cellspacing="0"> <thead> <tr> <th>Protective Device Name</th> <th>Device Type</th> <th>Initial States</th> <th>Final States</th> <th>Changes</th> </tr> </thead> <tbody>""" for device in protDevInitStatus.keys(): row_str = "<tr><td>" + device + "</td><td>" devType = protDevTypes[device] if devType == 'fuse': row_str += "Fuse (F)</td><td>" elif devType == 'switch': row_str += "Switch (S)</td><td>" elif devType == 'recloser': row_str += "Recloser (R)</td><td>" elif devType == 'sectionalizer': row_str += "Sectionalizer (X)</td><td>" else: row_str += "Unknown</td><td>" for phase in protDevInitStatus[device].keys(): row_str += "Phase " + phase + " = " + protDevInitStatus[device][ phase] + "</br>" row_str += "</td><td>" for phase in protDevFinalStatus[device].keys(): row_str += "Phase " + phase + " = " + protDevFinalStatus[device][ phase] + "</br>" row_str += "</td>" noChange = True change_str = "" for phase in protDevFinalStatus[device].keys(): try: if protDevInitStatus[device][phase] != protDevFinalStatus[ device][phase]: change_str += "Phase " + phase + " : " + protDevInitStatus[ device][phase] + " -> " + protDevFinalStatus[device][ phase] + "</br>" noChange = False except: pass #key error... if noChange: row_str += "<td>No Change" else: row_str += "<td style=\"color: red;\">" row_str += change_str row_str += "</td></tr>" html_str += row_str html_str += """</tbody></table>""" return html_str
import omf.feeder as feeder feed = feeder.parse('./Taxonomy_Feeders-solar/GC-12.47-1-solarAdd.glm') feeder.attachRecorders(feed, 'Inverter', 'object', 'inverter') output = open('./taxo_temp/GC-12.47-1-solarAdd.glm', 'w') output.write(feeder.write(feed)) output.close() # phaseA_V_Out, phaseB_V_Out, phaseC_V_Out, phaseA_I_Out, phaseB_I_Out, phaseC_I_Out;
def read_glm_files(directory): """ Read each .glm file and return a dictionary of dictionaries """ os.chdir(directory) return [feeder.parse(file_path) for file_path in glob.glob("*.glm")]
from omf import feeder from helperfuncs import * if "parsedFeeder" not in globals(): # Parsing the feeder takes a long time, so I don't want to do it every time I modify and re-run this script print "parsing RectorAlphaPhases.glm" parsedFeeder = feeder.parse("RectorAlphaPhases.glm") newtree = {} print "Parsing done, converting dict" for guid, t in parsedFeeder.items(): treeObj = dictcopy(t) if treeObj.get("name") and treeObj.get("phases"): treeObj["id"] = guid newtree[treeObj["name"]] = treeObj print "Done" links = {k:v for k, v in newtree.items() if v.get("from") and v.get("to")} problemlinks = [] print "Finding problem links" for lname, ldata in links.items(): fromnode = newtree[ldata["from"]] tonode = newtree[ldata["to"]] if gp(ldata) != gp(fromnode) or gp(ldata) != gp(tonode): problemlinks.append({ "fromnode":fromnode, "ldata":ldata, "tonode":tonode })
from omf import feeder from helperfuncs import * if "parsedFeeder" not in globals(): # Parsing the feeder takes a long time, so I don't want to do it every time I modify and re-run this script print "parsing RectorAlphaPhases.glm" parsedFeeder = feeder.parse("RectorAlphaPhases.glm") newtree = {} print "Parsing done, converting dict" for guid, t in parsedFeeder.items(): treeObj = dictcopy(t) if treeObj.get("name") and treeObj.get("phases"): treeObj["id"] = guid newtree[treeObj["name"]] = treeObj print "Done" links = {k: v for k, v in newtree.items() if v.get("from") and v.get("to")} problemlinks = [] print "Finding problem links" for lname, ldata in links.items(): fromnode = newtree[ldata["from"]] tonode = newtree[ldata["to"]] if gp(ldata) != gp(fromnode) or gp(ldata) != gp(tonode): problemlinks.append({ "fromnode": fromnode, "ldata": ldata, "tonode": tonode })
from os.path import basename import os # TODO: Put into callable function, with glmFile and playerFolderPath as input glmFile = '../static/testFiles/solarToNegLoads.glm' playerFolderPath = '../static/testFiles/solarToNegLoadPlayerFiles/' solarObjs = [] dieselObjs = [] solarKeys = [] inverterKeys = [] inverters = [] meterKeys = [] meterNames = [] meters = [] invs = [] tree = feeder.parse(glmFile) # Find solar objects for row in tree: if 'object' in tree[row].keys(): if tree[row]['object'] == 'solar': solarObjs.append(tree[row]) solarKeys.append(row) inverters.append(tree[row]['parent']) # Find inverters of solar objs for row in tree: if 'object' in tree[row].keys(): if tree[row]['object'] == 'inverter': if tree[row]['name'] in inverters: inverterKeys.append(row) invs.append(tree[row]) meterNames.append(tree[row]['parent'])
def viz(pathToOmdOrGlm, forceLayout=False, outputPath=None, outputName='viewer.html', open_file=True): ''' Vizualize a distribution system.''' # HACK: make sure we have our homebrew binaries available. os.environ['PATH'] += os.pathsep + '/usr/local/bin' # Load in the feeder. with open(pathToOmdOrGlm, 'r') as feedFile: if pathToOmdOrGlm.endswith('.omd'): thisFeed = { 'tree': json.load(feedFile)['tree'] } # TODO: later bring back attachments. elif pathToOmdOrGlm.endswith('.glm'): thisFeed = {'tree': feeder.parse(pathToOmdOrGlm, filePath=True)} tree = thisFeed['tree'] ## Force layout of feeders with no lat/lon information so we can actually see what's there. if forceLayout: print( 'forceLayout was set to True, so force layout is applied regardless of coordinate detection.' ) insert_coordinates(tree) elif not contains_coordinates(tree): print( 'Warning: no lat/lon coordinates detected, so force layout required.' ) insert_coordinates(tree) # Set up temp directory and copy the feeder and viewer in to it. if outputPath is None: tempDir = tempfile.mkdtemp() else: tempDir = os.path.abspath(outputPath) #HACK: make sure we get the required files from the right place. # shutil.copy(omf.omfDir + '/templates/distNetViz.html', tempDir + '/' + outputName) # shutil.copy(omf.omfDir + '/static/svg-pan-zoom.js', tempDir + '/svg-pan-zoom.js') # Grab the library we need. with open(omf.omfDir + '/static/svg-pan-zoom.js', 'r') as pzFile: pzData = pzFile.read() with open(omf.omfDir + '/static/chroma.min.js', 'r') as chromaFile: chromaData = chromaFile.read() with open(omf.omfDir + '/static/papaparse.min.js', 'r') as papaFile: papaData = papaFile.read() with open(omf.omfDir + '/static/jquery.js', 'r') as jquery_file: jquery_data = jquery_file.read() with open(omf.omfDir + '/static/jquery-ui.min.js', 'r') as jquery_ui_file: jquery_ui_data = jquery_ui_file.read() with open(omf.omfDir + '/static/jquery-ui.min.css', 'r') as jquery_css_file: jquery_css_data = jquery_css_file.read() # TEMPLATE HACKING from jinja2 import Template templateCont = open(omf.omfDir + '/templates/distNetViz.html', 'r+').read() templateString = templateCont.encode('utf-8') template = Template(templateString) def id(): return "" component_json = get_components() rend = template.render(thisFeederData=json.dumps(thisFeed), thisFeederName=pathToOmdOrGlm, thisFeederNum=1, thisModelName="Local Filesystem", thisOwner="NONE", components=component_json, jasmine=None, spec=None, publicFeeders=[], userFeeders=[], csrf_token=id, showFileMenu=True, currentUser=None) with open(tempDir + '/' + outputName, 'w') as outFile: outFile.write(rend) # Insert the panZoom library. # Note: you can't juse open the file in r+ mode because, based on the way the file is mapped to memory, you can only overwrite a line with another of exactly the same length. for line in fileinput.input(tempDir + '/' + outputName, inplace=1): if line.lstrip().startswith( '<link rel="stylesheet" href="/static/jquery-ui.min.css">'): print "" elif line.lstrip().startswith( '<script type="text/javascript" src="/static/jquery.js"></script>' ): print "" elif line.lstrip().startswith( '<script type="text/javascript" src="/static/jquery-ui.min.js"></script>' ): print "" elif line.lstrip().startswith( '<script type="text/javascript" src="/static/svg-pan-zoom.js"></script>' ): print "" elif line.lstrip().startswith( '<script type="text/javascript" src="/static/chroma.min.js"></script>' ): print "" elif line.lstrip().startswith( '<script type="text/javascript" src="/static/papaparse.min.js"></script>' ): print "" elif line.lstrip().startswith( '<link rel="shortcut icon" href="/static/favicon.ico"/>'): print( '<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAQAAAAAAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAioqKAGlpaQDU1NQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiIiIiIAAgACAAIAAgACAzIzMjMyMwIDAgMCAwIDAiIiIiIiIgMCAwEDAgMCAwIDMTMyMzIzAgMBAwIDAgMCIiIiIiIiAwIDAQMCAwIDAgMxMzIzMjMCAwEDAgMCAwIiIiIiIiIDAAMAAwADAAMAAzMzMzMzMwAAAAAAAAAAAABwAAd3cAAEABAABVVQAAAAUAAFVVAABAAQAAVVUAAAAFAABVVQAAQAEAAFVVAAAABQAA3d0AAMABAAD//wAA"/>' ) elif line.lstrip().startswith('<script id="panZoomInsert">'): print '<script id="panZoomInsert">\n' + pzData # load up the new feeder. elif line.lstrip().startswith('<script id="chromaInsert">'): print '<script id="chromaInsert">\n' + chromaData elif line.lstrip().startswith('<script id="papaParseInsert">'): print '<script id="papaParseInsert">\n' + papaData elif line.lstrip().startswith('<script id="jqueryInsert">'): print '<script id="jqueryInsert">\n' + jquery_data elif line.lstrip().startswith('<script id="jqueryUiInsert">'): print '<script id="jqueryUiInsert">\n' + jquery_ui_data elif line.lstrip().startswith('<style id="jqueryCssInsert">'): print '<style id="jqueryCssInsert">\n' + jquery_css_data else: print line.rstrip() # os.system('open -a "Google Chrome" ' + '"file://' + tempDir + '/' + outputName"') # webbrowser.open_new("file://" + tempDir + '/' + outputName) if open_file: open_browser(tempDir, outputName)
def work(modelDir, inputDict): ''' Run the model in its directory. ''' outData = {} feederName = [x for x in os.listdir(modelDir) if x.endswith('.omd')][0][:-4] inputDict['feederName1'] = feederName with open(pJoin(modelDir, feederName + '.omd')) as f: omd = json.load(f) if inputDict.get('layoutAlgorithm', 'geospatial') == 'geospatial': neato = False else: neato = True path = pJoin(modelDir, feederName + '.omd') if path.endswith('.glm'): tree = feeder.parse(path) attachments = [] elif path.endswith('.omd'): with open(path) as f: omd = json.load(f) tree = omd.get('tree', {}) attachments = omd.get('attachments', []) else: raise Exception('Invalid input file type. We require a .glm or .omd.') # dictionary to hold info on lines present in glm edge_bools = dict.fromkeys([ 'underground_line', 'overhead_line', 'triplex_line', 'transformer', 'regulator', 'fuse', 'switch' ], False) # Get rid of schedules and climate and check for all edge types: for key in list(tree.keys()): obtype = tree[key].get('object', '') if obtype == 'underground_line': edge_bools['underground_line'] = True elif obtype == 'overhead_line': edge_bools['overhead_line'] = True elif obtype == 'triplex_line': edge_bools['triplex_line'] = True elif obtype == 'transformer': edge_bools['transformer'] = True elif obtype == 'regulator': edge_bools['regulator'] = True elif obtype == 'fuse': edge_bools['fuse'] = True elif obtype == 'switch': edge_bools['switch'] = True if tree[key].get('argument', '') == '\"schedules.glm\"' or tree[key].get( 'tmyfile', '') != '': del tree[key] # print edge_bools # 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' } tree[str(biggestKey * 10 + 1)] = { 'object': 'currdump', 'filename': 'currDump.csv' } # Line rating dumps tree[feeder.getMaxKey(tree) + 1] = {'module': 'tape'} for key in edge_bools.keys(): if edge_bools[key]: tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=' + key + '"', 'limit': 1, 'property': 'continuous_rating', 'file': key + '_cont_rating.csv' } if edge_bools['regulator']: tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=regulator"', 'limit': 1000, 'property': 'tap_A', 'file': 'tap_A.csv', 'interval': 0 } tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=regulator"', 'limit': 1000, 'property': 'tap_B', 'file': 'tap_B.csv', 'interval': 0 } tree[feeder.getMaxKey(tree) + 1] = { 'object': 'group_recorder', 'group': '"class=regulator"', 'limit': 1000, 'property': 'tap_C', 'file': 'tap_C.csv', 'interval': 0 } # get start and stop time for the simulation [startTime, stopTime] = ['', ''] for key in tree.keys(): obname = tree[key].get('object', '') starttime = tree[key].get('starttime', '') stoptime = tree[key].get('stoptime', '') if starttime != '' and stoptime != '': startTime = tree[key]['starttime'] stopTime = tree[key]['stoptime'] break # Map to speed up name lookups. nameToIndex = {tree[key].get('name', ''): key for key in tree.keys()} # find the key of the relavant added DER components addedDerKey = nameToIndex[inputDict['newGeneration']] addedDerInverterKey = nameToIndex[tree[addedDerKey]['parent']] addedBreakKey = nameToIndex[inputDict['newGenerationBreaker']] poi = tree[addedBreakKey]['to'] # set solar generation to provided insolation value insolation = float(inputDict['newGenerationInsolation']) if insolation > 1000: insolation = 1000 elif insolation < 0: insolation = 0 # cant set insolation directly but without climate object it defaults to 1000 # whilch is about 10x max sun output and we can set shading factor between 0 and 1 # to effectively control insolation tree[addedDerKey]['shading_factor'] = insolation / 1000 # initialize variables flickerViolations = [] flickerThreshold = float(inputDict['flickerThreshold']) voltageViolations = [] [upperVoltThresh, lowerVoltThresh, lowerVoltThresh600] = [1.05, 0.95, 0.975] thermalViolations = [] thermalThreshold = float(inputDict['thermalThreshold']) / 100 reversePowerFlow = [] tapViolations = [] tapThreshold = float(inputDict['tapThreshold']) faults = ['SLG-A', 'SLG-B', 'SLG-C', 'TLG'] faultLocs = [ inputDict['newGenerationBreaker'], inputDict['newGenerationStepUp'] ] faultBreaker = [[] for i in range(2 * len(faults)) ] # the 2 is for the 2 load conditions faultStepUp = [[] for i in range(2 * len(faults))] faultCurrentViolations = [] faultCurrentThreshold = float(inputDict['faultCurrentThreshold']) faultPOIVolts = [] faultVoltsThreshold = float(inputDict['faultVoltsThreshold']) # run analysis for both load conditions for loadCondition in ['Peak', 'Min']: # if a peak load file is provided, use it to set peak loads in the tree if (loadCondition == 'Peak') and (inputDict['peakLoadData'] != ''): peakLoadData = inputDict['peakLoadData'].split('\r\n') for data in peakLoadData: if str(data) != '': key = data.split(',')[0] val = data.split(',')[1] tree[key]['power_12'] = val elif (loadCondition == 'Min'): # if a min load file is provided use is to set the min loads if inputDict['minLoadData'] != '': minLoadData = inputDict['minLoadData'].split('\r\n') for data in minLoadData: if str(data) != '': key = data.split(',')[0] val = data.split(',')[1] tree[key]['power_12'] = val else: # if no min load file is provided set min load to be 1/3 of peak + noise for key in tree.keys(): obtype = tree[key].get('object', '') if obtype == 'triplex_node': load = tree[key].get('power_12', '') if load != '': load = float(load) minLoad = (load / 3) + (load * 0.1 * random.triangular(-1, 1)) tree[key]['power_12'] = str(minLoad) # initialize variables flicker = {} [maxFlickerLocation, maxFlickerVal] = ['', 0] # run analysis with DER on and off under both load conditions for der in ['On', 'Off']: # if der is Off set added DER offline, if its On set DER online if der is 'Off': tree[addedDerKey]['generator_status'] = 'OFFLINE' tree[addedDerInverterKey]['generator_status'] = 'OFFLINE' else: # der is on tree[addedDerKey]['generator_status'] = 'ONLINE' tree[addedDerInverterKey]['generator_status'] = 'ONLINE' # run gridlab solver data = runGridlabAndProcessData(tree, attachments, edge_bools, workDir=modelDir) print(tree[addedDerKey]) print(tree[addedDerInverterKey]) # generate voltage, current and thermal plots filename = 'voltageDer' + der + loadCondition chart = drawPlot(tree, nodeDict=data['percentChangeVolts'], neatoLayout=neato, nodeFlagBounds=[114, 126], defaultNodeVal=120) chart.savefig(pJoin(modelDir, filename + 'Chart.png')) with open(pJoin(modelDir, filename + 'Chart.png'), 'rb') as inFile: outData[filename] = base64.standard_b64encode( inFile.read()).decode('ascii') filename = 'currentDer' + der + loadCondition chart = drawPlot(tree, nodeDict=data['edgeCurrentSum'], neatoLayout=neato) chart.savefig(pJoin(modelDir, filename + 'Chart.png')) with open(pJoin(modelDir, filename + 'Chart.png'), 'rb') as inFile: outData[filename] = base64.standard_b64encode( inFile.read()).decode('ascii') filename = 'thermalDer' + der + loadCondition chart = drawPlot(tree, nodeDict=data['edgeValsPU'], neatoLayout=neato) chart.savefig(pJoin(modelDir, filename + 'Chart.png')) with open(pJoin(modelDir, filename + 'Chart.png'), 'rb') as inFile: outData[filename] = base64.standard_b64encode( inFile.read()).decode('ascii') # calculate max and min voltage and track badwidth violations [maxVoltsLocation, maxVoltsVal] = ['', 0] [minVoltsLocation, minVoltsVal] = ['', float('inf')] for key in data['nodeVolts'].keys(): voltVal = float(data['nodeVolts'][key]) nominalVoltVal = float(data['nominalVolts'][key]) if maxVoltsVal <= voltVal: maxVoltsVal = voltVal maxVoltsLocation = key if minVoltsVal >= voltVal: minVoltsVal = voltVal minVoltsLocation = key change = 100 * ((voltVal - nominalVoltVal) / nominalVoltVal) if voltVal > 600: violation = (voltVal >= (upperVoltThresh*nominalVoltVal)) or \ (voltVal <= (lowerVoltThresh600*nominalVoltVal)) else: violation = (voltVal >= (upperVoltThresh*nominalVoltVal)) or \ (voltVal <= (lowerVoltThresh*nominalVoltVal)) content = [key, nominalVoltVal, voltVal, change, \ loadCondition +' Load, DER ' + der,violation] voltageViolations.append(content) outData['maxVolts' + loadCondition + 'Der' + der] = [maxVoltsLocation, maxVoltsVal] outData['minVolts' + loadCondition + 'Der' + der] = [minVoltsLocation, minVoltsVal] # check for thermal violations for key in data['edgeValsPU'].keys(): thermalVal = float(data['edgeValsPU'][key]) content = [key, 100*thermalVal,\ loadCondition+' Load, DER '+der,(thermalVal>=thermalThreshold)] thermalViolations.append(content) if edge_bools['regulator']: # check for reverse regulator powerflow for key in tree.keys(): obtype = tree[key].get("object", "") obname = tree[key].get("name", "") if obtype == 'regulator': powerVal = float(data['edgePower'][obname]) content = [obname, powerVal,\ loadCondition+' Load, DER '+der,(powerVal<0)] reversePowerFlow.append(content) # check for tap_position values and violations if der == 'On': tapPositions = copy.deepcopy(data['tapPositions']) else: # der off for tapType in ['tapA', 'tapB', 'tapC']: for key in tapPositions[tapType].keys(): # calculate tapPositions tapDerOn = int(tapPositions[tapType][key]) tapDerOff = int(data['tapPositions'][tapType][key]) tapDifference = abs(tapDerOn - tapDerOff) # check for violations content = [loadCondition, key+' '+tapType, tapDerOn, \ tapDerOff,tapDifference, (tapDifference>=tapThreshold)] tapViolations.append(content) #induce faults and measure fault currents for faultLocation in faultLocs: for faultNum, faultType in enumerate(faults): faultIndex = faultNum if loadCondition == 'Min': faultIndex = faultNum + len(faults) treeCopy = createTreeWithFault( tree, \ faultType, faultLocation, startTime, stopTime ) faultData = runGridlabAndProcessData(treeCopy, attachments, \ edge_bools, workDir=modelDir) faultVolts = faultData['nodeVolts'] faultCurrents = faultData['edgeCurrentSum'] # get fault current values at the breaker when # the fault is at the breaker if faultLocation == inputDict['newGenerationBreaker']: if der == 'On': faultBreaker[faultIndex] = [ loadCondition, faultType ] faultBreaker[faultIndex].append(\ float(faultCurrents[\ inputDict['newGenerationBreaker']])) else: #der off faultBreaker[faultIndex].append(\ float(faultCurrents[inputDict['newGenerationBreaker']])) faultBreaker[faultIndex].append(\ faultBreaker[faultIndex][2] - \ faultBreaker[faultIndex][3]) # get fault voltage values at POI preFaultval = data['nodeVolts'][poi] postFaultVal = faultVolts[poi] percentChange = 100 * (postFaultVal / preFaultval) faultPOIVolts.append(['Der '+ der + ' ' + \ loadCondition + ' Load', poi, faultType, preFaultval,\ postFaultVal, percentChange, \ (percentChange>=faultVoltsThreshold)]) # get fault current values at the transformer when # the fault is at the transformer else: #faultLocation == newGenerationStepUp if der == 'On': faultStepUp[faultIndex] = [ loadCondition, faultType ] faultStepUp[faultIndex].append(\ float(faultCurrents[\ inputDict['newGenerationStepUp']])) else: #der off faultStepUp[faultIndex].append(\ float(faultCurrents[inputDict[\ 'newGenerationStepUp']])) faultStepUp[faultIndex].append(\ faultStepUp[faultIndex][2] - \ faultStepUp[faultIndex][3]) # get fault violations when der is on if der == 'On': for key in faultCurrents.keys(): preFaultval = float(data['edgeCurrentSum'][key]) postFaultVal = float(faultCurrents[key]) difference = abs(preFaultval - postFaultVal) if preFaultval == 0: percentChange = 0 else: percentChange = 100 * (difference / preFaultval) content = [loadCondition, faultLocation, faultType, key, \ preFaultval, postFaultVal, percentChange, \ (percentChange>=faultCurrentThreshold)] faultCurrentViolations.append(content) # calculate flicker, keep track of max, and violations if der == 'On': flicker = copy.deepcopy(data['nodeVolts']) else: # der off for key in flicker.keys(): # calculate flicker derOn = float(flicker[key]) derOff = float(data['nodeVolts'][key]) flickerVal = 100 * (1 - (derOff / derOn)) flicker[key] = flickerVal # check for max if maxFlickerVal <= flickerVal: maxFlickerVal = flickerVal maxFlickerLocation = key # check for violations content = [key, flickerVal,loadCondition+' Load',\ (flickerVal>=flickerThreshold)] flickerViolations.append(content) # plot flicker filename = 'flicker' + loadCondition chart = drawPlot(tree, nodeDict=flicker, neatoLayout=neato) chart.savefig(pJoin(modelDir, filename + 'Chart.png')) with open(pJoin(modelDir, filename + 'Chart.png'), "rb") as inFile: outData[filename] = base64.standard_b64encode( inFile.read()).decode('ascii') # save max flicker info to output dictionary outData['maxFlicker' + loadCondition] = [maxFlickerLocation, maxFlickerVal] outData['voltageViolations'] = voltageViolations outData['flickerViolations'] = flickerViolations outData['thermalViolations'] = thermalViolations outData['reversePowerFlow'] = reversePowerFlow outData['tapViolations'] = tapViolations outData['faultBreaker'] = faultBreaker outData['faultStepUp'] = faultStepUp outData['faultCurrentViolations'] = faultCurrentViolations outData['faultPOIVolts'] = faultPOIVolts return outData
def _get_house_archetypes(): return feeder.parse(os.path.join(omf.omfDir, "static/testFiles/houseArchetypes.glm"))
0.0, # TODO: get a windspeed data_hum[i], data_solar[i] * 0.75, # TODO: better solar direct data_solar[i] * 0.25, # TODO: better solar diffuse data_solar[i] ] data_full.append(row) with open(CSV_NAME,'w') as wFile: weather_writer = csv.writer(wFile) weather_writer.writerow(['temperature','wind_speed','humidity','solar_dir','solar_diff','solar_global']) for row in data_full: weather_writer.writerow(row) # Add stuff to the feeder. myTree = feeder.parse(GLM_PATH) # Delete all climate then reinsert. reader_name = 'weatherReader' climate_name = 'MyClimate' for key in myTree.keys(): obName = myTree[key].get('name','') obType = myTree[key].get('object','') if obName in [reader_name, climate_name] or obType is 'climate': del myTree[key] 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':reader_name, 'filename':CSV_NAME} myTree[oldMax + 4] = {'object':'climate', 'name':climate_name, 'reader': reader_name, 'tmyfile':CSV_NAME}