hspf 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hspf/__init__.py +0 -0
- hspf/data/ParseTable.csv +2541 -0
- hspf/data/Timeseries Catalog/IMPLND/IQUAL.txt +10 -0
- hspf/data/Timeseries Catalog/IMPLND/IWATER.txt +9 -0
- hspf/data/Timeseries Catalog/IMPLND/IWTGAS.txt +6 -0
- hspf/data/Timeseries Catalog/IMPLND/SOLIDS.txt +2 -0
- hspf/data/Timeseries Catalog/PERLND/MSTLAY.txt +2 -0
- hspf/data/Timeseries Catalog/PERLND/PQUAL.txt +19 -0
- hspf/data/Timeseries Catalog/PERLND/PSTEMP.txt +4 -0
- hspf/data/Timeseries Catalog/PERLND/PWATER.txt +39 -0
- hspf/data/Timeseries Catalog/PERLND/PWATGAS.txt +21 -0
- hspf/data/Timeseries Catalog/PERLND/SEDMNT.txt +8 -0
- hspf/data/Timeseries Catalog/PERLND/SNOW.txt +22 -0
- hspf/data/Timeseries Catalog/RCHRES/CONS.txt +7 -0
- hspf/data/Timeseries Catalog/RCHRES/GQUAL.txt +22 -0
- hspf/data/Timeseries Catalog/RCHRES/HTRCH.txt +8 -0
- hspf/data/Timeseries Catalog/RCHRES/HYDR.txt +27 -0
- hspf/data/Timeseries Catalog/RCHRES/NUTRX.txt +50 -0
- hspf/data/Timeseries Catalog/RCHRES/OXRX.txt +8 -0
- hspf/data/Timeseries Catalog/RCHRES/PLANK.txt +24 -0
- hspf/data/Timeseries Catalog/RCHRES/SEDTRN.txt +8 -0
- hspf/hbn.py +487 -0
- hspf/helpers.py +94 -0
- hspf/hspfModel.py +203 -0
- hspf/parser/__init__.py +6 -0
- hspf/parser/graph.py +934 -0
- hspf/parser/parsers.py +516 -0
- hspf/reports.py +1230 -0
- hspf/uci.py +643 -0
- hspf/wdm.py +355 -0
- hspf/wdmReader.py +588 -0
- hspf-2.0.0.dist-info/METADATA +19 -0
- hspf-2.0.0.dist-info/RECORD +34 -0
- hspf-2.0.0.dist-info/WHEEL +4 -0
hspf/parser/graph.py
ADDED
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Feb 6 14:50:45 2025
|
|
4
|
+
|
|
5
|
+
@author: mfratki
|
|
6
|
+
"""
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np
|
|
10
|
+
import math
|
|
11
|
+
|
|
12
|
+
class Node(object):
|
|
13
|
+
nodes = []
|
|
14
|
+
|
|
15
|
+
def __init__(self, label):
|
|
16
|
+
self._label = label
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return self._label
|
|
20
|
+
|
|
21
|
+
# class PerlndNode(Node):
|
|
22
|
+
# raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
# class ReachNode(Node):
|
|
25
|
+
# raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
# class ImplndNode(Node):
|
|
28
|
+
# raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
# class SourceNode(Node):
|
|
31
|
+
# raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
# class TargetNode(Node):
|
|
34
|
+
# raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
# class MetNode(Node):
|
|
37
|
+
# raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# class wdmNode(Node):
|
|
42
|
+
# raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# # Add Parameter Nodes Add edges at same time since it's expensive to determine associated plern/implnd/reach node
|
|
46
|
+
# keys = [key for key in uci.uci.keys() if key[0] in ['IMPLND','RCHRES','PERLND']]
|
|
47
|
+
# for operation,table_name,table_id in keys:
|
|
48
|
+
# parms = uci.table(operation,table_name,table_id)
|
|
49
|
+
# for opnid, row in parms.iterrows():
|
|
50
|
+
# target_node = graph.get_node(G,operation,opnid)
|
|
51
|
+
# for parameter in row.index:
|
|
52
|
+
# G.add_node(max(G.nodes) + 1, type = 'Parameter', value = row[parameter], name = parameter, operation = operation, table_name = table_name, table_id = table_id)
|
|
53
|
+
# #labels[(operation,parameter,table_id)] = [max(G.nodes)]
|
|
54
|
+
# G.add_edge(max(G.nodes), target_node)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_graph(uci):
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Define Node labels
|
|
62
|
+
opn_sequence = uci.table('OPN SEQUENCE').reset_index(drop=True)
|
|
63
|
+
opn_sequence.set_index(['OPERATION','SEGMENT'],inplace=True)
|
|
64
|
+
opn_sequence_labels = opn_sequence.index.drop_duplicates().to_list()
|
|
65
|
+
G = nx.MultiDiGraph()
|
|
66
|
+
[G.add_node(node, id = node, category = 'OPERATION', type_id = label[1], type = label[0] ) for node,label in enumerate(opn_sequence_labels)]
|
|
67
|
+
|
|
68
|
+
# ext_sources = uci.table('EXT SOURCES').reset_index(drop=True)
|
|
69
|
+
# ext_sources.set_index(['SVOL','SVOLNO'],inplace=True)
|
|
70
|
+
# ext_sources_labels = ext_sources.index.drop_duplicates().to_list()
|
|
71
|
+
# [G.add_node(max(G.nodes) + 1,id = max(G.nodes) + 1, type = label[0], type_id = label[1], category = 'WDM') for node,label in enumerate(ext_sources_labels)]
|
|
72
|
+
|
|
73
|
+
labels = {v: i for i, v in enumerate(opn_sequence_labels)}# + ext_sources_labels)}
|
|
74
|
+
|
|
75
|
+
#Define edges from Schematic Block
|
|
76
|
+
schematic = uci.table('SCHEMATIC').reset_index(drop=True).set_index(['SVOL','SVOLNO'])
|
|
77
|
+
schematic['snode'] = schematic.index.map(labels)
|
|
78
|
+
schematic.reset_index(inplace=True)
|
|
79
|
+
schematic = schematic.set_index(['TVOL','TVOLNO'])
|
|
80
|
+
schematic['tnode'] = schematic.index.map(labels)
|
|
81
|
+
schematic.reset_index(inplace=True)
|
|
82
|
+
# Nodes in the schematic block that are missing from the opn sequence block (usually the outlet reach)
|
|
83
|
+
#schematic.loc[schematic.index.map(labels).isna()]
|
|
84
|
+
schematic = schematic.loc[schematic[['snode','tnode']].dropna().index] # For now remove that missing node
|
|
85
|
+
schematic.loc[:,'TMEMSB1'].replace('',pd.NA,inplace=True)
|
|
86
|
+
schematic.loc[:,'TMEMSB2'].replace('',pd.NA,inplace=True)
|
|
87
|
+
schematic.loc[:,'MLNO'].replace('',pd.NA,inplace=True)
|
|
88
|
+
|
|
89
|
+
schematic = schematic.astype({'snode': int,'tnode':int,'MLNO':pd.Int64Dtype(),'TMEMSB1':pd.Int64Dtype(),'TMEMSB2':pd.Int64Dtype()})
|
|
90
|
+
for index, row in schematic.iterrows():
|
|
91
|
+
if row['SVOL'] == 'GENER':
|
|
92
|
+
G.add_edge(row['snode'],row['tnode'],
|
|
93
|
+
mlno = row['MLNO'],
|
|
94
|
+
count = row['AFACTR'],
|
|
95
|
+
tmemsb1 = row['TMEMSB1'],
|
|
96
|
+
tmemsb2 = row['TMEMSB2'])
|
|
97
|
+
else:
|
|
98
|
+
G.add_edge(row['snode'],row['tnode'],
|
|
99
|
+
mlno = row['MLNO'],
|
|
100
|
+
area = row['AFACTR'],
|
|
101
|
+
tmemsb1 = row['TMEMSB1'],
|
|
102
|
+
tmemsb2 = row['TMEMSB2'])
|
|
103
|
+
|
|
104
|
+
# _ = [G.add_edge(row['snode'],row['tnode'],
|
|
105
|
+
# mlno = row['MLNO'],
|
|
106
|
+
# area = row['AFACTR'],
|
|
107
|
+
# tmemsb1 = row['TMEMSB1'],
|
|
108
|
+
# tmemsb2 = row['TMEMSB2']) for index, row in schematic.iterrows()]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
#Define edges from Ext Sources
|
|
113
|
+
# ext_sources['snode'] = ext_sources.index.map(labels)
|
|
114
|
+
# ext_sources.set_index(['TVOL','TOPFST'],inplace=True)
|
|
115
|
+
# ext_sources['tnode'] = ext_sources.index.map(labels)
|
|
116
|
+
# _ = [G.add_edge(row['snode'],row['tnode'],
|
|
117
|
+
# smemn = row['SMEMN'],
|
|
118
|
+
# smemsb = row['SMEMSB'],
|
|
119
|
+
# mfactor = row['MFACTOR'],
|
|
120
|
+
# tran = row['TRAN'],
|
|
121
|
+
# tmemn = row['TMEMN'],
|
|
122
|
+
# tmemsb1 = row['TMEMSB1'],
|
|
123
|
+
# tmemsb2 = row['TMEMSB2']) for index, row in ext_sources.iterrows()]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Add property information
|
|
128
|
+
geninfo = uci.table('PERLND','GEN-INFO')
|
|
129
|
+
for index,row in geninfo.iterrows():
|
|
130
|
+
G.nodes[labels[('PERLND',index)]]['name'] = row['LSID']
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
geninfo = uci.table('IMPLND','GEN-INFO')
|
|
134
|
+
for index,row in geninfo.iterrows():
|
|
135
|
+
G.nodes[labels[('IMPLND',index)]]['name'] = row['LSID']
|
|
136
|
+
|
|
137
|
+
geninfo = uci.table('RCHRES','GEN-INFO')
|
|
138
|
+
for index,row in geninfo.iterrows():
|
|
139
|
+
G.nodes[labels[('RCHRES',index)]]['name'] = row['RCHID']
|
|
140
|
+
G.nodes[labels[('RCHRES',index)]]['lkfg'] = row['LKFG']
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# # Add property information
|
|
144
|
+
# bininfo = uci.table('PERLND','BINARY-INFO')
|
|
145
|
+
# for index,row in geninfo.iterrows():
|
|
146
|
+
# G.nodes[labels[('PERLND',index)]]['name'] = row['LSID']
|
|
147
|
+
|
|
148
|
+
# bininfo = uci.table('IMPLND','BINARY-INFO')
|
|
149
|
+
# for index,row in geninfo.iterrows():
|
|
150
|
+
# G.nodes[labels[('IMPLND',index)]]['name'] = row['LSID']
|
|
151
|
+
|
|
152
|
+
# bininfo = uci.table('RCHRES','BINARY-INFO')
|
|
153
|
+
# for index,row in geninfo.iterrows():
|
|
154
|
+
# G.nodes[labels[('RCHRES',index)]]['name'] = row['RCHID']
|
|
155
|
+
# G.nodes[labels[('RCHRES',index)]]['lkfg'] = row['LKFG']
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# labels = {}
|
|
159
|
+
# for n, d in G.nodes(data=True):
|
|
160
|
+
# l = (d['operation'],d['opnid'])
|
|
161
|
+
# labels[l] = labels.get(l, [])
|
|
162
|
+
# labels[l].append(n)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
G.labels = labels
|
|
167
|
+
return G
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# def create_subgraph(G,start_node):
|
|
171
|
+
# sub_G = nx.MultDiGraph()
|
|
172
|
+
# for n in G.successors_iter(start_node):
|
|
173
|
+
# sub_G.add_path([start_node,n])
|
|
174
|
+
# create_subgraph(G,sub_G,n)
|
|
175
|
+
|
|
176
|
+
# Binary info
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
CREATE TABLE Operation (
|
|
180
|
+
opn VARCHAR,
|
|
181
|
+
opnid INTEGER,
|
|
182
|
+
PRIMARY KEY (opn, opnid)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
"""
|
|
197
|
+
CREATE TABLE Files (
|
|
198
|
+
ftype VARCHAR,
|
|
199
|
+
unit INTEGER NOT NULL PRIMARY KEY,,
|
|
200
|
+
filename VARCHAR
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
CREATE TABLE GenInfo (
|
|
208
|
+
pk INTEGER NOT NULL,
|
|
209
|
+
opn VARCHAR,
|
|
210
|
+
opnid INTEGER,
|
|
211
|
+
PRIMARY KEY (opn, opnid)
|
|
212
|
+
iunits INTEGER,
|
|
213
|
+
ounits INTEGER,
|
|
214
|
+
punit1 INTEGER,
|
|
215
|
+
punit2 INTEGER,
|
|
216
|
+
BUNIT1 INTEGER,
|
|
217
|
+
BUNIT2 INTEGER
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
# # Files
|
|
223
|
+
# files = uci.table('FILES')
|
|
224
|
+
# files['FTYPE'] = files['FTYPE'].replace({'WDM': 'WDM1'})
|
|
225
|
+
# dfs = []
|
|
226
|
+
|
|
227
|
+
# # PerlndInfo
|
|
228
|
+
# operation = 'PERLND'
|
|
229
|
+
# geninfo = uci.table(operation,'GEN-INFO')
|
|
230
|
+
# binaryinfo = uci.table(operation,'BINARY-INFO')
|
|
231
|
+
# if operation == 'RCHRES':
|
|
232
|
+
# geninfo = geninfo.rename(columns = {'RCHID':'LSID',
|
|
233
|
+
# 'BUNITE':'BUNIT1',
|
|
234
|
+
# 'BUNITM': 'BUNIT2',
|
|
235
|
+
# 'PUNITE': 'PUNIT1',
|
|
236
|
+
# 'PUNITM': 'PUNIT2'})
|
|
237
|
+
# df = pd.merge(geninfo,binaryinfo, left_index = True, right_index = True, how = 'outer').reset_index()
|
|
238
|
+
# df.insert(0,'OPN',operation)
|
|
239
|
+
# df = pd.merge(df,files, left_on = 'BUNIT1', right_on = 'UNIT')
|
|
240
|
+
|
|
241
|
+
# # Schematic Table
|
|
242
|
+
# schematic = uci.table('SCHEMATIC')
|
|
243
|
+
|
|
244
|
+
# # Masslink Table
|
|
245
|
+
# masslinks = []
|
|
246
|
+
# for table_name in uci.table_names('MASS-LINK'):
|
|
247
|
+
# mlno = table_name.split('MASS-LINK')[1]
|
|
248
|
+
# masslink = uci.table('MASS-LINK',table_name)
|
|
249
|
+
# masslink.insert(0,'MLNO',mlno)
|
|
250
|
+
# masslinks.append(masslink)
|
|
251
|
+
# masslinks = pd.concat(masslinks)
|
|
252
|
+
|
|
253
|
+
# #masslinks['QUALID'] = (masslinks['SMEMSB1'].str.strip().replace('','0').astype(int)-1).replace(-1,pd.NA)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# hbn_name = uci.table('PERLND','QUAL-PROPS', int(row['SMEMSB1']) - 1).iloc[0]['QUALID']
|
|
258
|
+
|
|
259
|
+
# operation = row['SVOL']
|
|
260
|
+
# activity = row['SGRPN']
|
|
261
|
+
# ts_name = row['SMEMN']
|
|
262
|
+
|
|
263
|
+
# hbn_name = row['SMEMN'] + hbn_name
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# schematic = uci.table('SCHEMATIC')
|
|
267
|
+
# schematic = pd.merge(schematic,masslinks,left_on = 'MLNO',right_on = 'MLNO')
|
|
268
|
+
|
|
269
|
+
# all(schematic['SVOL_x'] == schematic['SVOL_y'])
|
|
270
|
+
# all(schematic['TVOL_x'] == schematic['TVOL_y'])
|
|
271
|
+
# schematic.loc[schematic['TMEMSB1_x'] == '', 'TMEMSB1_y'] = schematic['TMEMSB1_x']
|
|
272
|
+
# schematic.loc[schematic['TMEMSB2_x'] == '', 'TMEMSB2_y'] = schematic['TMEMSB2_x']
|
|
273
|
+
|
|
274
|
+
# schematic = schematic.drop(columns=['TMEMSB1_y','TMEMSB2_y','TVOL_y','SVOL_y'])
|
|
275
|
+
# schematic = schematic.rename(columns = {'SVOL_x':'SVOL',
|
|
276
|
+
# 'TVOL_x':'TVOL',
|
|
277
|
+
# 'TMEMSB2_x':'TMEMSB2',
|
|
278
|
+
# 'TMEMSB1_x':'TMEMSB1'})
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# # Watershed Weighted Mean
|
|
283
|
+
# subwatersheds = uci.network.subwatersheds()
|
|
284
|
+
# subwatersheds = subwatersheds.loc[subwatersheds['SVOL'] == 'PERLND'].reset_index()
|
|
285
|
+
# df = cal.model.hbns.get_multiple_timeseries('PERLND',5,'PERO',test['SVOLNO'].values).mean().reset_index()
|
|
286
|
+
# df.columns = ['OPNID','value']
|
|
287
|
+
# weighted_mean = df[['value','AFACTR']].groupby(df['LSID']).apply(lambda x: (x['value'] * x['AFACTR']).sum() / x['AFACTR'].sum())
|
|
288
|
+
# weighted_mean.loc['combined'] = (df['value'] * df['AFACTR']).sum() / df['AFACTR'].sum()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# # annual weighted timeseries watershed
|
|
292
|
+
# reach_ids = [103,119,104,118]
|
|
293
|
+
# subwatersheds = uci.network.subwatersheds().loc[reach_ids]
|
|
294
|
+
# subwatersheds = subwatersheds.loc[subwatersheds['SVOL'] == 'PERLND'].reset_index()
|
|
295
|
+
# df = cal.model.hbns.get_multiple_timeseries('PERLND',5,'PERO',test['SVOLNO'].values).mean().reset_index()
|
|
296
|
+
# df.columns = ['OPNID','value']
|
|
297
|
+
# df = pd.merge(subwatersheds,df,left_on = 'SVOLNO', right_on='OPNID')
|
|
298
|
+
# weighted_mean = (df['value'] * df['AFACTR']).sum() / df['AFACTR'].sum()
|
|
299
|
+
# df[f'weighted_{ts_name}'] = df.groupby('LSID')[parameter].transform(lambda x: (x * df.loc[x.index, 'AFACTR']).sum() / df.loc[x.index, 'AFACTR'].sum())
|
|
300
|
+
# weighted_mean.loc['combined'] = (df['value'] * df['AFACTR']).sum() / df['AFACTR'].sum()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# # parameter average weighted by landcover area
|
|
305
|
+
# table_name = 'PWAT-PARM2'
|
|
306
|
+
# parameter = 'LZSN'
|
|
307
|
+
# table_id = 0
|
|
308
|
+
# operation = 'PERLND'
|
|
309
|
+
|
|
310
|
+
# subwatersheds = uci.network.subwatersheds()
|
|
311
|
+
# subwatersheds = subwatersheds.loc[subwatersheds['SVOL'] == 'PERLND'].reset_index()
|
|
312
|
+
# df = uci.table(operation,table_name,table_id)[parameter].reset_index()
|
|
313
|
+
# df.columns = ['OPNID',parameter]
|
|
314
|
+
# df = pd.merge(subwatersheds,df,left_on = 'SVOLNO', right_on='OPNID')
|
|
315
|
+
# df[f'weighted_{parameter}'] = df.groupby('TVOLNO')[parameter].transform(lambda x: (x * df.loc[x.index, 'AFACTR']).sum() / df.loc[x.index, 'AFACTR'].sum())
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# #df[f'weighted_{parameter}'] = df.groupby('LSID')[parameter].transform(lambda x: (x * df.loc[x.index, 'AFACTR']).sum() / df.loc[x.index, 'AFACTR'].sum())
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# extsources = uci.table('EXT SOURCES')
|
|
324
|
+
# extsources['SVOL'] = extsources['SVOL'].replace({'WDM': 'WDM1'})
|
|
325
|
+
|
|
326
|
+
# df = pd.merge(extsources,df,left_on = 'SVOL',right_on = 'FTYPE',how = 'right')
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# exttargets = uci.table('EXT TARGETS')
|
|
330
|
+
# schematic = uci.table('SCHEMATIC')
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
#%% Methods using universal node id
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _add_subgraph_labels(G,G_sub):
|
|
337
|
+
G_sub.labels = {label:node for label, node in G.labels.items() if node in G_sub.nodes}
|
|
338
|
+
return G_sub
|
|
339
|
+
|
|
340
|
+
def subgraph(G,nodes):
|
|
341
|
+
def add_subgraph_labels(G,G_sub):
|
|
342
|
+
G_sub.labels = {label:node for label, node in G.labels.items() if node in G_sub.nodes}
|
|
343
|
+
return G_sub
|
|
344
|
+
return add_subgraph_labels(G,G.subgraph(nodes).copy())
|
|
345
|
+
|
|
346
|
+
def _predecessors(G,node_id:int):
|
|
347
|
+
return [G.nodes[node] for node in G.predecessors(node_id)]
|
|
348
|
+
|
|
349
|
+
def _successors(G, node_id:int):
|
|
350
|
+
return [G.nodes[node] for node in G.successors(node_id)]
|
|
351
|
+
|
|
352
|
+
def _ancestors(G,node_id:int):
|
|
353
|
+
'''
|
|
354
|
+
Returns a list of nodes reachable from node node_id
|
|
355
|
+
'''
|
|
356
|
+
#set(nx.get_node_attributes(G,'type').values()) # Get all node types
|
|
357
|
+
return [G.nodes[node] for node in list(nx.ancestors(G,node_id))]
|
|
358
|
+
|
|
359
|
+
def _descendants(G,node_id:int):
|
|
360
|
+
'''
|
|
361
|
+
Returns a list of nodes reachable from node node_id
|
|
362
|
+
'''
|
|
363
|
+
return [G.nodes[node] for node in list(nx.descendants(G,node_id))]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def predecessors(G,node_type:str,node_id:int):
|
|
367
|
+
'''
|
|
368
|
+
Returns a list of nodes of a give type with a direct connection to node_id
|
|
369
|
+
'''
|
|
370
|
+
return [G.nodes[node] for node in list(G.predecessors(node_id)) if G.nodes[node]['type'] == node_type]
|
|
371
|
+
|
|
372
|
+
def successors(G,node_type:str,node_id:int):
|
|
373
|
+
'''
|
|
374
|
+
Returns a list of nodes of a given type that node_id has a direct connection to
|
|
375
|
+
'''
|
|
376
|
+
return [G.nodes[node] for node in list(G.successors(node_id)) if G.nodes[node]['type'] == node_type]
|
|
377
|
+
|
|
378
|
+
def ancestors(G,node_id:int,ancestor_node_type:str):
|
|
379
|
+
'''
|
|
380
|
+
Returns a list of nodes of a give type reachable from node node_id
|
|
381
|
+
'''
|
|
382
|
+
#set(nx.get_node_attributes(G,'type').values()) # Get all node types
|
|
383
|
+
return [G.nodes[node] for node in list(nx.ancestors(G,node_id)) if G.nodes[node]['type'] == ancestor_node_type]
|
|
384
|
+
|
|
385
|
+
def descendants(G,node_id:int,descendant_node_type:str):
|
|
386
|
+
'''
|
|
387
|
+
Returns a list of nodes of a give type reachable from node node_id
|
|
388
|
+
'''
|
|
389
|
+
return [G.nodes[node] for node in list(nx.descendants(G,node_id)) if G.nodes[node]['type'] == descendant_node_type]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def node_types(G):
|
|
393
|
+
return set(nx.get_node_attributes(G,'type').values())
|
|
394
|
+
|
|
395
|
+
def node_categories(G):
|
|
396
|
+
return set(nx.get_node_attributes(G,'category').values())
|
|
397
|
+
|
|
398
|
+
def node_labels(G):
|
|
399
|
+
return {(node['type'],node['type_id']): node['id'] for _, node in G.nodes(data=True)}
|
|
400
|
+
|
|
401
|
+
def get_node_id(G,node_type,node_type_id):
|
|
402
|
+
return node_labels(G)[(node_type,node_type_id)]
|
|
403
|
+
|
|
404
|
+
def get_nodes(G,node_type):
|
|
405
|
+
return [data for node_id, data in G.nodes(data=True) if data['type'] == node_type]
|
|
406
|
+
|
|
407
|
+
def get_node_ids(G,node_type):
|
|
408
|
+
return [node_id for node_id, data in G.nodes(data=True) if data['type'] == node_type]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def nodes(G,node_type,node_type_id,adjacent_node_type):
|
|
412
|
+
return (node for node in predecessors(G,node_type,G.labels[(node_type,node_type_id)]) if G.nodes[node]['type'] == adjacent_node_type)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
#%% Methods using node_type, node_type_id interface
|
|
418
|
+
|
|
419
|
+
def upstream_network(G,reach_id):
|
|
420
|
+
return G.subgraph(nx.ancestors(G,get_node_id(G,'RCHRES',reach_id))).copy()
|
|
421
|
+
|
|
422
|
+
def downstream_network(G,reach_id):
|
|
423
|
+
return G.subgraph(nx.descendants(G,get_node_id(G,'RCHRES',reach_id))).copy()
|
|
424
|
+
|
|
425
|
+
def subset_network(G,reach_id,upstream_reach_ids = None):
|
|
426
|
+
G = upstream_network(G,reach_id)
|
|
427
|
+
if upstream_reach_ids is not None:
|
|
428
|
+
[G.remove_nodes_from(nx.ancestors(G,upstream_reach_id)) for upstream_reach_id in upstream_reach_ids if upstream_reach_id in G.nodes]
|
|
429
|
+
[G.remove_nodes_from([upstream_reach_id]) for upstream_reach_id in upstream_reach_ids if upstream_reach_id in G.nodes]
|
|
430
|
+
#assert([len(sinks(G)) == 0,sinks(G)[0] == reach_id])
|
|
431
|
+
return G
|
|
432
|
+
|
|
433
|
+
def upstream_nodes(G,reach_id,upstream_node_type):
|
|
434
|
+
return ancestors(G,get_node_id(G,'RCHRES',reach_id),upstream_node_type)
|
|
435
|
+
|
|
436
|
+
def downstream_nodes(G,reach_id,downstream_node_type):
|
|
437
|
+
return descendants(G,get_node_id(G,'RCHRES',reach_id),downstream_node_type)
|
|
438
|
+
|
|
439
|
+
def adjacent_nodes(G,reach_id):
|
|
440
|
+
node_id = get_node_id(G,'RCHRES',reach_id)
|
|
441
|
+
return _predecessors(G,node_id) + _successors(G,node_id)
|
|
442
|
+
|
|
443
|
+
def adjacent_upstream_nodes(G,reach_id,upstream_node_type):
|
|
444
|
+
return predecessors(G,upstream_node_type,get_node_id(G,'RCHRES',reach_id))
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def adjacent_downstream_nodes(G,reach_id,downstream_node_type):
|
|
448
|
+
return successors(G,downstream_node_type,get_node_id(G,'RCHRES',reach_id))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def reach_node(G,reach_id):
|
|
452
|
+
return get_node_id(G,'RCHRES',reach_id)
|
|
453
|
+
|
|
454
|
+
def get_perlnd_node(G,perlnd_id):
|
|
455
|
+
return get_node_id(G,'PERLND',perlnd_id)
|
|
456
|
+
|
|
457
|
+
def get_implnd_node(G,implnd_id):
|
|
458
|
+
return get_node_id(G,'IMPLND',implnd_id)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
#%%# Public interfaces
|
|
466
|
+
|
|
467
|
+
def get_node_type_ids(G,node_type = 'RCHRES'):
|
|
468
|
+
return [data['type_id'] for node, data in G.nodes(data = True) if data['type'] == node_type]
|
|
469
|
+
|
|
470
|
+
def get_reaches(G):
|
|
471
|
+
return get_node_type_ids(G, node_type = 'RCHRES')
|
|
472
|
+
|
|
473
|
+
def outlets(G):
|
|
474
|
+
return [G.nodes[node]['type_id'] for node, out_degree in G.out_degree(get_node_ids(G,'RCHRES')) if out_degree == 0]
|
|
475
|
+
|
|
476
|
+
def adjacent_operations(G,operation,reach_id):
|
|
477
|
+
assert operation in ['RCHRES','PERLND','IMPLND']
|
|
478
|
+
return [G.nodes[perlnd_node_id]['id'] for perlnd_node_id in predecessors(G,operation,G.labels[('RCHRES',reach_id)])]
|
|
479
|
+
|
|
480
|
+
def adjacent_perlnds(G,reach_id):
|
|
481
|
+
return [G.nodes[perlnd_node_id] for perlnd_node_id in predecessors(G,'PERLND',G.labels[('RCHRES',reach_id)])]
|
|
482
|
+
|
|
483
|
+
def adjacent_implnds(G,reach_id):
|
|
484
|
+
return [G.nodes[perlnd_node_id] for perlnd_node_id in predecessors(G,'IMPLND',G.labels[('RCHRES',reach_id)])]
|
|
485
|
+
|
|
486
|
+
def adjacent_reaches(G,reach_id):
|
|
487
|
+
return [G.nodes[perlnd_node_id] for perlnd_node_id in predecessors(G,'RCHRES',G.labels[('RCHRES',reach_id)])]
|
|
488
|
+
|
|
489
|
+
def upstream_adjacent_reachs(G,reach_id):
|
|
490
|
+
return [G.nodes[reach_id]['id'] for reach_id in predecessors(G,'RCHRES',G.labels[('RCHRES',reach_id)])]
|
|
491
|
+
|
|
492
|
+
def downstream_adjacent_reachs(G,reach_id):
|
|
493
|
+
return successors(G,'RCHRES',G.labels[('RCHRES',reach_id)])
|
|
494
|
+
|
|
495
|
+
def upstream_reachs(G,reach_id,upstream_reach_ids = None):
|
|
496
|
+
return [node['type_id'] for node in ancestors(G,get_node_id(G,'RCHRES',reach_id),'RCHRES')]
|
|
497
|
+
|
|
498
|
+
def downstream_reachs(G,reach_id, upstream_reach_ids = None):
|
|
499
|
+
return [node['type_id'] for node in descendants(G,get_node_id(G,'RCHRES',reach_id),'RCHRES')]
|
|
500
|
+
|
|
501
|
+
def routing_reachs(G):
|
|
502
|
+
return [reach_id for reach_id in get_reaches(G) if is_routing(G,reach_id)]
|
|
503
|
+
|
|
504
|
+
def is_routing(G,reach_id):
|
|
505
|
+
return all([node['type'] not in ['PERLND', 'IMPLND'] for node in adjacent_nodes(G,reach_id)])
|
|
506
|
+
|
|
507
|
+
def watershed_area(G,reach_ids):
|
|
508
|
+
return float(np.nansum(list(nx.get_edge_attributes(make_watershed(G,reach_ids),'area').values())))
|
|
509
|
+
|
|
510
|
+
def catchment_area(G,reach_id):
|
|
511
|
+
return float(np.nansum(list(nx.get_edge_attributes(make_catchment(G,reach_id),'area').values())))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def paths(G,reach_id,source_type = 'RCHRES'):
|
|
515
|
+
reach_node = get_node_id(G,'RCHRES',reach_id)
|
|
516
|
+
inv_labels = {v: k[1] for k, v in node_labels(G).items()}
|
|
517
|
+
return {inv_labels[source['id']]:[inv_labels[node] for node in nx.shortest_path(G,source['id'],reach_node)] for source in ancestors(G,reach_node,source_type)}
|
|
518
|
+
|
|
519
|
+
def count_ancestors(G,node_type,ancestor_node_type):
|
|
520
|
+
return {node['type_id']:len(ancestors(G,node['id'],ancestor_node_type)) for node in get_nodes(G,node_type)}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# Catchment constructor
|
|
525
|
+
def make_catchment(G,reach_id):
|
|
526
|
+
node_id = get_node_id(G,'RCHRES',reach_id)
|
|
527
|
+
catchment = G.edge_subgraph(G.in_edges(node_id,keys=True))
|
|
528
|
+
nx.set_node_attributes(catchment,node_id,'catchment_id')
|
|
529
|
+
return catchment
|
|
530
|
+
|
|
531
|
+
from itertools import chain
|
|
532
|
+
|
|
533
|
+
def make_watershed(G,reach_ids):
|
|
534
|
+
'''
|
|
535
|
+
Creates a sugraph representing the the catchments upstream of the specified hspf model reaches. Note that a negative reach_ids indicate to subtract that area from the total.
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
'''
|
|
539
|
+
node_ids = set([get_node_id(G,'RCHRES',reach_id) for reach_id in reach_ids if reach_id > 0])
|
|
540
|
+
nodes_to_exclude = set([get_node_id(G,'RCHRES',abs(reach_id)) for reach_id in reach_ids if reach_id < 0])
|
|
541
|
+
node_ids = node_ids - nodes_to_exclude
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
nodes = [list(nx.ancestors(G,node_id)) for node_id in node_ids]
|
|
545
|
+
nodes.append(node_ids)
|
|
546
|
+
nodes = list(set(chain.from_iterable(nodes)))
|
|
547
|
+
watershed = subgraph(G, nodes)
|
|
548
|
+
catchment_id = '_'.join([str(reach_id) for reach_id in reach_ids])
|
|
549
|
+
nx.set_node_attributes(watershed,node_ids,catchment_id)
|
|
550
|
+
return watershed
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def catcments(G):
|
|
554
|
+
return None
|
|
555
|
+
# Catchment selectors
|
|
556
|
+
|
|
557
|
+
'''
|
|
558
|
+
Properties of an HSPF catchment
|
|
559
|
+
- area
|
|
560
|
+
- outlet
|
|
561
|
+
- inlets
|
|
562
|
+
- inflows
|
|
563
|
+
- outflows
|
|
564
|
+
- reach
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
'''
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# def area_by_landcover(G):
|
|
572
|
+
# for k, v in nx.get_edge_attributes(catchment,'aread').items
|
|
573
|
+
# np.nansum([v for k, v in nx.get_edge_attributes(catchment,'area').items()])
|
|
574
|
+
# combined_data = []
|
|
575
|
+
# for u, v, edge_data in test.edges(data=True):
|
|
576
|
+
# row = {
|
|
577
|
+
# 'source': u,
|
|
578
|
+
# 'target': v,
|
|
579
|
+
# **test.nodes[u],
|
|
580
|
+
# **test.nodes[v],
|
|
581
|
+
# **edge_data
|
|
582
|
+
# }
|
|
583
|
+
# combined_data.append(row)
|
|
584
|
+
|
|
585
|
+
def area_by_landcover(catchment):
|
|
586
|
+
return np.nansum([v for k, v in nx.get_edge_attributes(catchment,'area').items()])
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def area(catchment):
|
|
590
|
+
return np.nansum([v for k, v in nx.get_edge_attributes(catchment,'area').items()])
|
|
591
|
+
|
|
592
|
+
def operation_ids(catchment,operation):
|
|
593
|
+
return [k[1] for k,v in catchment.labels.items() if k[0] == operation]
|
|
594
|
+
|
|
595
|
+
def dsn(catchment,tmemn):
|
|
596
|
+
return [catchment.nodes[k[0]]['id'] for k,v in nx.get_edge_attributes(catchment,'tmemn').items() if v == tmemn]
|
|
597
|
+
|
|
598
|
+
# catchment is a subset of a networkx graph constructed from a UCI file.
|
|
599
|
+
class Catchment():
|
|
600
|
+
def __init__(self,catchment):
|
|
601
|
+
self.catchment = catchment
|
|
602
|
+
|
|
603
|
+
def area(self):
|
|
604
|
+
return np.nansum([v for k, v in nx.get_edge_attributes(self.catchment,'area').items()])
|
|
605
|
+
|
|
606
|
+
def operation_ids(self,operation):
|
|
607
|
+
return [self.catchment.nodes[node]['type_id'] for node in get_node_ids(self.catchment,operation) if node in self.catchment.nodes]
|
|
608
|
+
|
|
609
|
+
def dsn(self,tmemn):
|
|
610
|
+
return [self.catchment.nodes[k[0]]['id'] for k,v in nx.get_edge_attributes(self.catchment,'tmemn').items() if v == tmemn]
|
|
611
|
+
|
|
612
|
+
def to_dataframe():
|
|
613
|
+
return
|
|
614
|
+
# def _watershed(G,reach_id):
|
|
615
|
+
|
|
616
|
+
# predecessors = (list(G.predecessors(node)))
|
|
617
|
+
# node = G.labels[('RCHRES',reach_id)]
|
|
618
|
+
# subgraph = nx.subgraph(G,nx.ancestors(G,node))
|
|
619
|
+
# return subgraph
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# def _catchment(G,reach_id):
|
|
623
|
+
|
|
624
|
+
# node = G.labels[('RCHRES',reach_id)]
|
|
625
|
+
|
|
626
|
+
# subgraph = nx.subgraph(G,nx.ancestors(G,node))
|
|
627
|
+
# return subgraph
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
# def paths(G,reach_id):
|
|
631
|
+
|
|
632
|
+
# {source:[node for node in nx.shortest_path(G,source,reach_id)] for source in nx.ancestors(G,reach_id)}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
#%% Legacy Methods for Backwards compatability
|
|
638
|
+
class reachNetwork():
|
|
639
|
+
def __init__(self,uci,reach_id = None):
|
|
640
|
+
self.G = create_graph(uci)
|
|
641
|
+
self.schematic = uci.table('SCHEMATIC').astype({'TVOLNO': int, "SVOLNO": int, 'AFACTR':float})
|
|
642
|
+
self.uci = uci
|
|
643
|
+
|
|
644
|
+
def get_node_type_ids(self,node_type):
|
|
645
|
+
return get_node_type_ids(self.G, node_type)
|
|
646
|
+
|
|
647
|
+
def _upstream(self,reach_id,node_type = 'RCHRES'):
|
|
648
|
+
'''
|
|
649
|
+
Returns list of model reaches upstream of inclusive of reach_id
|
|
650
|
+
|
|
651
|
+
'''
|
|
652
|
+
upstream = [node['type_id'] for node in upstream_nodes(self.G,reach_id,node_type) if node['type'] == 'RCHRES']
|
|
653
|
+
upstream.append(reach_id)
|
|
654
|
+
return upstream
|
|
655
|
+
|
|
656
|
+
def _downstream(self,reach_id,node_type = 'RCHRES'):
|
|
657
|
+
'''
|
|
658
|
+
Returns list of model reaches downstream inclusive of reach_id
|
|
659
|
+
|
|
660
|
+
'''
|
|
661
|
+
downstream = [node['type_id'] for node in downstream_nodes(self.G,reach_id,node_type) if node['type'] == 'RCHRES']
|
|
662
|
+
downstream.insert(0,reach_id)
|
|
663
|
+
return downstream
|
|
664
|
+
|
|
665
|
+
def calibration_order(self,reach_id,upstream_reach_ids = None):
|
|
666
|
+
return calibration_order(self.G,reach_id,upstream_reach_ids)
|
|
667
|
+
|
|
668
|
+
def station_order(self,reach_ids):
|
|
669
|
+
raise NotImplementedError()
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def downstream(self,reach_id):
|
|
673
|
+
'''
|
|
674
|
+
Downstream adjacent reaches
|
|
675
|
+
|
|
676
|
+
'''
|
|
677
|
+
return [node['type_id'] for node in successors(self.G,'RCHRES',get_node_id(self.G,'RCHRES',reach_id))]
|
|
678
|
+
|
|
679
|
+
def upstream(self,reach_id):
|
|
680
|
+
'''
|
|
681
|
+
Upstream adjacent reaches
|
|
682
|
+
|
|
683
|
+
'''
|
|
684
|
+
return [node['type_id'] for node in predecessors(self.G,'RCHRES',get_node_id(self.G,'RCHRES',reach_id))]
|
|
685
|
+
|
|
686
|
+
def get_opnids(self,operation,reach_id, upstream_reach_ids = None):
|
|
687
|
+
'''
|
|
688
|
+
Operation IDs with a path to reach_id. Operations upstream of upstream_reach_ids will not be included
|
|
689
|
+
|
|
690
|
+
'''
|
|
691
|
+
return get_opnids(self.G,operation=operation,reach_id = reach_id, upstream_reach_ids = upstream_reach_ids)
|
|
692
|
+
|
|
693
|
+
def operation_area(self,operation,opnids = None):
|
|
694
|
+
return operation_area(self.uci,operation)
|
|
695
|
+
|
|
696
|
+
def drainage(self,reach_id):
|
|
697
|
+
# Merge source node attributes into edge attributes
|
|
698
|
+
|
|
699
|
+
edges = []
|
|
700
|
+
for u, v, edge_data in make_catchment(self.G,reach_id).edges(data=True):
|
|
701
|
+
source_node_attributes = self.G.nodes[u]
|
|
702
|
+
# Add or update edge attributes with source node attributes
|
|
703
|
+
edge_data["source_type"] = source_node_attributes.get("type")
|
|
704
|
+
edge_data["source_name"] = source_node_attributes.get("name")
|
|
705
|
+
edge_data["source_type_id"] = source_node_attributes.get("type_id")
|
|
706
|
+
edges.append(edge_data)
|
|
707
|
+
|
|
708
|
+
return pd.DataFrame(edges)
|
|
709
|
+
|
|
710
|
+
def subwatersheds(self,reach_ids = None):
|
|
711
|
+
df = subwatersheds(self.uci)
|
|
712
|
+
if reach_ids is not None:
|
|
713
|
+
df = df.loc[df.index.intersection(reach_ids)]
|
|
714
|
+
return df
|
|
715
|
+
|
|
716
|
+
def subwatershed(self,reach_id):
|
|
717
|
+
return subwatershed(self.uci,reach_id) #.loc[reach_id]
|
|
718
|
+
|
|
719
|
+
def subwatershed_area(self,reach_id):
|
|
720
|
+
return self.drainage(reach_id).query("source_type in ['PERLND','IMPLND']")['area'].sum()
|
|
721
|
+
|
|
722
|
+
def reach_contributions(self,operation,opnids):
|
|
723
|
+
return reach_contributions(self.uci,operation,opnids)
|
|
724
|
+
|
|
725
|
+
def drainage_area(self,reach_ids):
|
|
726
|
+
return watershed_area(self.G,reach_ids)
|
|
727
|
+
|
|
728
|
+
def drainage_area_landcover(self,reach_id,group = True):
|
|
729
|
+
reach_ids = self._upstream(reach_id)
|
|
730
|
+
areas = pd.concat([self.subwatershed(reach_id) for reach_id in reach_ids]).groupby(['SVOL','SVOLNO'])['AFACTR'].sum()
|
|
731
|
+
|
|
732
|
+
if group:
|
|
733
|
+
areas = pd.concat([areas[operation].groupby(self.uci.opnid_dict[operation].loc[areas[operation].index,'LSID'].values).sum() for operation in ['PERLND','IMPLND']])
|
|
734
|
+
return areas
|
|
735
|
+
|
|
736
|
+
def outlets(self):
|
|
737
|
+
return [self.G.nodes[node]['type_id'] for node, out_degree in self.G.out_degree() if (out_degree == 0) & (self.G.nodes[node]['type'] == 'RCHRES')]
|
|
738
|
+
|
|
739
|
+
def paths(self,reach_id):
|
|
740
|
+
return paths(self.G,reach_id)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def calibration_order(G,reach_id,upstream_reach_ids = None):
|
|
744
|
+
'''
|
|
745
|
+
Determines the order in which the specified reaches should be calibrated to
|
|
746
|
+
prevent upstream influences. Primarily helpful when calibrating sediment and
|
|
747
|
+
adjusting in channel erosion rates.
|
|
748
|
+
'''
|
|
749
|
+
|
|
750
|
+
order = []
|
|
751
|
+
Gsub = subgraph(G,get_node_ids(G,'RCHRES'))
|
|
752
|
+
while(len(Gsub.nodes)) > 0:
|
|
753
|
+
|
|
754
|
+
nodes_to_remove = [node for node, in_degree in Gsub.in_degree() if in_degree == 0]
|
|
755
|
+
order.append([G.nodes[node]['type_id'] for node in nodes_to_remove])
|
|
756
|
+
Gsub.remove_nodes_from(nodes_to_remove)
|
|
757
|
+
return order
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def get_opnids(G,operation,reach_id = None, upstream_reach_ids = None):
|
|
763
|
+
G = subset_network(G,reach_id,upstream_reach_ids)
|
|
764
|
+
perlnds = [node['type_id'] for node in get_nodes(G,'PERLND')]
|
|
765
|
+
implnds = [node['type_id'] for node in get_nodes(G,'IMPLND')]
|
|
766
|
+
reachs = [node['type_id'] for node in get_nodes(G,'RCHRES')]
|
|
767
|
+
return {'RCHRES':reachs,'PERLND':perlnds,'IMPLND':implnds}[operation]
|
|
768
|
+
#return reachs,perlnds,implnds
|
|
769
|
+
|
|
770
|
+
def drainage(uci,reach_ids):
|
|
771
|
+
return subwatersheds(uci).loc[reach_ids].reset_index()[['SVOL','LSID','AFACTR']].groupby(['LSID','SVOL']).sum()
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def drainage_area(uci,reach_ids,drng_area = 0):
|
|
776
|
+
if len(reach_ids) == 0:
|
|
777
|
+
return drng_area
|
|
778
|
+
else:
|
|
779
|
+
sign = math.copysign(1,reach_ids[0])
|
|
780
|
+
reach_id = int(reach_ids[0]*sign)
|
|
781
|
+
drng_area = drng_area + sign*uci.network.drainage_area(reach_id)
|
|
782
|
+
drainage_area(uci,reach_ids[1:],drng_area)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def reach_contributions(uci,operation,opnids):
|
|
786
|
+
schematic = uci.table('SCHEMATIC').set_index('SVOL')
|
|
787
|
+
schematic = schematic[schematic.index == operation]
|
|
788
|
+
schematic = schematic[schematic['TVOL'] == 'RCHRES'][['SVOLNO','TVOLNO','AFACTR']].astype({'SVOLNO':int,'TVOLNO':int,'AFACTR':float})
|
|
789
|
+
schematic = pd.concat([schematic[['SVOLNO','TVOLNO','AFACTR']][schematic['SVOLNO'] == opnid] for opnid in opnids])
|
|
790
|
+
schematic = schematic.reset_index()
|
|
791
|
+
schematic = schematic.groupby(['SVOL','SVOLNO','TVOLNO']).sum()
|
|
792
|
+
#schematic.columns = [operation,'reach','reachshed']
|
|
793
|
+
#schematic.set_index(operation,drop = True,inplace = True)
|
|
794
|
+
return schematic
|
|
795
|
+
|
|
796
|
+
def subwatersheds(uci):
|
|
797
|
+
schematic = uci.table('SCHEMATIC').set_index('SVOL')
|
|
798
|
+
schematic = schematic[(schematic.index == 'PERLND') | (schematic.index == 'IMPLND')]
|
|
799
|
+
schematic = schematic[schematic['TVOL'] == 'RCHRES'][['SVOLNO','TVOLNO','AFACTR','MLNO']].astype({'SVOLNO':int,'TVOLNO':int,'AFACTR':float,'MLNO':int})
|
|
800
|
+
schematic.reset_index(inplace=True,drop=False)
|
|
801
|
+
schematic.set_index('TVOLNO',inplace=True)
|
|
802
|
+
|
|
803
|
+
dfs = []
|
|
804
|
+
for operation in ['PERLND','IMPLND']:
|
|
805
|
+
df = schematic.loc[schematic['SVOL'] == operation].reset_index()
|
|
806
|
+
df = df.set_index('SVOLNO')
|
|
807
|
+
dfs.append(df.join(uci.table(operation,'GEN-INFO').iloc[:,0]))
|
|
808
|
+
|
|
809
|
+
df = pd.concat(dfs).reset_index()
|
|
810
|
+
df = df.set_index('TVOLNO')
|
|
811
|
+
return df
|
|
812
|
+
|
|
813
|
+
def subwatershed(uci,reach_id):
|
|
814
|
+
return subwatersheds(uci).loc[[reach_id]]
|
|
815
|
+
|
|
816
|
+
def drains_to(uci,opnid,operation):
|
|
817
|
+
schematic = uci.table('SCHEMATIC').set_index('SVOL')
|
|
818
|
+
schematic = schematic[schematic.index == operation]
|
|
819
|
+
schematic = schematic[schematic['TVOL'] == 'RCHRES'][['SVOLNO','TVOLNO','AFACTR']].astype({'SVOLNO':int,'TVOLNO':int,'AFACTR':float})
|
|
820
|
+
schematic = schematic[schematic['TVOLNO'] == opnid]
|
|
821
|
+
return schematic
|
|
822
|
+
|
|
823
|
+
def landcover_area(uci):
|
|
824
|
+
return pd.concat([operation_area(uci,operation) for operation in ['PERLND','IMPLND']])
|
|
825
|
+
|
|
826
|
+
def operation_area(uci,operation):
|
|
827
|
+
# schematic = uci.table('SCHEMATIC').copy()
|
|
828
|
+
# schematic = schematic.astype({'TVOLNO': int, "SVOLNO": int, 'AFACTR':float})
|
|
829
|
+
# schematic = schematic.groupby(['SVOL','SVOLNO']).sum()
|
|
830
|
+
# schematic = schematic.loc[[operation]].droplevel(0)['AFACTR'].to_frame()
|
|
831
|
+
df = subwatersheds(uci)
|
|
832
|
+
df = df.loc[df['SVOL'] == operation,['AFACTR','SVOLNO']]
|
|
833
|
+
df = df.set_index('SVOLNO')
|
|
834
|
+
df['LSID'] = uci.table(operation,'GEN-INFO').iloc[:,0].loc[df.index].values
|
|
835
|
+
return df
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
# p = paths(G,reach_id,'RCHRES')
|
|
845
|
+
# ptotout = hbn.get_multiple_timeseries('RCHRES',4,'PTOTOUT',reach_ids)
|
|
846
|
+
# ptotin = hbn.get_multiple_timeseries('RCHRES',4,'PTOTIN',reach_ids)
|
|
847
|
+
# reach_losses = 1-(ptotin-ptotout)/ptotin
|
|
848
|
+
# loads = subwatershed_total_phosphorous_loading(uci,hbn,t_code=4)
|
|
849
|
+
|
|
850
|
+
# loss_factors = pd.concat([reach_losses[v].prod(axis=1) for k,v in p.items()],axis=1)
|
|
851
|
+
# loss_factors.columns = list(p.keys())
|
|
852
|
+
# allocations = loads[loss_factors.columns].mul(loss_factors.values,axis=1)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
# def loss_factor(G,reach_id,reach_losses):
|
|
857
|
+
# p = paths(G,reach_id,'RCHRES')
|
|
858
|
+
# loss_factors = pd.concat([reach_losses[v].prod(axis=1) for k,v in p.items()],axis=1)
|
|
859
|
+
# loss_factors.columns = list(p.keys())
|
|
860
|
+
|
|
861
|
+
# return return_loss_factors
|
|
862
|
+
|
|
863
|
+
# def allocation(uci,reach_id):
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
# subwatersheds = uci.network.subwatersheds()
|
|
869
|
+
# load = total_phosphorous(uci,hbn,4)
|
|
870
|
+
# load[subwatersheds.loc[subwatersheds['SVOL'] == 'PERLND']['SVOLNO'].to_list()]
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
# def catchment_area(G,reach_id):
|
|
874
|
+
# node = G.labels[('RCHRES',reach_id)]
|
|
875
|
+
# return [attributes['area'] for _,_, attributes in G.in_edges(node, data=True) if 'area' in attributes.keys()]
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# def upstream_reach(G,reach_id):
|
|
884
|
+
# node = G.labels[('RCHRES',reach_id)]
|
|
885
|
+
# upstream_reach_ids = [node for node in list(G.predecessors(node)) if G.nodes[node]['type'] == 'RCHRES']
|
|
886
|
+
# return upstream_reach_ids
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
# def downstream_reachs(G,reach_id):
|
|
890
|
+
# node = G.labels[('RCHRES',reach_id)]
|
|
891
|
+
# downstream_reach_ids = [node for node in list(G.successors(node)) if G.nodes[node]['type'] == 'RCHRES']
|
|
892
|
+
# return downstream_reach_ids
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
# neighbors = list(G.predecessors(427))
|
|
899
|
+
# upstream_reach_ids = [node for node,operation in nx.get_node_attributes(G,'operation').items() if (operation == 'RCHRES') & (node in neighbors)]
|
|
900
|
+
|
|
901
|
+
# upstream_reach_nodes = [node for node in nx.neighbors(G,node) if 'operation' in node.keys() &
|
|
902
|
+
# subset_graph
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
# def upstream_network(G,reach_id):
|
|
907
|
+
# G = deepcopy(G)
|
|
908
|
+
# ancestors = list(nx.ancestors(G,reach_id))
|
|
909
|
+
# ancestors.insert(0,reach_id)
|
|
910
|
+
# drop = [node for node in G.nodes if node not in ancestors]
|
|
911
|
+
# G.remove_nodes_from(drop)
|
|
912
|
+
# return G
|
|
913
|
+
|
|
914
|
+
# def downstream_network(G,reach_id):
|
|
915
|
+
# G = deepcopy(G)
|
|
916
|
+
# descendants = list(nx.descendants(G,reach_id))
|
|
917
|
+
# drop = [node for node in G.nodes if node not in descendants]
|
|
918
|
+
# G.remove_nodes_from(drop)
|
|
919
|
+
# return G
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
# def subset_graph(G,reach_id, upstream_reach_ids = None):
|
|
923
|
+
# G = upstream_network(G,reach_id)
|
|
924
|
+
# if upstream_reach_ids is not None:
|
|
925
|
+
# [G.remove_nodes_from(nx.ancestors(G,upstream_reach_id)) for upstream_reach_id in upstream_reach_ids if upstream_reach_id in G.nodes]
|
|
926
|
+
# [G.remove_nodes_from([upstream_reach_id]) for upstream_reach_id in upstream_reach_ids if upstream_reach_id in G.nodes]
|
|
927
|
+
# assert([len(sinks(G)) == 0,sinks(G)[0] == reach_id])
|
|
928
|
+
# return G
|
|
929
|
+
|
|
930
|
+
# def sinks(G):
|
|
931
|
+
# return [node for node in G.nodes if (G.out_degree(node) == 0)]
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
|