forked from respec/HSPsquared
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHBNOutput.py
143 lines (123 loc) · 5.71 KB
/
HBNOutput.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
from struct import unpack
from numpy import fromfile
import pandas as pd
from pandas import DataFrame
from datetime import datetime, timedelta
from collections import defaultdict
from typing import Union
class HBNOutput:
def __init__(self, file_name:str) -> None:
self.data_frames = []
self.file_name = file_name
self.simulation_duration_count = 0
self.summary = []
self.summarycols = []
self.summaryindx = []
self.output_dictionary = {}
self.tcodes = {1: 'Minutely', 2: 'Hourly', 3: 'Daily', 4: 'Monthly', 5: 'Yearly'}
def read_data(self) -> None:
"""
Reads ALL data from hbn_file and return them in DataFrame
Parameters
----------
hbn_file : str
Name/path of HBN created by HSPF.
Returns
-------
df_summary : DataFrame
Summary information of data found in HBN file (also saved to HDF5 file.)
"""
data = fromfile(self.file_name, 'B')
if data[0] != 0xFD:
print('BAD HBN FILE - must start with magic number 0xFD')
return
# Build layout maps of the file's contents
mapn = defaultdict(list)
mapd = defaultdict(list)
index = 1 # already used first byte (magic number)
while index < len(data):
rc1, rc2, rc3, rc, rectype, operation, id, activity = unpack('4BI8sI8s', data[index:index + 28])
rc1 = int(rc1 >> 2)
rc2 = int(rc2) * 64 + rc1 # 2**6
rc3 = int(rc3) * 16384 + rc2 # 2**14
reclen = int(rc) * 4194304 + rc3 - 24 # 2**22
operation = operation.decode('ascii').strip() # Python3 converts to bytearray not string
activity = activity.decode('ascii').strip()
if operation not in {'PERLND', 'IMPLND', 'RCHRES'}:
print('ALIGNMENT ERROR', operation)
if rectype == 1: # data record
tcode = unpack('I', data[index + 32: index + 36])[0]
mapd[operation, id, activity, tcode].append((index, reclen))
elif rectype == 0: # data names record
i = index + 28
slen = 0
while slen < reclen:
ln = unpack('I', data[i + slen: i + slen + 4])[0]
n = unpack(f'{ln}s', data[i + slen + 4: i + slen + 4 + ln])[0].decode('ascii').strip()
mapn[operation, id, activity].append(n.replace('-', ''))
slen += 4 + ln
else:
print('UNKNOW RECTYPE', rectype)
if reclen < 36:
index += reclen + 29 # found by trial and error
else:
index += reclen + 30
self.data_frames = []
self.summary = []
self.summarycols = ['Operation', 'Activity', 'segment', 'Frequency', 'Shape', 'Start', 'Stop']
self.summaryindx = []
for (operation, id, activity, tcode) in mapd:
rows = []
times = []
nvals = len(mapn[operation, id, activity])
for (index, reclen) in mapd[operation, id, activity, tcode]:
yr, mo, dy, hr, mn = unpack('5I', data[index + 36: index + 56])
dt = datetime(yr, mo, dy, 0, mn) + timedelta(hours=hr)
times.append(dt)
index += 56
row = unpack(f'{nvals}f', data[index:index + (4 * nvals)])
rows.append(row)
dfname = f'{operation}_{activity}_{id:03d}_{tcode}'
if self.simulation_duration_count == 0:
self.simulation_duration_count = len(times)
df = DataFrame(rows, index=times, columns=mapn[operation, id, activity]).sort_index('index')
self.data_frames.append(df)
self.summaryindx.append(dfname)
self.summary.append((operation, activity, str(id), self.tcodes[tcode], str(df.shape), df.index[0], df.index[-1]))
self.output_dictionary[dfname] = mapn[operation, id, activity]
def get_time_series(self, t_opn:str, t_opn_id:int, t_cons:str, t_activity:str, time_unit:str) -> Union[pd.Series, None]:
"""
get a single time series based on:
1. t_opn: RCHRES, IMPLND, PERLND
2. t_opn_id: 1, 2, 3, etc
3. t_cons: target constituent name
4. t_activity: HYDR, IQUAL, etc
5. time_unit: yearly, monthly, full (default is 'full' simulation duration)
"""
target_tcode = 2
for tcode_key in self.tcodes.keys():
if self.tcodes[tcode_key].lower() == time_unit:
target_tcode = tcode_key
break
target_data_frames = {}
for index_group_key in self.summaryindx:
if index_group_key.endswith(str(target_tcode)):
group_index = self.summaryindx.index(index_group_key)
target_data_frames[index_group_key] = self.data_frames[group_index]
for key in target_data_frames.keys():
(operation, activity, oid, tu) = key.split('_')
if operation == t_opn and int(oid) == t_opn_id and activity == t_activity:
data_frame = target_data_frames[key]
for cons_key in data_frame.keys():
if cons_key == t_cons:
return data_frame[cons_key]
return None
#PRT - I think we'll deprecate this method
@staticmethod
def save_time_series_to_file(file_name:str, time_series:pd.Series) -> None:
with open(file_name, 'w+') as f:
for row in range(len(time_series.index)):
dt = time_series.index[row]
dv = time_series.values[row]
# f.write(f'{dt},{"{:.2f}".format(dv)}\n')
f.write(f'{dt},{dv}\n')