def fix_delta_cycle(links, nodes, cyc_links, imshape): """ Attempt to resolve a single cycle. Attempts to resolve a single cycle within a delta network. The general logic is that all link directions of the cycle are un-set except for those set by highly-reliable algorithms, and a modified direction-setting recipe is implemented to re-set these algorithms. This was developed according to the most commonly-encountered cases for real deltas, but could certainly be improved. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. cyc_links : list Link ids comprising the cycle to fix. imshape : tuple Shape of binary mask as (nrows, ncols). Returns ------- links : dict Network links and associated properties with cycle fixed, if possible. nodes : dict Network nodes and associated properties with cycle fixed, if possible. fixed : bool True if the cycle was resolved, else False. """ def re_set_linkdirs(links, nodes, imshape): # Cycle links are attempted to be reset according to algorithms here. angthreshs = np.linspace(0, 1.2, 20) for a in angthreshs: links, nodes = dy.set_by_known_flow_directions( links, nodes, imshape, angthresh=a, lenthresh=0, alg=dy.algmap('known_fdr_rs')) return links, nodes # Track if fix was successful (1:yes, 0:no) fixed = 1 # List of algorithm ids that should not be reset if previously used to # determine direction # dont_reset_algs = [-1, 0, 4, 5, 13] dont_reset_algs = [ dy.algmap(key) for key in [ 'manual_set', 'inletoutlet', 'main_chans', 'bridges', 'longest_steepest' ] ] # Simplest method: unset the cycle links and reset them according to angles # Get resettale links toreset = [ l for l in cyc_links if links['certain_alg'][links['id'].index(l)] not in dont_reset_algs ] # Get original link orientations in case fix does not work orig = dy.cycle_get_original_orientation(links, toreset) # Set certainty of cycle links to zero for tr in toreset: links['certain'][links['id'].index(tr)] = 0 links, nodes = re_set_linkdirs(links, nodes, imshape) # Check that all links were reset if sum([links['certain'][links['id'].index(l)] for l in toreset]) != len(toreset): fixed = 0 # Check that cycle was resolved cyclenode = links['conn'][links['id'].index(toreset[0])][0] cyc_n, cyc_l = dy.get_cycles(links, nodes, checknode=cyclenode) # If the cycle was not fixed, try again, but set the cycle links AND the # links connected to the cycle to unknown if cyc_n is not None and cyclenode in cyc_n[0]: # First return to original orientation links = dy.cycle_return_to_original_orientation(links, orig) # Get all cycle links and those connected to cycle toreset = set() for cn in cyc_n[0]: conn = nodes['conn'][nodes['id'].index(cn)] toreset.update(conn) toreset = list(toreset) # Save original orientation in case cycle cannot be fixed orig_links = dy.cycle_get_original_orientation(links, toreset) # Un-set the cycle+connected links for tr in toreset: lidx = links['id'].index(tr) if links['certain_alg'][lidx] not in dont_reset_algs: links['certain'][lidx] = 0 links, nodes = re_set_linkdirs(links, nodes, imshape) # See if the fix resolved the cycle - if not, reset to original cyc_n, cyc_l = dy.get_cycles(links, nodes, checknode=cyclenode) if cyc_n is not None and cyclenode in cyc_n[0]: links = dy.cycle_return_to_original_orientation(links, orig_links) fixed = 0 return links, nodes, fixed
def fix_river_cycle(links, nodes, cyclelinks, cyclenodes, imshape): """ Attempt to fix a single cycle. Attempts to resolve a single cycle within a river network. The general logic is that all link directions of the cycle are un-set except for those set by highly-reliable algorithms, and a modified direction-setting recipe is implemented to re-set these algorithms. This was developed according to the most commonly-encountered cases for real braided rivers, but could certainly be improved. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. cyclelinks : list List of link ids that comprise a cycle. cyclenodes : list List of node ids taht comprise a cycle. imshape : tuple Shape of binary mask as (nrows, ncols). Returns ------- links : dict Network links and associated properties with the cycle fixed if possible. nodes : dict Network nodes and associated properties with the cycle fixed if possible. fixed : int 1 if the cycle was resolved, else 0. """ # dont_reset_algs = [20, 21, 22, 23, 0, 5] dont_reset_algs = [ dy.algmap(key) for key in [ 'manual_set', 'cl_dist_guess', 'cl_ang_guess', 'cl_dist_set', 'cl_ang_set', 'inletoutlet', 'bridges' ] ] fixed = 1 # One if fix was successful, else zero reset = 0 # One if original orientation need to be reset # If an artifical node triad is present, flip its direction and see if the # cycle is resolved. # See if any links are part of an artificial triad clset = set(cyclelinks) all_pars = [] for i, pl in enumerate(links['parallels']): if len(clset.intersection(set(pl))) > 0: all_pars.append(pl) # Get continuity violators before flipping pre_sourcesink = dy.check_continuity(links, nodes) if len( all_pars ) == 1: # There is one parallel link set, flip its direction and re-set other cycle links and see if cycle is resolved certzero = list(set(all_pars[0] + cyclelinks)) orig_links = dy.cycle_get_original_orientation( links, certzero ) # Save the original orientations in case the cycle can't be fixed for cz in certzero: links['certain'][links['id'].index(cz)] = 0 # Flip the links of the triad for l in all_pars[0]: links = lnu.flip_link(links, l) if len( all_pars ) > 1: # If there are multiple parallel pairs, more code needs to be written for these cases print( 'Multiple parallel pairs in the same cycle. Not implemented yet.') return links, nodes, 0 elif len( all_pars ) == 0: # No aritifical node triads; just re-set all the cycle links and see if cycle is resolved certzero = cyclelinks orig_links = dy.cycle_get_original_orientation(links, certzero) for cz in certzero: lidx = links['id'].index(cz) if links['certain_alg'][lidx] not in dont_reset_algs: links['certain'][lidx] = 0 # Resolve the unknown cycle links links, nodes = re_set_linkdirs(links, nodes, imshape) # See if the fix violated continuity - if not, reset to original post_sourcesink = dy.check_continuity(links, nodes) if len(set(post_sourcesink) - set(pre_sourcesink)) > 0: reset = 1 # See if the fix resolved the cycle - if not, reset to original cyc_n, cyc_l = dy.get_cycles(links, nodes, checknode=cyclenodes[0]) if cyc_n is not None and cyclenodes[0] in cyc_n[0]: reset = 1 # Return the links to their original orientations if cycle could not # be resolved if reset == 1: links = dy.cycle_return_to_original_orientation(links, orig_links) # Try a second method to fix the cycle: unset all the links of the # cycle AND the links connected to those links set_to_zero = set() for cn in cyclenodes: conn = nodes['conn'][nodes['id'].index(cn)] set_to_zero.update(conn) set_to_zero = list(set_to_zero) # Save original orientation in case cycle cannot be fixed orig_links = dy.cycle_get_original_orientation(links, set_to_zero) for s in set_to_zero: lidx = links['id'].index(s) if links['certain_alg'][lidx] not in dont_reset_algs: links['certain'][lidx] = 0 links, nodes = re_set_linkdirs(links, nodes, imshape) # See if the fix violated continuity - if not, reset to original post_sourcesink = dy.check_continuity(links, nodes) if len(set(post_sourcesink) - set(pre_sourcesink)) > 0: reset = 1 # See if the fix resolved the cycle - if not, reset to original cyc_n, cyc_l = dy.get_cycles(links, nodes, checknode=cyclenodes[0]) if cyc_n is not None and cyclenodes[0] in cyc_n[0]: reset = 1 if reset == 1: links = dy.cycle_return_to_original_orientation(links, orig_links) fixed = 0 return links, nodes, fixed
def fix_delta_cycles(links, nodes, imshape): """ Attempt to resolve cycles within the network. Attempts to resolve all cycles within the delta network. This function is essentially a wrapper for :func:`fix_delta_cycle`, which is where the heavy lifting is actually done. This function finds cycles, calls :func:`fix_delta_cycle` on each one, then aggregates the results. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. imshape : tuple Shape of binary mask as (nrows, ncols). Returns ------- links : dict Network links and associated properties with all possible cycles fixed. nodes : dict Network nodes and associated properties with all possible cycles fixed. allfixed : bool True if all cycles were resolved, else False. """ # Tracks if all cycles were fixed allfixed = 1 # Create networkx graph object G = nx.MultiDiGraph() G.add_nodes_from(nodes['id']) for lc in links['conn']: G.add_edge(lc[0], lc[1]) # Check for cycles cantfix_links = [] fixed_links = [] if nx.is_directed_acyclic_graph(G) is not True: # Get list of cycles to fix c_nodes, c_links = dy.get_cycles(links, nodes) # Combine all cycles that share links c_links = dy.merge_list_of_lists(c_links) # Fix the cycles for ic, cfix_links in enumerate(c_links): links, nodes, fixed = fix_delta_cycle(links, nodes, cfix_links, imshape) if fixed == 0: cantfix_links.append(ic) elif fixed == 1: fixed_links.append(ic) # Report if len(cantfix_links) > 0: allfixed = 0 print('Could not fix the following cycles (links): {}'.format( [c_links[i] for i in cantfix_links])) if len(c_links) > 0: allfixed = 0 print( 'The following cycles (links) were fixed, but should be manually checked: {}' .format([c_links[i] for i in fixed_links])) else: allfixed = 2 # Indicates there were no cycles to fix return links, nodes, allfixed
def fix_river_cycles(links, nodes, imshape): """ Attempt to resolve cycles in the network. Attempts to resolve all cycles within the river network. This function is essentially a wrapper for :func:`fix_river_cycle`, which is where the heavy lifting is actually done. This function finds cycles, calls :func:`fix_river_cycle` on each one, then aggregates the results. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. imshape : tuple Shape of binary mask as (nrows, ncols). Returns ------- links : dict Network links and associated properties with all possible cycles fixed. nodes : dict Network nodes and associated properties with all possible cycles fixed. cantfix_links : list of lists Contains link ids of unresolvable cycles. Length is equal to number of unresolvable cycles. cantfix_nodes : TYPE Contains node ids of unresolvable cycles. Length is equal to number of unresolvable cycles. """ # Create networkx graph object G = nx.DiGraph() G.add_nodes_from(nodes['id']) for lc in links['conn']: G.add_edge(lc[0], lc[1]) # Check for cycles cantfix_nodes = [] cantfix_links = [] if nx.is_directed_acyclic_graph(G) is not True: # Get list of cycles to fix c_nodes, c_links = dy.get_cycles(links, nodes) # Remove any cycles that are subsets of larger cycles isin = np.empty((len(c_links), 1)) isin[:] = np.nan for icn, cn in enumerate(c_nodes): for icn2, cn2 in enumerate(c_nodes): if cn2 == cn: continue elif len(set(cn) - set(cn2)) == 0: isin[icn] = icn2 break cfix_nodes = [ cn for icn, cn in enumerate(c_nodes) if np.isnan(isin[icn][0]) ] cfix_links = [ cl for icl, cl in enumerate(c_links) if np.isnan(isin[icl][0]) ] print('Attempting to fix {} cycles.'.format(len(cfix_nodes))) # Try to fix all the cycles for cnodes, clinks in zip(cfix_nodes, cfix_links): links, nodes, fixed = fix_river_cycle(links, nodes, clinks, cnodes, imshape) if fixed == 0: cantfix_nodes.append(cnodes) cantfix_links.append(clinks) return links, nodes, cantfix_links, cantfix_nodes