Skip to content

Commit

Permalink
Merge pull request #592 from mfranz13/develop
Browse files Browse the repository at this point in the history
Added unsupplied_junctions and elements_on_path to pandapipes
  • Loading branch information
dlohmeier committed Mar 26, 2024
2 parents 613fe60 + ac6b9b2 commit 2883f24
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 4 deletions.
78 changes: 78 additions & 0 deletions src/pandapipes/plotting/pipeflow_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import pandapipes.topology as top
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np


def pressure_profile_to_junction_geodata(net):
Expand Down Expand Up @@ -36,3 +38,79 @@ def pressure_profile_to_junction_geodata(net):
return pd.DataFrame({"x": dist.loc[junctions].values,
"y": net.res_junction.p_bar.loc[junctions].values},
index=junctions)


def plot_pressure_profile(net, ax=None, x0_junctions=None, plot_pressure_controller=True, xlabel="Distance from Slack in km",
ylabel="Pressure in bar", x0=0, pipe_color="tab:grey", pc_color="r",
junction_color="tab:blue", junction_size=3, pipes=None, **kwargs):
"""Plot the pressure profile depending on the distance from the x0_junction (slack).
Parameters
----------
net : pp.PandapowerNet
net including pipeflow results
ax : matplotlib.axes, optional
axis to plot to, by default None
x0_junctions : Any[list[int], pd.Index[int]], optional
list of junction indices which should be at position x0. If None, all in service slack junctions are considered,
by default None
plot_pressure_controller : bool, optional
Whether vertical lines should be plotted to display the pressure drop of the pressure controller,
by default True
xlabel : str, optional
xlable of the figure, by default "Distance from Slack in km"
ylabel : str, optional
ylable of the figure, by default "Pressure in bar"
x0 : int, optional
x0_junction position at the xaxis, by default 0
pipe_color : str, optional
color used to plot the pipes, by default "tab:grey"
pc_color : str, optional
color used to plot the pressure controller, by default "r"
junction_color : [str, dict[int, str]], optional
colors used to plot the junctions. Can be passed as string (to give all junctions the same color),
or as dict, by default "tab:blue"
junction_size : int, optional
size of junction representations, by default 3
pipes : Any[list[int], pd.Index[int]], optional
list of pipe indices which should be plottet. If None, all pipes are plotted, by default None
Returns
-------
matplotlib.axes
axis of the plot
"""
if ax is None:
plt.figure(facecolor="white", dpi=120)
ax = plt.gca()
if not net.converged:
raise ValueError("no results in this pandapipes network")
if pipes is None:
pipes = net.pipe.index
if x0_junctions is None:
x0_junctions = net.ext_grid[net.ext_grid.in_service].junction.values.tolist()

d = top.calc_distance_to_junctions(net, x0_junctions)
pipe_table = net.pipe[net.pipe.in_service & net.pipe.index.isin(pipes)]

x = np.array([d.loc[pipe_table.from_junction].values, d.loc[pipe_table.to_junction].values]) + x0
y = np.array([net.res_junction.p_bar.loc[pipe_table.from_junction].values, net.res_junction.p_bar.loc[pipe_table.to_junction].values])
linewidth = kwargs.get("linewidth", 1)
ax.plot(x, y, linewidth=linewidth, color=pipe_color, **kwargs)

x = d.values + x0
y = net.res_junction.p_bar.loc[d.index]
ax.plot(x, y, 'o', color=junction_color, ms=junction_size)

if plot_pressure_controller and ("press_control" in net.keys()):
pressure_controller = net.press_control.query('in_service')
x = np.array([d.loc[pressure_controller.from_junction].values, d.loc[pressure_controller.to_junction].values]) + x0
y = np.array([net.res_junction.p_bar.loc[pressure_controller.from_junction].values, net.res_junction.p_bar.loc[pressure_controller.to_junction].values])
ax.plot(x, y, color=pc_color, **{k: v for k, v in kwargs.items() if not k == "color"})

if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
return ax
34 changes: 34 additions & 0 deletions src/pandapipes/test/topology/test_graph_searches.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import pandapipes.networks as nw
import pandapipes.topology as top
import networkx as nx
import pytest


def test_connected_components():
Expand All @@ -18,3 +20,35 @@ def test_connected_components():
assert len(list(top.connected_components(mg))) == 6
mg = top.create_nxgraph(net, include_pipes=False, include_valves=False)
assert len(list(top.connected_components(mg))) == 8


def test_calc_distance_to_junctions():
net = nw.gas_versatility()
egs = net.ext_grid.junction
assert top.calc_distance_to_junctions(net, egs, respect_status_valves=True).sum() == 30.93
assert top.calc_distance_to_junctions(net, egs, respect_status_valves=False).sum() == 26.96


def test_unsupplied_buses_with_in_service():
net = nw.gas_versatility()
assert top.unsupplied_junctions(net) == set()
net.pipe.loc[7, "in_service"] = False
assert top.unsupplied_junctions(net) == {8}


def test_elements_on_path():
net = nw.gas_versatility()
for multi in [True, False]:
mg = top.create_nxgraph(net, multi=multi)
path = nx.shortest_path(mg, 0, 6)
assert top.elements_on_path(mg, path, "pipe") == [0, 9]
assert top.elements_on_path(mg, path) == [0, 9]
assert top.elements_on_path(mg, path, "valve") == []
assert top.elements_on_path(mg, path, "pump") == [0]
with pytest.raises(ValueError) as exception_info:
top.elements_on_path(mg, path, element="source")
assert str(exception_info.value) == "Invalid element type source"


