0

I'm trying to assign limiting capacities to pipes that are in series and also branch off of each other. If the current pipe's capacity is > the previous pipes capacity, then the current pipe's limiting capacity = the previous pipes capacity.

In this screenshot, the pipe circled in red would be the limiting capacity for the pipe that is to the right of it, however it wouldn't be the case for the pipe below it. In the screenshot above, the pipe circled in red would be the limiting capacity for the pipe that is to the right of it, however it wouldn't be the case for the pipe below it.

As of right now I've been able to select these pipe branches based on their connection points (Manhole IDs) using the following code:

#Selects pipes that are intersecting trunks
arcpy.Select_analysis (links, "Trunks", """ "TRUNK" = 'YES'""")
arcpy.SelectLayerByLocation_management(links, "INTERSECT", "Trunks")
arcpy.SelectLayerByAttribute_management(links, "REMOVE_FROM_SELECTION", """ "TRUNK" = 'YES'""")

#Creating a dictionary that stores the US (Inlet) and DS (Outlet) manholes and the struct_ID
system = dict()

with arcpy.da.SearchCursor(path, ["INLETNODE", "OUTLETNODE", "NAME"]) as cursor:
for row in cursor:
    if row[1] not in system:
        system[row[1]] = [],[]
    system[row[1]][0].append(row[0])
    system[row[1]][1].append(row[2])

#Finds the pipes upstream of the selected pipe and adds them into the us_pipe_list list
us_pipe_list = []

def find_us(inletnode):
    if inletnode in system:
        us_pipes = system[inletnode][1] #Struct_ID of pipes
        for us_pipe in us_pipes:
            if us_pipe in us_pipe_list: #If the Struct_ID of a pipe is in the list, move on
                return
            else:                       #If the Struct_ID isn't in the list:
                us_pipe_list.append(str(us_pipe)) #add it in,
                us_mh = system[inletnode][0] #and US manhole of pipe
        for mh in us_mh:
            find_us(mh)

pipe_layer = arcpy.mapping.Layer(links)              
with arcpy.da.SearchCursor(pipe_layer, "INLETNODE") as cursor:
    for row in cursor:
        find_us(row[0])

where_clause = "NAME in ("+str(us_pipe_list)[1:-1]+")"
arcpy.SelectLayerByAttribute_management(links, "ADD_TO_SELECTION", where_clause)

What I've thought of so far is to add in the Pipe Capacity field into the system dictionary:

with arcpy.da.SearchCursor(path, ["INLETNODE", "OUTLETNODE", "NAME", "PIPE_CAPACITY"]) as cursor:
for row in cursor:
    if row[1] not in system:
        system[row[1]] = [],[],[]
    system[row[1]][0].append(row[0])
    system[row[1]][1].append(row[2])
    system[row[1]][2].append(row[3])

And then call on these Capacities through the same find_us function that's used to add the pipes that are to be selected to the list (us_pipe_list) that is called on when using the actual Select by Attribute tool at the end. Below is the snippet of code that I'd add in to the find_us function after the us_pipe for loop:

lim_caps = system[inletnode][2] #Capacity of pipes
    for cap in lim_caps:
        if cap > cap_1:
            cap = cap_1
        else:
            cap = cap
        cap_1 = cap

With all that being said, I'm not sure how to actually populate the limiting capacity field after comparing the previous pipe's capacity (cap_1) and the current pipe's capacity (cap) to see if cap > cap_1.

1

This is a cool problem. I assume this is a pressure water network, no a gravity drainage network.

Your code could be a bit simpler and more compact if you used list (and dictionary) comprehensions. eg

system = {row[1]:(row[0],row[2]) for row in arcpy.da.SearchCursor(path, ["INLETNODE", "OUTLETNODE", "NAME"]) }

I find it helpful to create a valence field that has the count of pipes per node. Then you can easily exclude the ends.

I wrote each pass back to disk, so it could be optimised in memory if there were a lot of pipes and iterations, but there were only 4 in the sample.

Here is my solution: Final pass with differences highlighted

#-------------------------------------------------------------------------------
# Name:        capacity
# Purpose:     find limiting capacity of a pressure network.
#
# Author:      kimo
#
# Created:     25/10/2019
# Copyright:   (c) kimo 2019
# Licence:     Creative Commons 3.1 New Zealand
#-------------------------------------------------------------------------------
# Method:
""" given network of nodes and links with pipe attribute capacity
    tag each link with the max upstream capacity found.
    assumes edges and junctions have populated node-id, from-node and to-node keys
    simply iterate until there are no more changes
"""
from __future__ import print_function
import os
import sys
import collections

