From 6a47c8183123e91e1b8ef8ad66fb5aa4c11095a5 Mon Sep 17 00:00:00 2001 From: GalPerelman Date: Sun, 13 Aug 2023 12:26:30 +0300 Subject: [PATCH 1/4] add function for reverse links direction --- wntr/morph/link.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/wntr/morph/link.py b/wntr/morph/link.py index 5a3a9b9cb..cfa3f7084 100644 --- a/wntr/morph/link.py +++ b/wntr/morph/link.py @@ -3,6 +3,8 @@ """ import logging import copy + +import wntr.network from wntr.network.elements import Reservoir, Pipe logger = logging.getLogger(__name__) @@ -209,8 +211,7 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, # Identify the segment that cross the split point and compute the new # junction coordinates for segment in segments: - if (segment['subtotal'] + segment['length'] >= split_length - and segment['subtotal'] < split_length): + if segment['subtotal'] + segment['length'] >= split_length > segment['subtotal']: split_at = ((split_length - segment['subtotal']) / segment['length']) x0 = segment['start_pos'][0] @@ -268,4 +269,17 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, logger.warn('You are splitting a pipe with a check valve. The new \ pipe will not have a check valve.') - return wn2 + return wn2 + + +def reverse_link(wn: wntr.network.WaterNetworkModel, link_name: str) -> wntr.network.WaterNetworkModel: + link = wn.get_link(link_name) + start_node = link.start_node + end_node = link.end_node + vertices = link.vertices + + link.start_node = end_node + link.end_node = start_node + link.vertices = vertices[::-1] + + return wn \ No newline at end of file From 1039faac2443a1e6202a1f97639e963807940f96 Mon Sep 17 00:00:00 2001 From: GalPerelman Date: Sat, 19 Aug 2023 11:18:29 +0300 Subject: [PATCH 2/4] add a test to reverse_pipes function --- wntr/tests/test_morph.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wntr/tests/test_morph.py b/wntr/tests/test_morph.py index 60daa6101..8953b5b2e 100644 --- a/wntr/tests/test_morph.py +++ b/wntr/tests/test_morph.py @@ -217,6 +217,19 @@ def test_split_break_pipe_vertices(self): self.assertEqual(len(pipe.vertices), 2) self.assertEqual(len(pipeB.vertices), 0) + def test_reverse_pipes(self): + inp_file = join(datadir, "io.inp") + wn = wntr.network.WaterNetworkModel(inp_file) + wn2 = wntr.morph.link.reverse_link(wn, "p1") + pipe2 = wn2.get_link("p1") + + # test start and end nodes + self.assertEqual(pipe2.start_node, wn2.get_node('j1')) + self.assertEqual(pipe2.end_node, wn2.get_node('t1')) + + # test vertices + self.assertEqual(pipe2.vertices, [(20.0, 5.0), (15.0, 5.0)]) + def test_skeletonize(self): inp_file = join(datadir, "skeletonize.inp") From 9ab155a03071fdc37a277f66d6d393784953beba Mon Sep 17 00:00:00 2001 From: GalPerelman Date: Sat, 19 Aug 2023 12:13:20 +0300 Subject: [PATCH 3/4] add docstring --- wntr/morph/link.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wntr/morph/link.py b/wntr/morph/link.py index cfa3f7084..b18f94edb 100644 --- a/wntr/morph/link.py +++ b/wntr/morph/link.py @@ -273,6 +273,23 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, def reverse_link(wn: wntr.network.WaterNetworkModel, link_name: str) -> wntr.network.WaterNetworkModel: + """ + Reverse a link to switch between the start and end nodes + The function can reverse any link (pipe, pump or valve) + Vertices order will be reversed to maintain the link layout + + Parameters + ---------- + wn: wntr.network.WaterNetworkModel + The network object where link should be reversed + link_name: string + The name of the link to revers + + Returns + ------- + wntr.network.WaterNetworkModel + A network object after link was reversed + """ link = wn.get_link(link_name) start_node = link.start_node end_node = link.end_node From fcc323b776a6b2c19a3f69e8c125c9a626349c46 Mon Sep 17 00:00:00 2001 From: GalPerelman Date: Thu, 12 Oct 2023 10:34:25 +0300 Subject: [PATCH 4/4] Resolve PR review comments --- wntr/morph/link.py | 97 +++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/wntr/morph/link.py b/wntr/morph/link.py index b18f94edb..abf88b338 100644 --- a/wntr/morph/link.py +++ b/wntr/morph/link.py @@ -6,10 +6,11 @@ import wntr.network from wntr.network.elements import Reservoir, Pipe +from wntr.network import WaterNetworkModel logger = logging.getLogger(__name__) - + def split_pipe(wn, pipe_name_to_split, new_pipe_name, new_junction_name, add_pipe_at_end=True, split_at_point=0.5, return_copy=True): """ @@ -61,15 +62,15 @@ def split_pipe(wn, pipe_name_to_split, new_pipe_name, new_junction_name, wntr WaterNetworkModel Water network model with split pipe """ - wn2 = _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, - [new_junction_name], - add_pipe_at_end, split_at_point, 'SPLIT', return_copy) - + wn2 = _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, + [new_junction_name], + add_pipe_at_end, split_at_point, 'SPLIT', return_copy) + return wn2 - + def break_pipe(wn, pipe_name_to_split, new_pipe_name, new_junction_name_old_pipe, - new_junction_name_new_pipe, add_pipe_at_end=True, + new_junction_name_new_pipe, add_pipe_at_end=True, split_at_point=0.5, return_copy=True): """ Break a pipe by adding a two unconnected junctions and one new pipe segment. @@ -126,23 +127,23 @@ def break_pipe(wn, pipe_name_to_split, new_pipe_name, new_junction_name_old_pipe wntr WaterNetworkModel Water network model with pipe break """ - wn2 = _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, - [new_junction_name_old_pipe, new_junction_name_new_pipe], - add_pipe_at_end, split_at_point, 'BREAK', return_copy) - + wn2 = _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, + [new_junction_name_old_pipe, new_junction_name_new_pipe], + add_pipe_at_end, split_at_point, 'BREAK', return_copy) + return wn2 -def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, + +def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, new_junction_names, add_pipe_at_end, split_at_point, flag, return_copy): - - if return_copy: # Get a copy of the WaterNetworkModel + if return_copy: # Get a copy of the WaterNetworkModel wn2 = copy.deepcopy(wn) else: wn2 = wn - + pipe = wn2.get_link(pipe_name_to_split) - + # Do sanity checks if not isinstance(pipe, Pipe): raise ValueError('You can only split pipes.') @@ -161,7 +162,7 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, # Get start and end node info start_node = pipe.start_node end_node = pipe.end_node - + # calculate the new elevation if isinstance(start_node, Reservoir): junction_elevation = end_node.elevation @@ -185,19 +186,19 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, for i in range(len(pipe_vertices) - 1): start_pos = pipe_vertices[i] end_pos = pipe_vertices[i + 1] - segment_length = (sum([(a-b)**2 for (a,b) in zip(start_pos, end_pos)]) + segment_length = (sum([(a - b) ** 2 for (a, b) in zip(start_pos, end_pos)]) ** 0.5) segments.append({'start_pos': start_pos, 'end_pos': end_pos, 'length': segment_length, 'subtotal': subtotal}) - + subtotal += segment_length - + last_segment = segments[-1] length = last_segment['subtotal'] + last_segment['length'] split_length = length * split_at_point - + # Loop over segments and assign vertices (start_pos) to the first or # second pipe. Skip the first segment (its start_pos is not a vertice) for segment in segments: @@ -207,7 +208,7 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, first_vertices.append(segment['start_pos']) else: last_vertices.append(segment['start_pos']) - + # Identify the segment that cross the split point and compute the new # junction coordinates for segment in segments: @@ -221,7 +222,7 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, junction_coordinates = (x0 + dx * split_at, y0 + dy * split_at) # calculate the new coordinates without vertices - else: + else: x0 = pipe.start_node.coordinates[0] dx = pipe.end_node.coordinates[0] - x0 y0 = pipe.start_node.coordinates[1] @@ -231,8 +232,8 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, # add the new junction for new_junction_name in new_junction_names: - wn2.add_junction(new_junction_name, base_demand=0.0, - demand_pattern=None, elevation=junction_elevation, + wn2.add_junction(new_junction_name, base_demand=0.0, + demand_pattern=None, elevation=junction_elevation, coordinates=junction_coordinates) original_length = pipe.length @@ -243,36 +244,36 @@ def _split_or_break_pipe(wn, pipe_name_to_split, new_pipe_name, elif flag == 'SPLIT': j0 = new_junction_names[0] j1 = new_junction_names[0] - + if add_pipe_at_end: - pipe.end_node = wn2.get_node(j0) + pipe.end_node = wn2.get_node(j0) # add new pipe and change original length wn2.add_pipe(new_pipe_name, j1, end_node.name, - original_length*(1-split_at_point), pipe.diameter, + original_length * (1 - split_at_point), pipe.diameter, pipe.roughness, pipe.minor_loss, pipe.status, pipe.check_valve) - pipe.length = original_length*split_at_point + pipe.length = original_length * split_at_point pipe.vertices = first_vertices new_pipe = wn2.get_link(new_pipe_name) new_pipe.vertices = last_vertices - else: # add pipe at start - pipe.start_node = wn2.get_node(j0) + else: # add pipe at start + pipe.start_node = wn2.get_node(j0) # add new pipe and change original length - wn2.add_pipe(new_pipe_name, start_node.name, j1, - original_length*split_at_point, pipe.diameter, + wn2.add_pipe(new_pipe_name, start_node.name, j1, + original_length * split_at_point, pipe.diameter, pipe.roughness, pipe.minor_loss, pipe.status, pipe.check_valve) - pipe.length = original_length*(1-split_at_point) + pipe.length = original_length * (1 - split_at_point) pipe.vertices = last_vertices new_pipe = wn2.get_link(new_pipe_name) new_pipe.vertices = first_vertices - + if pipe.check_valve: logger.warn('You are splitting a pipe with a check valve. The new \ pipe will not have a check valve.') - + return wn2 -def reverse_link(wn: wntr.network.WaterNetworkModel, link_name: str) -> wntr.network.WaterNetworkModel: +def reverse_link(wn: WaterNetworkModel, link_name: str, return_copy=True) -> WaterNetworkModel: """ Reverse a link to switch between the start and end nodes The function can reverse any link (pipe, pump or valve) @@ -280,17 +281,25 @@ def reverse_link(wn: wntr.network.WaterNetworkModel, link_name: str) -> wntr.net Parameters ---------- - wn: wntr.network.WaterNetworkModel - The network object where link should be reversed - link_name: string - The name of the link to revers + wn: WaterNetworkModel + The network object where link should be reversed. + link_name: string + The name of the link to revers. + return_copy: bool, optional + If True, modify and return a copy of the WaterNetworkModel object. + If False, modify and return the original WaterNetworkModel object. Returns ------- - wntr.network.WaterNetworkModel + WaterNetworkModel A network object after link was reversed """ - link = wn.get_link(link_name) + if return_copy: # Get a copy of the WaterNetworkModel + wn2 = copy.deepcopy(wn) + else: + wn2 = wn + + link = wn2.get_link(link_name) start_node = link.start_node end_node = link.end_node vertices = link.vertices @@ -299,4 +308,4 @@ def reverse_link(wn: wntr.network.WaterNetworkModel, link_name: str) -> wntr.net link.end_node = start_node link.vertices = vertices[::-1] - return wn \ No newline at end of file + return wn2