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
pyPRMS/constants.py ADDED
@@ -0,0 +1,131 @@
1
+ import numpy as np
2
+
3
+ from packaging.version import Version
4
+ from typing import Dict, List, NamedTuple, Union
5
+
6
+ # Define aliases for static typing
7
+ MetaDataType = Dict[str, Dict]
8
+
9
+ # Version = NamedTuple('Version', [('major', Union[int, None]),
10
+ # ('minor', Union[int, None]),
11
+ # ('revision', Union[int, None])])
12
+
13
+ # Default PRMS version to use for metadata
14
+ PRMS_VERSION = Version('5.2.1.1')
15
+
16
+ # PRMS6 experimental development version
17
+ PRMS6_DEV_VERSION = Version('60.0')
18
+
19
+ # Order to write control file parameters for printing and writing a new control file
20
+ ctl_order: List[str] = ['start_time', 'end_time', 'initial_deltat', 'executable_desc', 'executable_model', 'model_mode',
21
+ 'param_file', 'data_file', 'model_output_file', 'print_debug',
22
+ 'et_module', 'precip_module', 'soilzone_module', 'solrad_module', 'srunoff_module',
23
+ 'strmflow_module', 'temp_module', 'transp_module',
24
+ 'init_vars_from_file', 'save_vars_to_file', 'var_init_file', 'var_save_file',
25
+ 'albedo_day', 'cloud_cover_day', 'humidity_day', 'potet_day', 'precip_day', 'precip_grid',
26
+ 'swrad_day', 'tmax_day', 'tmax_grid', 'tmin_day', 'transp_day', 'windspeed_day',
27
+ 'tmin_grid',
28
+ 'albedo_cbh_flag', 'cascade_flag', 'cascadegw_flag', 'cbh_binary_flag', 'cbh_check_flag',
29
+ 'cloud_cover_cbh_flag', 'dprst_flag', 'frozen_flag', 'glacier_flag',
30
+ 'gwflow_cbh_flag', 'gwr_swale_flag', 'humidity_cbh_flag', 'mbInit_flag', 'orad_flag',
31
+ 'parameter_check_flag', 'snarea_curve_flag', 'snow_cbh_flag', 'snow_cloudcover_flag',
32
+ 'soilzone_aet_flag',
33
+ 'stream_temp_flag', 'stream_temp_shade_flag', 'strmtemp_humidity_flag',
34
+ 'subbasin_flag', 'windspeed_cbh_flag',
35
+ 'prms_warmup',
36
+ 'csvON_OFF', 'csv_output_file',
37
+ 'nhruOutON_OFF', 'nhruOutBaseFileName', 'nhruOutVars', 'nhruOutVar_names', 'nhruOut_freq',
38
+ 'nhruOut_format', 'nhruOutNcol', 'outputSelectDatesON_OFF', 'selectDatesFileName',
39
+ 'nsegmentOutON_OFF', 'nsegmentOutBaseFileName', 'nsegmentOutVars', 'nsegmentOutVar_names',
40
+ 'nsegmentOut_freq', 'nsegmentOut_format',
41
+ 'basinOutON_OFF', 'basinOutBaseFileName', 'basinOutVars', 'basinOutVar_names', 'basinOut_freq',
42
+ 'nsubOutON_OFF', 'nsubOutBaseFileName', 'nsubOutVars', 'nsubOutVar_names', 'nsubOut_format',
43
+ 'nsubOut_freq',
44
+ 'statsON_OFF', 'stat_var_file', 'nstatVars', 'statVar_element', 'statVar_names',
45
+ 'dyn_covden_flag', 'dyn_covtype_flag', 'dyn_dprst_flag', 'dyn_fallfrost_flag',
46
+ 'dyn_imperv_flag',
47
+ 'dyn_intcp_flag', 'dyn_potet_flag', 'dyn_radtrncf_flag', 'dyn_snareathresh_flag',
48
+ 'dyn_soil_flag',
49
+ 'dyn_springfrost_flag', 'dyn_sro2dprst_imperv_flag', 'dyn_sro2dprst_perv_flag',
50
+ 'dyn_transp_flag', 'dyn_transp_on_flag',
51
+ 'dynamic_param_log_file',
52
+ 'covden_sum_dynamic', 'covden_win_dynamic', 'covtype_dynamic', 'dprst_depth_dynamic',
53
+ 'dprst_frac_dynamic', 'fallfrost_dynamic', 'imperv_frac_dynamic', 'imperv_stor_dynamic',
54
+ 'potetcoef_dynamic', 'radtrncf_dynamic', 'snareathresh_dynamic', 'snow_intcp_dynamic',
55
+ 'soilmoist_dynamic', 'soilrechr_dynamic', 'springfrost_dynamic', 'srain_intcp_dynamic',
56
+ 'sro2dprst_imperv_dynamic', 'sro2dprst_perv_dynamic',
57
+ 'transp_on_dynamic', 'transpbeg_dynamic', 'transpend_dynamic', 'wrain_intcp_dynamic',
58
+ 'dprst_transferON_OFF', 'dprst_transfer_file', 'external_transferON_OFF',
59
+ 'external_transfer_file', 'gwr_transferON_OFF', 'gwr_transfer_file', 'lake_transferON_OFF',
60
+ 'lake_transfer_file', 'segment_transferON_OFF', 'segment_transfer_file', 'segmentOutON_OFF',
61
+ 'aniOutON_OFF', 'ani_output_file', 'naniOutVars', 'aniOutVar_names',
62
+ 'dispGraphsBuffSize', 'ndispGraphs', 'dispVar_element', 'dispVar_names', 'dispVar_plot',
63
+ 'mapOutON_OFF', 'nmapOutVars', 'mapOutVar_names']
64
+
65
+ # ctl_variable_modules: List[str] = ['et_module', 'precip_module', 'soilzone_module', 'solrad_module',
66
+ # 'srunoff_module', 'strmflow_module', 'temp_module', 'transp_module']
67
+
68
+ # ctl_summary_modules: List[str] = ['basin_sum', 'basin_summary', 'map_results',
69
+ # 'nhru_summary', 'nsegment_summary', 'nsub_summary', 'subbasin']
70
+
71
+ ctl_implicit_modules: Dict[str, str] = {'basin_module': 'basin',
72
+ 'intcp_module': 'intcp',
73
+ 'obs_module': 'obs',
74
+ 'snow_module': 'snowcomp',
75
+ 'gw_module': 'gwflow',
76
+ 'soilzone_module': 'soilzone'}
77
+
78
+ # This is a map between what is in the control file and what is in the parameters.xml file
79
+ internal_module_map = dict(et_module={'climate_hru': 'potet_hru'},
80
+ precip_module={'climate_hru': 'precipitation_hru'},
81
+ solrad_module={'climate_hru': 'solar_radiation_hru'},
82
+ temp_module={'climate_hru': 'temperature_hru'},
83
+ transp_module={'climate_hru': 'transpiration_hru'})
84
+
85
+ # This maps the internal parameters.xml names to the external control file module names
86
+ external_module_map = dict(potet_hru='climate_hru',
87
+ precipitation_hru='climate_hru',
88
+ solar_radiation_hru='climate_hru',
89
+ temperature_hru='climate_hru',
90
+ transpiration_hru='climate_hru')
91
+
92
+ # Constants related to parameter files
93
+ DIMENSIONS_HDR: str = 'Dimensions'
94
+ PARAMETERS_HDR: str = 'Parameters'
95
+ CATEGORY_DELIM: str = '**' # Delimiter for categories of variables
96
+ VAR_DELIM: str = '####' # Used to delimit dimensions and parameters
97
+ # DATA_TYPES: Dict[int, str] = {1: 'integer', 2: 'float', 3: 'double', 4: 'string'}
98
+
99
+ # Valid dimensions names for PRMS
100
+ # DIMENSION_NAMES: List[str] = ['mxnsos', 'ncascade', 'ncascdgw', 'nconsumed', 'ndays', 'ndepl',
101
+ # 'ndeplval', 'ngate', 'ngate2', 'ngate3', 'ngate4',
102
+ # 'nevap', 'nexternal', 'ngw', 'ngwcell', 'nhru', 'nhrucell', 'nhumid',
103
+ # 'nlake', 'nlakeelev', 'nlapse', 'nmonths', 'nobs', 'npoigages', 'nrain',
104
+ # 'nratetbl', 'nsegment', 'nsnow', 'nsol', 'nssr', 'nsub', 'ntemp',
105
+ # 'numlakes', 'nwateruse', 'nwind', 'one',
106
+ # 'nstage', 'nstage2', 'nstage3', 'nstage4']
107
+
108
+ # These dimensions are related and should have same size
109
+ # NOTE: 2025-02-05 PAN - this is currently needed by Bandit
110
+ HRU_DIMS: List[str] = ['nhru', 'ngw', 'nssr']
111
+
112
+ # Constants for NhmParamDb
113
+ REGIONS: List[str] = ['r01', 'r02', 'r03', 'r04', 'r05', 'r06', 'r07', 'r08', 'r09',
114
+ 'r10L', 'r10U', 'r11', 'r12', 'r13', 'r14', 'r15', 'r16', 'r17', 'r18']
115
+ PARAMETERS_XML: str = 'parameters.xml'
116
+ DIMENSIONS_XML: str = 'dimensions.xml'
117
+
118
+ # 2025-01-24 PAN - used by Parameters.write_parameter_netcdf()
119
+ NETCDF_DATATYPES: Dict[int, str] = {1: 'i4', 2: 'f4', 3: 'f8', 4: 'S1'}
120
+
121
+ # 2025-01-24 PAN - used by Parameters.xml_global_parameters()
122
+ NHM_DATATYPES: Dict[str, int] = {'I': 1, 'F': 2, 'D': 3, 'S': 4}
123
+
124
+ # 2025-01-24 PAN - used by ControlFile._read() and ParameterFile._read()
125
+ PTYPE_TO_DTYPE = {1: np.int32, 2: np.float32, 3: np.float64, 4: np.str_}
126
+
127
+ NEW_PTYPE_TO_DTYPE = {'int32': np.int32, 'float32': np.float32, 'float64': np.float64, 'string': np.str_, 'datetime': np.datetime64}
128
+ PTYPE_TO_PRMS_TYPE: Dict[str, int] = {'int32': 1, 'float32': 2, 'float64': 3, 'string': 4, 'datetime': 1}
129
+
130
+ # PARNAME_DATATYPES = {'long': 1, 'float': 2, 'double': 3, 'string': 4}
131
+ # DATATYPE_TO_DTYPE = {1: int, 2: np.float32, 3: np.float64, 4: np.str_}
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import io
4
+ import numpy as np
5
+ import operator
6
+ import pandas as pd # type: ignore
7
+ import pkgutil
8
+ import re
9
+ import xml.etree.ElementTree as xmlET
10
+
11
+ from typing import Dict, List, Optional, Sequence, Union # OrderedDict as OrderedDictType,
12
+
13
+ from networkx.utils.misc import check_create_using
14
+
15
+ from .ControlVariable import ControlVariable
16
+ from ..Exceptions_custom import ControlError
17
+ from ..constants import (ctl_order, ctl_implicit_modules, internal_module_map,
18
+ MetaDataType, VAR_DELIM, PTYPE_TO_PRMS_TYPE)
19
+
20
+ cond_check = {'=': operator.eq,
21
+ '>': operator.gt,
22
+ '<': operator.lt}
23
+
24
+ class Control(object):
25
+ """
26
+ Class object for a collection of control variables.
27
+ """
28
+
29
+ # Author: Parker Norton (pnorton@usgs.gov)
30
+ # Create date: 2019-04-18
31
+
32
+ def __init__(self, metadata: MetaDataType, verbose: Optional[bool] = False):
33
+ """Create Control object.
34
+ """
35
+
36
+ # Container to hold dicionary of ControlVariables
37
+ # self.__control_vars = OrderedDict()
38
+ self.__control_vars: Dict = {}
39
+ self.__header: Optional[List[str]] = None
40
+ self.__verbose = verbose
41
+
42
+ # Create an entry for each variable in the control section of
43
+ # the metadata dictionary
44
+ # for cvar, cvals in metadata['control'].items():
45
+ # self.add(name=cvar, meta=cvals)
46
+ for cvar in metadata['control'].keys():
47
+ self.add(name=cvar, meta=metadata['control'])
48
+
49
+ if verbose:
50
+ print('Pre-populate control variables done')
51
+
52
+ def __getitem__(self, item: str) -> ControlVariable:
53
+ """Get ControlVariable object for a variable.
54
+
55
+ :param item: name of control file variable
56
+ :returns: ControlVariable object
57
+ """
58
+ return self.get(item)
59
+
60
+ @property
61
+ def additional_modules(self) -> List[str]:
62
+ """Get list of summary modules in PRMS
63
+ """
64
+
65
+ # TODO: module_requirements should be added to metadata?
66
+ # NOTE: 20231109 PAN - we always want basin_sum included since the
67
+ # print_debug could be set to 4 (which uses the basin_sum module)
68
+ module_requirements = {'basin_sum': 'print_debug < 100',
69
+ # 'basin_sum': 'print_debug = 4',
70
+ 'basin_summary': 'basinOutON_OFF > 0',
71
+ 'map_results': 'mapOutON_OFF > 0',
72
+ 'nhru_summary': 'nhruOutON_OFF > 0',
73
+ 'nsegment_summary': 'nsegmentOutON_OFF > 0',
74
+ 'nsub_summary': 'nsubOutON_OFF > 0',
75
+ 'stream_temp': 'stream_temp_flag > 0',
76
+ 'subbasin': 'subbasin_flag = 1'}
77
+
78
+ active_modules = []
79
+
80
+ for cmod, cond in module_requirements.items():
81
+ if self._check_condition(cond):
82
+ active_modules.append(cmod)
83
+
84
+ return active_modules
85
+
86
+ @property
87
+ def cbh_files(self) -> List[str]:
88
+ """Get list of possible CBH filenames.
89
+
90
+ :returns: list of CBH files
91
+ """
92
+
93
+ # List of control variables that specify possible CBH files
94
+ ctl_cbh_files = ['albebo_day', 'cloud_cover_day', 'humidity_day', 'potet_day', 'precip_day',
95
+ 'swrad_day', 'tmax_day', 'tmin_day', 'transp_day', 'windspeed_day']
96
+ cbh_files = []
97
+
98
+ for cvar in ctl_cbh_files:
99
+ if self.exists(cvar):
100
+ cbh_files.append(self.get(cvar).values)
101
+
102
+ return sorted(list(set(cbh_files)))
103
+
104
+ @property
105
+ def control_variables(self) -> Dict[str, ControlVariable]:
106
+ # def control_variables(self) -> OrderedDictType[str, ControlVariable]:
107
+ """Get control variable objects.
108
+
109
+ :returns: control variable objects
110
+ """
111
+ return self.__control_vars
112
+
113
+ @property
114
+ def dynamic_parameters(self) -> List[str]:
115
+ """Get list of parameter names for which a dynamic flag set.
116
+
117
+ :returns: list of parameter names
118
+ """
119
+
120
+ dyn_params: List[str] = []
121
+
122
+ for dv in self.__control_vars.keys():
123
+ cvar = self.get(dv)
124
+
125
+ if (cvar.meta.get('valid_value_type', '') == 'parameter' and
126
+ (isinstance(cvar.values, int | np.int32 | np.int64))):
127
+ # Dynamic parameter flags should always be integers
128
+ if cvar.values > 0:
129
+ dyn_params.extend(cvar.dyn_param_meaning)
130
+ # dyn_params.append(self.get(dv).associated_values)
131
+ return dyn_params
132
+
133
+ @property
134
+ def has_dynamic_parameters(self) -> bool:
135
+ """Indicates if any dynamic parameters have been requested.
136
+
137
+ :returns: True if dynamic parameters are required
138
+ """
139
+ return len(self.dynamic_parameters) > 0
140
+
141
+ @property
142
+ def header(self) -> Optional[Sequence[str]]:
143
+ """Get header information defined for a control object.
144
+
145
+ This is typically taken from the first two lines of a control file.
146
+
147
+ :returns: Header information from control file
148
+ """
149
+ return self.__header
150
+
151
+ @header.setter
152
+ def header(self, info: Union[Sequence[str], str, None]):
153
+ """Set the header information.
154
+
155
+ :param info: list or string of header line(s)
156
+ """
157
+
158
+ if info is None:
159
+ self.__header = None
160
+ elif isinstance(info, list):
161
+ self.__header = info
162
+ elif isinstance(info, str):
163
+ self.__header = [info]
164
+
165
+ @property
166
+ def modules(self) -> Dict[str, str]:
167
+ """Get the modules defined in the control file.
168
+
169
+ Note: climate_hru is changed to precipitation_hru, temperature_hru,
170
+ potet_hru, or solar_radiation_hru depending on the module type. This
171
+ makes for easier identification of which process(es) is/are actually using
172
+ climate_hru. Interally the module type is still climate_hru.
173
+
174
+ :returns: dictionary of control variable, module name pairs
175
+ """
176
+
177
+ mod_dict = {}
178
+
179
+ for vv in self.control_variables.values():
180
+ if vv.meta.get('valid_value_type', '') == 'module':
181
+ mname = internal_module_map.get(vv.name, {}).get(vv.values, vv.values)
182
+
183
+ mod_dict[vv.name] = str(mname)
184
+
185
+ # Add the modules that are implicitly included
186
+ for mtype, mname in ctl_implicit_modules.items():
187
+ if mtype not in mod_dict:
188
+ mod_dict[mtype] = mname
189
+
190
+ return mod_dict
191
+
192
+ def add(self, name: str, meta=None):
193
+ """Add a control variable by name.
194
+
195
+ :param name: Name of the control variable
196
+ :param datatype: The datatype of the control variable
197
+
198
+ :raises ControlError: if control variable already exists
199
+ """
200
+
201
+ if self.exists(name):
202
+ raise ControlError("Control variable already exists")
203
+ self.__control_vars[name] = ControlVariable(name=name, meta=meta)
204
+ # self.__control_vars[name] = ControlVariable(name=name, datatype=datatype, meta=meta)
205
+
206
+ def exists(self, name: str) -> bool:
207
+ """Checks if control variable exists.
208
+
209
+ :param name: Name of the control variable
210
+ :returns: True if control variable exists otherwise False
211
+ """
212
+ return name in self.__control_vars.keys()
213
+
214
+ def get(self, name: str) -> ControlVariable:
215
+ """Returns the given control variable object.
216
+
217
+ :param name: Name of the control variable
218
+ :returns: Control variable object
219
+
220
+ :raises ValueError: if control variable does not exist
221
+ """
222
+
223
+ if self.exists(name):
224
+ return self.__control_vars[name]
225
+ raise ValueError(f'Control variable, {name}, does not exist.')
226
+
227
+ def remove(self, name: str):
228
+ """Delete a control variable if it exists.
229
+
230
+ :param name: Name of the control variable
231
+ """
232
+
233
+ if self.exists(name):
234
+ del self.__control_vars[name]
235
+
236
+ def to_dict(self):
237
+ """Dictionary of data for each control variable"""
238
+
239
+ return {kk: vv.values for (kk, vv) in self.__control_vars.items()}
240
+
241
+ def write(self, filename: str):
242
+ """Write a control file.
243
+
244
+ :param filename: Name of control file to create
245
+ """
246
+
247
+ outfile = open(filename, 'w')
248
+
249
+ if self.__header is not None:
250
+ for hh in self.__header:
251
+ outfile.write(f'{hh}\n')
252
+
253
+ order = ['datatype', 'values']
254
+
255
+ # Get set of variables in ctl_order that are missing from control_vars
256
+ setdiff = set(self.__control_vars.keys()).difference(set(ctl_order))
257
+
258
+ # Add missing control variables (setdiff) in ctl_order to the end of the list
259
+ ctl_order.extend(list(setdiff))
260
+
261
+ for kk in ctl_order:
262
+ if self.exists(kk):
263
+ cvar = self.get(kk)
264
+
265
+ outfile.write(f'{VAR_DELIM}\n')
266
+ outfile.write(f'{kk}\n')
267
+
268
+ for item in order:
269
+ if cvar.meta['datatype'] == 'datetime':
270
+ date_tmp = [int(xx) for xx in re.split(r'[-T:.]+', str(cvar.values))[0:6]]
271
+
272
+ if item == 'datatype':
273
+ outfile.write(f'{len(date_tmp)}\n')
274
+ outfile.write(f'{PTYPE_TO_PRMS_TYPE[cvar.meta["datatype"]]}\n')
275
+ if item == 'values':
276
+ for cval in date_tmp:
277
+ outfile.write(f'{cval}\n')
278
+ else:
279
+ if item == 'datatype':
280
+ outfile.write(f'{cvar.size}\n')
281
+ outfile.write(f'{PTYPE_TO_PRMS_TYPE[cvar.meta["datatype"]]}\n')
282
+ if item == 'values':
283
+ if cvar.meta['context'] == 'scalar':
284
+ # Single-values (e.g. int, float, str)
285
+ # print(type(cvar.values))
286
+ if isinstance(cvar.values, np.bytes_):
287
+ print("BYTES")
288
+ outfile.write(f'{cvar.values.decode()}\n')
289
+ else:
290
+ outfile.write(f'{cvar.values}\n')
291
+ else:
292
+ # Multiple-values
293
+ if isinstance(cvar.values, np.ndarray):
294
+ for cval in cvar.values:
295
+ outfile.write(f'{cval}\n')
296
+ else:
297
+ outfile.write(f'{cvar.values}\n')
298
+
299
+ outfile.close()
300
+
301
+ def write_metadata_csv(self, filename: str, sep: str = '\t'):
302
+ """Writes the control metadata to a CSV file"""
303
+
304
+ out_list = []
305
+
306
+ # <control_param name="soilrechr_dynamic" version="5.0">
307
+ # <default>dyn_soil_rechr.param</default>
308
+ # <force_default>1</force_default>
309
+ # <type>4</type>
310
+ # <numvals>1</numvals>
311
+ # <desc>Pathname of the time series of pre-processed values for dynamic parameter soil_rechr_max_frac</desc>
312
+ # </control_param>
313
+
314
+ for pk in sorted(list(self.__control_vars.keys())):
315
+ cvar = self.get(pk)
316
+ md = cvar.meta
317
+ assert md is not None
318
+
319
+ meta_default = md.get('default')
320
+ assert meta_default is not None
321
+
322
+ if pk in ['start_time', 'end_time']:
323
+ dt = pd.Timestamp(meta_default)
324
+ out_list.append([cvar.name,
325
+ 'int32',
326
+ md.get('description', ''),
327
+ f'{dt.year},{dt.month},{dt.day},{dt.hour},{dt.minute},{dt.second}'])
328
+ # pd.Timestamp(meta_default).strftime('%-Y,%-m,%-d,%-H,%-M,%-S')])
329
+ else:
330
+ out_list.append([cvar.name,
331
+ md.get('datatype', ''),
332
+ md.get('description', ''),
333
+ md.get('default')])
334
+
335
+ col_names = ['variable_name', 'datatype', 'description', 'default']
336
+
337
+ df = pd.DataFrame.from_records(out_list, columns=col_names)
338
+ if sep == ',': # pragma: no cover
339
+ df.to_csv(filename, sep=sep, quotechar='"', index=False)
340
+ else:
341
+ df.to_csv(filename, sep=sep, index=False)
342
+
343
+ def _check_condition(self, cstr: str) -> bool:
344
+ """Takes a string of the form '<control_var> <op> <value>' and checks
345
+ if the condition is True
346
+ """
347
+ # if len(cstr) == 0:
348
+ # return True
349
+ value: Union[int, str]
350
+
351
+ var, op, value = cstr.split(' ')
352
+ value = int(value)
353
+
354
+ ctl_val = self.get(var).values
355
+ assert ctl_val is not None
356
+
357
+ return cond_check[op](ctl_val, value)
358
+
359
+ def _read(self):
360
+ """Abstract function for reading.
361
+ """
362
+ assert False, 'Control._read() must be defined by child class'
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import numpy as np
4
+
5
+ from pathlib import Path
6
+ from typing import Optional, Union
7
+ from ..constants import PTYPE_TO_DTYPE, VAR_DELIM
8
+ from ..prms_helpers import get_file_iter
9
+ from .Control import Control
10
+ from ..Exceptions_custom import ControlError
11
+
12
+
13
+ class ControlFile(Control):
14
+ """
15
+ Class which handles the processing of PRMS control files.
16
+ """
17
+
18
+ # Author: Parker Norton (pnorton@usgs.gov)
19
+ # Create date: 2019-04-18
20
+ # Description: Class object to handle reading and writing PRMS control files.
21
+
22
+ def __init__(self, filename: Union[str, Path],
23
+ metadata,
24
+ verbose: Optional[bool] = False):
25
+ super(ControlFile, self).__init__(metadata=metadata, verbose=verbose)
26
+
27
+ self.__verbose = verbose
28
+ self.__isloaded = False
29
+
30
+ if isinstance(filename, str):
31
+ filename = Path(filename)
32
+ self.filename = filename
33
+
34
+ @property
35
+ def filename(self) -> Union[str, Path]:
36
+ """Get control filename.
37
+
38
+ :returns: Name of control file
39
+ """
40
+ return self.__filename
41
+
42
+ @filename.setter
43
+ def filename(self, filename: Union[str, Path]):
44
+ """Set the name of the control file and read it in.
45
+
46
+ :param filename: Name of the control file
47
+ """
48
+
49
+ self.__isloaded = False
50
+ self.__filename = filename
51
+ self._read()
52
+
53
+ def _read(self):
54
+ """Read contents of control file.
55
+
56
+ Reads the contents of a control file into the class.
57
+ """
58
+
59
+ if self.__verbose:
60
+ chk_vars = []
61
+
62
+ self.__isloaded = False
63
+
64
+ it = get_file_iter(self.filename)
65
+ header_tmp = []
66
+
67
+ for fidx, line in enumerate(it):
68
+ if fidx == 0:
69
+ header_tmp.append(line)
70
+ continue
71
+ elif line == VAR_DELIM:
72
+ continue
73
+ else:
74
+ # We're dealing with a control parameter/variable
75
+ # We're in a parameter section
76
+ varname = line.split(' ')[0]
77
+
78
+ if self.__verbose:
79
+ if varname in chk_vars:
80
+ print(f'WARNING: {varname} already exists')
81
+ chk_vars.append(varname)
82
+
83
+ numval = int(next(it)) # number of values for this variable
84
+ valuetype = int(next(it)) # Variable type (1 - integer, 2 - float, 4 - character)
85
+
86
+ try:
87
+ # if self.get(varname).context == 'scalar' and varname not in ['start_time', 'end_time']:
88
+ if self.get(varname).meta['context'] == 'scalar':
89
+ if numval == 1: # varname not in ['start_time', 'end_time']:
90
+ vals = PTYPE_TO_DTYPE[valuetype](next(it))
91
+ else:
92
+ # Currently only start_time and end_time are scalars with numval > 1
93
+ vals = np.zeros(numval, dtype=PTYPE_TO_DTYPE[valuetype])
94
+ for idx in range(0, numval):
95
+ # NOTE: string-float to int works but float to int does not
96
+ vals[idx] = next(it)
97
+ elif self.get(varname).meta['context'] == 'array':
98
+ if valuetype == 4:
99
+ # Arrays of strings should be objects
100
+ vals = np.zeros(numval, dtype=object)
101
+ else:
102
+ vals = np.zeros(numval, dtype=PTYPE_TO_DTYPE[valuetype])
103
+
104
+ for idx in range(0, numval):
105
+ # NOTE: string-float to int works but float to int does not
106
+ vals[idx] = next(it)
107
+ else:
108
+ print(f'WARNING: {varname} has context={self.get(varname).meta["context"]} which is not supported')
109
+
110
+ # After reading expected values make sure there aren't more values
111
+ # before the next delimiter.
112
+ try:
113
+ cnt = numval
114
+ while next(it) != VAR_DELIM:
115
+ cnt += 1
116
+
117
+ if cnt > numval:
118
+ raise ControlError(f'{varname}: too many values specified')
119
+ # print(f'WARNING: Too many values specified for {varname}')
120
+ # print(f' {numval} expected, {cnt} given')
121
+ # print(f' Keeping first {numval} values')
122
+ except StopIteration:
123
+ # Hit the end of the file
124
+ pass
125
+
126
+ self.get(varname).values = vals
127
+
128
+ except ValueError as err:
129
+ print(f'WARNING: {varname} is not a valid control variable')
130
+ print(err)
131
+
132
+ try:
133
+ while next(it) != VAR_DELIM:
134
+ pass
135
+ except StopIteration:
136
+ # Hit the end of the file
137
+ continue
138
+
139
+ self.header = header_tmp
140
+ self.__isloaded = True
141
+
142
+ # def clear_parameter_group(self, group_name):
143
+ # """Clear a parameter group.
144
+ #
145
+ # Given a single parameter group name will clear out values for that parameter
146
+ # and all related parameters.
147
+ #
148
+ # Args:
149
+ # group_name: The name of a group of related parameters.
150
+ # One of 'statVar', 'aniOut', 'mapOut', 'dispVar', 'nhruOut'.
151
+ # """
152
+ #
153
+ # groups = {'aniOut': {'naniOutVars': 0, 'aniOutON_OFF': 0, 'aniOutVar_names': []},
154
+ # 'dispVar': {'ndispGraphs': 0, 'dispVar_element': [], 'dispVar_names': [], 'dispVar_plot': []},
155
+ # 'mapOut': {'nmapOutVars': 0, 'mapOutON_OFF': 0, 'mapOutVar_names': []},
156
+ # 'nhruOut': {'nhruOutVars': 0, 'nhruOutON_OFF': 0, 'nhruOutVar_names': []},
157
+ # 'statVar': {'nstatVars': 0, 'statsON_OFF': 0, 'statVar_element': [], 'statVar_names': []}}
158
+ #
159
+ # for (kk, vv) in iteritems(groups[group_name]):
160
+ # if kk in self.__controldict:
161
+ # self.replace_values(kk, vv)