import arcpy

ws = 'g:/project/capacity/capacity/capacity.gdb'
arcpy.env.workspace = ws

print(arcpy.ListFeatureClasses())
print("pipe count", arcpy.management.GetCount('pipe'))
print("node count", arcpy.management.GetCount('node'))

# add a new field 'limit' to preserve capacity and fill with capacity value
if len(arcpy.ListFields('pipe','limit')) == 0:
    arcpy.management.AddField('pipe', 'limit','DOUBLE')
# initialise
with arcpy.da.UpdateCursor('pipe',['capacity','limit']) as cur:
    for row in cur:
        row[1] = row[0]
        cur.updateRow(row)

# populate valence to separate terminals
# get a combined list of fromnode and tonode ids
from_node = [row[0] for row in arcpy.da.SearchCursor('pipe', ['fromnode'])]
to_node = [row[0] for row in arcpy.da.SearchCursor('pipe', ['tonode'])]
# frequency dictionary
counter = collections.Counter(from_node + to_node)
# update valence field
with arcpy.da.UpdateCursor('node', ['nodeid','valence']) as icur:
    for row in icur:
        row[1] = counter.get(row[0],None)
        icur.updateRow(row)
print("updated valence")

# classify all the nodes as source, sink, (= 1) or  junction (> 1)

# load junctions from disk into python structures

sql = """valence > 1"""
nodes = {row[0]:row[1] for row in arcpy.da.SearchCursor('node', ['nodeid', 'maxcapacity'], sql)}
print(len(nodes), 'junction nodes')
# processing
iteration = 1
change = True
while change:
    # get dicts of pipes with different keys but from point of view of the nodes
    # to == into node, from == from node but fromnode etc is from point of view of edges
    # pipeid is the key for each edge
    fields1 = ['pipeid', 'fromnode', 'tonode', 'limit']
    to_pipes = {row[2]:(row[0],row[1],row[3]) for row in arcpy.da.SearchCursor('pipe', fields1)}

    # make a dict-tuple of all from_pipes for each node, using fromnode
    node_f_pipes = collections.defaultdict(list)
    # (from_node, pipeid) expect one to many
    pairs = [(row[1], row[0]) for row in arcpy.da.SearchCursor('pipe', fields1)]
    for k, v in pairs:
        node_f_pipes[k].append(v)
    ## print(8, node_to_pipes[8])
    # iterate over junction nodes finding max capacity from connected to-node pipes
    # there is only one to-node in a noncyclic directed graph structure
    max_cap = {node:to_pipes.get(node, None)[2] for node in nodes.keys()}
    print('max_cap', len(max_cap),max_cap[8])

    # save max_cap as limit for pipes  in all from-node to_pipes
    limit = {}
    for nodeid in nodes:
        for pipeid in node_f_pipes[nodeid]:
            # print("{}:,{} {}".format(nodeid, pipeid, max_cap[nodeid]))
            limit[pipeid] = max_cap[nodeid]
    ## print('limit', len(limit))

    #  update in nodes
    with arcpy.da.UpdateCursor('node',['nodeid', 'maxcapacity']) as icur:
        for row in icur:
            row[1] = max_cap.get(row[0], None)
            icur.updateRow(row)

    # now update all from-node pipes
    fields = ['pipeid', 'limit', 'capacity']
    change = False
    with arcpy.da.UpdateCursor('pipe', fields) as icur:
        ##print(fields)
        for row in icur:
            if limit.get(row[0]) < row[1]:
                if limit.get(row[0],None):
                    row[1] = limit.get(row[0], None)
                    change = True
            ##print(row)
            icur.updateRow(row)
    # since the limit will propagate we need to repeat len(nodes) times max

    iteration +=1

print("finished after {} iterations".format(iteration))
| improve this answer | |
  • Thanks @KimO, this works great! Unfortunately it looks like I didn't word the question properly. The goal is for the pipes upstream to be limited by the capacities of the pipes downstream of them, which sort of the inverse of your solution. So basically, the most downstream pipe's limiting capacity will always be its actual capacity. I'm currently going through your solution trying to find where to make the change but if you know where the change should be made let me know! Also the system is actually gravity based, since it's a sanitary sewer network. – Reed Oct 25 '19 at 18:47
  • I did start assuming a gravity network, but I thought that you would have to consider all inputs upstream are limited by the sum of the connections. There would never be the case of just one branch providing all the flow. – KimO Oct 25 '19 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.