pyPRMS 0.9.7__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.
Files changed (51) hide show
  1. pyPRMS/Exceptions_custom.py +31 -0
  2. pyPRMS/__init__.py +52 -0
  3. pyPRMS/cbh/Cbh.py +431 -0
  4. pyPRMS/cbh/CbhAscii.py +458 -0
  5. pyPRMS/cbh/CbhNetcdf.py +199 -0
  6. pyPRMS/cbh/__init__.py +3 -0
  7. pyPRMS/constants.py +131 -0
  8. pyPRMS/control/Control.py +362 -0
  9. pyPRMS/control/ControlFile.py +161 -0
  10. pyPRMS/control/ControlVariable.py +208 -0
  11. pyPRMS/control/__init__.py +3 -0
  12. pyPRMS/dimensions/Dimension.py +154 -0
  13. pyPRMS/dimensions/Dimensions.py +256 -0
  14. pyPRMS/dimensions/__init__.py +2 -0
  15. pyPRMS/input/DataFile.py +354 -0
  16. pyPRMS/input/InputVariable.py +61 -0
  17. pyPRMS/input/__init__.py +0 -0
  18. pyPRMS/metadata/__init__.py +1 -0
  19. pyPRMS/metadata/metadata.py +430 -0
  20. pyPRMS/parameters/ParamDb.py +73 -0
  21. pyPRMS/parameters/Parameter.py +624 -0
  22. pyPRMS/parameters/ParameterFile.py +190 -0
  23. pyPRMS/parameters/ParameterNetCDF.py +74 -0
  24. pyPRMS/parameters/ParameterSet.py +96 -0
  25. pyPRMS/parameters/Parameters.py +1506 -0
  26. pyPRMS/parameters/__init__.py +5 -0
  27. pyPRMS/plot_helpers.py +305 -0
  28. pyPRMS/prms_helpers.py +235 -0
  29. pyPRMS/py.typed +0 -0
  30. pyPRMS/summary/OutputCSV.py +64 -0
  31. pyPRMS/summary/OutputVariable.py +163 -0
  32. pyPRMS/summary/OutputVariables.py +227 -0
  33. pyPRMS/summary/__init__.py +2 -0
  34. pyPRMS/utilities/__init__.py +0 -0
  35. pyPRMS/utilities/convert_cbh.py +107 -0
  36. pyPRMS/utilities/convert_model_output.py +91 -0
  37. pyPRMS/utilities/convert_params.py +60 -0
  38. pyPRMS/version.py +13 -0
  39. pyPRMS/xml/cbh.xml +163 -0
  40. pyPRMS/xml/control.xml +1447 -0
  41. pyPRMS/xml/dimensions.xml +311 -0
  42. pyPRMS/xml/modules.xml +251 -0
  43. pyPRMS/xml/parameters.xml +6932 -0
  44. pyPRMS/xml/time_series_input.xml +198 -0
  45. pyPRMS/xml/variables.xml +8173 -0
  46. pyprms-0.9.7.dist-info/LICENSE.md +21 -0
  47. pyprms-0.9.7.dist-info/METADATA +67 -0
  48. pyprms-0.9.7.dist-info/RECORD +51 -0
  49. pyprms-0.9.7.dist-info/WHEEL +5 -0
  50. pyprms-0.9.7.dist-info/entry_points.txt +3 -0
  51. pyprms-0.9.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,354 @@
