def check_loads_connected(model, verbose=True): all_sources = [] all_loads = set() load_source_map = {} result = True for i in model.models: if isinstance(i, PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i, PowerSource): print( 'Warning - a PowerSource element has a None connecting element' ) if isinstance(i, Load): all_loads.add(i) load_source_map[i.name] = [] if len(all_sources) == 0: print('Model does not contain any power source') return False for source in all_sources: ditto_graph = Network() ditto_graph.build(model, source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches( model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph, source_name) for load in all_loads: min_dist = float('inf') load_connection = load.connecting_element if load_connection in all_paths: load_source_map[load.name].append(source_name) result = True sourceless_loads = [] multi_source_loads = {} for load in load_source_map: if len(load_source_map[load]) == 0: result = False sourceless_loads.append(load) if len(load_source_map[load]) > 1: result = False multi_source_loads[load.name] = load_source_map[load] if verbose: if len(sourceless_loads) > 0: print('Loads missing sources:') for load in sourceless_loads: print(load) if len(multi_source_loads) > 0: print('Loads with multiple sources:') for load in multi_source_loads: print(load + ': ' + multi_source_loads[load]) return result
def check_loops(model, verbose=True): all_sources = [] for i in model.models: if isinstance(i,PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i,PowerSource): print('Warning - a PowerSource element has a None connecting element') # TODO: Address issue in network.py where a single source is used to determine bfs order if len(all_sources) == 0: raise('Model does not contain any power source. Required to build networkx graph') for source in all_sources: print('Checking loops for source '+source.name) ditto_graph = Network() ditto_graph.build(model,source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only loops = nx.cycle_basis(ditto_graph.graph) number_of_loops = len(loops) if number_of_loops > 0: if verbose: print('Loops found:') print(loops) if number_of_loops == 0: return True return False
def check_transformer_phase_path(model, needs_transformers=False, verbose=True): all_sources = [] all_transformers = set() all_loads = set() load_transformer_map = { } # Not used but provides a mapping from load to transformer result = True for i in model.models: if isinstance(i, PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i, PowerSource): print( 'Warning - a PowerSource element has a None connecting element' ) if isinstance(i, PowerTransformer): all_transformers.add(i) if isinstance(i, Load): all_loads.add(i) if len(all_sources) > 1: print('Warning - using first source to orient the network') if len(all_sources) == 0: print('Model does not contain any power source') return False for source in all_sources: ditto_graph = Network() ditto_graph.build(model, source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches( model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph, source_name) break_load = False for load in all_loads: load_connection = load.connecting_element if load_connection in all_paths: ### check that each load has a path to the substation path = all_paths[load_connection] # print(load_connection,path) num_transformers = 0 transformer_names = [] ### check that only zero or one transformers are on the path from load to source (exclude regulators) transformer_low_side = None for i in range(len(path) - 1, 0, -1): element = ditto_graph.graph[path[i]][path[i - 1]] if element['equipment'] == 'PowerTransformer' and not element[ 'is_substation']: #TODO: check if the transformer is part of a regulator. Shouldn't be a problem but could be depending on how regulator defined transformer_names.append(element['name']) num_transformers += 1 if num_transformers == 0 and not element[ 'equipment'] == 'PowerTransformer': transformer_low_side = path[i - 1] ### Check that the low side of the transformer is connected to a line that leads to a load if num_transformers == 1: load_transformer_map[load.name] = element['name'] if model[transformer_names[ 0]].to_element != transformer_low_side: if verbose: print('Load ' + load.name + ' has connected transformer of ' + transformer_names[0] + ' incorrectly connected (likely backwards)') result = False elif num_transformers == 0 and needs_transformers: if verbose: print('Load ' + load.name + ' has no transformers connected.') result = False elif num_transformers > 2: result = False if verbose: print('Load ' + load.name + ' has the following transformers connected: ') for trans in transformer_names: print(trans) if num_transformers == 1 and not needs_transformers: print( 'Warning - transformer found for system where no transformers required between load and customer' ) if num_transformers == 1: low_phases = [ phase_winding.phase for phase_winding in model[ transformer_names[0]].windings[1].phase_windings ] high_phases = [ phase_winding.phase for phase_winding in model[ transformer_names[0]].windings[0].phase_windings ] prev_line_phases = ['A', 'B', 'C' ] # Assume 3 phase power at substation ### If there is a transformer, check that the phases on the low side are consistent with the transformer settign for i in range(len(path) - 1, 0, -1): element = ditto_graph.graph[path[i]][path[i - 1]] if element[ 'equipment'] == 'PowerTransformer' and not element[ 'is_substation']: break if element['equipment'] == 'Line': line_phases = [ wire.phase for wire in element['wires'] if wire.phase != 'N' ] #Neutral phases not included in the transformer if not set(line_phases) == set( low_phases ): #Low phase lines must match transformer exactly if verbose: print( 'Load ' + load.name + ' has incorrect phases on low side of transformer for line ' + element['name']) result = False break elif element['equipment'] != 'Regulator': print('Warning: element of type ' + element['equipment'] + ' found on path to load ' + load.name) ### If there is a transformer, check that there phases on the high side are consistent with the transformer setting, and increase until the substation for i in range(len(path) - 1): element = ditto_graph.graph[path[i]][path[i + 1]] if element[ 'equipment'] == 'PowerTransformer' and not element[ 'is_substation']: break if element['equipment'] == 'Line': line_phases = [ wire.phase for wire in element['wires'] if wire.phase != 'N' ] #Neutral phases not included in the transformer if not set(high_phases).issubset( set(line_phases) ): #MV phase line phase must be able to support transformer phase if verbose: print( 'Load ' + load.name + ' has incorrect phases on high side of transformer for line ' + element['name']) result = False break if len(line_phases) > len(prev_line_phases): if verbose: print( 'Number of phases increases along line ' + element['name'] + ' from ' + str(len(prev_line_phases)) + ' to ' + str(len(line_phases))) result = False break prev_line_phases = line_phases elif element['equipment'] != 'Regulator': print('Warning: element of type ' + element['equipment'] + ' found on path to load ' + load.name) if num_transformers == 0: prev_line_phases = ['A', 'B', 'C' ] # Assume 3 phase power at substation ### If there is no transformer, check that there phases increase until the substation for i in range(len(path) - 1): element = ditto_graph.graph[path[i]][path[i + 1]] if element['equipment'] == 'Line': line_phases = [ wire.phase for wire in element['wires'] if wire.phase != 'N' ] #Neutral phases not included in the transformer if len(line_phases) > len(prev_line_phases): if verbose: print( 'Number of phases increases along line ' + element['name'] + ' from ' + str(len(prev_line_phases)) + ' to ' + str(len(line_phases))) result = False break prev_line_phases = line_phases elif element['equipment'] != 'Regulator' and element[ 'equipment'] != 'PowerTransformer': print('Warning: element of type ' + element['equipment'] + ' found on path to load ' + load.name) return result
def fix_transformer_phase_path(model, needs_transformers=False, verbose=True): all_sources = [] all_transformers = set() all_loads = set() load_transformer_map = { } # Not used but provides a mapping from load to transformer result = True for i in model.models: if isinstance(i, PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i, PowerSource): print( 'Warning - a PowerSource element has a None connecting element' ) if isinstance(i, PowerTransformer): all_transformers.add(i) if isinstance(i, Load): all_loads.add(i) if len(all_sources) > 1: print('Warning - using first source to orient the network') if len(all_sources) == 0: print('Model does not contain any power source') return for source in all_sources: ditto_graph = Network() ditto_graph.build(model, source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches( model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph, source_name) break_load = False for load in all_loads: load_connection = load.connecting_element if load_connection in all_paths: ### check that each load has a path to the substation path = all_paths[load_connection] num_transformers = 0 transformer_names = [] ### Fix the low side of the transformer is connected to a line that leads to a load if possible transformer_low_side = None for i in range(len(path) - 1, 0, -1): element = ditto_graph.graph[path[i]][path[i - 1]] if element['equipment'] == 'PowerTransformer' and not element[ 'is_substation']: #TODO: check if the transformer is part of a regulator. Shouldn't be a problem but could be depending on how regulator defined transformer_names.append(element['name']) num_transformers += 1 if num_transformers == 0 and not element[ 'equipment'] == 'PowerTransformer': transformer_low_side = path[i - 1] ### Check that the low side of the transformer is connected to a line that leads to a load if num_transformers == 1: load_transformer_map[load.name] = element['name'] if model[transformer_names[ 0]].to_element != transformer_low_side: print( 'Load ' + load.name + ' has connected transformer of ' + transformer_names[0] + ' incorrectly connected (likely backwards). Trying to rotate...' ) if model[transformer_names[ 0]].from_element == transformer_low_side: from_element_orig = model[ transformer_names[0]].from_element to_element_orig = model[ transformer_names[0]].to_element model[transformer_names[ 0]].from_element = to_element_orig model[transformer_names[ 0]].to_element = from_element_orig print('Succeeded.') else: print('Failed.')
def check_unique_path(model, needs_transformers=False, show_all=False, verbose =True): all_sources = [] all_transformers = set() all_loads = set() result = True for i in model.models: if isinstance(i,PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i,PowerSource): print('Warning - a PowerSource element has a None connecting element') if isinstance(i,PowerTransformer): all_transformers.add(i) if isinstance(i,Load): all_loads.add(i) if len(all_sources) > 1: print('Warning - using first source to orient the network') if len(all_sources) == 0: print('Model does not contain any power source') return False load_transformer_map = {} all_bad_loads = [] for load in all_loads: load_transformer_map[load.name] = [] for source in all_sources: ditto_graph = Network() ditto_graph.build(model,source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element break_load = False for load in all_loads: ### check there is a unique path from each load to the source two_paths = nx.all_simple_paths(ditto_graph.graph, source_name, load.name) # Warning - this can take a very long time if the graph is has large numbers of loops (shouldn't hold for distribution systems) num_paths = 0 if break_load: break for candidate in two_paths: num_paths +=1 if num_paths > 1: print('Multiple paths from load '+load.name+' to '+source_name) result = False if not show_all: break_load = True all_bad_loads.append((">1",load.name)) break if num_paths == 0: print('No path from load '+load.name+' to ' + source_name) if not show_all: break_load = True all_bad_loads.append(("0",load.name)) result = False break if verbose: if show_all and len(all_bad_loads) >0: print('All bad loads:') elif len(all_bad_loads)>0: print('First bad load:') for load in all_bad_loads: print(load[0],'connections for load',load[1]) all_bad_loads = [] return result
def fix_transformer_phase_path(model,needs_transformers=False,verbose=True): all_sources = [] all_transformers = set() all_loads = set() load_transformer_map = {} # Not used but provides a mapping from load to transformer result = True for i in model.models: if isinstance(i,PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i,PowerSource): print('Warning - a PowerSource element has a None connecting element') if isinstance(i,PowerTransformer): all_transformers.add(i) if isinstance(i,Load): all_loads.add(i) if len(all_sources) > 1: print('Warning - using first source to orient the network') if len(all_sources) == 0: print('Model does not contain any power source') return for source in all_sources: ditto_graph = Network() ditto_graph.build(model,source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph,source_name) break_load = False for load in all_loads: load_connection = load.connecting_element if load_connection in all_paths: ### check that each load has a path to the substation path = all_paths[load_connection] num_transformers = 0 transformer_names = [] ### Fix the low side of the transformer is connected to a line that leads to a load if possible transformer_low_side = None for i in range(len(path)-1,0,-1): element = ditto_graph.graph[path[i]][path[i-1]] if element['equipment'] == 'PowerTransformer' and not element['is_substation']: #TODO: check if the transformer is part of a regulator. Shouldn't be a problem but could be depending on how regulator defined transformer_names.append(element['name']) num_transformers+=1 if num_transformers == 0 and not element['equipment'] == 'PowerTransformer': transformer_low_side = path[i-1] ### Check that the low side of the transformer is connected to a line that leads to a load if num_transformers ==1: load_transformer_map[load.name] = element['name'] if model[transformer_names[0]].to_element != transformer_low_side: print('Load '+load.name+' has connected transformer of '+transformer_names[0]+' incorrectly connected (likely backwards). Trying to rotate...') if model[transformer_names[0]].from_element == transformer_low_side: from_element_orig = model[transformer_names[0]].from_element to_element_orig = model[transformer_names[0]].to_element model[transformer_names[0]].from_element = to_element_orig model[transformer_names[0]].to_element = from_element_orig print('Succeeded.') else: print('Failed.') ### If there is a transformer, check that there phases on the high side are consistent with the transformer setting, and increase until the substation if num_transformers == 1: high_phases = [phase_winding.phase for phase_winding in model[transformer_names[0]].windings[0].phase_windings] for i in range(len(path)-1): element = ditto_graph.graph[path[i]][path[i+1]] if element['equipment'] == 'PowerTransformer' and not element['is_substation']: break if element['equipment'] == 'Line': line_phases = [wire.phase for wire in element['wires'] if wire.phase != 'N'] #Neutral phases not included in the transformer if not set(high_phases).issubset(set(line_phases)): #MV phase line phase must be able to support transformer phase if len(set(high_phases)) == 1 and len(set(line_phases)) == 1: print('Single phase transformer has incorrect phase of '+str(high_phases)+'. Setting to be '+str(line_phases)) high_phases = [phase_winding.phase for phase_winding in model[transformer_names[0]].windings[0].phase_windings] model[transformer_names[0]].windings[0].phase_windings[0].phase = line_phases[0] elif element['equipment'] != 'Regulator': print('Warning: element of type '+element['equipment'] +' found on path to load '+load.name)
def apply(cls, stack, model, *args, **kwargs): path_to_feeder_file = None path_to_switching_devices_file = None center_tap_postprocess = False switch_to_recloser = False if 'path_to_feeder_file' in kwargs: path_to_feeder_file = kwargs['path_to_feeder_file'] if 'path_to_switching_devices_file' in kwargs: path_to_switching_devices_file = kwargs[ 'path_to_switching_devices_file'] if 'switch_to_recloser' in kwargs: switch_to_recloser = kwargs['switch_to_recloser'] if 'center_tap_postprocess' in kwargs: center_tap_postprocess = kwargs['center_tap_postprocess'] #Make sure the voltage of st_mat is set model['st_mat'].nominal_voltage = 230000.0 #Create the modifier object modifier = system_structure_modifier(model, 'st_mat') #Center-tap loads if center_tap_postprocess: modifier.center_tap_load_preprocessing() #Set node nominal voltages modifier.set_nominal_voltages_recur() #Set line nominal voltages modifier.set_nominal_voltages_recur_line() #Create Feeder_metadata objects for each feeder #Open and read feeder.txt with open(path_to_feeder_file, 'r') as f: lines = f.readlines() all_feeder_data = set() for line in lines[1:]: #Parse the line node, sub, feed, sub_trans = map(lambda x: x.strip().lower(), line.split(' ')) sub_trans = sub_trans.replace('ctrafo', 'tr') all_feeder_data.add((feed, sub, sub_trans)) for feeder in all_feeder_data: modifier.set_feeder_metadata(feeder[0], feeder[1], feeder[2]) for i in modifier.model.models: if isinstance(i, PowerSource) and hasattr( i, 'is_sourcebus') and i.is_sourcebus == 1: i.positive_sequence_impedance = complex(1.1208, 3.5169) i.zero_sequence_impedance = complex(1.1208, 3.5169) #Set headnodes for each feeder modifier.set_feeder_headnodes() #Replace all switchin devices ampacity 3000.0 default values with nans #such that we can call the modifier method on it for obj in modifier.model.models: if isinstance(obj, Line): if obj.is_switch == 1 or obj.is_breaker == 1 or obj.is_sectionalizer == 1 or obj.is_fuse == 1 or obj.is_recloser == 1: for w in obj.wires: if w.ampacity == 3000.0: w.ampacity = np.nan #Do the actual mdifications modifier.set_switching_devices_ampacity() #Open the switches which need to be opened. modifier.open_close_switches(path_to_switching_devices_file) tmp_net = Network() tmp_net.build(modifier.model, 'st_mat') tmp_net.set_attributes(modifier.model) tmp_net.remove_open_switches(modifier.model) print(len(nx.cycle_basis(tmp_net.graph))) print(nx.cycle_basis(tmp_net.graph)) #Create the sub-transmission Feeder_metadata if not 'subtransmission' in modifier.model.model_names.keys(): api_feeder_metadata = Feeder_metadata(modifier.model) api_feeder_metadata.name = 'subtransmission' api_feeder_metadata.substation = modifier.source api_feeder_metadata.headnode = modifier.source api_feeder_metadata.transformer = modifier.source #switch to recloser post-processing # if switch_to_recloser: # modifier.replace_first_switch_with_recloser() #Return the modified model return modifier.model
def apply(cls, stack, model, *args, **kwargs): if 'path_to_feeder_file' in kwargs: path_to_feeder_file = kwargs['path_to_feeder_file'] path_to_no_feeder_file = None if 'path_to_no_feeder_file' in kwargs: path_to_no_feeder_file = kwargs['path_to_no_feeder_file'] if 'compute_metrics' in kwargs: compute_metrics = kwargs['compute_metrics'] else: compute_metrics = False if 'compute_kva_density_with_transformers' in kwargs: compute_kva_density_with_transformers = kwargs[ 'compute_kva_density_with_transformers'] else: compute_kva_density_with_transformers = True if compute_metrics: if 'excel_output' in kwargs: excel_output = kwargs['excel_output'] else: raise ValueError('Missing output file name for excel') if 'json_output' in kwargs: json_output = kwargs['json_output'] else: raise ValueError('Missing output file name for json') #Open and read feeder.txt with open(path_to_feeder_file, 'r') as f: lines = f.readlines() #Parse feeder.txt to have the feeder structure of the network feeders = {} substations = {} substation_transformers = {} for line in lines[1:]: #Parse the line node, sub, feed, sub_trans = map(lambda x: x.strip().lower(), line.split(' ')) #If feeder is new, then add it to the feeders dict if feed not in feeders: #Initialize with a list holding the node feeders[feed] = [node.lower().replace('.', '')] #Othewise, just append the node else: feeders[feed].append(node.lower().replace('.', '')) #Same thing for the substation if feed not in substations: substations[feed] = sub.lower().replace('.', '') #Same thing for substation_transformers if feed not in substation_transformers: substation_transformers[feed] = sub.lower().replace('.', '') if path_to_no_feeder_file is not None: with open(path_to_no_feeder_file, 'r') as f: lines = f.readlines() for line in lines[1:]: node, feed = map(lambda x: x.strip().lower(), line.split(' ')) if feed != 'mv-mesh': if 'subtransmission' not in feeders: feeders['subtransmission'] = [ node.lower().replace('.', '') ] else: feeders['subtransmission'].append(node.lower().replace( '.', '')) if 'subtransmission' not in substations: substations['subtransmission'] = '' #Create a network analyzer object network_analyst = NetworkAnalyzer(model) #Add the feeder information to the network analyzer network_analyst.add_feeder_information( list(feeders.keys()), list(feeders.values()), substations, '') #TODO find a way to get the feeder type #Split the network into feeders network_analyst.split_network_into_feeders() #Tag the objects network_analyst.tag_objects() #Set the names network_analyst.model.set_names() # Set reclosers. This algorithm finds to closest 1/3 of goabs to the feeder head (in topological order) # without common ancestry. i.e. no recloser should be upstream of another recloser. If this is not possible, # the number of reclosers is decreased recloser_proportion = 0.33 all_goabs = {} np.random.seed(0) tmp_network = Network() tmp_network.build(network_analyst.model, 'st_mat') tmp_network.set_attributes(network_analyst.model) tmp_network.remove_open_switches(network_analyst.model) tmp_network.rebuild_digraph(network_analyst.model, 'st_mat') sorted_elements = [] for element in nx.topological_sort(tmp_network.digraph): sorted_elements.append(element) for i in network_analyst.model.models: if isinstance( i, Line ) and i.is_switch is not None and i.is_switch and i.name is not None and 'goab' in i.name.lower( ): is_open = False for wire in i.wires: if wire.is_open: is_open = True if is_open: continue if hasattr( i, 'feeder_name' ) and i.feeder_name is not None and i.feeder_name != 'subtransmission': if i.feeder_name in all_goabs: all_goabs[i.feeder_name].append(i.name) else: all_goabs[i.feeder_name] = [i.name] for key in list(all_goabs.keys()): feeder_goabs_dic = { } # Topological sorting done by node. This matches goabs to their end-node for goab in all_goabs[key]: feeder_goabs_dic[ model[goab]. to_element] = goab # shouldn't have multiple switches ending at the same node feeder_goabs = [] feeder_goab_ends = [] for element in sorted_elements: if element in feeder_goabs_dic: feeder_goabs.append(feeder_goabs_dic[element]) feeder_goab_ends.append(element) connectivity_matrix = [[False for i in range(len(feeder_goabs))] for j in range(len(feeder_goabs))] for i in range(len(feeder_goabs)): recloser1 = feeder_goab_ends[i] for j in range(i + 1, len(feeder_goabs)): recloser2 = feeder_goab_ends[j] if recloser2 == recloser1: continue connected = nx.has_path(tmp_network.digraph, recloser1, recloser2) connectivity_matrix[i][j] = connected if connected: connectivity_matrix[j][i] = connected selected_goabs = [] num_goabs = int(len(feeder_goabs) * float(recloser_proportion)) finished = False if num_goabs == 0: finished = True while not finished: for i in range(len(feeder_goabs)): current_set = set([i]) for j in range(i + 1, len(feeder_goabs)): skip_this_one = False for k in current_set: if connectivity_matrix[j][ k]: #i.e. see if the candidate node has common anything upstream or downstream skip_this_one = True break if skip_this_one: continue current_set.add(j) if len(current_set) == num_goabs: break if len(current_set) == num_goabs: finished = True for k in current_set: selected_goabs.append(feeder_goabs[k]) break if not finished: num_goabs -= 1 #selected_goabs = np.random.choice(feeder_goabs,int(len(feeder_goabs)*float(recloser_proportion))) for recloser in selected_goabs: network_analyst.model[recloser].is_switch = False network_analyst.model[recloser].is_recloser = True if network_analyst.model[recloser].wires is not None: for wire in network_analyst.model[recloser].wires: wire.is_switch = False wire.is_recloser = True network_analyst.model[recloser].name = network_analyst.model[ recloser].name.replace('goab', 'recloser') network_analyst.model[recloser].nameclass = 'recloser' network_analyst.model.set_names() #Compute the metrics if needed if compute_metrics: #Compute metrics network_analyst.compute_all_metrics_per_feeder( compute_kva_density_with_transformers= compute_kva_density_with_transformers) #Export metrics to Excel network_analyst.export(excel_output) #Export metrics to JSON network_analyst.export_json(json_output) #Return the model return network_analyst.model
def parse_dg(self, model, **kwargs): """PV parser. :param model: DiTTo model :type model: DiTTo model :returns: 1 for success, -1 for failure :rtype: int """ if not self.use_reopt: return 1 model.set_names() network = Network() network.build(model,source="source") building_map = {} for element in self.geojson_content["features"]: if 'properties' in element and 'type' in element['properties'] and 'buildingId' in element['properties'] and element['properties']['type'] == 'ElectricalJunction': building_map[element['properties']['buildingId']] = element['properties']['id'] for element in self.geojson_content["features"]: if 'properties' in element and 'type' in element['properties'] and element['properties']['type'] == 'Building': id_value = element['properties']['id'] connecting_element = building_map[id_value] feature_data = self.get_feature_data(os.path.join(self.load_folder,id_value,'feature_reports','feature_report_reopt.json')) pv_kw = feature_data['distributed_generation']['total_solar_pv_kw'] upstream_transformer = model[network.get_upstream_transformer(model,connecting_element)] is_center_tap = upstream_transformer.is_center_tap if pv_kw >0: pv = Photovoltaic(model) pv.name = 'solar_'+id_value pv.connecting_element = connecting_element pv.nominal_voltage = upstream_transformer.windings[1].nominal_voltage phases = [] for ph_wdg in upstream_transformer.windings[1].phase_windings: phases.append(Unicode(ph_wdg.phase)) if is_center_tap: phases = [Unicode('A'),Unicode('B')] pv.phases = phases pv.rated_power = pv_kw pv.active_rating = 1.1*pv_kw # Should make this a parameter instead pv.connection_type = upstream_transformer.windings[1].connection_type if self.is_timeseries: load_data = pd.read_csv(os.path.join(self.load_folder,id_value,'feature_reports','feature_report_reopt.csv'),header=0) data = load_data['REopt:ElectricityProduced:PV:Total(kw)'] timestamps = load_data['Datetime'] delta = datetime.datetime.strptime(timestamps.loc[1],'%Y/%m/%d %H:%M:%S') - datetime.datetime.strptime(timestamps.loc[0],'%Y/%m/%d %H:%M:%S') data_pu = data/pv_kw if not self.timeseries_location is None: if not os.path.exists(self.timeseries_location): #Should have already been created for the loads os.makedirs(self.timeseries_location) data.to_csv(os.path.join(self.timeseries_location,'pv_'+id_value+'.csv'),header=False, index=False) data_pu.to_csv(os.path.join(self.timeseries_location,'pv_'+id_value+'_pu.csv'),header=False, index=False) timeseries = Timeseries(model) timeseries.feeder_name = pv.feeder_name timeseries.substation_name = pv.substation_name timeseries.interval = delta.seconds/3600.0 timeseries.data_type = 'float' timeseries.data_location = os.path.join(self.relative_timeseries_location,'pv_'+id_value+'_pu.csv') timeseries.data_label = 'feature_'+id_value timeseries.scale_factor = 1 pv.timeseries = [timeseries] return 1
def parse_loads(self, model, **kwargs): """Load parser. :param model: DiTTo model :type model: DiTTo model :returns: 1 for success, -1 for failure :rtype: int """ model.set_names() network = Network() network.build(model,source="source") building_map = {} for element in self.geojson_content["features"]: if 'properties' in element and 'type' in element['properties'] and 'buildingId' in element['properties'] and element['properties']['type'] == 'ElectricalJunction': building_map[element['properties']['buildingId']] = element['properties']['id'] for element in self.geojson_content["features"]: if 'properties' in element and 'type' in element['properties'] and element['properties']['type'] == 'Building': id_value = element['properties']['id'] connecting_element = building_map[id_value] load = Load(model) load.name = id_value load.connecting_element = connecting_element upstream_transformer = model[network.get_upstream_transformer(model,connecting_element)] is_center_tap = upstream_transformer.is_center_tap load.nominal_voltage = upstream_transformer.windings[1].nominal_voltage load_path = os.path.join(self.load_folder,id_value,'feature_reports') if os.path.exists(load_path): #We've found the load data load_data = None load_column = None if self.use_reopt: load_data = pd.read_csv(os.path.join(load_path,'feature_report_reopt.csv'),header=0) load_column = 'REopt:Electricity:Load:Total(kw)' else: load_data = pd.read_csv(os.path.join(load_path,'default_feature_report.csv'),header=0) load_column = 'Net Power(kW)' max_load = max(load_data[load_column]) phases = [] for ph_wdg in upstream_transformer.windings[1].phase_windings: phases.append(ph_wdg.phase) if is_center_tap: phases = ['A','B'] phase_loads = [] for phase in phases: phase_load = PhaseLoad(model) phase_load.phase = phase power_factor = 0.95 phase_load.p = max_load/len(phases) phase_load.q = phase_load.p * ((1/power_factor-1)**0.5) phase_loads.append(phase_load) load.phase_loads = phase_loads if self.is_timeseries: data = load_data[load_column] timestamps = load_data['Datetime'] delta = datetime.datetime.strptime(timestamps.loc[1],'%Y/%m/%d %H:%M:%S') - datetime.datetime.strptime(timestamps.loc[0],'%Y/%m/%d %H:%M:%S') data_pu = data/max_load if not self.timeseries_location is None: if not os.path.exists(self.timeseries_location): os.makedirs(self.timeseries_location) data.to_csv(os.path.join(self.timeseries_location,'load_'+id_value+'.csv'),header=False, index=False) data_pu.to_csv(os.path.join(self.timeseries_location,'load_'+id_value+'_pu.csv'),header=False, index=False) timestamps.to_csv(os.path.join(self.timeseries_location,'timestamps.csv'),header=True,index=False) timeseries = Timeseries(model) timeseries.feeder_name = load.feeder_name timeseries.substation_name = load.substation_name timeseries.interval = delta.seconds/3600.0 #assume 15 minute loads timeseries.data_type = 'float' timeseries.data_location = os.path.join(self.relative_timeseries_location,'load_'+id_value+'_pu.csv') timeseries.data_label = 'feature_'+id_value timeseries.scale_factor = 1 load.timeseries = [timeseries] else: print('Load information missing for '+id_value) return 1
def fix_undersized_transformers(model,verbose=True): all_sources = [] all_transformers = set() all_loads = set() transformer_load_map = {} # provides a mapping of all the loads connected to a transformer if there's a path from the load to a source result = True transformer_sizes = [15,25,50,75,100,300,500,1000,2000,3000,5000] # upgrade sizes in kva model.set_names() for i in model.models: if isinstance(i,PowerSource) and i.connecting_element is not None: all_sources.append(i) elif isinstance(i,PowerSource): print('Warning - a PowerSource element has a None connecting element') if isinstance(i,PowerTransformer): all_transformers.add(i) if isinstance(i,Load): all_loads.add(i) if len(all_sources) > 1: print('Warning - using first source to orient the network') if len(all_sources) == 0: print('Model does not contain any power source') return for source in all_sources: ditto_graph = Network() ditto_graph.build(model,source.connecting_element) ditto_graph.set_attributes(model) ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph,source_name) break_load = False for load in all_loads: load_connection = load.connecting_element if load_connection in all_paths: ### check that each load has a path to the substation path = all_paths[load_connection] num_transformers = 0 transformer_names = [] for i in range(len(path)-1,0,-1): element = ditto_graph.graph[path[i]][path[i-1]] if element['equipment'] == 'PowerTransformer' and not element['is_substation']: #TODO: check if the transformer is part of a regulator. Shouldn't be a problem but could be depending on how regulator defined transformer_names.append(element['name']) num_transformers+=1 ### If the transformer is connected correctly with the load underneath it, update the transformer-load mapping dictionary if num_transformers ==1: if not transformer_names[0] in transformer_load_map: transformer_load_map[transformer_names[0]] = [] transformer_load_map[transformer_names[0]].append(load.name) for transformer in transformer_load_map: transformer_size = model[transformer].windings[0].rated_power/1000 total_load_kw = 0 for load in transformer_load_map[transformer]: load_kw = 0 for phase_load in model[load].phase_loads: total_load_kw += phase_load.p/1000 load_kw += phase_load.p/1000 if transformer_size <total_load_kw: new_transformer_size = None for sz in transformer_sizes: if sz > total_load_kw: new_transformer_size = sz break if new_transformer_size is not None: print(f'Tranformer {transformer} with size {transformer_size} kVA serves {total_load_kw} kW. Upgrading to {new_transformer_size} kVA') for winding in model[transformer].windings: winding.rated_power = new_transformer_size*1000 else: print(f'Tranformer {transformer} with size {transformer_size} kVA serves {total_load_kw} kW. No upgrade size found (max is 5000 kVA)')
def apply(cls, stack, model, feeders=100, equipment_type=None, selection=('Random', 15), seed=0, placement_folder=''): subset = [] if selection is None: return model # TODO: Apply placements to feeders selectively. Currently applying to all equipment in the distribution system # Currently just including random selections. Need to also include and document other selection options if selection[0] == 'Reclosers': # Get a subset of nodes at the end of Reclosers. This algorithm finds to closest goabs to the feeder head (in topological order) # without common ancestry. i.e. no goab should be upstream of another goab. If this is not possible, # the number of reclosers is decreased # Percentage of feeders that each Reclosers number is used for e.g. if selection[1] = 4 and feeders = [50,40,30,20], 50% of feedes have 1st goab, 40% of feeders have second etc. feeders_str = str(feeders) if isinstance(feeders, list): feeders_str = '' for f in feeders: feeders_str = feeders_str + str(f) + '-' feeders_str = feeders_str.strip('-') file_name = str(feeders_str) + '_Node_' + selection[0] + '-' + str( selection[1]) + '-' + str(selection[2]) + '.json' all_goabs = {} random.seed(seed) tmp_network = Network() tmp_network.build(model, 'st_mat') tmp_network.set_attributes(model) tmp_network.remove_open_switches(model) tmp_network.rebuild_digraph(model, 'st_mat') sorted_elements = [] for element in nx.topological_sort(tmp_network.digraph): sorted_elements.append(element) for i in model.models: if isinstance( i, Line) and i.is_recloser is not None and i.is_recloser: is_open = False for wire in i.wires: if wire.is_open: is_open = True if is_open: continue if hasattr( i, 'feeder_name' ) and i.feeder_name is not None and i.feeder_name != 'subtransmission': if i.feeder_name in all_goabs: all_goabs[i.feeder_name].append(i.name) else: all_goabs[i.feeder_name] = [i.name] goab_key_list = list(all_goabs.keys()) random.seed(seed) random.shuffle(goab_key_list) goab_counter = 0 for key in goab_key_list: goab_counter += 1 feeder_goabs_dic = { } # Topological sorting done by node. This matches goabs to their end-node for goab in all_goabs[key]: feeder_goabs_dic[ model[goab]. to_element] = goab # shouldn't have multiple switches ending at the same node feeder_goabs = [] feeder_goab_ends = [] for element in sorted_elements: if element in feeder_goabs_dic: feeder_goabs.append(feeder_goabs_dic[element]) feeder_goab_ends.append(element) connectivity_matrix = [[ False for i in range(len(feeder_goabs)) ] for j in range(len(feeder_goabs))] for i in range(len(feeder_goabs)): goab1 = feeder_goab_ends[i] for j in range(i + 1, len(feeder_goabs)): goab2 = feeder_goab_ends[j] if goab1 == goab2: continue connected = nx.has_path(tmp_network.digraph, goab1, goab2) connectivity_matrix[i][j] = connected if connected: connectivity_matrix[j][i] = connected selected_goabs = [] num_goabs = selection[2] finished = False if num_goabs == 0: finished = True while not finished: for i in range(len(feeder_goabs)): current_set = set([i]) for j in range(i + 1, len(feeder_goabs)): skip_this_one = False for k in current_set: if connectivity_matrix[j][ k]: #i.e. see if the candidate node has common anything upstream or downstream skip_this_one = True break if skip_this_one: continue current_set.add(j) if len(current_set) == num_goabs: break if len(current_set) == num_goabs: finished = True for k in current_set: selected_goabs.append(feeder_goabs[k]) break if not finished: num_goabs -= 1 for i in range(min(len(selected_goabs), selection[1])): if goab_counter / float( len(goab_key_list)) * 100 <= feeders[i]: subset.append(model[selected_goabs[i]].to_element) if selection[0] == 'Random': class_equipment_type = locate(equipment_type) all_equipment = [] for i in model.models: if isinstance(i, class_equipment_type): all_equipment.append(i.name) if len(selection) == 3: random.seed(seed) random.shuffle(all_equipment) start_pos = math.floor( len(all_equipment) * float(selection[1]) / 100.0) end_pos = math.floor( len(all_equipment) * float(selection[2]) / 100.0) subset = all_equipment[start_pos:end_pos] file_name = str(feeders) + '_' + equipment_type.split( '.')[-1] + '_' + selection[0] + '-' + str( selection[1]) + '-' + str( selection[2]) + '_' + str(seed) + '.json' if len(selection) == 2: random.seed(seed) subset = random.sample( all_equipment, math.floor( len(all_equipment) * float(selection[1]) / 100.0)) file_name = str(feeders) + '_' + equipment_type.split( '.')[-1] + '_' + selection[0] + '-' + str( selection[1]) + '_' + str(seed) + '.json' if not os.path.exists(placement_folder): os.makedirs(placement_folder) with open(os.path.join(placement_folder, file_name), "w") as f: f.write(json_tricks.dumps(subset, sort_keys=True, indent=4)) return model