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

Leakage Controller added, creating test in pipeflow_internals and documentation updated #115

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
12 changes: 11 additions & 1 deletion doc/source/controller/controller_classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@ This is used to read the data from a DataSource and write it to a network.

.. _ConstControl:
.. autoclass:: pandapower.control.controller.const_control.ConstControl
:members:
:members:

Leakage Controller
==================

With the help of the Leakage Controller it is possible to define
leaks with a given outlet area on valves, pipes or heat exchangers.

.. _LeakageController:
.. autoclass:: pandapipes.control.controller.leakage_controller.LeakageController
:members:
1 change: 1 addition & 0 deletions pandapipes/control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

from pandapipes.control.run_control import run_control
from pandapipes.control.controller.leakage_controller import LeakageController
144 changes: 144 additions & 0 deletions pandapipes/control/controller/leakage_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright (c) 2020 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

import pandapipes as pp
import numpy
from pandapower.control.basic_controller import Controller

try:
import pplog as logging
except ImportError:
import logging

logger = logging.getLogger(__name__)


class LeakageController(Controller):
"""
Controller to consider a leak at pipes, valves or heat exchangers.

:param net: The net in which the controller resides
:type net: pandapipesNet
:param element: Element (first only "pipe", "valve", "heat_exchanger")
:type element: string
:param element_index: IDs of controlled elements
:type element_index: int[]
:param output_area_m2: Size of the leakage in m^2
:type output_area_m2: float[]
:param in_service: Indicates if the controller is currently in_service
:type in_service: bool, default True
:param recycle: Re-use of internal-data
:type recycle: bool, default True
:param drop_same_existing_ctrl: Indicates if already existing controllers of the same type and with the same matching parameters (e.g. at same element) should be dropped
:type drop_same_existing_ctrl: bool, default False
:param kwargs: Parameters for pipeflow
:type kwargs: dict

:Example:
>>> kwargs = {'stop_condition': 'tol', 'iter': 100, 'tol_p': 1e-7, 'tol_v': 1e-7, 'friction_model': 'colebrook',
>>> 'mode': 'hydraulics', 'only_update_hydraulic_matrix': False}
>>> LeakageController(net, element='pipe', element_index=0, output_area_m2=1, **kwargs)
>>> run_control(net)

"""

def __init__(self, net, element, element_index, output_area_m2, profile_name=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also be able to use something like kg_per_m_s meaning mass per meter and second.

scale_factor=1.0, in_service=True, recycle=True, order=0, level=0,
drop_same_existing_ctrl=False, set_q_from_cosphi=False, matching_params=None, initial_pipeflow=False,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intial_run instead of initial_pipeflow. Please update pandapipes.

**kwargs):

