From da2b52dd6f308e03e71eb80892091fd225d2fef2 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 25 Sep 2023 08:50:01 +0200 Subject: [PATCH 01/23] Added nodes/pipes to excluded to the skel proc --- documentation/morph.rst | 1 + wntr/morph/skel.py | 13 ++++++++++--- wntr/tests/test_morph.py | 23 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/documentation/morph.rst b/documentation/morph.rst index 88b503b43..6f497d295 100644 --- a/documentation/morph.rst +++ b/documentation/morph.rst @@ -82,6 +82,7 @@ This initial set of operations can generate new branch pipes, pipes in series, a This cycle repeats until the network can no longer be reduced. The user can specify if branch trimming, series pipe merge, and/or parallel pipe merge should be included in the skeletonization operations. The user can also specify a maximum number of cycles to include in the process. +The user can also specify a list of nodes and pipes which should be excluded from skeletonization operations. .. only:: latex diff --git a/wntr/morph/skel.py b/wntr/morph/skel.py index ee80f1316..7129f2b2b 100644 --- a/wntr/morph/skel.py +++ b/wntr/morph/skel.py @@ -14,7 +14,8 @@ logger = logging.getLogger(__name__) def skeletonize(wn, pipe_diameter_threshold, branch_trim=True, series_pipe_merge=True, - parallel_pipe_merge=True, max_cycles=None, use_epanet=True, + parallel_pipe_merge=True, max_cycles=None, use_epanet=True, + pipes_to_exclude:list=[], nodes_to_exclude:list=[], return_map=False, return_copy=True): """ Perform network skeletonization using branch trimming, series pipe merge, @@ -43,6 +44,10 @@ def skeletonize(wn, pipe_diameter_threshold, branch_trim=True, series_pipe_merge use_epanet: bool, optional If True, use the EpanetSimulator to compute headloss in pipes. If False, use the WNTRSimulator to compute headloss in pipes. + pipes_to_exclude: list, optional + List of pipe names to exclude from skeletonization + nodes_to_exclude: list, optional + List of node names to exclude from skeletonization return_map: bool, optional If True, return a skeletonization map. The map is a dictionary that includes original nodes as keys and a list of skeletonized nodes @@ -60,7 +65,7 @@ def skeletonize(wn, pipe_diameter_threshold, branch_trim=True, series_pipe_merge nodes as keys and a list of skeletonized nodes that were merged into each original node as values. """ - skel = _Skeletonize(wn, use_epanet, return_copy) + skel = _Skeletonize(wn, use_epanet, return_copy, pipes_to_exclude, nodes_to_exclude) skel.run(pipe_diameter_threshold, branch_trim, series_pipe_merge, parallel_pipe_merge, max_cycles) @@ -73,7 +78,7 @@ def skeletonize(wn, pipe_diameter_threshold, branch_trim=True, series_pipe_merge class _Skeletonize(object): - def __init__(self, wn, use_epanet, return_copy): + def __init__(self, wn, use_epanet, return_copy, pipes_to_exclude, nodes_to_exclude): if return_copy: # Get a copy of the WaterNetworkModel @@ -102,7 +107,9 @@ def __init__(self, wn, use_epanet, return_copy): elif isinstance(req, Pipe): pipe_with_controls.append(req.name) self.junc_with_controls = list(set(junc_with_controls)) + self.junc_with_controls.extend(nodes_to_exclude) self.pipe_with_controls = list(set(pipe_with_controls)) + self.pipe_with_controls.extend(pipes_to_exclude) # Calculate pipe headloss using a single period EPANET simulation duration = self.wn.options.time.duration diff --git a/wntr/tests/test_morph.py b/wntr/tests/test_morph.py index 60daa6101..ce6331123 100644 --- a/wntr/tests/test_morph.py +++ b/wntr/tests/test_morph.py @@ -315,6 +315,29 @@ def test_skeletonize_with_controls(self): self.assertEqual(skel_wn.num_nodes, wn.num_nodes - 17) self.assertEqual(skel_wn.num_links, wn.num_links - 22) + def test_skeletonize_with_excluding_nodes_and_pipes(self): + + inp_file = join(datadir, "skeletonize.inp") + wn = wntr.network.WaterNetworkModel(inp_file) + + skel_wn = wntr.morph.skeletonize(wn, 12.0 * 0.0254, use_epanet=False, nodes_to_exclude=["13"], pipes_to_exclude=["60"]) + + self.assertEqual(skel_wn.num_nodes, wn.num_nodes - 17) + self.assertEqual(skel_wn.num_links, wn.num_links - 22) + + wn = wntr.network.WaterNetworkModel(inp_file) + + # Change link 60 and 11 diameter to > 12, should get some results as above + link = wn.get_link("60") + link.diameter = 16 * 0.0254 + link = wn.get_link("11") + link.diameter = 16 * 0.0254 + + skel_wn = wntr.morph.skeletonize(wn, 12.0 * 0.0254, use_epanet=False) + + self.assertEqual(skel_wn.num_nodes, wn.num_nodes - 17) + self.assertEqual(skel_wn.num_links, wn.num_links - 22) + def test_series_merge_properties(self): wn = wntr.network.WaterNetworkModel() From f2b08fccde8f70162d374a56bbba3c1f7f9276e5 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 26 Nov 2023 13:27:03 +0100 Subject: [PATCH 02/23] update --- .gitignore | 1 + wntr/epanet/io.py | 71 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 643ae47a6..1dc0938f8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ wntr.egg-info/ dist/ docker/ +/.vscode *.pyd *.pyc diff --git a/wntr/epanet/io.py b/wntr/epanet/io.py index 27936ba3c..ba5df50d2 100644 --- a/wntr/epanet/io.py +++ b/wntr/epanet/io.py @@ -245,39 +245,30 @@ def __init__(self): self.top_comments = [] self.curves = OrderedDict() - def read(self, inp_files, wn=None): + @staticmethod + def read_sections(inp_files:list) -> tuple: """ - Read an EPANET INP file and load data into a water network model object. - Both EPANET 2.0 and EPANET 2.2 INP file options are recognized and handled. + Read an EPANET INP file and return a dictionary of sections. Parameters ---------- inp_files : str or list An EPANET INP input file or list of INP files to be combined - wn : WaterNetworkModel, optional - An optional network model to append onto; by default a new model is created. Returns ------- - :class:`~wntr.network.model.WaterNetworkModel` - A water network model object + tuple + A tuple of two elements: a dictionary (OrderedDict) of sections, and a list of top comments """ - if wn is None: - wn = WaterNetworkModel() - self.wn = wn if not isinstance(inp_files, list): inp_files = [inp_files] - wn.name = inp_files[0] - self.curves = OrderedDict() - self.top_comments = [] - self.sections = OrderedDict() + sections = OrderedDict() for sec in _INP_SECTIONS: - self.sections[sec] = [] - self.mass_units = None - self.flow_units = None + sections[sec] = [] + top_comments = [] for filename in inp_files: section = None lnum = 0 @@ -315,13 +306,55 @@ def read(self, inp_files, wn=None): else: raise RuntimeError('%(fname)s:%(lnum)d: Invalid section "%(sec)s"' % edata) elif section is None and line.startswith(';'): - self.top_comments.append(line[1:]) + top_comments.append(line[1:]) continue elif section is None: logger.debug('Found confusing line: %s', repr(line)) raise RuntimeError('%(fname)s:%(lnum)d: Non-comment outside of valid section!' % edata) # We have text, and we are in a section - self.sections[section].append((lnum, line)) + sections[section].append((lnum, line)) + return sections, top_comments + + def read(self, inp_files, wn=None, sections=None, top_comments=None): + """ + Read an EPANET INP file and load data into a water network model object. + Both EPANET 2.0 and EPANET 2.2 INP file options are recognized and handled. + + Parameters + ---------- + inp_files : str or list + An EPANET INP input file or list of INP files to be combined + wn : WaterNetworkModel, optional + An optional network model to append onto; by default a new model is created. + + Returns + ------- + :class:`~wntr.network.model.WaterNetworkModel` + A water network model object + + """ + if wn is None: + wn = WaterNetworkModel() + self.wn = wn + if not isinstance(inp_files, list): + inp_files = [inp_files] + wn.name = inp_files[0] + + self.curves = OrderedDict() + + self.mass_units = None + self.flow_units = None + + if sections is None: + self.sections, self.top_comments = self.read_sections(inp_files) + else: + self.sections = sections + self.top_comments = top_comments + if top_comments is None: + self.top_comments = [] + + + assert len([key for key in self.sections.keys()]) == len(_INP_SECTIONS), 'Missing sections in INP file' # Parse each of the sections # The order of operations is important as certain things require prior knowledge From 2ed1c7eb8ded9ab82716f4af8c59e771e246cbb2 Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 18 Apr 2024 11:00:53 -0700 Subject: [PATCH 03/23] reverted changes to epanet/io.py --- wntr/epanet/io.py | 71 +++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 52 deletions(-) diff --git a/wntr/epanet/io.py b/wntr/epanet/io.py index c74c7e918..43d580c70 100644 --- a/wntr/epanet/io.py +++ b/wntr/epanet/io.py @@ -236,30 +236,39 @@ def __init__(self): self.top_comments = [] self.curves = OrderedDict() - @staticmethod - def read_sections(inp_files:list) -> tuple: + def read(self, inp_files, wn=None): """ - Read an EPANET INP file and return a dictionary of sections. + Read an EPANET INP file and load data into a water network model object. + Both EPANET 2.0 and EPANET 2.2 INP file options are recognized and handled. Parameters ---------- inp_files : str or list An EPANET INP input file or list of INP files to be combined + wn : WaterNetworkModel, optional + An optional network model to append onto; by default a new model is created. Returns ------- - tuple - A tuple of two elements: a dictionary (OrderedDict) of sections, and a list of top comments + WaterNetworkModel + A water network model object """ + if wn is None: + wn = WaterNetworkModel() + self.wn = wn if not isinstance(inp_files, list): inp_files = [inp_files] + wn.name = inp_files[0] - sections = OrderedDict() + self.curves = OrderedDict() + self.top_comments = [] + self.sections = OrderedDict() for sec in _INP_SECTIONS: - sections[sec] = [] + self.sections[sec] = [] + self.mass_units = None + self.flow_units = None - top_comments = [] for filename in inp_files: section = None lnum = 0 @@ -297,55 +306,13 @@ def read_sections(inp_files:list) -> tuple: else: raise ENSyntaxError(201, line_num=lnum, line = line) elif section is None and line.startswith(';'): - top_comments.append(line[1:]) + self.top_comments.append(line[1:]) continue elif section is None: logger.debug('Found confusing line: %s', repr(line)) raise ENSyntaxError(201, line_num=lnum, line=line) # We have text, and we are in a section - sections[section].append((lnum, line)) - return sections, top_comments - - def read(self, inp_files, wn=None, sections=None, top_comments=None): - """ - Read an EPANET INP file and load data into a water network model object. - Both EPANET 2.0 and EPANET 2.2 INP file options are recognized and handled. - - Parameters - ---------- - inp_files : str or list - An EPANET INP input file or list of INP files to be combined - wn : WaterNetworkModel, optional - An optional network model to append onto; by default a new model is created. - - Returns - ------- - :class:`~wntr.network.model.WaterNetworkModel` - A water network model object - - """ - if wn is None: - wn = WaterNetworkModel() - self.wn = wn - if not isinstance(inp_files, list): - inp_files = [inp_files] - wn.name = inp_files[0] - - self.curves = OrderedDict() - - self.mass_units = None - self.flow_units = None - - if sections is None: - self.sections, self.top_comments = self.read_sections(inp_files) - else: - self.sections = sections - self.top_comments = top_comments - if top_comments is None: - self.top_comments = [] - - - assert len([key for key in self.sections.keys()]) == len(_INP_SECTIONS), 'Missing sections in INP file' + self.sections[section].append((lnum, line)) # Parse each of the sections # The order of operations is important as certain things require prior knowledge From 55fe80c5d1d1c2a473dff67ff7002306dbef183c Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 18 Apr 2024 12:34:29 -0700 Subject: [PATCH 04/23] updated test --- wntr/tests/test_morph.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/wntr/tests/test_morph.py b/wntr/tests/test_morph.py index 888e6b9b2..cc267b802 100644 --- a/wntr/tests/test_morph.py +++ b/wntr/tests/test_morph.py @@ -295,7 +295,13 @@ def test_skeletonize_with_controls(self): inp_file = join(datadir, "skeletonize.inp") wn = wntr.network.WaterNetworkModel(inp_file) - + + # Run skeletonization without excluding junctions or pipes + skel_wn = wntr.morph.skeletonize(wn, 12.0 * 0.0254, use_epanet=False) + # Junction 13 and Pipe 60 are not in the skeletonized model + assert "13" not in skel_wn.junction_name_list + assert "60" not in skel_wn.pipe_name_list + # add control to a link action = wntr.network.ControlAction( wn.get_link("60"), "status", wntr.network.LinkStatus.Closed @@ -310,21 +316,10 @@ def test_skeletonize_with_controls(self): control = wntr.network.Control(condition=condition, then_action=action) wn.add_control("raise_node", control) + # Rerun skeletonize skel_wn = wntr.morph.skeletonize(wn, 12.0 * 0.0254, use_epanet=False) - - self.assertEqual(skel_wn.num_nodes, wn.num_nodes - 17) - self.assertEqual(skel_wn.num_links, wn.num_links - 22) - - wn = wntr.network.WaterNetworkModel(inp_file) - - # Change link 60 and 11 diameter to > 12, should get some results as above - link = wn.get_link("60") - link.diameter = 16 * 0.0254 - link = wn.get_link("11") - link.diameter = 16 * 0.0254 - - skel_wn = wntr.morph.skeletonize(wn, 12.0 * 0.0254, use_epanet=False) - + assert "13" in skel_wn.junction_name_list + assert "60" in skel_wn.pipe_name_list self.assertEqual(skel_wn.num_nodes, wn.num_nodes - 17) self.assertEqual(skel_wn.num_links, wn.num_links - 22) From 0ebcf9e20c5f316c7a2fa633b601735128cca908 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 07:50:56 -0700 Subject: [PATCH 05/23] install geopandas on windows --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5ca60f1fb..d8a06c5ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,8 +11,8 @@ plotly<=5.11.0 folium utm openpyxl -geopandas; sys_platform == "darwin" or sys_platform == "linux" -rtree; sys_platform == "darwin" or sys_platform == "linux" +geopandas +rtree # Documentation sphinx From 3081d580753452ff0a3d15829ff67cfd230269cc Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 07:57:34 -0700 Subject: [PATCH 06/23] Add build branch to workflow --- .github/workflows/build_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index b1010b9ed..97630e0c1 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -5,9 +5,9 @@ name: build on: push: - branches: [ main, dev ] + branches: [ main, dev, build ] pull_request: - branches: [ main, dev ] + branches: [ main, dev, build ] schedule: - cron: '0 0 1 * *' From bda99a2444bc3ed317e723b54e27dc71ef58c4cc Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 08:25:42 -0700 Subject: [PATCH 07/23] resolve numpy deprecation warning, extract single element --- wntr/epanet/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wntr/epanet/io.py b/wntr/epanet/io.py index 43d580c70..fea4e3651 100644 --- a/wntr/epanet/io.py +++ b/wntr/epanet/io.py @@ -2683,7 +2683,7 @@ def read(self, filename, convergence_error=False, darcy_weisbach=False, convert= """ logger.debug('... read energy data ...') for i in range(npumps): - pidx = int(np.fromfile(fin,dtype=np.int32, count=1)) + pidx = int(np.fromfile(fin,dtype=np.int32, count=1)[0]) energy = np.fromfile(fin, dtype=np.dtype(ftype), count=6) self.save_energy_line(pidx, linknames[pidx-1], energy) peakenergy = np.fromfile(fin, dtype=np.dtype(ftype), count=1) From 1df8f3c360bf9ce077fc8886bc9517ec761c5db4 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 08:26:49 -0700 Subject: [PATCH 08/23] resolve pandas future warning, integer keys --- wntr/metrics/topographic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wntr/metrics/topographic.py b/wntr/metrics/topographic.py index 452cd0b2b..7741b4d50 100644 --- a/wntr/metrics/topographic.py +++ b/wntr/metrics/topographic.py @@ -232,9 +232,7 @@ def valve_segments(G, valve_layer): seg_label[all_names.index(node_name)] = seg_index # Collect valved link names - valved_link_names = [] - for i, row in valve_layer.iterrows(): - valved_link_names.append(row[0]) + valved_link_names = list(valve_layer['link'].unique()) # Remove valved edges from G valved_edges = [] From d3b0b250e6b506123faff37ad7b966829baa6547 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 09:30:12 -0700 Subject: [PATCH 09/23] resolved pandas timedelta warnings --- wntr/network/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wntr/network/model.py b/wntr/network/model.py index 2e5869778..26aaeffc1 100644 --- a/wntr/network/model.py +++ b/wntr/network/model.py @@ -1304,8 +1304,8 @@ def assign_demand(self, demand, pattern_prefix="ResetDemand"): # Extract the node demand pattern and resample to match the pattern timestep demand_pattern = demand.loc[:, junc_name] - demand_pattern.index = pd.TimedeltaIndex(demand_pattern.index, "s") - resample_offset = str(int(self.options.time.pattern_timestep)) + "S" + demand_pattern.index = pd.to_timedelta(demand_pattern.index, "s") + resample_offset = str(int(self.options.time.pattern_timestep)) + "s" demand_pattern = demand_pattern.resample(resample_offset).mean() / self.options.hydraulic.demand_multiplier # Add the pattern From 8d3ff0812655510ffec4f5ba1c48f3688a800442 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 09:30:41 -0700 Subject: [PATCH 10/23] commented out windows tests that skip geopandas --- .github/workflows/build_tests.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 97630e0c1..c57475f7d 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -88,16 +88,16 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt python -m pip install -e . - - name: Run Tests on Windows - if: matrix.os == 'windows-latest' - run: | # the following skips windows rst doctests that rely on geopandas (gis.rst and model_io.rst) because skipif is not working properly - coverage erase - coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" -m pytest --doctest-modules --doctest-glob="*.rst" wntr - coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" --append -m pytest --doctest-glob="*.rst" --ignore-glob="*model_io.rst" --ignore-glob="*gis.rst" documentation - env: - COVERAGE_FILE: .coverage.${{ matrix.python-version }}.${{ matrix.os }} + #- name: Run Tests on Windows + # if: matrix.os == 'windows-latest' + # run: | # the following skips windows rst doctests that rely on geopandas (gis.rst and model_io.rst) because skipif is not working properly + # coverage erase + # coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" -m pytest --doctest-modules --doctest-glob="*.rst" wntr + # coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" --append -m pytest --doctest-glob="*.rst" --ignore-glob="*model_io.rst" --ignore-glob="*gis.rst" documentation + # env: + # COVERAGE_FILE: .coverage.${{ matrix.python-version }}.${{ matrix.os }} - name: Run Tests - if: matrix.os != 'windows-latest' + #if: matrix.os != 'windows-latest' run: | coverage erase coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" -m pytest --doctest-modules --doctest-glob="*.rst" wntr From ea7d97a0c6a33d1093298662bb71148cbe1cd8af Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 09:40:18 -0700 Subject: [PATCH 11/23] Added plt.close to doc tests --- documentation/disaster_models.rst | 3 ++- documentation/fragility.rst | 4 +++- documentation/gis.rst | 7 ++++++- documentation/graphics.rst | 19 +++++++++++++------ documentation/layers.rst | 2 ++ documentation/morph.rst | 6 ++++-- documentation/resultsobject.rst | 6 ++++-- 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/documentation/disaster_models.rst b/documentation/disaster_models.rst index 4d57e3128..930763306 100644 --- a/documentation/disaster_models.rst +++ b/documentation/disaster_models.rst @@ -93,7 +93,8 @@ The earthquake properties can be plotted on the network using the following exam >>> plt.tight_layout() >>> plt.savefig('network_pga.png', dpi=300) - + >>> plt.close() + .. _fig-network: .. figure:: figures/network_pga.png :width: 640 diff --git a/documentation/fragility.rst b/documentation/fragility.rst index 81255ec2c..1be848bef 100644 --- a/documentation/fragility.rst +++ b/documentation/fragility.rst @@ -54,6 +54,7 @@ The following example defines a fragility curve with two damage states: Minor da >>> plt.tight_layout() >>> plt.savefig('fragility_curve.png', dpi=300) + >>> plt.close() :numref:`fig-fragility` illustrates the fragility curve as a function of peak ground acceleration. For example, if the peak ground acceleration is 0.3 at @@ -108,7 +109,8 @@ To plot the damage state on the network, the state (i.e., Major) can be converte >>> plt.tight_layout() >>> plt.savefig('damage_state.png', dpi=300) - + >>> plt.close() + .. _fig-damage-state: .. figure:: figures/damage_state.png :width: 640 diff --git a/documentation/gis.rst b/documentation/gis.rst index 7592c5615..bbf5959ca 100644 --- a/documentation/gis.rst +++ b/documentation/gis.rst @@ -427,6 +427,7 @@ the hydrants snapped to the junctions in Net1. >>> bounds = ax.axis('equal') >>> plt.tight_layout() >>> plt.savefig('snap_points.png', dpi=300) + >>> plt.close() .. _fig-snap-points: .. figure:: figures/snap_points.png @@ -511,6 +512,7 @@ illustrates the valve layer created by snapping points to lines in Net1. >>> bounds = ax.axis('equal') >>> plt.tight_layout() >>> plt.savefig('snap_lines.png', dpi=300) + >>> plt.close() .. _fig-snap-lines: .. figure:: figures/snap_lines.png @@ -615,7 +617,8 @@ The pipes are colored based upon their maximum earthquake probability. >>> bounds = ax.axis('equal') >>> plt.tight_layout() >>> plt.savefig('intersect_earthquake.png', dpi=300) - + >>> plt.close() + .. _fig-intersect-earthquake: .. figure:: figures/intersect_earthquake.png :width: 640 @@ -699,6 +702,7 @@ The pipes are colored based upon their weighted mean landslide probability. >>> bounds = ax.axis('equal') >>> plt.tight_layout() >>> plt.savefig('intersect_landslide.png', dpi=300) + >>> plt.close() .. _fig-intersect-landslide: .. figure:: figures/intersect_landslide.png @@ -807,6 +811,7 @@ the census tracts (polygons) is different than the junction and pipe attributes. >>> bounds = ax.axis('equal') >>> plt.tight_layout() >>> plt.savefig('intersect_demographics.png', dpi=300) + >>> plt.close() .. _fig-intersect-demographics: .. figure:: figures/intersect_demographics.png diff --git a/documentation/graphics.rst b/documentation/graphics.rst index 256d59861..ec3363006 100644 --- a/documentation/graphics.rst +++ b/documentation/graphics.rst @@ -63,7 +63,8 @@ which can be further customized by the user. >>> plt.tight_layout() >>> plt.savefig('plot_basic_network.png', dpi=300) - + >>> plt.close() + .. _fig-network-2: .. figure:: figures/plot_basic_network.png :width: 640 @@ -118,7 +119,8 @@ See https://matplotlib.org for more colormap options. >>> plt.tight_layout() >>> plt.savefig('plot_subplot_basic_network.png', dpi=300) - + >>> plt.close() + .. _fig-network-3: .. figure:: figures/plot_subplot_basic_network.png :width: 800 @@ -267,7 +269,8 @@ The following example uses simulation results from above, and converts the graph >>> plt.tight_layout() >>> plt.savefig('plot_pump_curve.png', dpi=300) - + >>> plt.close() + .. _fig-interactive-timeseries: .. figure:: figures/interactive_timeseries.png :width: 640 @@ -331,7 +334,8 @@ The following example plots a pump curve (:numref:`fig-pump`). >>> plt.tight_layout() >>> plt.savefig('plot_pump_curve.png', dpi=300) - + >>> plt.close() + .. _fig-pump: .. figure:: figures/plot_pump_curve.png :width: 640 @@ -380,7 +384,8 @@ level of the tank is included in the figure. >>> plt.tight_layout() >>> plt.savefig('plot_tank_volume_curve.png', dpi=300) - + >>> plt.close() + .. _fig-tank: .. figure:: figures/plot_tank_volume_curve.png :width: 800 @@ -420,6 +425,7 @@ The valves and valve segments are plotted on the network (:numref:`fig-valve_seg >>> plt.tight_layout() >>> plt.savefig('plot_valve_segment.png', dpi=300) + >>> plt.close() .. _fig-valve_segment: .. figure:: figures/plot_valve_segment.png @@ -447,7 +453,8 @@ valves surrounding each valve is plotted on the network >>> plt.tight_layout() >>> plt.savefig('plot_valve_segment_attributes.png', dpi=300) - + >>> plt.close() + .. _fig-valve_segment_attributes: .. figure:: figures/plot_valve_segment_attributes.png :width: 640 diff --git a/documentation/layers.rst b/documentation/layers.rst index ac9a59292..5d7dd8ca9 100644 --- a/documentation/layers.rst +++ b/documentation/layers.rst @@ -80,6 +80,7 @@ The valve layer can be included in water network graphics (:numref:`fig-random-v >>> plt.tight_layout() >>> plt.savefig('random_valve_layer.png', dpi=300) + >>> plt.close() .. _fig-random-valve-layer: .. figure:: figures/random_valve_layer.png @@ -104,6 +105,7 @@ The valve layer can be included in water network graphics (:numref:`fig-strategi >>> plt.tight_layout() >>> plt.savefig('strategic_valve_layer.png', dpi=300) + >>> plt.close() .. _fig-strategic-valve-layer: .. figure:: figures/strategic_valve_layer.png diff --git a/documentation/morph.rst b/documentation/morph.rst index 9400d3700..8d9220828 100644 --- a/documentation/morph.rst +++ b/documentation/morph.rst @@ -147,7 +147,8 @@ approximately 3000 to approximately 1000 (:numref:`fig-skel-example`). >>> plt.tight_layout() >>> plt.savefig('skel_example.png', dpi=300) - + >>> plt.close() + .. _fig-skel-example: .. figure:: figures/skel_example.png :width: 800 @@ -202,7 +203,8 @@ Pressure differences are very small in this example. >>> plt.tight_layout() >>> plt.savefig('skel_hydraulics.png', dpi=300) - + >>> plt.close() + .. _fig-skel-hydraulics: .. figure:: figures/skel_hydraulics.png :width: 640 diff --git a/documentation/resultsobject.rst b/documentation/resultsobject.rst index 7ba089176..eff4c44ce 100644 --- a/documentation/resultsobject.rst +++ b/documentation/resultsobject.rst @@ -153,7 +153,8 @@ Data can be plotted as a time series, as shown in :numref:`fig-plot-timeseries`: >>> plt.tight_layout() >>> plt.savefig('plot_timeseries.png', dpi=300) - + >>> plt.close() + .. _fig-plot-timeseries: .. figure:: figures/plot_timeseries.png :width: 640 @@ -182,7 +183,8 @@ plotted in a similar manner. >>> plt.tight_layout() >>> plt.savefig('plot_network.png', dpi=300) - + >>> plt.close() + .. _fig-plot-network: .. figure:: figures/plot_network.png :width: 640 From de198faf5507d2c50e30edb0dfce4d5d3af807e0 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 09:54:31 -0700 Subject: [PATCH 12/23] resolve pandas deprecation warning --- wntr/metrics/topographic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wntr/metrics/topographic.py b/wntr/metrics/topographic.py index 7741b4d50..a1f291b21 100644 --- a/wntr/metrics/topographic.py +++ b/wntr/metrics/topographic.py @@ -269,7 +269,7 @@ def valve_segments(G, valve_layer): # and label link and unvalved node together if link_valves.shape[0] == 1: both_node_names = [node1_name, node2_name] - valved_node_name = link_valves.iloc[0][1] + valved_node_name = link_valves.iloc[0]['node'] both_node_names.remove(valved_node_name) unvalved_node_name = both_node_names[0] unvalved_node_index = all_names.index('N_'+unvalved_node_name) @@ -281,7 +281,7 @@ def valve_segments(G, valve_layer): seg_label[link_index] = seg_label[unvalved_node_index] # Links with link_valves.size == 2 are already labelled (isolated link) - elif link_valves.shape[0] ==2: + elif link_valves.shape[0] == 2: continue else: raise Exception("Each link should have a maximum of two valves.") From f6b6dd442bf5c958fb10a7b6ff8cc74e52a56b49 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 10:05:36 -0700 Subject: [PATCH 13/23] resolve future warning --- wntr/gis/geospatial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wntr/gis/geospatial.py b/wntr/gis/geospatial.py index 40e52d352..d73fbd8cc 100644 --- a/wntr/gis/geospatial.py +++ b/wntr/gis/geospatial.py @@ -256,7 +256,7 @@ def intersect(A, B, B_value=None, include_background=False, background_value=0): for i in B.index: B_geom = gpd.GeoDataFrame(B.loc[[i],:], crs=B.crs) - val = float(B_geom[B_value]) + val = float(B_geom.iloc[0][B_value]) A_subset = A.loc[stats['intersections'].apply(lambda x: i in x),:] #print(i, lines_subset) A_clip = gpd.clip(A_subset, B_geom) From 8a417660e31ffaa78bb7c7c6b2078457a6173d30 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 10:13:12 -0700 Subject: [PATCH 14/23] clean up build workflow --- .github/workflows/build_tests.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index c57475f7d..c7e928b67 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -88,16 +88,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt python -m pip install -e . - #- name: Run Tests on Windows - # if: matrix.os == 'windows-latest' - # run: | # the following skips windows rst doctests that rely on geopandas (gis.rst and model_io.rst) because skipif is not working properly - # coverage erase - # coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" -m pytest --doctest-modules --doctest-glob="*.rst" wntr - # coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" --append -m pytest --doctest-glob="*.rst" --ignore-glob="*model_io.rst" --ignore-glob="*gis.rst" documentation - # env: - # COVERAGE_FILE: .coverage.${{ matrix.python-version }}.${{ matrix.os }} - name: Run Tests - #if: matrix.os != 'windows-latest' run: | coverage erase coverage run --context=${{ matrix.os }}.py${{ matrix.python-version }} --source=wntr --omit="*/tests/*","*/sim/network_isolation/network_isolation.py","*/sim/aml/evaluator.py" -m pytest --doctest-modules --doctest-glob="*.rst" wntr From dd803e982d8c2a4201cfa535427f8338cc8714d6 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 10:13:38 -0700 Subject: [PATCH 15/23] Remove version limit on plotly --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d8a06c5ae..dcac431d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ matplotlib setuptools # Optional -plotly<=5.11.0 +plotly folium utm openpyxl From a8d9cf0dc03c11fcf7115a9e880258f4b1c90ca3 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 10:26:03 -0700 Subject: [PATCH 16/23] updated install instructions for geopandas --- documentation/installation.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/installation.rst b/documentation/installation.rst index 113580795..8e1a3fb8a 100644 --- a/documentation/installation.rst +++ b/documentation/installation.rst @@ -285,8 +285,7 @@ To install optional dependencies, run:: .. note:: Proper installation of geopandas requires installing several geopandas dependencies, including fiona, pyproj, and shapely. See https://geopandas.org/en/stable/getting_started/install.html for more information. - On Linux and Mac OS X, installing geopandas through the conda-forge channel will install the dependencies. - On Windows, the dependencies must be installed manually, see https://geoffboeing.com/2014/09/using-geopandas-windows/ for more information. + On Windows, the dependencies can be installed manually, see https://geoffboeing.com/2014/09/using-geopandas-windows/ for more information. .. The following is not shown in the UM WNTR includes a beta version of a Pyomo hydraulic simulator which requires installing From 2896838be6636e18b4af7ac1f449fa97e077039d Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 7 May 2024 12:42:34 -0700 Subject: [PATCH 17/23] removed build branch from workflow --- .github/workflows/build_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index c7e928b67..7a63a3def 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -5,9 +5,9 @@ name: build on: push: - branches: [ main, dev, build ] + branches: [ main, dev ] pull_request: - branches: [ main, dev, build ] + branches: [ main, dev ] schedule: - cron: '0 0 1 * *' From 8dde3ee73792a31f3323669a82f44e1e455b34d7 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 08:24:52 -0700 Subject: [PATCH 18/23] added python 3.12, updated python versions --- .github/workflows/build_deploy_pages.yml | 2 +- .github/workflows/build_tests.yml | 10 +++++----- .github/workflows/release.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_deploy_pages.yml b/.github/workflows/build_deploy_pages.yml index 006ed2fbf..47d69eaf7 100644 --- a/.github/workflows/build_deploy_pages.yml +++ b/.github/workflows/build_deploy_pages.yml @@ -21,7 +21,7 @@ jobs: git remote -v - uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install package run: | pip install -e . diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 7a63a3def..5ab2aac83 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] os: [windows-latest, macOS-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 @@ -47,7 +47,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] os: [windows-latest, macOS-latest, ubuntu-latest] steps: - name: Set up Python @@ -74,7 +74,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] os: [windows-latest, macOS-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 @@ -109,7 +109,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.11 - uses: actions/checkout@v2 - name: Install coverage run: | @@ -155,7 +155,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.11 - uses: actions/checkout@v2 - name: Install coverage run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fa45e892..609502d22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: build the sdist run: | python -m pip install --upgrade build From 9ed1864f2098212600c0f3bcec598f8cc165a789 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 09:16:41 -0700 Subject: [PATCH 19/23] added setuptools to required --- .github/workflows/build_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 5ab2aac83..a28f89426 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -64,7 +64,7 @@ jobs: - name: Install wntr run: | python -m pip install --upgrade pip - pip install wheel numpy scipy networkx pandas matplotlib + pip install wheel numpy scipy networkx pandas matplotlib setuptools pip install --no-index --pre --find-links=. wntr - name: Usage of wntr run: | From f4ebc6a08bc2bae204acd6a2d00b0e7efc82f8e5 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 09:24:47 -0700 Subject: [PATCH 20/23] renamed actions --- .github/workflows/build_tests.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index a28f89426..6e28fe818 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -41,8 +41,7 @@ jobs: name: wntr_${{ matrix.python-version }}_${{ matrix.os }}.whl path: dist/wntr* - test: - name: Test install and usage of wntr + install_import: needs: build runs-on: ${{ matrix.os }} strategy: @@ -70,7 +69,7 @@ jobs: run: | python -c "import wntr" - create_coverage_reports: + pytest_coverage: runs-on: ${{ matrix.os }} strategy: matrix: @@ -103,7 +102,7 @@ jobs: path: .coverage.${{ matrix.python-version }}.${{ matrix.os }} combine_reports: - needs: [ create_coverage_reports ] + needs: [ pytest_coverage ] runs-on: ubuntu-latest steps: - name: Set up Python @@ -148,7 +147,7 @@ jobs: path: htmlcov combine_reports_upload_coveralls: - needs: [ create_coverage_reports ] + needs: [ pytest_coverage ] runs-on: ubuntu-latest continue-on-error: true steps: From 768cd7ec610439042398b100e4dc33026e1da368 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 10:01:54 -0700 Subject: [PATCH 21/23] docs building on py3.11 --- documentation/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/environment.yml b/documentation/environment.yml index ce0758f3d..187b9aa9d 100644 --- a/documentation/environment.yml +++ b/documentation/environment.yml @@ -1,6 +1,6 @@ name: wntr dependencies: - - python=3.10 + - python=3.11 - numpy - scipy - networkx From d9999bcd121329b59c3080bdc8e1b855046020ab Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 10:02:06 -0700 Subject: [PATCH 22/23] added py3.12 to docs --- documentation/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/installation.rst b/documentation/installation.rst index 8e1a3fb8a..d32013c43 100644 --- a/documentation/installation.rst +++ b/documentation/installation.rst @@ -8,7 +8,7 @@ Installation ====================================== .. include:: -WNTR requires 64-bit Python (tested on versions 3.9, 3.10, and 3.11) along with several +WNTR requires 64-bit Python (tested on versions 3.9, 3.10, 3.11, and 3.12) along with several Python package dependencies. See :ref:`requirements` and :ref:`optional_dependencies` for more information. WNTR can be installed as a Python package as briefly described below. @@ -58,7 +58,7 @@ Step 1: Setup the Python environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python can be installed on Windows, Linux, and Mac OS X operating systems. - WNTR requires 64-bit Python (tested on versions 3.9, 3.10, and 3.11) along with several Python package dependencies. + WNTR requires 64-bit Python (tested on versions 3.9, 3.10, 3.11, and 3.12) along with several Python package dependencies. Python distributions, such as Anaconda, are recommended to manage the Python environment. Anaconda can be downloaded from https://www.anaconda.com/products/individual. Additional instructions for setting up a Python environment independent of Anaconda are available at https://docs.python.org/. @@ -240,7 +240,7 @@ To test WNTR, developers can run software tests locally using the following comm Requirements ------------- -Requirements for WNTR include 64-bit Python (tested on versions 3.9, 3.10, and 3.11) along with several Python packages. +Requirements for WNTR include 64-bit Python (tested on versions 3.9, 3.10, 3.11, and 3.12) along with several Python packages. Users should have experience using Python (https://www.python.org/), including the installation of additional Python packages. The following Python packages are required: * NumPy :cite:p:`vacv11`: used to support large, multi-dimensional arrays and matrices, From 775aba61181a50834cf02e51283112194e68bf8e Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 8 May 2024 10:02:14 -0700 Subject: [PATCH 23/23] added release notes --- documentation/whatsnew.rst | 4 ++++ documentation/whatsnew/v1.2.0.rst | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 documentation/whatsnew/v1.2.0.rst diff --git a/documentation/whatsnew.rst b/documentation/whatsnew.rst index 8cc2afc92..d1d2659db 100644 --- a/documentation/whatsnew.rst +++ b/documentation/whatsnew.rst @@ -1,6 +1,10 @@ Release notes ================ +.. _whatsnew_120: + +.. include:: whatsnew/v1.2.0.rst + .. _whatsnew_110: .. include:: whatsnew/v1.1.0.rst diff --git a/documentation/whatsnew/v1.2.0.rst b/documentation/whatsnew/v1.2.0.rst new file mode 100644 index 000000000..0814855d9 --- /dev/null +++ b/documentation/whatsnew/v1.2.0.rst @@ -0,0 +1,11 @@ +v1.2.0 (main) +--------------------------------------------------- +WNTR version 1.2.0 includes the following updates: + +* Added basic and geospatial jupyter notebook demos, updated documentation, dropped Python 3.7 and 3.8 from testing https://github.com/USEPA/WNTR/pull/419 +* Fix: plot_network bug due to changed networkx draw function behavior https://github.com/USEPA/WNTR/pull/417 +* Fix: Addressing bug caused when units="SI" in a call to write_inp() https://github.com/USEPA/WNTR/pull/410 +* Added EpanetException class and subclasses that allow for cleaner error reporting during IO https://github.com/USEPA/WNTR/pull/381 +* Added google analytics key https://github.com/USEPA/WNTR/pull/406 +* Documentation updates to install WNTR without Anaconda https://github.com/USEPA/WNTR/pull/403 +* Added setuptools and removed readthedocs config https://github.com/USEPA/WNTR/pull/396 \ No newline at end of file