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,430 @@
1
+ import io
2
+ import pkgutil
3
+ import xml.etree.ElementTree as xmlET # type: ignore
4
+
5
+ from collections import defaultdict
6
+ from packaging.version import Version
7
+ from typing import Dict, Optional, Union
8
+
9
+ from pyPRMS.prms_helpers import set_date
10
+ from pyPRMS.constants import MetaDataType, NEW_PTYPE_TO_DTYPE, PRMS_VERSION
11
+
12
+ from rich.console import Console
13
+ from rich import pretty
14
+
15
+ pretty.install()
16
+ con = Console(record=False, force_jupyter=False)
17
+
18
+ # For each metadata type, define the outer element name for each variable in the XML file
19
+ outside_elem = {'control': 'control_param',
20
+ 'parameters': 'parameter',
21
+ 'dimensions': 'dimension',
22
+ 'variables': 'variable',
23
+ 'cbh': 'variable'}
24
+
25
+ NEW_DTYPE = {1: 'int32', 2: 'float32', 3: 'float64', 4: 'string'}
26
+ NEW_PARAM_DTYPE = {'I': 'int32', 'F': 'float32', 'D': 'float64', 'S': 'string'}
27
+
28
+
29
+ class MetaData(object):
30
+ """Class to handle variable and parameter metadata"""
31
+
32
+ def __init__(self, version: Union[str, Version] = PRMS_VERSION,
33
+ verbose: bool = False):
34
+ # meta_type - one of control, dimension, parameter, output
35
+ # version - PRMS major version to use for filtering
36
+
37
+ fcn_map = {'control': self.__control_to_dict,
38
+ 'dimensions': self.__dimensions_to_dict,
39
+ 'parameters': self.__parameters_to_dict,
40
+ 'variables': self.__variables_to_dict,
41
+ 'cbh': self.__cbh_to_dict}
42
+
43
+ self.__meta_dict: MetaDataType = {}
44
+
45
+ if isinstance(version, str):
46
+ version = Version(version)
47
+
48
+ self.__version: Version = version
49
+ self.__verbose = verbose
50
+
51
+ # Add information about the metadata
52
+ self.__meta_dict['info'] = {'version': str(self.__version)}
53
+
54
+ if self.__verbose:
55
+ con.print(f'[green]INFO[/]: Metadata for PRMS version {self.__version}')
56
+
57
+ # meta_type: one of - control, dimensions, parameters, variables
58
+ for mt, mf in fcn_map.items():
59
+ xml_fh = io.StringIO(pkgutil.get_data('pyPRMS', f'xml/{mt}.xml').decode('utf-8'))
60
+ xml_tree = xmlET.parse(xml_fh)
61
+ xml_root = xml_tree.getroot()
62
+
63
+ if self.__verbose:
64
+ con.print(f'[bold green]{mt}[/bold green]')
65
+ self.__meta_dict[mt] = mf(xml_root, mt, self.__version)
66
+
67
+ @property
68
+ def version(self) -> Version:
69
+ """Return the PRMS version used for metadata selection
70
+
71
+ :returns: PRMS version
72
+ """
73
+
74
+ return self.__version
75
+
76
+ @property
77
+ def metadata(self) -> MetaDataType:
78
+ return self.__meta_dict
79
+
80
+ def __control_to_dict(self, xml_root: xmlET.Element,
81
+ meta_type: str,
82
+ req_version: Version) -> Dict:
83
+ """Convert control variables metadata to dictionary
84
+
85
+ :param xml_root: XML root element
86
+ :param meta_type: Type of metadata
87
+ :param req_version: Required minimum version for filtering
88
+ """
89
+
90
+ meta_dict: Dict = {}
91
+
92
+ for elem in xml_root.findall(outside_elem[meta_type]):
93
+ name = elem.attrib.get('name')
94
+
95
+ meta_dict[name] = {}
96
+ try:
97
+ var_version = Version(elem.attrib.get('version'))
98
+
99
+ if var_version > req_version:
100
+ if self.__verbose: # pragma: no cover
101
+ print(f'{name} rejected by version {str(var_version)}, req: {str(req_version)}')
102
+
103
+ del meta_dict[name]
104
+ continue
105
+ meta_dict[name]['version'] = str(var_version)
106
+ except TypeError:
107
+ pass
108
+
109
+ try:
110
+ depr_version = Version(elem.attrib.get('deprecated'))
111
+
112
+ if depr_version <= req_version:
113
+ if self.__verbose: # pragma: no cover
114
+ print(f'{name} rejected by deprecation version {str(depr_version)}, req: {str(req_version)}')
115
+
116
+ del meta_dict[name]
117
+ continue
118
+ meta_dict[name]['deprecated'] = depr_version
119
+ except TypeError:
120
+ pass
121
+
122
+ if name in ['start_time', 'end_time']:
123
+ meta_dict[name]['datatype'] = 'datetime'
124
+ else:
125
+ datatype = int(elem.find('type').text)
126
+ meta_dict[name]['datatype'] = NEW_DTYPE[datatype]
127
+
128
+ elems = {'description': 'desc',
129
+ 'numvals': 'numvals',
130
+ 'default': 'default', }
131
+
132
+ for ek, ev in elems.items():
133
+ try:
134
+ if ev == 'numvals':
135
+ tmp = elem.find(ev).text
136
+
137
+ if tmp in ['1', '6']:
138
+ meta_dict[name]['context'] = 'scalar'
139
+ else:
140
+ meta_dict[name]['context'] = 'array'
141
+ # meta_dict[name][ek] = int(elem.find(ev).text)
142
+ elif ev == 'default':
143
+ cdtype = NEW_PTYPE_TO_DTYPE[meta_dict[name]['datatype']]
144
+
145
+ if meta_dict[name]['datatype'] == 'datetime':
146
+ meta_dict[name][ek] = cdtype(set_date(elem.find(ev).text))
147
+ else:
148
+ meta_dict[name][ek] = cdtype(elem.find(ev).text)
149
+ else:
150
+ meta_dict[name][ek] = elem.find(ev).text
151
+ except ValueError:
152
+ meta_dict[name][ek] = elem.find(ev).text
153
+ except AttributeError:
154
+ pass
155
+
156
+ # meta_dict[name]['description'] = elem.find('desc').text
157
+ # meta_dict[name]['numvals'] = elem.find('numvals').text
158
+
159
+ if elem.find('force_default') is not None:
160
+ meta_dict[name]['force_default'] = elem.find('force_default').text == '1'
161
+
162
+ # Possible valid values for variable
163
+ # outvals = {}
164
+ for cvals in elem.findall('./values'):
165
+ meta_dict[name]['valid_value_type'] = cvals.attrib.get('type')
166
+
167
+ meta_dict[name]['valid_values'] = {}
168
+ for cv in cvals.findall('./value'):
169
+ meta_dict[name]['valid_values'][cv.attrib.get('name')] = cv.text
170
+
171
+ return meta_dict
172
+
173
+ def __parameters_to_dict(self, xml_root: xmlET.Element,
174
+ meta_type: str,
175
+ req_version: Version) -> Dict:
176
+ """Convert parameter metadata to dictionary"""
177
+
178
+ meta_dict: Dict = {}
179
+
180
+ for elem in xml_root.findall(outside_elem[meta_type]):
181
+ name = elem.attrib.get('name')
182
+
183
+ meta_dict[name] = defaultdict(list)
184
+
185
+ try:
186
+ var_version = Version(elem.attrib.get('version'))
187
+
188
+ if var_version > req_version:
189
+ if self.__verbose: # pragma: no cover
190
+ print(f'{name} rejected by version {str(var_version)}, req: {str(req_version)}')
191
+
192
+ del meta_dict[name]
193
+ continue
194
+ meta_dict[name]['version'] = str(var_version)
195
+ except TypeError:
196
+ pass
197
+
198
+ try:
199
+ depr_version = Version(elem.attrib.get('deprecated'))
200
+
201
+ if depr_version <= req_version:
202
+ if self.__verbose: # pragma: no cover
203
+ print(f'{name} rejected by deprecation version {str(depr_version)}, req: {str(req_version)}')
204
+
205
+ del meta_dict[name]
206
+ continue
207
+ meta_dict[name]['deprecated'] = depr_version
208
+ except TypeError:
209
+ pass
210
+
211
+ # var_version = version_info(elem.attrib.get('version'))
212
+ # depr_version = version_info(elem.attrib.get('deprecated'))
213
+ #
214
+ # if var_version.major is not None and var_version.major > version.major:
215
+ # if self.__verbose: # pragma: no cover
216
+ # print(f'{name} rejected by version')
217
+ # continue
218
+ # if depr_version.major is not None and depr_version.major <= version.major:
219
+ # if self.__verbose: # pragma: no cover
220
+ # print(f'{name} rejected by deprecation version')
221
+ # continue
222
+ #
223
+ # meta_dict[name] = defaultdict(list)
224
+ #
225
+ # var_version = elem.attrib.get('version')
226
+ # if var_version is not None:
227
+ # meta_dict[name]['version'] = var_version
228
+ # # meta_dict[name]['version'] = elem.attrib.get('version')
229
+ #
230
+ # depr_version = elem.attrib.get('deprecated')
231
+ # if depr_version is not None:
232
+ # meta_dict[name]['deprecated'] = depr_version
233
+
234
+ datatype = elem.find('type').text
235
+ meta_dict[name]['datatype'] = NEW_PARAM_DTYPE[datatype]
236
+
237
+ elems = {'description': 'desc',
238
+ 'help': 'help',
239
+ 'units': 'units',
240
+ 'default': 'default',
241
+ 'minimum': 'minimum',
242
+ 'maximum': 'maximum', }
243
+
244
+ for ek, ev in elems.items():
245
+ if ek in ['default', 'minimum', 'maximum']:
246
+ # Try to convert to the parameter datatype
247
+ # Bounded parameters will fail
248
+ cdtype = NEW_PTYPE_TO_DTYPE[meta_dict[name]['datatype']]
249
+
250
+ try:
251
+ meta_dict[name][ek] = cdtype(elem.find(ev).text)
252
+ except ValueError:
253
+ # Leave the value as a string
254
+ if elem.find(ev).text == 'bounded':
255
+ meta_dict[name][ek] = meta_dict[name]['default']
256
+ else:
257
+ meta_dict[name][ek] = elem.find(ev).text
258
+ except AttributeError:
259
+ # Occurs when element does not exist; just default to string
260
+ meta_dict[name][ek] = ''
261
+ else:
262
+ try:
263
+ meta_dict[name][ek] = elem.find(ev).text
264
+ except AttributeError:
265
+ pass
266
+
267
+ for cdim in elem.findall('./dimensions/dimension'):
268
+ meta_dict[name]['dimensions'].append(cdim.attrib.get('name'))
269
+
270
+ for cmod in elem.findall('./modules/module'):
271
+ meta_dict[name]['modules'].append(cmod.text)
272
+
273
+ for creq in elem.findall('./requires/*'):
274
+ meta_dict[name][f'requires_{creq.tag}'].append(creq.text)
275
+
276
+ return meta_dict
277
+
278
+ def __dimensions_to_dict(self, xml_root: xmlET.Element,
279
+ meta_type: str,
280
+ req_version: Version) -> Dict:
281
+ """Convert control variables metadata to dictionary"""
282
+
283
+ meta_dict: Dict = {}
284
+
285
+ for elem in xml_root.findall(outside_elem[meta_type]):
286
+ name = elem.attrib.get('name')
287
+
288
+ meta_dict[name] = {}
289
+
290
+ elems = {'description': {'orig_name': 'desc',
291
+ 'datatype': str},
292
+ 'size': {'orig_name': 'size',
293
+ 'datatype': int},
294
+ 'default': {'orig_name': 'default',
295
+ 'datatype': int},
296
+ 'is_fixed': {'orig_name': 'is_fixed',
297
+ 'datatype': bool}}
298
+
299
+ for ek, ev in elems.items():
300
+ try:
301
+ meta_dict[name][ek] = ev['datatype'](elem.find(ev['orig_name']).text)
302
+ except AttributeError:
303
+ if ek == 'is_fixed':
304
+ meta_dict[name][ek] = False
305
+ pass
306
+
307
+ for creq in elem.findall('./requires/*'):
308
+ meta_dict[name].setdefault(f'requires_{creq.tag}', list()).append(creq.text)
309
+
310
+ return meta_dict
311
+
312
+ def __variables_to_dict(self, xml_root: xmlET.Element,
313
+ meta_type: str,
314
+ req_version: Version) -> Dict:
315
+ """Convert output variables metadata to dictionary"""
316
+
317
+ meta_dict: Dict = {}
318
+
319
+ for elem in xml_root.findall(outside_elem[meta_type]):
320
+ name = elem.attrib.get('name')
321
+ # var_version = version_info(elem.attrib.get('version'))
322
+ # depr_version = version_info(elem.attrib.get('deprecated'))
323
+ #
324
+ # if var_version.major is not None and var_version.major > version:
325
+ # if self.__verbose:
326
+ # print(f'{name} rejected by version')
327
+ # continue
328
+ # if depr_version.major is not None and depr_version.major <= version:
329
+ # if self.__verbose:
330
+ # print(f'{name} rejected by deprecation version')
331
+ # continue
332
+
333
+ meta_dict[name] = defaultdict(list)
334
+
335
+ # var_version = elem.attrib.get('version')
336
+ # if var_version is not None:
337
+ # meta_dict[name]['version'] = var_version
338
+ # # meta_dict[name]['version'] = elem.attrib.get('version')
339
+ #
340
+ # depr_version = elem.attrib.get('deprecated')
341
+ # if depr_version is not None:
342
+ # meta_dict[name]['deprecated'] = depr_version
343
+
344
+ datatype = elem.find('type').text
345
+ meta_dict[name]['datatype'] = NEW_PARAM_DTYPE[datatype]
346
+
347
+ elems = {'description': 'desc',
348
+ 'units': 'units', }
349
+
350
+ for ek, ev in elems.items():
351
+ try:
352
+ meta_dict[name][ek] = elem.find(ev).text
353
+ except AttributeError:
354
+ pass
355
+
356
+ for cdim in elem.findall('./dimensions/dimension'):
357
+ meta_dict[name]['dimensions'].append(cdim.attrib.get('name'))
358
+
359
+ for cmod in elem.findall('./modules/module'):
360
+ meta_dict[name]['modules'].append(cmod.text)
361
+
362
+ # for creq in elem.findall('./requires/*'):
363
+ # meta_dict[name][f'requires_{creq.tag}'].append(creq.text)
364
+
365
+ return meta_dict
366
+
367
+ def __cbh_to_dict(self, xml_root: xmlET.Element,
368
+ meta_type: str,
369
+ req_version: Version) -> Dict:
370
+ """Convert cbh variables metadata to dictionary"""
371
+
372
+ meta_dict: Dict = {}
373
+
374
+ for elem in xml_root.findall(outside_elem[meta_type]):
375
+ name = elem.attrib.get('name')
376
+
377
+ meta_dict[name] = defaultdict(list)
378
+
379
+ try:
380
+ var_version = Version(elem.attrib.get('version'))
381
+
382
+ if var_version > req_version:
383
+ if self.__verbose: # pragma: no cover
384
+ print(f'{name} rejected by version {str(var_version)}, req: {str(req_version)}')
385
+
386
+ del meta_dict[name]
387
+ continue
388
+ meta_dict[name]['version'] = str(var_version)
389
+ except TypeError:
390
+ pass
391
+
392
+ try:
393
+ depr_version = Version(elem.attrib.get('deprecated'))
394
+
395
+ if depr_version <= req_version:
396
+ if self.__verbose: # pragma: no cover
397
+ print(f'{name} rejected by deprecation version {str(depr_version)}, req: {str(req_version)}')
398
+
399
+ del meta_dict[name]
400
+ continue
401
+ meta_dict[name]['deprecated'] = depr_version
402
+ except TypeError:
403
+ pass
404
+
405
+ datatype = elem.find('type').text
406
+ meta_dict[name]['datatype'] = NEW_PARAM_DTYPE[datatype]
407
+
408
+ elems = {'description': 'desc',
409
+ 'help': 'help',
410
+ 'units': 'units',
411
+ 'default': 'default',
412
+ 'minimum': 'minimum',
413
+ 'maximum': 'maximum', }
414
+
415
+ for ek, ev in elems.items():
416
+ try:
417
+ meta_dict[name][ek] = elem.find(ev).text
418
+ except AttributeError:
419
+ pass
420
+
421
+ for cdim in elem.findall('./dimensions/dimension'):
422
+ meta_dict[name]['dimensions'].append(cdim.attrib.get('name'))
423
+
424
+ for cmod in elem.findall('./modules/module'):
425
+ meta_dict[name]['modules'].append(cmod.text)
426
+
427
+ for creq in elem.findall('./requires/*'):
428
+ meta_dict[name][f'requires_{creq.tag}'].append(creq.text)
429
+
430
+ return meta_dict
@@ -0,0 +1,73 @@
1
+
2
+ import os
3
+ import pandas as pd # type: ignore
4
+ from typing import cast, Optional
5
+
6
+ from ..prms_helpers import read_xml
7
+ from .Parameters import Parameters
8
+ from ..constants import NEW_PTYPE_TO_DTYPE, PARAMETERS_XML, DIMENSIONS_XML
9
+
10
+
11
+ class ParamDb(Parameters):
12
+ def __init__(self, paramdb_dir: str,
13
+ metadata,
14
+ verbose: Optional[bool] = False):
15
+ """Initialize ParamDb object.
16
+
17
+ This object handles the monolithic parameter database.
18
+
19
+ :param paramdb_dir: Path to the ParamDb directory
20
+ :param verbose: Output additional debug information
21
+ """
22
+
23
+ super(ParamDb, self).__init__(metadata=metadata, verbose=verbose)
24
+ self.__paramdb_dir = paramdb_dir
25
+ self.__verbose = verbose
26
+
27
+ # Read the parameters from the parameter database
28
+ self._read()
29
+
30
+ def _read(self):
31
+ """Read a parameter database.
32
+ """
33
+
34
+ # Get the parameters available from the parameter database
35
+ # Returns a dictionary of parameters and associated units and types
36
+
37
+ # Get the parameters available from the parameter database
38
+ # Returns a dictionary of parameters and associated units and types
39
+ global_params_file = f'{self.__paramdb_dir}/{PARAMETERS_XML}'
40
+ global_dimens_file = f'{self.__paramdb_dir}/{DIMENSIONS_XML}'
41
+
42
+ # Read in the parameters.xml and dimensions.xml file
43
+ params_root = read_xml(global_params_file)
44
+ dimens_root = read_xml(global_dimens_file)
45
+
46
+ # Populate the global dimensions from the xml file
47
+ for xml_dim in dimens_root.findall('dimension'):
48
+ self.dimensions.add(name=cast(str, xml_dim.attrib.get('name')), size=cast(int, xml_dim.find('size').text))
49
+
50
+ # Populate parameterSet with all available parameter names
51
+ for param in params_root.findall('parameter'):
52
+ xml_param_name = cast(str, param.attrib.get('name'))
53
+ curr_file = f'{self.__paramdb_dir}/{xml_param_name}.csv'
54
+
55
+ if self.exists(xml_param_name):
56
+ # Sometimes the global parameter xml file has duplicates of parameters
57
+ print(f'WARNING: {xml_param_name} is duplicated in {PARAMETERS_XML}; skipping')
58
+ continue
59
+
60
+ if os.path.exists(curr_file):
61
+ self.add(xml_param_name)
62
+
63
+ cdtype = NEW_PTYPE_TO_DTYPE[self.get(xml_param_name).meta['datatype']]
64
+ tmp_data = pd.read_csv(curr_file,
65
+ skiprows=0,
66
+ usecols=[1],
67
+ dtype={1: cdtype}).squeeze('columns').to_numpy()
68
+
69
+ self.get(xml_param_name).data = tmp_data
70
+ else:
71
+ print(f'WARNING: {xml_param_name}, ParamDb file does not exist; skipping')
72
+
73
+ self.adjust_bounded_parameters()