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/hbn.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Wed Mar 30 15:33:52 2022
|
|
4
|
+
Utility functions for accessing data from the hbn files as they relate to the
|
|
5
|
+
nutrients relevant for our current calibration methods. (See calibration_helpers.py)
|
|
6
|
+
|
|
7
|
+
@author: mfratki
|
|
8
|
+
"""
|
|
9
|
+
from pyhspf import helpers
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import math
|
|
12
|
+
from struct import unpack
|
|
13
|
+
from numpy import fromfile
|
|
14
|
+
from pandas import DataFrame
|
|
15
|
+
from datetime import datetime, timedelta, timezone
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
import numpy as np
|
|
18
|
+
#from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#TIMSERIES_CATALOG = Path('C:/Users/mfratki/Documents/GitHub/hspf_tools/parser/Timeseries Catalog')
|
|
22
|
+
|
|
23
|
+
# catalog = []
|
|
24
|
+
# columns = ['operation','activity','name', 'sub1','sub2','type','units_eng','units_met','comments']
|
|
25
|
+
# for operation in ['PERLND','IMPLND','RCHRES']:
|
|
26
|
+
# files = [file for file in TIMSERIES_CATALOG.joinpath(operation).iterdir()]
|
|
27
|
+
# for file in files:
|
|
28
|
+
# lines = open(file).readlines()
|
|
29
|
+
# for line in lines:
|
|
30
|
+
# values = [col for col in line.split(' ') if col != '']
|
|
31
|
+
# comments = ' '.join(values[6:]).strip('\n')
|
|
32
|
+
# values = values[0:6]
|
|
33
|
+
# values.append(comments)
|
|
34
|
+
# values.insert(0,file.stem)
|
|
35
|
+
# values.insert(0,operation)
|
|
36
|
+
# catalog.append(pd.Series(data = values,index = columns))
|
|
37
|
+
|
|
38
|
+
# df = pd.concat(catalog,axis=1).transpose()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# TIMESERIES_CATALOG = pd.read_csv('C:/Users/mfratki/Documents/GitHub/hspf_tools/parser/Timeseries Catalog/TIMSERIES_CATALOG.csv')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# def timeseries_info(t_opn,t_activity,t_cons):
|
|
45
|
+
# ts_catalog = TIMESERIES_CATALOG.loc[(TIMESERIES_CATALOG['operation'] == t_opn) &
|
|
46
|
+
# (TIMESERIES_CATALOG['activity'] == t_activity)]
|
|
47
|
+
|
|
48
|
+
# ts_info = [row for index,row in ts_catalog.iterrows() if t_cons.startswith(row['name'])]
|
|
49
|
+
|
|
50
|
+
# assert(len(ts_info) <= 1)
|
|
51
|
+
# return ts_info
|
|
52
|
+
|
|
53
|
+
CF2CFS = {'hourly':3600,
|
|
54
|
+
'daily':86400,
|
|
55
|
+
'monthly':2592000,
|
|
56
|
+
'yearly':31536000,
|
|
57
|
+
'h':3600,
|
|
58
|
+
'D':86400,
|
|
59
|
+
'ME':2592000,
|
|
60
|
+
'Y':31536000,
|
|
61
|
+
'YE':31536000,
|
|
62
|
+
2:3600,
|
|
63
|
+
3:86400,
|
|
64
|
+
4:2592000,
|
|
65
|
+
5:31536000}
|
|
66
|
+
|
|
67
|
+
AGG_DEFAULTS = {'cfs':'mean',
|
|
68
|
+
'mg/l':'mean',
|
|
69
|
+
'degF': 'mean',
|
|
70
|
+
'lb':'sum'}
|
|
71
|
+
|
|
72
|
+
UNIT_DEFAULTS = {'Q': 'cfs',
|
|
73
|
+
'TSS': 'mg/l',
|
|
74
|
+
'TP' : 'mg/l',
|
|
75
|
+
'OP' : 'mg/l',
|
|
76
|
+
'TKN': 'mg/l',
|
|
77
|
+
'N' : 'mg/l',
|
|
78
|
+
'WT' : 'degF',
|
|
79
|
+
'WL' : 'ft'}
|
|
80
|
+
|
|
81
|
+
#agg_func = AGG_DEFAULTS[unit]
|
|
82
|
+
def get_simulated_implnd_constituent(hbn,constituent,time_step):
|
|
83
|
+
t_cons = helpers.get_tcons(constituent,'IMPLND')
|
|
84
|
+
df = sum([hbn.get_multiple_timeseries(t_opn='IMPLND',
|
|
85
|
+
t_con= t_con,
|
|
86
|
+
t_code = time_step) for t_con in t_cons])
|
|
87
|
+
# df.loc[:,'OPN'] = 'IMPLND'
|
|
88
|
+
# df.columns = ['OPNID',constituent,'SVOL']
|
|
89
|
+
if constituent == 'TSS':
|
|
90
|
+
df = df*2000
|
|
91
|
+
|
|
92
|
+
return df
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_simulated_perlnd_constituent(hbn,constituent,time_step):
|
|
96
|
+
t_cons = helpers.get_tcons(constituent,'PERLND')
|
|
97
|
+
df = sum([hbn.get_multiple_timeseries(t_opn='PERLND',
|
|
98
|
+
t_con= t_con,
|
|
99
|
+
t_code = time_step) for t_con in t_cons])
|
|
100
|
+
# df.loc[:,'OPN'] = 'PERLND'
|
|
101
|
+
# df.columns = ['OPNID',constituent,'SVOL']
|
|
102
|
+
if constituent == 'TSS':
|
|
103
|
+
df = df*2000
|
|
104
|
+
|
|
105
|
+
return df
|
|
106
|
+
|
|
107
|
+
def get_simulated_catchment_constituent(hbn,constituent,time_step):
|
|
108
|
+
return pd.concat([get_simulated_perlnd_constituent(hbn,constituent,time_step),
|
|
109
|
+
get_simulated_implnd_constituent(hbn,constituent,time_step)])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_simulated_flow(hbn,time_step,reach_ids,unit = None):
|
|
113
|
+
|
|
114
|
+
if unit is None:
|
|
115
|
+
unit = 'cfs'
|
|
116
|
+
assert unit in ['cfs','acrft']
|
|
117
|
+
|
|
118
|
+
# if sign is None:
|
|
119
|
+
# exclude = [1 for i in enumerate(reach_ids)]
|
|
120
|
+
sign = [math.copysign(1,reach_id) for reach_id in reach_ids]
|
|
121
|
+
reach_ids = [abs(reach_id) for reach_id in reach_ids]
|
|
122
|
+
|
|
123
|
+
flows = hbn.get_multiple_timeseries('RCHRES',time_step,'ROVOL',reach_ids)
|
|
124
|
+
flows = (flows*sign).sum(axis=1) # Correct instances when a flow needs to be subtracted (rare)
|
|
125
|
+
|
|
126
|
+
if unit == 'cfs':
|
|
127
|
+
flows = flows/CF2CFS[time_step]*43560 #Acrfeet/invl to cubic feet/s
|
|
128
|
+
|
|
129
|
+
flows.attrs['unit'] = unit
|
|
130
|
+
return flows
|
|
131
|
+
|
|
132
|
+
def get_simulated_temperature(hbn,units,time_step,reach_ids):
|
|
133
|
+
raise NotImplementedError()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_simulated_reach_constituent(hbn,constituent,time_step,reach_ids,unit = None):
|
|
137
|
+
# if exclude is None:
|
|
138
|
+
# exclude = [1 for i in enumerate(reach_ids)]
|
|
139
|
+
sign = [math.copysign(1,reach_id) for reach_id in reach_ids]
|
|
140
|
+
|
|
141
|
+
if unit is None:
|
|
142
|
+
unit = UNIT_DEFAULTS[constituent]
|
|
143
|
+
else:
|
|
144
|
+
assert(unit in ['mg/l','lb','cfs','degF'])
|
|
145
|
+
|
|
146
|
+
t_cons = helpers.get_tcons(constituent,'RCHRES','lb')
|
|
147
|
+
|
|
148
|
+
# Correct instances when a flow needs to be subtracted (rare)
|
|
149
|
+
df = pd.concat([hbn.get_multiple_timeseries('RCHRES',time_step,t_con,[abs(reach_id) for reach_id in reach_ids])*sign for t_con in t_cons],axis=1).sum(axis=1)
|
|
150
|
+
|
|
151
|
+
if constituent == 'TSS':
|
|
152
|
+
df = df*2000
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if unit == 'mg/l':
|
|
156
|
+
#if time_step not in ['h','hourly']:
|
|
157
|
+
flow = get_simulated_flow(hbn,time_step,reach_ids,'acrft')*1233481.8375475 #(acrft to Liters)
|
|
158
|
+
df = df*453592.37 # lbs to mg/l
|
|
159
|
+
df = df/flow
|
|
160
|
+
|
|
161
|
+
df.attrs['unit'] = unit
|
|
162
|
+
df.attrs['constituent'] = constituent
|
|
163
|
+
df.attrs['reach_ids'] = reach_ids
|
|
164
|
+
return df
|
|
165
|
+
|
|
166
|
+
class hbnInterface:
|
|
167
|
+
def __init__(self,file_paths,Map = True):
|
|
168
|
+
self.names = [file_path for file_path in file_paths]
|
|
169
|
+
self.hbns = [hbnClass(file_path,Map) for file_path in file_paths]
|
|
170
|
+
|
|
171
|
+
def _clear_cache(self):
|
|
172
|
+
[hbn._clear_cache() for hbn in self.hbns]
|
|
173
|
+
|
|
174
|
+
def get_time_series(self, t_opn, t_cons, t_code, opnid, activity = None):
|
|
175
|
+
return pd.concat([hbn.get_time_series(t_opn, t_cons, t_code, opnid, activity) for hbn in self.hbns],axis = 1)
|
|
176
|
+
|
|
177
|
+
def get_multiple_timeseries(self,t_opn,t_code,t_con,opnids = None,activity = None,axis = 1):
|
|
178
|
+
return pd.concat([hbn.get_multiple_timeseries(t_opn,t_code,t_con,opnids,activity) for hbn in self.hbns],axis = 1)
|
|
179
|
+
|
|
180
|
+
def get_reach_constituent(self,constituent,reach_ids,time_step,unit = None):
|
|
181
|
+
if constituent == 'Q':
|
|
182
|
+
df = get_simulated_flow(self,time_step,reach_ids,unit = unit)
|
|
183
|
+
elif constituent == 'WT':
|
|
184
|
+
df = get_simulated_temperature(self,time_step,reach_ids)
|
|
185
|
+
else:
|
|
186
|
+
df = get_simulated_reach_constituent(self,constituent,time_step,reach_ids,unit)
|
|
187
|
+
return df.to_frame()
|
|
188
|
+
|
|
189
|
+
def output_names(self):
|
|
190
|
+
# dd = defaultdict(list)
|
|
191
|
+
# dics = [hbn.output_names() for hbn in self.hbns]
|
|
192
|
+
# for dic in dics:
|
|
193
|
+
# for key, vals in dic.items():
|
|
194
|
+
# [dd[key].append(val) for val in vals]
|
|
195
|
+
dd = defaultdict(set)
|
|
196
|
+
dics = [hbn.output_names() for hbn in self.hbns]
|
|
197
|
+
for dic in dics:
|
|
198
|
+
for key, vals in dic.items():
|
|
199
|
+
[dd[key].add(val) for val in vals]
|
|
200
|
+
return dd
|
|
201
|
+
|
|
202
|
+
def get_perlnd_data(self,constituent,t_code = 'yearly'):
|
|
203
|
+
t_cons = helpers.get_tcons(constituent,'PERLND')
|
|
204
|
+
|
|
205
|
+
df = pd.concat([self.get_multiple_timeseries(t_opn = 'PERLND',
|
|
206
|
+
t_code = t_code,
|
|
207
|
+
t_con = t_con,
|
|
208
|
+
opnids = None)
|
|
209
|
+
for t_con in t_cons],axis = 0)
|
|
210
|
+
|
|
211
|
+
return df
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_rchres_data(self,constituent,reach_ids,units = 'mg/l',t_code = 'daily'):
|
|
215
|
+
'''
|
|
216
|
+
Convience function for accessing the hbn time series associated with our current
|
|
217
|
+
calibration method. Assumes you are summing across all dataframes.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
hbn : TYPE
|
|
222
|
+
DESCRIPTION.
|
|
223
|
+
nutrient_id : TYPE
|
|
224
|
+
DESCRIPTION.
|
|
225
|
+
reach_ids : TYPE
|
|
226
|
+
DESCRIPTION.
|
|
227
|
+
flux : TYPE, optional
|
|
228
|
+
DESCRIPTION. The default is None.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
df : TYPE
|
|
233
|
+
DESCRIPTION.
|
|
234
|
+
|
|
235
|
+
'''
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
t_cons = helpers.get_tcons(constituent,'RCHRES',units)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
df = pd.concat([self.get_multiple_timeseries(t_opn = 'RCHRES',
|
|
244
|
+
t_code =t_code,
|
|
245
|
+
t_con = t_con,
|
|
246
|
+
opnids = reach_ids)
|
|
247
|
+
for t_con in t_cons],axis = 1).sum(1).to_frame()
|
|
248
|
+
|
|
249
|
+
if (constituent == 'Q') & (units == 'cfs'):
|
|
250
|
+
df = df/CF2CFS[t_code]*43560 #Acrfeet/invl to cubic feet/s
|
|
251
|
+
|
|
252
|
+
df.attrs['unit'] = units
|
|
253
|
+
df.attrs['constituent'] = constituent
|
|
254
|
+
df.attrs['reach_ids'] = reach_ids
|
|
255
|
+
return df
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def reach_losses(self,constituent,t_code):
|
|
259
|
+
inflows = pd.concat([self.get_multiple_timeseries('RCHRES',t_code,t_cons) for t_cons in LOSS_MAP[constituent][0]],axis=1).sum()
|
|
260
|
+
outflows = pd.concat([self.get_multiple_timeseries('RCHRES',t_code,t_cons) for t_cons in LOSS_MAP[constituent][1]],axis=1).sum()
|
|
261
|
+
return inflows/outflows
|
|
262
|
+
|
|
263
|
+
LOSS_MAP = {'Q':(['IVOL'],['ROVOL']),
|
|
264
|
+
'TSS': (['ISEDTOT'],['ROSEDTOT']),
|
|
265
|
+
'TP': (['PTOTIN'],['PTOTOUT']),
|
|
266
|
+
'N': ([ 'NO2INTOT', 'NO3INTOT'],['NO3OUTTOT','NO2OUTTOT']),
|
|
267
|
+
'TKN':(['TAMINTOT','NTOTORGIN'],['TAMOUTTOT','NTOTORGOUT']),
|
|
268
|
+
'OP': (['PO4INDIS'],['PO4OUTDIS'])}
|
|
269
|
+
TCODES2FREQ = {1:'min',2:'h',3:'D',4:'M',5:'Y'}
|
|
270
|
+
|
|
271
|
+
class hbnClass:
|
|
272
|
+
def __init__(self,file_name,Map = True):
|
|
273
|
+
self.data(file_name,Map)
|
|
274
|
+
self.tcodes = {'minutely':1,'hourly':2,'daily':3,'monthly':4,'yearly':5,
|
|
275
|
+
1:'minutely',2:'hourly',3:'daily',4:'monthly',5:'yearly',
|
|
276
|
+
'min':1,'h':2,'D':3,'M':4,'Y':5,'H':2,'ME':4,'YE':5}
|
|
277
|
+
self.pandas_tcodes = {1:'min',2:'h',3:'D',4:'ME',5:'YE'}
|
|
278
|
+
def data(self,file_name,Map = False):
|
|
279
|
+
self.file_name = file_name
|
|
280
|
+
self.data = fromfile(self.file_name, 'B')
|
|
281
|
+
if self.data[0] != 0xFD:
|
|
282
|
+
print('BAD HBN FILE - must start with magic number 0xFD')
|
|
283
|
+
return
|
|
284
|
+
if Map == True:
|
|
285
|
+
self.map_hbn()
|
|
286
|
+
else:
|
|
287
|
+
self._clear_cache()
|
|
288
|
+
|
|
289
|
+
def map_hbn(self):
|
|
290
|
+
"""
|
|
291
|
+
Reads ALL data from hbn_file and return them in DataFrame
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
hbn_file : str
|
|
295
|
+
Name/path of HBN created by HSPF.
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
df_summary : DataFrame
|
|
299
|
+
Summary information of data found in HBN file (also saved to HDF5 file.)
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
self.simulation_duration_count = 0
|
|
303
|
+
self.data_frames = {}
|
|
304
|
+
self.summary = []
|
|
305
|
+
self.summarycols = ['Operation', 'Activity', 'segment', 'Frequency', 'Shape', 'Start', 'Stop']
|
|
306
|
+
self.summaryindx = []
|
|
307
|
+
self.output_dictionary = {}
|
|
308
|
+
|
|
309
|
+
data = self.data
|
|
310
|
+
|
|
311
|
+
# Build layout maps of the file's contents
|
|
312
|
+
mapn = defaultdict(list)
|
|
313
|
+
mapd = defaultdict(list)
|
|
314
|
+
index = 1 # already used first byte (magic number)
|
|
315
|
+
while index < len(data):
|
|
316
|
+
rc1, rc2, rc3, rc, rectype, operation, id, activity = unpack('4BI8sI8s', data[index:index + 28])
|
|
317
|
+
rc1 = int(rc1 >> 2)
|
|
318
|
+
rc2 = int(rc2) * 64 + rc1 # 2**6
|
|
319
|
+
rc3 = int(rc3) * 16384 + rc2 # 2**14
|
|
320
|
+
reclen = int(rc) * 4194304 + rc3 - 24 # 2**22
|
|
321
|
+
|
|
322
|
+
operation = operation.decode('ascii').strip() # Python3 converts to bytearray not string
|
|
323
|
+
activity = activity.decode('ascii').strip()
|
|
324
|
+
|
|
325
|
+
if operation not in {'PERLND', 'IMPLND', 'RCHRES'}:
|
|
326
|
+
print('ALIGNMENT ERROR', operation)
|
|
327
|
+
|
|
328
|
+
if rectype == 1: # data record
|
|
329
|
+
tcode = unpack('I', data[index + 32: index + 36])[0]
|
|
330
|
+
mapd[operation, id, activity, tcode].append((index, reclen))
|
|
331
|
+
elif rectype == 0: # data names record
|
|
332
|
+
i = index + 28
|
|
333
|
+
slen = 0
|
|
334
|
+
while slen < reclen:
|
|
335
|
+
ln = unpack('I', data[i + slen: i + slen + 4])[0]
|
|
336
|
+
n = unpack(f'{ln}s', data[i + slen + 4: i + slen + 4 + ln])[0].decode('ascii').strip()
|
|
337
|
+
mapn[operation, id, activity].append(n.replace('-', ''))
|
|
338
|
+
slen += 4 + ln
|
|
339
|
+
else:
|
|
340
|
+
print('UNKNOW RECTYPE', rectype)
|
|
341
|
+
if reclen < 36:
|
|
342
|
+
index += reclen + 29 # found by trial and error
|
|
343
|
+
else:
|
|
344
|
+
index += reclen + 30
|
|
345
|
+
self.mapn = dict(mapn)
|
|
346
|
+
self.mapd = dict(mapd)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def read_data(self,operation,id,activity,tcode):
|
|
350
|
+
rows = []
|
|
351
|
+
times = []
|
|
352
|
+
nvals = len(self.mapn[operation, id, activity]) # number constituent timeseries
|
|
353
|
+
utc_offset = timezone(timedelta(hours=-6)) #UTC is 6hours ahead of CST
|
|
354
|
+
for (index, reclen) in self.mapd[operation, id, activity, tcode]:
|
|
355
|
+
yr, mo, dy, hr, mn = unpack('5I', self.data[index + 36: index + 56])
|
|
356
|
+
hr = hr-1
|
|
357
|
+
dt = datetime(yr, mo, dy, 0, mn ,tzinfo=utc_offset) + timedelta(hours=hr)
|
|
358
|
+
times.append(dt)
|
|
359
|
+
|
|
360
|
+
index += 56
|
|
361
|
+
row = unpack(f'{nvals}f', self.data[index:index + (4 * nvals)])
|
|
362
|
+
rows.append(row)
|
|
363
|
+
dfname = f'{operation}_{activity}_{id:03d}_{tcode}'
|
|
364
|
+
if self.simulation_duration_count == 0:
|
|
365
|
+
self.simulation_duration_count = len(times)
|
|
366
|
+
df = DataFrame(rows, index=times, columns=self.mapn[operation, id, activity]).sort_index(level = 'index')
|
|
367
|
+
if len(df) > 0:
|
|
368
|
+
#if tcode in ['daily',3]:
|
|
369
|
+
self.summaryindx.append(dfname)
|
|
370
|
+
self.summary.append((operation, activity, str(id), self.tcodes[tcode], str(df.shape), df.index[0], df.index[-1]))
|
|
371
|
+
self.output_dictionary[dfname] = self.mapn[operation, id, activity]
|
|
372
|
+
self.data_frames[dfname] = df.resample(self.pandas_tcodes[tcode]).mean() # sets the hours to 00 for non hourly time steps # an expensive operation probably
|
|
373
|
+
return self.data_frames[dfname]
|
|
374
|
+
else:
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
def _clear_cache(self):
|
|
378
|
+
self.data_frames = {}
|
|
379
|
+
self.summary = []
|
|
380
|
+
self.summarycols = ['Operation', 'Activity', 'segment', 'Frequency', 'Shape', 'Start', 'Stop']
|
|
381
|
+
self.summaryindx = []
|
|
382
|
+
self.output_dictionary = {}
|
|
383
|
+
|
|
384
|
+
# def read_data2(self,operation,id,activity,tcode):
|
|
385
|
+
|
|
386
|
+
# rows = []
|
|
387
|
+
# times = []
|
|
388
|
+
|
|
389
|
+
# nvals = len(self.mapn[operation, id, activity]) # number of constituent time series
|
|
390
|
+
# #utc_offset = timezone(timedelta(hours=6)) # UTC is 6 hours ahead of CST
|
|
391
|
+
|
|
392
|
+
# indices, reclens = zip(*self.mapd[operation, id, activity, tcode])
|
|
393
|
+
# indices = np.array(indices)
|
|
394
|
+
# data_array = np.frombuffer(self.data, dtype=np.uint8) # Convert raw data to NumPy array
|
|
395
|
+
|
|
396
|
+
# times = [np.frombuffer(data_array[indice+36: indice+56], dtype=np.int32,count=5) for indice in indices]
|
|
397
|
+
# times = [datetime(time[0],time[1],time[2],time[3]-1) for time in times]
|
|
398
|
+
# rows = [np.frombuffer(data_array[indice + 56:indice +56 + (4 * nvals)], dtype=np.float32) for indice in indices]
|
|
399
|
+
|
|
400
|
+
# df = pd.DataFrame(rows, index=times, columns=self.mapn[operation, id, activity]).sort_index(level = 'index')
|
|
401
|
+
# return df
|
|
402
|
+
|
|
403
|
+
def infer_opnids(self,t_opn, t_cons,activity):
|
|
404
|
+
result = [k[-2] for k,v in self.mapn.items() if (t_cons in v) & (k[0] == t_opn) & (k[-1] == activity)]
|
|
405
|
+
if len(result) == 0:
|
|
406
|
+
return print('No Constituent-OPNID relationship found')
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def infer_activity(self,t_opn, t_cons):
|
|
411
|
+
result = [k[-1] for k,v in self.mapn.items() if (t_cons in v) & (k[0] == t_opn)]
|
|
412
|
+
if len(result) == 0:
|
|
413
|
+
return print('No Constituent-Activity relationship found')
|
|
414
|
+
assert(len(set(result)) == 1)
|
|
415
|
+
return result[0]
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def get_time_series(self, t_opn, t_cons, t_code, opnid, activity = None):
|
|
419
|
+
"""
|
|
420
|
+
get a single time series based on:
|
|
421
|
+
1. t_opn: RCHRES, IMPLND, PERLND
|
|
422
|
+
2. t_opn_id: 1, 2, 3, etc
|
|
423
|
+
3. t_cons: target constituent name
|
|
424
|
+
4. t_activity: HYDR, IQUAL, etc
|
|
425
|
+
5. time_unit: yearly, monthly, full (default is 'full' simulation duration)
|
|
426
|
+
"""
|
|
427
|
+
if isinstance(t_code,str):
|
|
428
|
+
t_code = self.tcodes[t_code]
|
|
429
|
+
|
|
430
|
+
if activity is None:
|
|
431
|
+
activity = self.infer_activity(t_opn,t_cons)
|
|
432
|
+
if activity is None:
|
|
433
|
+
return None
|
|
434
|
+
summaryindx = f'{t_opn}_{activity}_{opnid:03d}_{t_code}'
|
|
435
|
+
if summaryindx in self.summaryindx:
|
|
436
|
+
df = self.data_frames[summaryindx][t_cons].copy()
|
|
437
|
+
#df.index = df.index.shift(-1,TCODES2FREQ[t_code])
|
|
438
|
+
df = df[df.index >= '1996-01-01']
|
|
439
|
+
|
|
440
|
+
elif (t_opn, opnid, activity,t_code) in self.mapd.keys():
|
|
441
|
+
df = self.read_data(t_opn,opnid,activity,t_code)[t_cons].copy()
|
|
442
|
+
#df.index = df.index.shift(-1,TCODES2FREQ[t_code])
|
|
443
|
+
df = df[df.index >= '1996-01-01']
|
|
444
|
+
else:
|
|
445
|
+
df = None
|
|
446
|
+
|
|
447
|
+
return df
|
|
448
|
+
def get_multiple_timeseries(self,t_opn,t_code,t_con,opnids = None,activity = None):
|
|
449
|
+
# a single constituent but multiple opnids
|
|
450
|
+
if isinstance(t_code,str):
|
|
451
|
+
t_code = self.tcodes[t_code]
|
|
452
|
+
|
|
453
|
+
if activity is None:
|
|
454
|
+
activity = self.infer_activity(t_opn,t_con)
|
|
455
|
+
if activity is None:
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
if opnids is None:
|
|
459
|
+
opnids = self.infer_opnids(t_opn,t_con,activity)
|
|
460
|
+
if opnids is None:
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
df = None
|
|
464
|
+
frames = []
|
|
465
|
+
mapd_list = list(self.mapd.keys())
|
|
466
|
+
for opnid in opnids:
|
|
467
|
+
if (t_opn,opnid,activity,t_code) in mapd_list:
|
|
468
|
+
frames.append(self.get_time_series(t_opn,t_con,t_code,opnid,activity).rename(opnid))
|
|
469
|
+
if len(frames)>0:
|
|
470
|
+
df = pd.concat(frames,axis=1)
|
|
471
|
+
|
|
472
|
+
return df
|
|
473
|
+
|
|
474
|
+
def output_names(self):
|
|
475
|
+
activities = set([k[-1] for k,v in self.mapn.items()])
|
|
476
|
+
dic = {}
|
|
477
|
+
for activity in activities:
|
|
478
|
+
t_cons = [v for k,v in self.mapn.items() if k[-1] == activity]
|
|
479
|
+
dic[activity] = set([item for sublist in t_cons for item in sublist])
|
|
480
|
+
return dic
|
|
481
|
+
|
|
482
|
+
@staticmethod
|
|
483
|
+
def get_perlands(summary_indxs):
|
|
484
|
+
perlands = [int(summary_indx.split('_')[-2]) for summary_indx in summary_indxs]
|
|
485
|
+
return perlands
|
|
486
|
+
|
|
487
|
+
|
hspf/helpers.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Wed Nov 10 15:48:19 2021
|
|
4
|
+
|
|
5
|
+
@author: mfratki
|
|
6
|
+
"""
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
#import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def decompose_perlands(metzones,landcovers):
|
|
15
|
+
perlands = {}
|
|
16
|
+
for metzone in metzones:
|
|
17
|
+
metzone = int(metzone)
|
|
18
|
+
for landcover in landcovers:
|
|
19
|
+
landcover = int(landcover)
|
|
20
|
+
perlands[metzone+landcover] = (metzone,landcover)
|
|
21
|
+
return perlands
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_months(month):
|
|
26
|
+
months = {1:'JAN', 2:'FEB', 3:'MAR', 4:'APR', 5:'MAY', 6:'JUN', 7:'JUL',
|
|
27
|
+
8:'AUG', 9:'SEP', 10:'OCT', 11:'NOV', 12:'DEC'}
|
|
28
|
+
return months[month]
|
|
29
|
+
|
|
30
|
+
def get_adjacent_month(month,side = 1):
|
|
31
|
+
months = {1:[12,2], 2:[1,3], 3:[2,4], 4:[3,5], 5:[4,6], 6:[5,7], 7:[6,8],
|
|
32
|
+
8:[7,9], 9:[8,10], 10:[9,11], 11:[10,12], 12:[11,1]}
|
|
33
|
+
return months[month][side]
|
|
34
|
+
|
|
35
|
+
def get_tcons(nutrient_name,operation,units = 'mg/l'):
|
|
36
|
+
''' Convience function for getting the consntituent time series names associated with the nutrients we are
|
|
37
|
+
calibrating for. Note tehat Qual Prop 4 (BOD)
|
|
38
|
+
'''
|
|
39
|
+
|
|
40
|
+
if operation == 'RCHRES':
|
|
41
|
+
MAP = {'mg/l':{'TSS' :['SSEDTOT'], # TSS
|
|
42
|
+
'TKN' :['TAMCONCDIS','NTOTORGCONC'], # TKN
|
|
43
|
+
'N' :['NO2CONCDIS','NO3CONCDIS'], # N
|
|
44
|
+
'OP' :['PO4CONCDIS'], # Ortho
|
|
45
|
+
'TP' :['PTOTCONC']},# BOD is the difference of ptot and ortho
|
|
46
|
+
'lb': {'TSS' :['ROSEDTOT'], # TSS
|
|
47
|
+
'TKN' :['TAMOUTTOT','NTOTORGOUT'], # TKN
|
|
48
|
+
'N' :['NO3OUTTOT','NO2OUTTOT'], # N
|
|
49
|
+
'OP' :['PO4OUTDIS'], # Ortho
|
|
50
|
+
'TP' :['PTOTOUT'],
|
|
51
|
+
'BOD' :['BODOUTTOT']},
|
|
52
|
+
'cfs': {'Q': ['ROVOL']},
|
|
53
|
+
'acrft' : {'Q': ['ROVOL']}}
|
|
54
|
+
|
|
55
|
+
t_cons = MAP[units]
|
|
56
|
+
if operation == 'PERLND':
|
|
57
|
+
t_cons = {'TSS' :['SOSED'],
|
|
58
|
+
'TKN' :['POQUALNH3+NH4'],
|
|
59
|
+
'N' :['POQUALNO3'],
|
|
60
|
+
'OP' :['POQUALORTHO P'],
|
|
61
|
+
'BOD' :['POQUALBOD'],
|
|
62
|
+
'Q' : ['PERO']} # BOD is the difference of ptot and ortho
|
|
63
|
+
if operation == 'IMPLND':
|
|
64
|
+
t_cons = {'TSS' :['SLDS'],
|
|
65
|
+
'TKN' :['POQUALNH3+NH4'],
|
|
66
|
+
'N' :['POQUALNO3'],
|
|
67
|
+
'OP' :['POQUALORTHO P'],
|
|
68
|
+
'BOD' :['POQUALBOD'],
|
|
69
|
+
'Q' : ['SURO']} # BOD is the difference of ptot and ortho
|
|
70
|
+
|
|
71
|
+
return t_cons[nutrient_name]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def nutrient_name(nutrient_id: int):
|
|
75
|
+
key = {0:'TSS',
|
|
76
|
+
1:'TKN',
|
|
77
|
+
2:'N',
|
|
78
|
+
3:'OP',
|
|
79
|
+
4:'TP',# REally this is BOD but you subtract orthophosphate to get BOD
|
|
80
|
+
5:'ChlA',
|
|
81
|
+
6:'DO',
|
|
82
|
+
7: 'Q'}
|
|
83
|
+
return key[nutrient_id]
|
|
84
|
+
|
|
85
|
+
def nutrient_id(nutrient_name: str):
|
|
86
|
+
key = {'TSS' :0,
|
|
87
|
+
'TKN' :1,
|
|
88
|
+
'N' :2,
|
|
89
|
+
'OP' :3,
|
|
90
|
+
'TP' :4, # REally this is BOD but you subtract orthophosphate to get BOD
|
|
91
|
+
'ChlA':5,
|
|
92
|
+
'DO' :6,
|
|
93
|
+
'Q' :7}
|
|
94
|
+
return key[nutrient_name]
|