if __name__ == "__main__":
pytest.main([__file__])
1 change: 1 addition & 0 deletions src/pandapipes/topology/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

from pandapipes.topology.create_graph import *
from pandapipes.topology.topology_toolbox import *
from pandapipes.topology.graph_searches import *
from pandapower.topology.graph_searches import connected_component, connected_components
112 changes: 108 additions & 4 deletions src/pandapipes/topology/graph_searches.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import networkx as nx
import pandas as pd
from pandapipes.topology.create_graph import create_nxgraph
from pandapipes.topology.topology_toolbox import get_all_branch_component_table_names


def calc_distance_to_junction(net, junction, notravjunctions=None, nogojunctions=None,
Expand Down Expand Up @@ -78,7 +79,110 @@ def calc_minimum_distance_to_junctions(net, junctions, notravjunctions=None, nog
return pd.Series(nx.single_source_dijkstra_path_length(mg, junction))


if __name__ == '__main__':
import pandapipes.networks as nw
net = nw.gas_meshed_delta()
dist = calc_minimum_distance_to_junctions(net, net.ext_grid.junction.values)
def calc_distance_to_junctions(net, junctions, respect_status_valves=True, notravjunctions=None, nogojunctions=None, weight="weight"):
"""
Calculates the shortest distance between every source junction and all junctions connected to it.
INPUT:
**net** (pandapipesNet) - Variable that contains a pandapipes network.
**junctions** (integer) - Index of the source junctions.
OPTIONAL:
**respect_status_valve** (boolean, True) - Flag whether the "opened" column shall be considered and out\
of service valves neglected.
**nogojunctions** (integer/list, None) - nogojunctions are not being considered
**notravjunctions** (integer/list, None) - lines connected to these junctions are not being
considered
**weight** (string, None) – Edge data key corresponding to the edge weight
OUTPUT:
**dist** - Returns a pandas series with containing all distances to the source junction
in km. If weight=None dist is the topological distance (int).
EXAMPLE:
import pandapipes.topology as top
dist = top.calc_distance_to_junctions(net, [5, 6])
"""
g = create_nxgraph(net, respect_status_valves=respect_status_valves, nogojunctions=nogojunctions,
notravjunctions=notravjunctions)
d = nx.multi_source_dijkstra_path_length(g, set(junctions), weight=weight)
return pd.Series(d)


def unsupplied_junctions(net, mg=None, slacks=None, respect_valves=True):
"""
Finds junctions, that are not connected to an external grid.
INPUT:
**net** (pandapipesNet) - variable that contains a pandapipes network
OPTIONAL:
**mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapipes network.
**in_service_only** (boolean, False) - Defines whether only in service junctions should be
included in unsupplied_junctions.
**slacks** (set, None) - junctions which are considered as root / slack junctions. If None, all
existing slack junctions are considered.
**respect_valves** (boolean, True) - Fixes how to consider valves - only in case of no
given mg.
OUTPUT:
**uj** (set) - unsupplied junctions
EXAMPLE:
import pandapipes.topology as top
top.unsupplied_junctions(net)
"""

mg = mg or create_nxgraph(net, respect_status_valves=respect_valves)
if slacks is None:
slacks = set(net.ext_grid[net.ext_grid.in_service].junction.values)
not_supplied = set()
for cc in nx.connected_components(mg):
if not set(cc) & slacks:
not_supplied.update(set(cc))
return not_supplied


def elements_on_path(mg, path, element="pipe", check_element_validity=True):
"""
Finds all elements that connect a given path of junctions.
INPUT:
**mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapipes network.
**path** (list) - List of connected junctions.
**element** (string, "pipe") - element type of type BranchComponent
**check_element_validity** (boolean, True) - Check if element is a valid pandapipes table_name
OUTPUT:
**elements** (list) - Returns a list of all elements on the path.
EXAMPLE:
import topology as top
mg = top.create_nxgraph(net)
elements = top.elements_on_path(mg, [4, 5, 6])
"""
if check_element_validity:
table_names = get_all_branch_component_table_names()
if element not in table_names:
raise ValueError("Invalid element type %s" % element)
if isinstance(mg, nx.MultiGraph):
return [edge[1] for b1, b2 in zip(path, path[1:]) for edge in mg.get_edge_data(b1, b2).keys()
if edge[0] == element]
else:
return [mg.get_edge_data(b1, b2)["key"][1] for b1, b2 in zip(path, path[1:])
if mg.get_edge_data(b1, b2)["key"][0] == element]
42 changes: 42 additions & 0 deletions src/pandapipes/topology/topology_toolbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) 2020-2024 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel, and University of Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

from pandapipes.component_models.abstract_models.branch_models import BranchComponent
import logging


def get_all_branch_component_models():
"""
Get all models of available branch components
:return: branch model
:rtype: list
"""
def get_all_subclasses(cls):
all_subclasses = list()
for subclass in cls.__subclasses__():
all_subclasses.append(subclass)
all_subclasses.extend(get_all_subclasses(subclass))
return all_subclasses

all_branch_components = get_all_subclasses(BranchComponent)
filtered = list()
for bc in all_branch_components:
try:
bc.table_name()
filtered.append(bc)
except Exception as e:
logging.info(f"branch component {bc} has no table name {e}")
return filtered


def get_all_branch_component_table_names():
"""
Get all table names of available branch components
:return: table names
:rtype: list
"""
cm = get_all_branch_component_models()
return [c.table_name() for c in cm]

0 comments on commit 2883f24

Please sign in to comment.