Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System structure modifier updates #407

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0d2ca67
system_structure_modifier: delete method that has warning not to use it
nlaws-camus Sep 20, 2022
8a10247
use the recommended system_structure_modifier.set_nominal_voltages_re…
nlaws-camus Sep 20, 2022
3ffe4e4
refactor `is not ""` to correct `!= ""` in NetworkAnalyzer
nlaws-camus Sep 20, 2022
4929915
rm broken, unused set_nominal_voltages in system_structure_modifier
nlaws-camus Sep 20, 2022
1015822
minor formatting in system_structure_modifier
nlaws-camus Sep 20, 2022
0aca2f5
add debug msg when changing node nominal_voltage in system_structure_…
nlaws-camus Sep 20, 2022
0f0a3a5
rm unused arg from Network.get_upstream_transformer
nlaws-camus Sep 20, 2022
499d951
update network_analysis.py doc string
nlaws-camus Sep 20, 2022
642a8bf
minor formatting in Network.__init__
nlaws-camus Sep 20, 2022
8e00e70
make warnings logger.warn (were logger.debug)
nlaws-camus Sep 20, 2022
7ff8a18
system_structure_modifier: delete method that has warning not to use it
nlaws-camus Sep 20, 2022
5672f83
use the recommended system_structure_modifier.set_nominal_voltages_re…
nlaws-camus Sep 20, 2022
0fa7227
refactor `is not ""` to correct `!= ""` in NetworkAnalyzer
nlaws-camus Sep 20, 2022
f319687
rm broken, unused set_nominal_voltages in system_structure_modifier
nlaws-camus Sep 20, 2022
9a5e238
minor formatting in system_structure_modifier
nlaws-camus Sep 20, 2022
3be9558
add debug msg when changing node nominal_voltage in system_structure_…
nlaws-camus Sep 20, 2022
06ba51b
rm unused arg from Network.get_upstream_transformer
nlaws-camus Sep 20, 2022
9300e45
update network_analysis.py doc string
nlaws-camus Sep 20, 2022
411c518
minor formatting in Network.__init__
nlaws-camus Sep 20, 2022
6b4396d
make warnings logger.warn (were logger.debug)
nlaws-camus Sep 20, 2022
66414a8
Merge branch 'sys-struct-modifier-updates' of https://github.com/nlaw…
nlaws-camus Nov 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions ditto/metrics/network_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
):
Expand Down Expand Up @@ -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
):
Expand All @@ -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
):
Expand Down
218 changes: 12 additions & 206 deletions ditto/modify/system_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand All @@ -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(
[
Expand All @@ -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)

Expand Down Expand Up @@ -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.
Expand Down
28 changes: 10 additions & 18 deletions ditto/network/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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))
Expand All @@ -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...
Expand Down
4 changes: 1 addition & 3 deletions ditto/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down