1
+ import os
2
+ import pandas as pd # type: ignore
3
+
4
+ from rich.console import Console
5
+ from rich import pretty
6
+
7
+ from typing import Dict, List, Optional, Sequence, Union
8
+ pretty.install()
9
+ con = Console()
10
+
11
+ from .InputVariable import InputVariable
12
+
13
+ # TS_FORMAT = '%Y %m %d %H %M %S' # 1915 1 13 0 0 0
14
+
15
+ HEADER_SEP = '//////////'
16
+ STATION_START = '// Station IDs for'
17
+ UNITS_START = '// Unit:'
18
+ DATA_SEP = '####'
19
+ COMMENT = '//'
20
+
21
+
22
+ class DataFile(object):
23
+ """Class for working with observed streamflow in the PRMS ASCII data file format"""
24
+
25
+ def __init__(self, filename: Union[str, os.PathLike],
26
+ missing: Sequence[str] = ('-99.9', '-999.0', '-9999.0'),
27
+ verbose: bool = False,
28
+ include_metadata: bool = True):
29
+ """Create the DataFile object.
30
+
31
+ :param filename: name of data file
32
+ :param missing: list of missing values
33
+ :param verbose: output debugging information
34
+ :param include_metadata: whether to include metadata
35
+ """
36
+
37
+ self.__missing = missing
38
+ self.filename = filename
39
+ self.__verbose = verbose
40
+ self.__include_metadata = include_metadata
41
+
42
+ self.__timecols = 6 # number columns for time in the file
43
+ self.__header = '' # data file header from first line of the file
44
+
45
+ # Dictionary of input variables and InputVariable objects
46
+ self.__input_vars: Dict[str, InputVariable] = {}
47
+
48
+ # Internal dictionary of input variables and associated metadata
49
+ self.__input_vars_intern: Dict[str, Dict[str, Union[int, str, List[str], pd.DataFrame]]] = {}
50
+
51
+ self.__data_raw: Optional[pd.DataFrame] = None
52
+
53
+ self.load_file(self.filename)
54
+
55
+ @property
56
+ def data(self) -> pd.DataFrame:
57
+ """Pandas dataframe of the data file for each input variable
58
+
59
+ :returns: Pandas dataframe of the data file
60
+ """
61
+
62
+ return self.__data_raw
63
+
64
+ @property
65
+ def input_variables(self) -> Dict[str, Dict[str, Union[int, str, List[str], pd.DataFrame]]]:
66
+ """Get the input variables in the data file.
67
+
68
+ :returns: Dictionary of input variables that are available in the data file
69
+ """
70
+
71
+ return self.__input_vars_intern
72
+
73
+ def data_by_variable(self, variable: str) -> pd.DataFrame:
74
+ """Get the data for a specific input variable
75
+
76
+ :param variable: name of input variable
77
+ :returns: Pandas dataframe of the data for the input variable
78
+ """
79
+
80
+ import warnings
81
+
82
+ msg = "DataFile.data_by_variable() method to be deprecated"
83
+ warnings.warn(msg, DeprecationWarning)
84
+ data = self.__input_vars[variable].data.copy()
85
+
86
+ # The names are a headache any other way, hard to make them truly
87
+ # backwards compatible using the old code.
88
+ data.columns = variable + "_" + data.columns
89
+ assert type(data) is pd.DataFrame
90
+ return data
91
+
92
+ def get(self, name: str) -> InputVariable:
93
+ """Get the metadata for a specific input variable.
94
+
95
+ :param name: name of input variable
96
+ :returns: InputVariable object
97
+ """
98
+
99
+ return self.__input_vars[name]
100
+
101
+ def load_file(self, filename: Union[str, os.PathLike]):
102
+ """Read the PRMS ASCII streamflow data file.
103
+
104
+ :param filename: name of data file
105
+ """
106
+
107
+ header_info = []
108
+
109
+ with open(filename, 'r') as fhdl:
110
+ # First line is a descriptive header
111
+ self.__header = fhdl.readline().rstrip()
112
+
113
+ # Get the input variable names and sizes
114
+ while line := fhdl.readline():
115
+ line = line.rstrip()
116
+
117
+ if len(line) == 0:
118
+ continue
119
+ if line[0:len(COMMENT)] == COMMENT:
120
+ header_info.append(line)
121
+ continue
122
+ if line[0:len(DATA_SEP)] == DATA_SEP:
123
+ break
124
+
125
+ # Get the input variable name and total size for the variable
126
+ nm: str
127
+ sz: Union[str, int]
128
+
129
+ nm, sz = tuple(line.split())
130
+ sz = int(sz)
131
+
132
+ if sz > 0:
133
+ if nm in self.__input_vars_intern:
134
+ raise KeyError(f'{nm} declared multiple times in the data file')
135
+ self.__input_vars_intern[nm] = dict(size=sz)
136
+
137
+ # =============================
138
+ # Process metadata
139
+ self._add_metadata(header_info)
140
+
141
+ # =============================
142
+ # Read the input variables data
143
+ # The first 6 columns are [year month day hour minute seconds]
144
+ time_col_names = ['year', 'month', 'day', 'hour', 'minute', 'second']
145
+ data_col_names = self._data_column_names()
146
+ col_names = time_col_names.copy()
147
+ col_names.extend(data_col_names)
148
+
149
+ # Use pandas to read the data in from the remainder of the file
150
+ self.__data_raw = pd.read_csv(fhdl, sep=r'\s+', header=None, na_values=self.__missing,
151
+ names=col_names, engine='c', skipinitialspace=True)
152
+ self.__data_raw['time'] = pd.to_datetime(self.__data_raw[time_col_names], yearfirst=True)
153
+ self.__data_raw.drop(columns=time_col_names, inplace=True)
154
+ self.__data_raw.set_index('time', inplace=True)
155
+
156
+ # Add data to each input variable
157
+ self._add_variable_data()
158
+
159
+ def _add_metadata(self, header_info: List[str]):
160
+ """Add metadata from data file.
161
+
162
+ :param header_info: list of header lines from the data file
163
+ """
164
+
165
+ it = iter(header_info)
166
+ if 'Downsizer' in self.__header or 'Bandit' in self.__header:
167
+ for line in it:
168
+ if line[0:len(STATION_START)] == STATION_START:
169
+ # Process the station information
170
+ station_vars = line[len(STATION_START):].replace(' ', '').strip(':').split(',')
171
+
172
+ line = next(it)
173
+ if line == '// ID':
174
+ # Skip the metadata column information line (found Bandit data files)
175
+ line = next(it)
176
+
177
+ while line[0:len(HEADER_SEP)] != HEADER_SEP and len(line) > 2:
178
+ for cvar in station_vars:
179
+ if cvar not in self.__input_vars_intern:
180
+ raise KeyError(f'{cvar} is not one of the input variables declared in the data file')
181
+ self.__input_vars_intern[cvar].setdefault('stations', []).extend(line.
182
+ replace(COMMENT, '').
183
+ replace(' ', '').
184
+ split(','))
185
+ line = next(it)
186
+ elif line[0:len(UNITS_START)] == UNITS_START:
187
+ # Process the units
188
+ while line[0:len(HEADER_SEP)] != HEADER_SEP:
189
+ for elem in (line.replace(UNITS_START, '').replace(COMMENT, '').replace(' ', '').split(',')):
190
+ cvar, cunits = elem.split('=')
191
+ try:
192
+ self.__input_vars_intern[cvar]['units'] = cunits
193
+ except KeyError:
194
+ con.print(f'[red]{cvar}[/] is not a valid input variable name in this data file')
195
+ pass
196
+ line = next(it)
197
+
198
+ def _add_variable_data(self):
199
+ """Add data to each input variable.
200
+ """
201
+
202
+ # Create a data key for each input variable that maps to their respective parts of the dataframe
203
+ st_idx = 0
204
+ for cvar, cmeta in self.__input_vars_intern.items():
205
+ self.__input_vars[cvar] = InputVariable(name=cvar,
206
+ data=self.__data_raw.iloc[:, st_idx:(st_idx + cmeta['size'])],
207
+ units=cmeta.get('units', None))
208
+ # self.__input_vars_intern[cvar]['data'] = self.__data_raw.iloc[:, st_idx:(st_idx + cmeta['size'])]
209
+ st_idx += cmeta['size']
210
+
211
+ def _data_column_names(self) -> List[str]:
212
+ """Create column names for the dataframe.
213
+
214
+ :returns: list of column names
215
+ """
216
+
217
+ var_col_names = []
218
+
219
+ for cvar, meta in self.__input_vars_intern.items():
220
+ if 'stations' in meta:
221
+ for cstn in meta['stations']:
222
+ var_col_names.append(f'{cvar}_{cstn}')
223
+ else:
224
+ # No usable metadata in the data file
225
+ for idx in range(1, meta['size']+1):
226
+ var_col_names.append(f'{cvar}_{idx}')
227
+
228
+ return var_col_names
229
+
230
+ # def write_selected_stations(self, filename):
231
+ # """Writes station observations to a new file"""
232
+ # # Either writes out all station observations or, if stations are selected,
233
+ # # then a subset of station observations.
234
+ #
235
+ # # Sample header format
236
+ #
237
+ # # $Id:$
238
+ # # ////////////////////////////////////////////////////////////
239
+ # # // Station metadata (listed in the same order as the data):
240
+ # # // ID Type Latitude Longitude Elevation
241
+ # # // <station info>
242
+ # # ////////////////////////////////////////////////////////////
243
+ # # // Unit: runoff = ft3 per sec, elevation = feet
244
+ # # ////////////////////////////////////////////////////////////
245
+ # # runoff <number of stations for each type>
246
+ # # ################################################################################
247
+ #
248
+ # top_line = '$Id:$\n'
249
+ # section_sep = '////////////////////////////////////////////////////////////\n'
250
+ # meta_header_1 = '// Station metadata (listed in the same order as the data):\n'
251
+ # # metaHeader2 = '// ID Type Latitude Longitude Elevation'
252
+ # meta_header_2 = '// %s\n' % ' '.join(self.metaheader)
253
+ # data_section = '################################################################################\n'
254
+ #
255
+ # # ----------------------------------
256
+ # # Get the station information for each selected station
257
+ # type_count = {} # Counts the number of stations for each type of data (e.g. 'runoff')
258
+ # stninfo = ''
259
+ # if self.__selectedStations is None:
260
+ # for xx in self.__stations:
261
+ # if xx[1] not in type_count:
262
+ # # index 1 should be the type field
263
+ # type_count[xx[1]] = 0
264
+ # type_count[xx[1]] += 1
265
+ #
266
+ # stninfo += '// %s\n' % ' '.join(xx)
267
+ # else:
268
+ # for xx in self.__selectedStations:
269
+ # cstn = self.__stations[self.__stationIndex[xx]]
270
+ #
271
+ # if cstn[1] not in type_count:
272
+ # # index 1 should be the type field
273
+ # type_count[cstn[1]] = 0
274
+ #
275
+ # type_count[cstn[1]] += 1
276
+ #
277
+ # stninfo += '// %s\n' % ' '.join(cstn)
278
+ # # stninfo = stninfo.rstrip('\n')
279
+ #
280
+ # # ----------------------------------
281
+ # # Get the units information
282
+ # unit_line = '// Unit:'
283
+ # for uu in self.__units:
284
+ # unit_line += ' %s,' % ' = '.join(uu)
285
+ # unit_line = '%s\n' % unit_line.rstrip(',')
286
+ #
287
+ # # ----------------------------------
288
+ # # Create the list of types of data that are being included
289
+ # tmpl = []
290
+ #
291
+ # # Create list of types in the correct order
292
+ # for (kk, vv) in self.__types.items():
293
+ # if kk in type_count:
294
+ # tmpl.insert(vv[0], [kk, type_count[kk]])
295
+ #
296
+ # type_line = ''
297
+ # for tt in tmpl:
298
+ # type_line += '%s %d\n' % (tt[0], tt[1])
299
+ # # typeLine = typeLine.rstrip('\n')
300
+ #
301
+ # # Write out the header to the new file
302
+ # outfile = open(filename, 'w')
303
+ # outfile.write(top_line)
304
+ # outfile.write(section_sep)
305
+ # outfile.write(meta_header_1)
306
+ # outfile.write(meta_header_2)
307
+ # outfile.write(stninfo)
308
+ # outfile.write(section_sep)
309
+ # outfile.write(unit_line)
310
+ # outfile.write(section_sep)
311
+ # outfile.write(type_line)
312
+ # outfile.write(data_section)
313
+ #
314
+ # # Write out the data to the new file
315
+ # # Using quoting=csv.QUOTE_NONE results in an error when using a customized date_format
316
+ # # A kludgy work around is to write with quoting and then re-open the file
317
+ # # and write it back out, stripping the quote characters.
318
+ # self.data.to_csv(outfile, index=True, header=False, date_format='%Y %m %d %H %M %S', sep=' ')
319
+ # outfile.close()
320
+ #
321
+ # old = open(filename, 'r').read()
322
+ # new = re.sub('["]', '', old)
323
+ # open(filename, 'w').write(new)
324
+ #
325
+ # # def getRecurrenceInterval(self, thetype):
326
+ # # """Returns the recurrence intervals for each station"""
327
+ # #
328
+ # # # Copy the subset of data
329
+ # # xx = self.seldata(thetype)
330
+ # #
331
+ # # ri = np.zeros(xx.shape)
332
+ # # ri[:,:] = -1.
333
+ # #
334
+ # # # for each station we need to compute the RI for non-zero values
335
+ # # for ss in range(0,xx.shape[1]):
336
+ # # tmp = xx[:,ss] # copy values for current station
337
+ # #
338
+ # # # Get array of indices that would result in a sorted array
339
+ # # sorted_ind = np.argsort(tmp)
340
+ # # #print "sorted_ind.shape:", sorted_ind.shape
341
+ # #
342
+ # # numobs = tmp[(tmp > 0.0),].shape[0] # Number of observations > 0.
343
+ # # nyr = float(numobs / 365) # Number of years of non-zero observations
344
+ # #
345
+ # # nz_cnt = 0 # non-zero value counter
346
+ # # for si in sorted_ind:
347
+ # # if tmp[si] > 0.:
348
+ # # nz_cnt += 1
349
+ # # rank = numobs - nz_cnt + 1
350
+ # # ri[si,ss] = (nyr + 1.) / float(rank)
351
+ # # #print "%s: [%d]: %d %d %0.3f %0.3f" % (ss, si, numobs, rank, tmp[si], ri[si,ss])
352
+ # #
353
+ # # return ri
354
+ # ***** END of class streamflow()
@@ -0,0 +1,61 @@
1
+ import pandas as pd
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class InputVariable(object):
7
+ """Class for working with input variables."""
8
+
9
+ def __init__(self, name: str,
10
+ data: pd.DataFrame,
11
+ units: Optional[str] = None):
12
+ """Initialize the InputVariable object.
13
+
14
+ :param name: Name or kind of the input variable
15
+ :param data: Input variable data
16
+ :param units: Units of the input variable
17
+ """
18
+ self.__name = name
19
+ self.__units = units
20
+ self.data = data
21
+
22
+ @property
23
+ def data(self) -> pd.DataFrame:
24
+ """Returns the input variable data.
25
+
26
+ :returns: Input variable dataframe
27
+ """
28
+
29
+ return self.__data
30
+
31
+ @data.setter
32
+ def data(self, data_in: pd.DataFrame):
33
+ """Set the input variable data.
34
+
35
+ :param data_in: Input variable data
36
+ """
37
+
38
+ col_names = {}
39
+ for xx in data_in.columns:
40
+ col_names[xx] = xx.split('_')[1]
41
+
42
+ self.__data = data_in.copy()
43
+ self.__data.rename(columns=col_names, inplace=True)
44
+
45
+ @property
46
+ def name(self) -> str:
47
+ """Returns the input variable kind.
48
+
49
+ :returns: Input variable kind
50
+ """
51
+
52
+ return self.__name
53
+
54
+ @property
55
+ def units(self) -> str:
56
+ """Returns the input variable units.
57
+
58
+ :returns: Input variable units
59
+ """
60
+
61
+ return self.__units
File without changes
@@ -0,0 +1 @@
1
+ from .metadata import MetaData