if element not in ["pipe", "valve", "heat_exchanger"]:
raise Exception("Only 'pipe', 'valve' or 'heat_exchanger' is allowed as element.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice it we extend it also for junctions.


if matching_params is None:
matching_params = {"element": element, "element_index": element_index}

# just calling init of the parent
super().__init__(net, in_service=in_service, recycle=recycle, order=order, level=level,
drop_same_existing_ctrl=drop_same_existing_ctrl,
matching_params=matching_params, initial_powerflow=initial_pipeflow,
**kwargs)

self.matching_params = {"element": element, "element_index": element_index}
if numpy.isscalar(element_index):
self.element_index = [element_index]
self.output_area_m2 = [output_area_m2]
else:
self.element_index = element_index
self.output_area_m2 = output_area_m2
self.element = element
self.values = None
self.profile_name = profile_name
self.scale_factor = scale_factor
self.initial_pipeflow = initial_pipeflow
self.kwargs = kwargs
self.rho_kg_per_m3 = [] # densities for the calculation of leakage mass flows
self.v_m_per_s = [] # current flow velocities at pipes
self.mass_flow_kg_per_s_init = [] # initial/ previous leakage mass flows
self.mass_flow_kg_per_s = [] # current leakage mass flows
self.leakage_index = [] # index of the sinks for leakages

if set_q_from_cosphi:
logger.error("Parameter set_q_from_cosphi deprecated!")
raise ValueError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have reactive power in pandapipes


def initialize_control(self):
"""
First calculation of a pipeflow without leakage. \n
Then define the initial values and create the sinks representing the leaks.
"""
pp.pipeflow(self.net, self.kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do a pipeflow in intialize_control, use the flag initial_run instead


for i in range(len(self.element_index)):
self.init_values(self.element_index[i])

index = pp.create_sink(self.net, self.net[self.element].loc[self.element_index[i], "to_junction"],
mdot_kg_per_s=0, name="leakage"+str(i))
self.leakage_index.append(index)
self.mass_flow_kg_per_s_init.append(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please prevent loops


def init_values(self, index):
"""
Initialize the flow velocity and density for the individual control steps.
"""
self.v_m_per_s.append(self.net["res_"+self.element].loc[index, "v_mean_m_per_s"])

temp_1 = self.net.res_junction.loc[self.net[self.element].loc[index, "from_junction"], "t_k"]
temp_2 = self.net.res_junction.loc[self.net[self.element].loc[index, "to_junction"], "t_k"]
self.rho_kg_per_m3.append((self.net.fluid.get_density(temp_1) + self.net.fluid.get_density(temp_2)) / 2)

def is_converged(self):
"""
Convergence Condition: Difference between mass flows smaller than 1e-5 = 0.00001 \n
Delete the sinks that represented the leaks.
"""
if not self.mass_flow_kg_per_s:
return False

for i in range(len(self.element_index)):
if abs(self.mass_flow_kg_per_s_init[i] - self.mass_flow_kg_per_s[i]) > 1e-5:
return False

self.net.sink = self.net.sink.drop(labels=self.leakage_index)
self.net.res_sink = self.net.res_sink.drop(labels=self.leakage_index)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen in finalize_control


return True

def control_step(self):
"""
Calculate the new mass flow for each leak using the density, flow velocity and outlet area.
"""
pp.pipeflow(self.net, self.kwargs)
self.mass_flow_kg_per_s = []
self.v_m_per_s = []
self.rho_kg_per_m3 = []

for i in range(len(self.element_index)):
self.init_values(self.element_index[i])

self.net.sink.loc[self.leakage_index[i], "mdot_kg_per_s"] = self.rho_kg_per_m3[i] * self.v_m_per_s[i] * \
self.output_area_m2[i]
self.mass_flow_kg_per_s.append(self.net.sink.loc[self.leakage_index[i], "mdot_kg_per_s"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prevent loops due to performance issues.


self.mass_flow_kg_per_s_init = self.mass_flow_kg_per_s
6 changes: 3 additions & 3 deletions pandapipes/control/run_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import pandapipes as ppipe
from pandapipes.pipeflow import PipeflowNotConverged


def run_control(net, ctrl_variables=None, max_iter=30, continue_on_lf_divergence=False,
**kwargs):
**kwargs):
"""
Function to run a control of the pandapipes network.

Expand All @@ -26,7 +27,6 @@ def run_control(net, ctrl_variables=None, max_iter=30, continue_on_lf_divergence
if ctrl_variables is None:
ctrl_variables = prepare_run_ctrl(net, None)


run_control_pandapower(net, ctrl_variables=ctrl_variables, max_iter=max_iter,
continue_on_lf_divergence=continue_on_lf_divergence, **kwargs)

Expand All @@ -41,7 +41,7 @@ def prepare_run_ctrl(net, ctrl_variables):
:rtype: dict
"""
if ctrl_variables is None:
ctrl_variables = prepare_run_control_pandapower(net, None)
ctrl_variables = prepare_run_control_pandapower(net, None)
ctrl_variables["run"] = ppipe.pipeflow

ctrl_variables["errors"] = (PipeflowNotConverged)
Expand Down
94 changes: 94 additions & 0 deletions pandapipes/test/pipeflow_internals/test_leakage_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright (c) 2020 by Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

import pandapipes
import pytest
from pandapipes.control import LeakageController, run_control


def test_one_pipe_one_leakage():
net = pandapipes.create_empty_network("net", fluid="water", add_stdtypes=True)

j0 = pandapipes.create_junction(net, pn_bar=5, tfluid_k=293.15)
j1 = pandapipes.create_junction(net, pn_bar=5, tfluid_k=293.15)

pandapipes.create_ext_grid(net, j0, p_bar=5, t_k=293.15, type="pt")

pandapipes.create_sink(net, j1, mdot_kg_per_s=1)

pandapipes.create_pipe_from_parameters(net, j0, j1, diameter_m=0.75, k_mm=0.1, length_km=2)

kwargs = {'stop_condition': 'tol', 'iter': 100, 'tol_p': 1e-7, 'tol_v': 1e-7, 'friction_model': 'colebrook',
'mode': 'hydraulics', 'only_update_hydraulic_matrix': False}

LeakageController(net, element='pipe', element_index=0, output_area_m2=1, **kwargs)

run_control(net)


def test_two_pipes_two_leakages():
net = pandapipes.create_empty_network("net", fluid="water", add_stdtypes=True)

j0 = pandapipes.create_junction(net, pn_bar=3, tfluid_k=293.15)
j1 = pandapipes.create_junction(net, pn_bar=3, tfluid_k=293.15)
j2 = pandapipes.create_junction(net, pn_bar=3, tfluid_k=293.15)

pandapipes.create_ext_grid(net, j0, p_bar=3, t_k=293.15, type="pt")

pandapipes.create_sink(net, j1, mdot_kg_per_s=1)
pandapipes.create_sink(net, j2, mdot_kg_per_s=0.5)

pandapipes.create_pipe_from_parameters(net, j0, j1, diameter_m=0.75, k_mm=0.1, length_km=2)
pandapipes.create_pipe_from_parameters(net, j1, j2, diameter_m=0.6, k_mm=0.1, length_km=3)

kwargs = {'stop_condition': 'tol', 'iter': 100, 'tol_p': 1e-7, 'tol_v': 1e-7, 'friction_model': 'colebrook',
'mode': 'hydraulics', 'only_update_hydraulic_matrix': False}

LeakageController(net, element='pipe', element_index=[0, 1], output_area_m2=[1, 2.5], **kwargs)

run_control(net)


def test_one_valve_one_leakage():
net = pandapipes.create_empty_network("net", fluid="water", add_stdtypes=True)

j0 = pandapipes.create_junction(net, pn_bar=2, tfluid_k=293.15)
j1 = pandapipes.create_junction(net, pn_bar=2, tfluid_k=293.15)

pandapipes.create_ext_grid(net, j0, p_bar=2, t_k=293.15, type="pt")

pandapipes.create_sink(net, j1, mdot_kg_per_s=0.5)

pandapipes.create_valve(net, j0, j1, diameter_m=0.1, opened=True)

kwargs = {'stop_condition': 'tol', 'iter': 100, 'tol_p': 1e-7, 'tol_v': 1e-7, 'friction_model': 'nikuradse',
'mode': 'hydraulics', 'only_update_hydraulic_matrix': False}

LeakageController(net, element='valve', element_index=0, output_area_m2=0.5, **kwargs)

run_control(net)


def test_one_heat_exchanger_one_leakage():
net = pandapipes.create_empty_network("net", fluid="water", add_stdtypes=True)

j0 = pandapipes.create_junction(net, pn_bar=1, tfluid_k=293.15)
j1 = pandapipes.create_junction(net, pn_bar=1, tfluid_k=293.15)

pandapipes.create_ext_grid(net, j0, p_bar=1, t_k=350, type="pt")

pandapipes.create_sink(net, j1, mdot_kg_per_s=0.5)

pandapipes.create_heat_exchanger(net, j0, j1, diameter_m=0.8, qext_w=20000)

kwargs = {'stop_condition': 'tol', 'iter': 100, 'tol_p': 1e-7, 'tol_v': 1e-7, 'friction_model': 'colebrook',
'mode': 'all', 'only_update_hydraulic_matrix': False}

LeakageController(net, element='heat_exchanger', element_index=0, output_area_m2=0.1, **kwargs)

run_control(net)


if __name__ == "__main__":
pytest.main([r'pandapipes/test/pipeflow_internals/test_leakage_controller.py'])