diff --git a/ditto/metrics/network_analysis.py b/ditto/metrics/network_analysis.py index b446b24c..f5dc1d6f 100644 --- a/ditto/metrics/network_analysis.py +++ b/ditto/metrics/network_analysis.py @@ -157,7 +157,7 @@ def __init__(self, model, compute_network=True, *args): self.edge_equipment_name = None modifier = system_structure_modifier(self.model, source) - modifier.set_nominal_voltages() + modifier.set_nominal_voltages_recur() # IMPORTANT: the following two parameters define what is LV and what is MV. # - Object is LV if object.nominal_voltage<=LV_threshold # - Object is MV if MV_threshold>=object.nominal_voltage>LV_threshold @@ -205,12 +205,10 @@ def add_feeder_information( """ Use this function to add the feeder information if available. - :param feeder_names: List of the feeder names - :type feeder_names: List(str) - :param feeder_nodes: List of lists containing feeder nodes - :type feeder_nodes: List of Lists of strings - :param feeder_types: List of feeder types. - :type feeder_types: List or string if all feeders have the same type + :param feeder_names: List(str) of the feeder names + :param feeder_nodes: List of lists of strings containing feeder nodes + :param substations: List(str) of the substations names + :param feeder_types: List(str) of feeder types or string if all feeders have the same type """ if len(feeder_names) != len(feeder_nodes): raise ValueError( @@ -465,7 +463,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): @@ -498,7 +496,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): @@ -525,7 +523,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index b607c28a..7d6274ff 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -224,9 +224,10 @@ def set_nominal_voltages_recur(self, *args): previous = self.source else: node, voltage, previous = args - if (previous, node) in self.edge_equipment and self.edge_equipment[ - (previous, node) - ] == "PowerTransformer": + if ( + (previous, node) in self.edge_equipment and + self.edge_equipment[(previous, node)] == "PowerTransformer" + ): trans_name = self.edge_equipment_name[(previous, node)] new_value = min( [ @@ -235,9 +236,10 @@ def set_nominal_voltages_recur(self, *args): if w.nominal_voltage is not None ] ) - elif (node, previous) in self.edge_equipment and self.edge_equipment[ - (node, previous) - ] == "PowerTransformer": + elif ( + (node, previous) in self.edge_equipment and + self.edge_equipment[(node, previous)] == "PowerTransformer" + ): trans_name = self.edge_equipment_name[(node, previous)] new_value = min( [ @@ -248,8 +250,11 @@ def set_nominal_voltages_recur(self, *args): ) else: new_value = voltage + if hasattr(self.model[node], "nominal_voltage"): - self.model[node].nominal_voltage = new_value + if new_value != self.model[node].nominal_voltage: + logger.debug(f"Setting node {node} nominal voltage to {new_value} from {self.model[node].nominal_voltage}") + self.model[node].nominal_voltage = new_value for child in self.G.digraph.successors(node): self.set_nominal_voltages_recur(child, new_value, node) @@ -520,205 +525,6 @@ def test_feeder_cut(self): ) logger.debug(intersection) - def set_nominal_voltages(self): - """This function does the exact same thing as _set_nominal_voltages. - The implementation is less obvious but should be much faster. - - **Algorithm:** - - - Find all edges modeling transformers in the network - - Disconnect these edges (which should disconnect the network) - - Compute the connected components - - Group the nodes according to these connected components - - Re-connect the network by adding back the removed edges - - For every group of nodes: - - Find the nominal voltage of one node (look at secondary voltage of the upstream transformer) - - All nodes in this group get the same nominal voltage - - For every line: - - Set nominal voltage as the nominal voltage of one of the end-points (that we have thanks to the previous loop...) - - .. note:: This should be faster than _set_nominal_voltages since we only look upstream once for every group instead of doing it once for every node. - - .. warning:: Use set_nominal_voltages_recur instead. - - .. TODO:: Find out why the results of this and set_nominal_voltages_recur don't match... - """ - self.model.set_names() - - # We will remove all edges representing transformers - edges_to_remove = [ - edge - for edge in self.G.graph.edges(data=True) - if "equipment" in edge[2] and edge[2]["equipment"] == "PowerTransformer" - ] - - # Do it!! - self.G.graph.remove_edges_from(edges_to_remove) - - # Get the connected components - cc = nx.connected_components(self.G.graph) - - # Extract the groups of nodes with same nominal voltage - node_mapping = [component for component in cc] - - # Restaure the network by addind back the edges previously removed - self.G.graph.add_edges_from(edges_to_remove) - - # Graph should be connected, otherwise we broke it... - assert nx.is_connected(self.G.graph) - - # Instanciate the list of nominal voltages (one value for each group) - nominal_voltage_group = [None for _ in node_mapping] - upstream_transformer_name_group = [None for _ in node_mapping] - - # For every group... - for idx, group in enumerate(node_mapping): - - # ...first node is volonteered to be searched - volonteer = group.pop() - while not isinstance(self.model[volonteer], Node): - volonteer = group.pop() - - # Get the name of the upstream transformer - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, volonteer - ) - - # If we got None, there is nothing we can do. Otherwise... - if upstream_transformer_name is not None: - - # ...get the transformer object - upstream_transformer_object = self.model[upstream_transformer_name] - upstream_transformer_name_group[idx] = upstream_transformer_name - - # Get the nominal voltage of the secondary - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - - secondary_voltage = min(volts) - # And assign this value as the nominal voltage for the group of nodes - nominal_voltage_group[idx] = secondary_voltage - - # Now, we simply loop over all the groups - for idx, group in enumerate(node_mapping): - - # And all the nodes inside of the groups - for n in group: - - # And set the nominal voltage as the group value - self.model[n].nominal_voltage = nominal_voltage_group[idx] - if isinstance(self.model[n], Load): - self.model[ - n - ].upstream_transformer_name = upstream_transformer_name_group[idx] - - # Now we take care of the Lines. - # Since we should have the nominal voltage for every node (in a perfect world), - # We just have to grab the nominal voltage of one of the end-points. - for obj in self.model.models: - # If we get a line - if isinstance(obj, Line) and obj.nominal_voltage is None: - # Get the from node - if hasattr(obj, "from_element") and obj.from_element is not None: - node_from_object = self.model[obj.from_element] - - # If the from node has a known nominal voltage, then use this value - if ( - hasattr(node_from_object, "nominal_voltage") - and node_from_object.nominal_voltage is not None - ): - obj.nominal_voltage = node_from_object.nominal_voltage - - def _set_nominal_voltages(self): - """This function looks for all nodes and lines which have empty nominal_voltage fields. - The function goes upstream in the network representation to find a transformer. - The nominal voltage of the secondary of this transformer is then used to fill the empty nominal_voltage fields. - - .. warning:: DO NOT USE. Use set_nominal_voltages instead - - .. TODO:: Remove this once everything is stable. - """ - self.model.set_names() - # Loop over the objects - for obj in self.model.models: - - # If we get a Node with an empty nominal_voltage field - if isinstance(obj, Node) and obj.nominal_voltage is None: - # Get the upstream transformer name - try: - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, obj.name - ) - except: - continue - if upstream_transformer_name is not None: - # Get the corresponding object - upstream_transformer_object = self.model[upstream_transformer_name] - # If possible, get all the winding voltages and select the minimum as the secondary voltage - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - secondary_voltage = min(volts) - # Finally, assign this value to the object's nominal voltage - obj.nominal_voltage = secondary_voltage - - # If we get a line - if isinstance(obj, Line) and obj.nominal_voltage is None: - # Get the from node - if hasattr(obj, "from_element") and obj.from_element is not None: - node_from_object = self.model[obj.from_element] - - # If the from node has a known nominal voltage, then use this value - if ( - hasattr(node_from_object, "nominal_voltage") - and node_from_object.nominal_voltage is not None - ): - obj.nominal_voltage = node_from_object.nominal_voltage - - # Otherwise, do as before with the from node - # - # TODO: Put the following code into a function - # - else: - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, node_from_object.name - ) - if upstream_transformer_name is not None: - upstream_transformer_object = self.model[ - upstream_transformer_name - ] - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - secondary_voltage = min(volts) - obj.nominal_voltage = secondary_voltage - def center_tap_load_preprocessing(self): """Performs the center tap load pre-processing step. This function is responsible for setting the correct phase of center-tap loads. diff --git a/ditto/network/network.py b/ditto/network/network.py index 8133b572..7bdd27ca 100644 --- a/ditto/network/network.py +++ b/ditto/network/network.py @@ -15,17 +15,11 @@ class Network: def __init__(self): - self.graph = None - self.digraph = None # Doesn't contain attributes, just topology - self.class_map = ( - {} - ) # Map the networkx names to the object type (not included in attributes) - self.is_built = ( - False # Flag that indicates whether the Network has been built or not. - ) - self.attributes_set = ( - False # Flag that indicates whether the attributes have been set or not. - ) + self.graph = None # becomes nx.Graph() in self.build + self.digraph = None # becomes nx.DiGraph() in self.build. Doesn't contain attributes, just topology + self.class_map = {} # Map the networkx names to the object type (not included in attributes) + self.is_built = False # Flag that indicates whether the Network has been built or not. + self.attributes_set = False # Flag that indicates whether the attributes have been set or not. def provide_graphs(self, graph, digraph): """ @@ -346,7 +340,7 @@ def remove_open_switches(self, model): if self.digraph.has_edge(m.to_element, m.from_element): self.digraph.remove_edge(m.to_element, m.from_element) - def get_upstream_transformer(self, model, node): + def get_upstream_transformer(self, node): curr_node = node curr = list(self.digraph.predecessors(node)) @@ -369,21 +363,19 @@ def get_all_elements_downstream(self, model, source): model.set_names() # Checking that the network is already built - # TODO: Log instead of printing... if not self.is_built: - logger.debug( + logger.warn( "Warning. Trying to use Network model without building the network." ) - logger.debug("Calling build() with source={}".format(source)) + logger.warn("Calling build() with source={}".format(source)) self.build(model, source=source) # Checking that the attributes have been set - # TODO: Log instead of printing... if not self.attributes_set: - logger.debug( + logger.warn( "Warning. Trying to use Network model without setting the attributes first." ) - logger.debug("Setting the attributes...") + logger.warn("Setting the attributes...") self.set_attributes(model) # Run the dfs or die trying... diff --git a/ditto/store.py b/ditto/store.py index e6f53b7f..47663ec2 100644 --- a/ditto/store.py +++ b/ditto/store.py @@ -187,9 +187,7 @@ def set_node_voltages(self): self.set_names() for i in self.models: if isinstance(i, Node) and hasattr(i, "name") and i.name is not None: - upstream_transformer = self._network.get_upstream_transformer( - self, i.name - ) + upstream_transformer = self._network.get_upstream_transformer(i.name) try: upstream_voltage = ( self[upstream_transformer].windings[-1].nominal_voltage