From fd92380acabecef4364d23888cc3a4d22cfe3bd5 Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Wed, 12 Aug 2020 14:15:24 +0200 Subject: [PATCH 1/7] LeakageController implemented -> suitable for pipe, valve and heat exchanger --- pandapipes/control/__init__.py | 1 + .../control/controller/leakage_controller.py | 135 ++++++++++++++++++ pandapipes/control/run_control.py | 7 +- .../test_leakage_controller.py | 94 ++++++++++++ 4 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 pandapipes/control/controller/leakage_controller.py create mode 100644 pandapipes/test/pipeflow_internals/test_leakage_controller.py diff --git a/pandapipes/control/__init__.py b/pandapipes/control/__init__.py index a2cd8e5a..efdb179b 100644 --- a/pandapipes/control/__init__.py +++ b/pandapipes/control/__init__.py @@ -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 diff --git a/pandapipes/control/controller/leakage_controller.py b/pandapipes/control/controller/leakage_controller.py new file mode 100644 index 00000000..fa1cee93 --- /dev/null +++ b/pandapipes/control/controller/leakage_controller.py @@ -0,0 +1,135 @@ +# 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): + """ + Leakage Controller + + :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 + + """ + + def __init__(self, net, element, element_index, output_area_m2, profile_name=None, + 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, + **kwargs): + + if element not in ["pipe", "valve", "heat_exchanger"]: + raise Exception("Only 'pipe', 'valve' or 'heat_exchanger' is allowed as element.") + + 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 + + def initialize_control(self): + """ + + """ + pp.pipeflow(self.net, self.kwargs) + + 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) + + def init_values(self, index): + """ + + """ + 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 + """ + 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) + + return True + + def control_step(self): + """ + + """ + 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"]) + + self.mass_flow_kg_per_s_init = self.mass_flow_kg_per_s diff --git a/pandapipes/control/run_control.py b/pandapipes/control/run_control.py index d4a6978c..cd691ee9 100644 --- a/pandapipes/control/run_control.py +++ b/pandapipes/control/run_control.py @@ -41,7 +41,12 @@ 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) + # does not work like this with LeakageController + controller_order, initial_powerflow, run = prepare_run_control_pandapower(net, None) + + ctrl_variables = {"controller_order": controller_order, "initial_powerflow": initial_powerflow, "run": run} + ctrl_variables["run"] = ppipe.pipeflow ctrl_variables["errors"] = (PipeflowNotConverged) diff --git a/pandapipes/test/pipeflow_internals/test_leakage_controller.py b/pandapipes/test/pipeflow_internals/test_leakage_controller.py new file mode 100644 index 00000000..0e7972e6 --- /dev/null +++ b/pandapipes/test/pipeflow_internals/test_leakage_controller.py @@ -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/pipflow_internals/test_leakage_controller.py']) \ No newline at end of file From 5e46f45dfa8e59ffd0a5e7a8b5011b35573f9392 Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Wed, 12 Aug 2020 14:53:47 +0200 Subject: [PATCH 2/7] blank space removed/ added --- pandapipes/control/run_control.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pandapipes/control/run_control.py b/pandapipes/control/run_control.py index cd691ee9..162025df 100644 --- a/pandapipes/control/run_control.py +++ b/pandapipes/control/run_control.py @@ -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. @@ -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) @@ -41,12 +41,7 @@ def prepare_run_ctrl(net, ctrl_variables): :rtype: dict """ if ctrl_variables is None: - # ctrl_variables = prepare_run_control_pandapower(net, None) - # does not work like this with LeakageController - controller_order, initial_powerflow, run = prepare_run_control_pandapower(net, None) - - ctrl_variables = {"controller_order": controller_order, "initial_powerflow": initial_powerflow, "run": run} - + ctrl_variables = prepare_run_control_pandapower(net, None) ctrl_variables["run"] = ppipe.pipeflow ctrl_variables["errors"] = (PipeflowNotConverged) From 004349ba9b89d043baba27c7629baa92f3752715 Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Wed, 12 Aug 2020 15:41:57 +0200 Subject: [PATCH 3/7] Leakage Controller added to the documentation. --- doc/source/controller/controller_classes.rst | 12 +++++++++- .../control/controller/leakage_controller.py | 23 +++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/doc/source/controller/controller_classes.rst b/doc/source/controller/controller_classes.rst index 7e394853..b6d268c6 100644 --- a/doc/source/controller/controller_classes.rst +++ b/doc/source/controller/controller_classes.rst @@ -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: \ No newline at end of file + :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: diff --git a/pandapipes/control/controller/leakage_controller.py b/pandapipes/control/controller/leakage_controller.py index fa1cee93..6c8e2acc 100644 --- a/pandapipes/control/controller/leakage_controller.py +++ b/pandapipes/control/controller/leakage_controller.py @@ -16,7 +16,7 @@ class LeakageController(Controller): """ - Leakage Controller + Controller to consider a leak at pipes, valves or heat exchangers. :param net: The net in which the controller resides :type net: pandapipesNet @@ -30,9 +30,16 @@ class LeakageController(Controller): :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 + :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) """ @@ -78,7 +85,8 @@ def __init__(self, net, element, element_index, output_area_m2, profile_name=Non 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) @@ -92,7 +100,7 @@ def initialize_control(self): 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"]) @@ -102,7 +110,8 @@ def init_values(self, index): def is_converged(self): """ - Convergence Condition: Difference between mass flows smaller than 1e-5 = 0.00001 + 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 @@ -118,7 +127,7 @@ def is_converged(self): 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 = [] From b07091b05f7c47f18f73818229cc22f856053675 Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Thu, 13 Aug 2020 16:03:56 +0200 Subject: [PATCH 4/7] added a letter --- pandapipes/test/pipeflow_internals/test_leakage_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandapipes/test/pipeflow_internals/test_leakage_controller.py b/pandapipes/test/pipeflow_internals/test_leakage_controller.py index 0e7972e6..94c43bd8 100644 --- a/pandapipes/test/pipeflow_internals/test_leakage_controller.py +++ b/pandapipes/test/pipeflow_internals/test_leakage_controller.py @@ -91,4 +91,4 @@ def test_one_heat_exchanger_one_leakage(): if __name__ == "__main__": - pytest.main([r'pandapipes/test/pipflow_internals/test_leakage_controller.py']) \ No newline at end of file + pytest.main([r'pandapipes/test/pipeflow_internals/test_leakage_controller.py']) \ No newline at end of file From 1d4c21e620c5b003fe3b0d7a2e9f9d1519e80e1f Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Tue, 25 Aug 2020 11:46:25 +0200 Subject: [PATCH 5/7] - loops as far as possible, except one, because here create_sink must be called - corrections made --- .../control/controller/leakage_controller.py | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/pandapipes/control/controller/leakage_controller.py b/pandapipes/control/controller/leakage_controller.py index 6c8e2acc..3608ad8c 100644 --- a/pandapipes/control/controller/leakage_controller.py +++ b/pandapipes/control/controller/leakage_controller.py @@ -45,7 +45,7 @@ class LeakageController(Controller): def __init__(self, net, element, element_index, output_area_m2, profile_name=None, 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, + drop_same_existing_ctrl=False, matching_params=None, initial_run=True, **kwargs): if element not in ["pipe", "valve", "heat_exchanger"]: @@ -57,7 +57,7 @@ def __init__(self, net, element, element_index, output_area_m2, profile_name=Non # 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, + matching_params=matching_params, initial_run=initial_run, **kwargs) self.matching_params = {"element": element, "element_index": element_index} @@ -71,57 +71,43 @@ def __init__(self, net, element, element_index, output_area_m2, profile_name=Non self.values = None self.profile_name = profile_name self.scale_factor = scale_factor - self.initial_pipeflow = initial_pipeflow + self.initial_run = initial_run 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.rho_kg_per_m3 = numpy.array([]) # densities for the calculation of leakage mass flows + self.v_m_per_s = numpy.array([]) # 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 - def initialize_control(self): """ - First calculation of a pipeflow without leakage. \n - Then define the initial values and create the sinks representing the leaks. + Define the initial values and create the sinks representing the leaks. """ - pp.pipeflow(self.net, self.kwargs) - 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)) + mdot_kg_per_s=0, name="leakage" + str(i)) self.leakage_index.append(index) self.mass_flow_kg_per_s_init.append(0) - def init_values(self, index): + def init_values(self): """ 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"]) + self.v_m_per_s = numpy.array(self.net["res_" + self.element].loc[self.element_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) + temp_1 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "from_junction"], "t_k"] + temp_2 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "to_junction"], "t_k"] + self.rho_kg_per_m3 = (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. + Convergence Condition: Difference between mass flows smaller than 1e-5 = 0.00001 """ - if not self.mass_flow_kg_per_s: + if numpy.array(self.mass_flow_kg_per_s).size == 0: 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) + if abs(numpy.subtract(self.mass_flow_kg_per_s, self.mass_flow_kg_per_s_init)).all() > 1e-5: + return False return True @@ -131,14 +117,20 @@ def control_step(self): """ pp.pipeflow(self.net, self.kwargs) self.mass_flow_kg_per_s = [] - self.v_m_per_s = [] - self.rho_kg_per_m3 = [] + self.v_m_per_s = numpy.array([]) + self.rho_kg_per_m3 = numpy.array([]) - for i in range(len(self.element_index)): - self.init_values(self.element_index[i]) + self.init_values() - 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"]) + self.net.sink.loc[self.leakage_index, "mdot_kg_per_s"] = self.rho_kg_per_m3 * self.v_m_per_s * \ + numpy.array(self.output_area_m2) + self.mass_flow_kg_per_s = self.net.sink.loc[self.leakage_index, "mdot_kg_per_s"] self.mass_flow_kg_per_s_init = self.mass_flow_kg_per_s + + def finalize_control(self): + """ + Delete the sinks that represented the leaks. + """ + self.net.sink = self.net.sink.drop(labels=self.leakage_index) + self.net.res_sink = self.net.res_sink.drop(labels=self.leakage_index) From 060c71e1ebee2533bb476cd6f312ca497f83dc8a Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Wed, 26 Aug 2020 16:18:53 +0200 Subject: [PATCH 6/7] LekageController extended: - a leakage at a junction can now be defined - with the restriction that the junction leak is not only connected to a pump - more tests created --- .../control/controller/leakage_controller.py | 70 ++++++++++++++---- .../test_leakage_controller.py | 71 ++++++++++++++++++- 2 files changed, 128 insertions(+), 13 deletions(-) diff --git a/pandapipes/control/controller/leakage_controller.py b/pandapipes/control/controller/leakage_controller.py index 3608ad8c..164d69ac 100644 --- a/pandapipes/control/controller/leakage_controller.py +++ b/pandapipes/control/controller/leakage_controller.py @@ -5,6 +5,7 @@ import pandapipes as pp import numpy from pandapower.control.basic_controller import Controller +from pandapipes.component_models import Pipe, Valve, HeatExchanger try: import pplog as logging @@ -16,11 +17,11 @@ class LeakageController(Controller): """ - Controller to consider a leak at pipes, valves or heat exchangers. + Controller to consider a leak with an outlet area at pipes, valves, heat exchangers or junctions. :param net: The net in which the controller resides :type net: pandapipesNet - :param element: Element (first only "pipe", "valve", "heat_exchanger") + :param element: Element (first only "pipe", "valve", "heat_exchanger", "junction") :type element: string :param element_index: IDs of controlled elements :type element_index: int[] @@ -48,8 +49,8 @@ def __init__(self, net, element, element_index, output_area_m2, profile_name=Non drop_same_existing_ctrl=False, matching_params=None, initial_run=True, **kwargs): - if element not in ["pipe", "valve", "heat_exchanger"]: - raise Exception("Only 'pipe', 'valve' or 'heat_exchanger' is allowed as element.") + if element not in ["pipe", "valve", "heat_exchanger", "junction"]: + raise Exception("Only 'pipe', 'valve', 'heat_exchanger' or 'junction' is allowed as element.") if matching_params is None: matching_params = {"element": element, "element_index": element_index} @@ -74,30 +75,75 @@ def __init__(self, net, element, element_index, output_area_m2, profile_name=Non self.initial_run = initial_run self.kwargs = kwargs self.rho_kg_per_m3 = numpy.array([]) # densities for the calculation of leakage mass flows - self.v_m_per_s = numpy.array([]) # current flow velocities at pipes + self.v_m_per_s = numpy.full(len(self.element_index), numpy.nan, float) # 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 + self.branches = [] # branch components in current net def initialize_control(self): """ Define the initial values and create the sinks representing the leaks. """ for i in range(len(self.element_index)): - 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)) + if self.element == "junction": + index = pp.create_sink(self.net, self.element_index[i], + mdot_kg_per_s=0, name="leakage" + str(i)) + else: + 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) + if self.element == "junction": + # determine branch components in the network, necessary for further calculations + branches = {Pipe: "pipe", Valve: "valve", HeatExchanger: "heat_exchanger"} + + for key in branches: + if (numpy.array(self.net.component_list) == key).any(): + self.branches.append(branches[key]) + def init_values(self): """ Initialize the flow velocity and density for the individual control steps. """ - self.v_m_per_s = numpy.array(self.net["res_" + self.element].loc[self.element_index, "v_mean_m_per_s"]) + if self.element == "junction": + # identify the branches connected to a junction leakage to obtain the flow velocity + for i in range(len(self.element_index)): + for branch in self.branches: + ind = numpy.where(numpy.array(self.net[branch]["to_junction"]) == self.element_index[i]) + + if ind[0].size != 0: + if ind[0].size == 1: + index = ind[0] + else: + index = ind[0][0] + self.v_m_per_s[i] = abs(self.net["res_" + branch].loc[index, "v_mean_m_per_s"]) + break + + ind = numpy.where(numpy.array(self.net[branch]["from_junction"]) == self.element_index[i]) + + if ind[0].size != 0: + if ind[0].size == 1: + index = ind[0] + else: + index = ind[0][0] + self.v_m_per_s[i] = abs(self.net["res_" + branch].loc[index, "v_mean_m_per_s"]) + break + + if (self.v_m_per_s == numpy.nan).any(): + raise Exception("One or more junctions are only connected to a pump. Leakage calculation " + "not yet possible here.") + + temp = self.net.res_junction.loc[self.element_index, "t_k"] + self.rho_kg_per_m3 = self.net.fluid.get_density(temp) + + else: + self.v_m_per_s = numpy.array(self.net["res_" + self.element].loc[self.element_index, "v_mean_m_per_s"]) - temp_1 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "from_junction"], "t_k"] - temp_2 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "to_junction"], "t_k"] - self.rho_kg_per_m3 = (self.net.fluid.get_density(temp_1) + self.net.fluid.get_density(temp_2)) / 2 + temp_1 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "from_junction"], "t_k"] + temp_2 = self.net.res_junction.loc[self.net[self.element].loc[self.element_index, "to_junction"], "t_k"] + self.rho_kg_per_m3 = (self.net.fluid.get_density(temp_1) + self.net.fluid.get_density(temp_2)) / 2 def is_converged(self): """ @@ -117,7 +163,7 @@ def control_step(self): """ pp.pipeflow(self.net, self.kwargs) self.mass_flow_kg_per_s = [] - self.v_m_per_s = numpy.array([]) + self.v_m_per_s = numpy.full(len(self.element_index), numpy.nan, float) self.rho_kg_per_m3 = numpy.array([]) self.init_values() diff --git a/pandapipes/test/pipeflow_internals/test_leakage_controller.py b/pandapipes/test/pipeflow_internals/test_leakage_controller.py index 94c43bd8..a41d904e 100644 --- a/pandapipes/test/pipeflow_internals/test_leakage_controller.py +++ b/pandapipes/test/pipeflow_internals/test_leakage_controller.py @@ -90,5 +90,74 @@ def test_one_heat_exchanger_one_leakage(): run_control(net) +def test_one_junction_one_leakage(): + net = pandapipes.create_empty_network("net", fluid="water", add_stdtypes=True) + + j0 = pandapipes.create_junction(net, pn_bar=1.5, tfluid_k=293.15) + j1 = pandapipes.create_junction(net, pn_bar=1.5, tfluid_k=293.15) + + pandapipes.create_ext_grid(net, j0, p_bar=1.5, t_k=293.15, type="pt") + + pandapipes.create_sink(net, j1, mdot_kg_per_s=2) + + pandapipes.create_pipe_from_parameters(net, j0, j1, diameter_m=0.8, 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='junction', element_index=1, output_area_m2=3, **kwargs) + + run_control(net) + + +def test_three_junctions_three_leakages(): + 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) + j2 = pandapipes.create_junction(net, pn_bar=2, tfluid_k=293.15) + j3 = 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, j2, mdot_kg_per_s=2) + pandapipes.create_sink(net, j3, mdot_kg_per_s=1) + + pandapipes.create_pipe_from_parameters(net, j1, j0, diameter_m=0.7, k_mm=0.1, length_km=0.5) + pandapipes.create_pipe_from_parameters(net, j1, j2, diameter_m=0.8, k_mm=0.1, length_km=1) + + pandapipes.create_valve(net, j3, j2, diameter_m=0.1, opened=True) + + 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='junction', element_index=[1, 2, 3], output_area_m2=[1, 3, 0.5], **kwargs) + + run_control(net) + + +def test_two_junction_leakage_one_heat_exchanger(): + 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) + j2 = pandapipes.create_junction(net, pn_bar=2, tfluid_k=293.15) + + pandapipes.create_ext_grid(net, j0, p_bar=4, t_k=293.15, type="pt") + + pandapipes.create_sink(net, j2, mdot_kg_per_s=2) + + pandapipes.create_pipe_from_parameters(net, j1, j0, diameter_m=0.75, k_mm=0.1, length_km=1) + + pandapipes.create_heat_exchanger(net, j2, j1, diameter_m=0.08, 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='junction', element_index=[0, 2], output_area_m2=[3, 1], **kwargs) + + run_control(net) + + if __name__ == "__main__": - pytest.main([r'pandapipes/test/pipeflow_internals/test_leakage_controller.py']) \ No newline at end of file + pytest.main([r'pandapipes/test/pipeflow_internals/test_leakage_controller.py']) From 0bf17139661b89d32be0484bc781dfff9e641b12 Mon Sep 17 00:00:00 2001 From: Tabea Trummel Date: Wed, 26 Aug 2020 16:29:33 +0200 Subject: [PATCH 7/7] documentation of LekageController updated --- doc/source/controller/controller_classes.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/source/controller/controller_classes.rst b/doc/source/controller/controller_classes.rst index b6d268c6..a0a2d300 100644 --- a/doc/source/controller/controller_classes.rst +++ b/doc/source/controller/controller_classes.rst @@ -28,7 +28,11 @@ 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. +leaks with a given outlet area on valves, pipes, heat exchangers and junctions. +In the case of leaks at junctions, the current restriction is that they are not +only connected to a pump, they must have at least one connection to a pipe, +a valve or a heat exchanger. In addition, a leak at a junction can be realised +very simply with a sink, if a mass flow for the loss through the leak can be specified. .. _LeakageController: .. autoclass:: pandapipes.control.controller.leakage_controller.LeakageController