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.
- pyPRMS/Exceptions_custom.py +31 -0
- pyPRMS/__init__.py +52 -0
- pyPRMS/cbh/Cbh.py +431 -0
- pyPRMS/cbh/CbhAscii.py +458 -0
- pyPRMS/cbh/CbhNetcdf.py +199 -0
- pyPRMS/cbh/__init__.py +3 -0
- pyPRMS/constants.py +131 -0
- pyPRMS/control/Control.py +362 -0
- pyPRMS/control/ControlFile.py +161 -0
- pyPRMS/control/ControlVariable.py +208 -0
- pyPRMS/control/__init__.py +3 -0
- pyPRMS/dimensions/Dimension.py +154 -0
- pyPRMS/dimensions/Dimensions.py +256 -0
- pyPRMS/dimensions/__init__.py +2 -0
- pyPRMS/input/DataFile.py +354 -0
- pyPRMS/input/InputVariable.py +61 -0
- pyPRMS/input/__init__.py +0 -0
- pyPRMS/metadata/__init__.py +1 -0
- pyPRMS/metadata/metadata.py +430 -0
- pyPRMS/parameters/ParamDb.py +73 -0
- pyPRMS/parameters/Parameter.py +624 -0
- pyPRMS/parameters/ParameterFile.py +190 -0
- pyPRMS/parameters/ParameterNetCDF.py +74 -0
- pyPRMS/parameters/ParameterSet.py +96 -0
- pyPRMS/parameters/Parameters.py +1506 -0
- pyPRMS/parameters/__init__.py +5 -0
- pyPRMS/plot_helpers.py +305 -0
- pyPRMS/prms_helpers.py +235 -0
- pyPRMS/py.typed +0 -0
- pyPRMS/summary/OutputCSV.py +64 -0
- pyPRMS/summary/OutputVariable.py +163 -0
- pyPRMS/summary/OutputVariables.py +227 -0
- pyPRMS/summary/__init__.py +2 -0
- pyPRMS/utilities/__init__.py +0 -0
- pyPRMS/utilities/convert_cbh.py +107 -0
- pyPRMS/utilities/convert_model_output.py +91 -0
- pyPRMS/utilities/convert_params.py +60 -0
- pyPRMS/version.py +13 -0
- pyPRMS/xml/cbh.xml +163 -0
- pyPRMS/xml/control.xml +1447 -0
- pyPRMS/xml/dimensions.xml +311 -0
- pyPRMS/xml/modules.xml +251 -0
- pyPRMS/xml/parameters.xml +6932 -0
- pyPRMS/xml/time_series_input.xml +198 -0
- pyPRMS/xml/variables.xml +8173 -0
- pyprms-0.9.7.dist-info/LICENSE.md +21 -0
- pyprms-0.9.7.dist-info/METADATA +67 -0
- pyprms-0.9.7.dist-info/RECORD +51 -0
- pyprms-0.9.7.dist-info/WHEEL +5 -0
- pyprms-0.9.7.dist-info/entry_points.txt +3 -0
- pyprms-0.9.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import numpy as np
|
|
5
|
+
import re
|
|
6
|
+
from typing import Callable, Dict, List, Optional, Sequence, Union
|
|
7
|
+
|
|
8
|
+
from ..constants import NEW_PTYPE_TO_DTYPE
|
|
9
|
+
from pyPRMS.prms_helpers import cond_check, set_date
|
|
10
|
+
from ..Exceptions_custom import ControlError
|
|
11
|
+
|
|
12
|
+
class ControlVariable(object):
|
|
13
|
+
"""
|
|
14
|
+
Class object for a single control variable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Author: Parker Norton (pnorton@usgs.gov)
|
|
18
|
+
# Create date: 2019-04-18
|
|
19
|
+
|
|
20
|
+
def __init__(self, name: str,
|
|
21
|
+
value = None,
|
|
22
|
+
meta: Optional[Dict] = None,
|
|
23
|
+
strict: Optional[bool] = True):
|
|
24
|
+
"""Initialize a control variable object.
|
|
25
|
+
|
|
26
|
+
:param name: Name of control variable
|
|
27
|
+
:param meta: Metadata of the control variable
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
self.__name = name
|
|
31
|
+
self.__values: Union[np.ndarray, None] = None
|
|
32
|
+
|
|
33
|
+
if meta is None:
|
|
34
|
+
if strict:
|
|
35
|
+
raise ValueError(f'Strict is true but no metadata was supplied')
|
|
36
|
+
else:
|
|
37
|
+
self.meta = {}
|
|
38
|
+
else:
|
|
39
|
+
if strict:
|
|
40
|
+
if name in meta:
|
|
41
|
+
self.meta = meta[name]
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError(f'`{self.name}` does not exist in metadata')
|
|
44
|
+
else:
|
|
45
|
+
# The meta must be supplied as an adhoc dictionary
|
|
46
|
+
self.meta = meta
|
|
47
|
+
|
|
48
|
+
self.values = value
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
outstr = f'----- ControlVariable -----\n'
|
|
52
|
+
outstr += f'name: {self.name}\n'
|
|
53
|
+
|
|
54
|
+
for kk, vv in self.meta.items():
|
|
55
|
+
outstr += f'{kk}: {vv}\n'
|
|
56
|
+
|
|
57
|
+
return outstr
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def dyn_param_meaning(self):
|
|
61
|
+
"""Returns a list of meanings for the value of a parameter flag"""
|
|
62
|
+
meaning = self.meta.get('valid_values', {}).get(str(self.values), None)
|
|
63
|
+
|
|
64
|
+
if meaning is None:
|
|
65
|
+
return list()
|
|
66
|
+
else:
|
|
67
|
+
return meaning.split(',')
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def name(self) -> str:
|
|
71
|
+
"""Returns the name of the control variable.
|
|
72
|
+
|
|
73
|
+
:returns: Name of control variable
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
return self.__name
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def size(self) -> int:
|
|
80
|
+
"""Return number of values"""
|
|
81
|
+
if self.__values is None:
|
|
82
|
+
return 0
|
|
83
|
+
elif isinstance(self.__values, np.ndarray):
|
|
84
|
+
return self.__values.size
|
|
85
|
+
else:
|
|
86
|
+
# int, float, str scalars
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def values(self) -> Union[np.ndarray, int, float, str, None]:
|
|
91
|
+
"""Get the values for the control variable.
|
|
92
|
+
|
|
93
|
+
If force_default is True then the default value is returned regardless
|
|
94
|
+
of what the value is set to; otherwise, current value is returned.
|
|
95
|
+
|
|
96
|
+
:returns: Value(s) of control variable
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
force_default = self.meta.get('force_default', False)
|
|
100
|
+
|
|
101
|
+
if force_default or self.__values is None:
|
|
102
|
+
if isinstance(self.meta.get('default', None), np.str_ | np.int32 | np.float32):
|
|
103
|
+
return self.meta.get('default', None).item()
|
|
104
|
+
else:
|
|
105
|
+
return self.meta.get('default', None)
|
|
106
|
+
else:
|
|
107
|
+
if isinstance(self.__values, np.str_ | np.int32 | np.float32):
|
|
108
|
+
return self.__values.item()
|
|
109
|
+
else:
|
|
110
|
+
return self.__values
|
|
111
|
+
|
|
112
|
+
@values.setter
|
|
113
|
+
def values(self, data: Union[Sequence[str], str, int, float, datetime.datetime]):
|
|
114
|
+
"""Set the value(s) for the control variable.
|
|
115
|
+
|
|
116
|
+
:param data: list or string of value(s)
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# if self.meta is not None:
|
|
120
|
+
if data is None:
|
|
121
|
+
self.__values = self.meta.get('default', None)
|
|
122
|
+
else:
|
|
123
|
+
cdtype = NEW_PTYPE_TO_DTYPE[self.meta['datatype']]
|
|
124
|
+
|
|
125
|
+
if self.meta['context'] == 'scalar':
|
|
126
|
+
if isinstance(data, list):
|
|
127
|
+
# Only first value is used for scalars
|
|
128
|
+
self.__values = np.array(data, dtype=cdtype)[0]
|
|
129
|
+
elif isinstance(data, np.ndarray):
|
|
130
|
+
if self.meta['datatype'] == 'datetime':
|
|
131
|
+
self.__values = cdtype(set_date(data))
|
|
132
|
+
elif data.dtype == cdtype:
|
|
133
|
+
self.__values = data[0]
|
|
134
|
+
else:
|
|
135
|
+
raise TypeError(f'{self.name}: {cdtype} expected but got {data[0].dtype}')
|
|
136
|
+
# print(f'WARNING: {self.name} - datatype inconsistency; no values added')
|
|
137
|
+
elif ((cdtype == np.str_ and not isinstance(data, str | cdtype)) or
|
|
138
|
+
(cdtype == np.int32 and not isinstance(data, int | cdtype)) or
|
|
139
|
+
(cdtype == np.float32 and not isinstance(data, float | cdtype))):
|
|
140
|
+
raise TypeError(f'{self.name}: {cdtype} expected but got {type(data)}')
|
|
141
|
+
else:
|
|
142
|
+
self.__values = cdtype(data)
|
|
143
|
+
else:
|
|
144
|
+
if isinstance(data, list):
|
|
145
|
+
self.__values = np.array(data, dtype=cdtype)
|
|
146
|
+
elif isinstance(data, np.ndarray):
|
|
147
|
+
if data.dtype == cdtype:
|
|
148
|
+
# TODO: 20230707 PAN - currently never hit with string arrays
|
|
149
|
+
self.__values = data # pragma: no cover
|
|
150
|
+
else:
|
|
151
|
+
# Attempt to convert to correct datatype
|
|
152
|
+
self.__values = np.array(data, dtype=cdtype)
|
|
153
|
+
else:
|
|
154
|
+
# TODO: 20230707 PAN - currently never hit
|
|
155
|
+
self.__values = np.array([data], dtype=cdtype) # pragma: no cover
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def value_meaning(self) -> Union[str, None]:
|
|
159
|
+
"""Returns the meaning for a given value if it exists.
|
|
160
|
+
|
|
161
|
+
:returns: Control variable value meaning
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# This will fail for keys that are conditionals (e.g. ">0")
|
|
165
|
+
meaning = self.meta.get('valid_values', None)
|
|
166
|
+
|
|
167
|
+
if meaning is None:
|
|
168
|
+
# valid_values key does not exist
|
|
169
|
+
return meaning
|
|
170
|
+
|
|
171
|
+
return self._value_meaning_test(self.values, meaning)
|
|
172
|
+
# return meaning.get(self.values, meaning.get(str(self.values), None))
|
|
173
|
+
|
|
174
|
+
def _value_meaning_test(self, key, src_dict):
|
|
175
|
+
try:
|
|
176
|
+
return src_dict[key]
|
|
177
|
+
except KeyError:
|
|
178
|
+
# Maybe the key is a string
|
|
179
|
+
try:
|
|
180
|
+
return src_dict[str(key)]
|
|
181
|
+
except KeyError:
|
|
182
|
+
# Maybe one of the keys is a conditional?
|
|
183
|
+
patterns = ['[><]']
|
|
184
|
+
regex = [re.compile('^' + pat).match for pat in patterns]
|
|
185
|
+
|
|
186
|
+
tt = {kk: vv for kk, vv in src_dict.items()
|
|
187
|
+
if any (reg(kk) for reg in regex)}
|
|
188
|
+
|
|
189
|
+
if len(tt) > 0:
|
|
190
|
+
# So there is a conditional
|
|
191
|
+
for mm in tt:
|
|
192
|
+
# print(mm.split())
|
|
193
|
+
if cond_check[mm[0]](key, int(mm[1:])):
|
|
194
|
+
return src_dict[mm]
|
|
195
|
+
# print(f'{mm}: {src_dict[mm]}')
|
|
196
|
+
raise ValueError('Invalid control value')
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# try:
|
|
200
|
+
# if 'valid_values' in self.meta:
|
|
201
|
+
# # We want a KeyError here if the key is missing
|
|
202
|
+
# return self.meta['valid_values'][self.values]
|
|
203
|
+
#
|
|
204
|
+
# return None
|
|
205
|
+
# except KeyError:
|
|
206
|
+
# # Try again but return None if the key is still missing
|
|
207
|
+
# return self.meta['valid_values'].get(str(self.values), None)
|
|
208
|
+
# return None
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from typing import Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Dimension(object):
|
|
5
|
+
"""Defines a single dimension."""
|
|
6
|
+
|
|
7
|
+
__name: str = ''
|
|
8
|
+
__size: int = 0
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str,
|
|
11
|
+
meta: Optional[Dict] = None,
|
|
12
|
+
size: Optional[int] = None,
|
|
13
|
+
strict: Optional[bool] = True):
|
|
14
|
+
"""Create a new dimension object.
|
|
15
|
+
|
|
16
|
+
A dimension has a name and a size associated with it.
|
|
17
|
+
|
|
18
|
+
:param name: The name of the dimension
|
|
19
|
+
:param size: The size of the dimension
|
|
20
|
+
:param strict: Enforce use of valid dimensions metadata
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
self.__name = name
|
|
24
|
+
|
|
25
|
+
if meta is None:
|
|
26
|
+
if strict:
|
|
27
|
+
raise ValueError(f'Strict is true but no metadata was supplied')
|
|
28
|
+
else:
|
|
29
|
+
self.meta = {}
|
|
30
|
+
else:
|
|
31
|
+
if strict:
|
|
32
|
+
if name in meta:
|
|
33
|
+
self.meta = meta[name]
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError(f'`{self.name}` does not exist in metadata')
|
|
36
|
+
else:
|
|
37
|
+
if isinstance(meta, dict):
|
|
38
|
+
# Assume we have a dictionary of metadata for this dimension
|
|
39
|
+
self.meta = meta
|
|
40
|
+
|
|
41
|
+
if size is None:
|
|
42
|
+
self.size = self.meta.get('default', 0)
|
|
43
|
+
else:
|
|
44
|
+
self.size = size
|
|
45
|
+
|
|
46
|
+
def __iadd__(self, other: int):
|
|
47
|
+
"""Add a number to dimension size.
|
|
48
|
+
|
|
49
|
+
:param other: Integer value
|
|
50
|
+
|
|
51
|
+
:returns: Dimension size
|
|
52
|
+
|
|
53
|
+
:raises ValueError: if type of parameter is not an integer
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Augment in-place addition so the instance plus a number results
|
|
57
|
+
# in a change to self.__size
|
|
58
|
+
if not isinstance(other, int):
|
|
59
|
+
raise ValueError('Dimension size type must be an integer')
|
|
60
|
+
self.size += other
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def __isub__(self, other: int):
|
|
64
|
+
"""Subtracts integer from dimension size.
|
|
65
|
+
|
|
66
|
+
:param other: Integer value
|
|
67
|
+
|
|
68
|
+
:returns: Dimension size
|
|
69
|
+
|
|
70
|
+
:raises ValueError: if type of parameter is not an integer
|
|
71
|
+
:raises ValeuError: if parameter is not a positive integer
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# Augment in-place addition so the instance minus a number results
|
|
75
|
+
# in a change to self.__size
|
|
76
|
+
if not isinstance(other, int):
|
|
77
|
+
raise ValueError('Dimension size type must be an integer')
|
|
78
|
+
self.size -= other
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
"""String respresentation of dimension.
|
|
83
|
+
|
|
84
|
+
:returns: string with name and size of dimension
|
|
85
|
+
"""
|
|
86
|
+
return f"Dimension(name='{self.name}', meta={self.meta}, size={self.size}, strict=False)"
|
|
87
|
+
|
|
88
|
+
def __str__(self) -> str:
|
|
89
|
+
"""Return friendly string representation of dimension
|
|
90
|
+
"""
|
|
91
|
+
outstr = f'----- Dimension -----\n'
|
|
92
|
+
outstr += f'name: {self.name}\n'
|
|
93
|
+
|
|
94
|
+
for kk, vv in self.meta.items():
|
|
95
|
+
if kk != 'size':
|
|
96
|
+
outstr += f'{kk}: {vv}\n'
|
|
97
|
+
|
|
98
|
+
outstr += f'size: {self.size}\n'
|
|
99
|
+
|
|
100
|
+
return outstr
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def is_fixed(self):
|
|
104
|
+
return self.meta.get('is_fixed', False)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def name(self) -> str:
|
|
108
|
+
"""Name of the dimension.
|
|
109
|
+
|
|
110
|
+
:returns: Name of the dimension
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
return self.__name
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def size(self) -> int:
|
|
117
|
+
"""Size of the dimension.
|
|
118
|
+
|
|
119
|
+
:returns: Size of the dimension
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
return self.__size
|
|
123
|
+
|
|
124
|
+
@size.setter
|
|
125
|
+
def size(self, value: Union[int, str]):
|
|
126
|
+
"""Set the size of the dimension.
|
|
127
|
+
|
|
128
|
+
:param value: Size of the dimension
|
|
129
|
+
:raises ValueError: if dimension size is not a positive integer
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
if isinstance(value, float):
|
|
133
|
+
raise ValueError(f'{self.name} size cannot be a float value')
|
|
134
|
+
|
|
135
|
+
value = int(value)
|
|
136
|
+
def_value = self.meta.get('default', 0)
|
|
137
|
+
|
|
138
|
+
if value < 0:
|
|
139
|
+
raise ValueError('Dimension size must be a positive integer')
|
|
140
|
+
|
|
141
|
+
if self.is_fixed:
|
|
142
|
+
if 0 < value != def_value:
|
|
143
|
+
raise ValueError(f'{self.name} is a fixed dimension and cannot be changed')
|
|
144
|
+
|
|
145
|
+
self.__size = def_value
|
|
146
|
+
else:
|
|
147
|
+
# The size of a dimension should never be less than the default
|
|
148
|
+
if value < def_value:
|
|
149
|
+
raise ValueError(f'{self.name} size cannot be less than default value ({def_value})')
|
|
150
|
+
|
|
151
|
+
self.__size = max(value, def_value)
|
|
152
|
+
|
|
153
|
+
# TODO: 2023-06-07 PAN - should the metadata size also get changed?
|
|
154
|
+
self.meta['size'] = self.__size
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, Dict, Optional, Union # , OrderedDict as OrderedDictType, Union
|
|
3
|
+
|
|
4
|
+
import xml.etree.ElementTree as xmlET
|
|
5
|
+
|
|
6
|
+
from .Dimension import Dimension
|
|
7
|
+
from ..constants import MetaDataType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Dimensions(object):
|
|
11
|
+
"""Container of Dimension objects."""
|
|
12
|
+
__dimensions: Dict[str, Dimension]
|
|
13
|
+
|
|
14
|
+
def __init__(self, metadata: Optional[MetaDataType] = None,
|
|
15
|
+
verbose: Optional[bool] = False,
|
|
16
|
+
strict: Optional[bool] = True):
|
|
17
|
+
"""Create dictionary containing Dimension objects.
|
|
18
|
+
|
|
19
|
+
:param verbose: Output additional debug information
|
|
20
|
+
"""
|
|
21
|
+
self.__dimensions: Dict[str, Dimension] = {}
|
|
22
|
+
self.__verbose = verbose
|
|
23
|
+
self.__strict = strict
|
|
24
|
+
self.metadata: Union[Dict, None] = None
|
|
25
|
+
|
|
26
|
+
if strict:
|
|
27
|
+
if metadata is None:
|
|
28
|
+
raise ValueError(f'Metadata is required but was not supplied')
|
|
29
|
+
self.metadata = metadata['dimensions']
|
|
30
|
+
else:
|
|
31
|
+
if metadata is None:
|
|
32
|
+
self.metadata = {}
|
|
33
|
+
else:
|
|
34
|
+
# TODO: 20230707 PAN - is adhoc metadata a useful idea?
|
|
35
|
+
self.metadata = metadata
|
|
36
|
+
|
|
37
|
+
# if metadata is not None:
|
|
38
|
+
# self.metadata = metadata['dimensions']
|
|
39
|
+
|
|
40
|
+
# if self.metadata is not None:
|
|
41
|
+
# for cdim, cvals in self.metadata.items():
|
|
42
|
+
# self.add(name=cdim, meta=self.metadata)
|
|
43
|
+
#
|
|
44
|
+
|
|
45
|
+
def __getattr__(self, name: str) -> Any:
|
|
46
|
+
"""Get named dimension.
|
|
47
|
+
|
|
48
|
+
:param name: name of dimensions
|
|
49
|
+
:returns: dimension object
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# print('ATTR: {}'.format(name))
|
|
53
|
+
# https://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
|
|
54
|
+
if name == "__setstate__":
|
|
55
|
+
raise AttributeError(name)
|
|
56
|
+
return getattr(self.__dimensions, name)
|
|
57
|
+
|
|
58
|
+
def __getitem__(self, item: str) -> Dimension:
|
|
59
|
+
"""Get named dimension.
|
|
60
|
+
|
|
61
|
+
:param item: name of dimension
|
|
62
|
+
:returns: Dimension object
|
|
63
|
+
"""
|
|
64
|
+
return self.__dimensions[item]
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
"""Pretty-print dimensions.
|
|
68
|
+
|
|
69
|
+
:returns: Pretty-print string of dimensions
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
outstr = ''
|
|
73
|
+
if len(self.__dimensions) == 0:
|
|
74
|
+
outstr = '<empty>'
|
|
75
|
+
else:
|
|
76
|
+
for kk, vv in self.__dimensions.items():
|
|
77
|
+
outstr += f'{vv}\n'
|
|
78
|
+
return outstr
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def dimensions(self) -> Dict[str, Dimension]:
|
|
82
|
+
"""Get ordered dictionary of Dimension objects.
|
|
83
|
+
|
|
84
|
+
:returns: OrderedDict of Dimension objects
|
|
85
|
+
"""
|
|
86
|
+
return self.__dimensions
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def ndim(self) -> int:
|
|
90
|
+
"""Get number of dimensions.
|
|
91
|
+
|
|
92
|
+
:returns: Number of dimensions
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
return len(self.__dimensions)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def xml(self) -> xmlET.Element:
|
|
99
|
+
"""Get xml element for the dimensions.
|
|
100
|
+
|
|
101
|
+
:returns: XML element for the dimensions
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
dims_xml = xmlET.Element('dimensions')
|
|
105
|
+
|
|
106
|
+
for kk, vv in self.dimensions.items():
|
|
107
|
+
dim_sub = xmlET.SubElement(dims_xml, 'dimension')
|
|
108
|
+
dim_sub.set('name', kk)
|
|
109
|
+
xmlET.SubElement(dim_sub, 'size').text = str(vv.size)
|
|
110
|
+
# dim_sub.set('size', str(vv.size))
|
|
111
|
+
return dims_xml
|
|
112
|
+
|
|
113
|
+
def add(self, name: str, size: Optional[int] = None):
|
|
114
|
+
"""Add a new Dimension object.
|
|
115
|
+
|
|
116
|
+
:param name: Name of the dimension
|
|
117
|
+
:param size: Size of the dimension
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
# This method adds a dimension if it doesn't exist
|
|
121
|
+
# Duplicate dimension names are silently ignored
|
|
122
|
+
if name not in self.__dimensions:
|
|
123
|
+
self.__dimensions[name] = Dimension(name=name, meta=self.metadata,
|
|
124
|
+
size=size,
|
|
125
|
+
strict=self.__strict)
|
|
126
|
+
# else:
|
|
127
|
+
# # TODO: Should this raise an error?
|
|
128
|
+
# print('Dimension {} already exists...skipping add name'.format(name))
|
|
129
|
+
|
|
130
|
+
def exists(self, name: str) -> bool:
|
|
131
|
+
"""Check if dimension exists.
|
|
132
|
+
|
|
133
|
+
:param name: Name of the dimension
|
|
134
|
+
:returns: True if dimension exists, otherwise False
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
return name in self.dimensions.keys()
|
|
138
|
+
|
|
139
|
+
def get(self, name: str) -> Dimension:
|
|
140
|
+
"""Get dimension.
|
|
141
|
+
|
|
142
|
+
:param name: Name of the dimension
|
|
143
|
+
|
|
144
|
+
:returns: Dimension object
|
|
145
|
+
|
|
146
|
+
:raises ValueError: if dimension does not exist
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
if self.exists(name):
|
|
150
|
+
return self.__dimensions[name]
|
|
151
|
+
else:
|
|
152
|
+
raise ValueError(f'Dimension, {name}, does not exist.')
|
|
153
|
+
|
|
154
|
+
def remove(self, name: str):
|
|
155
|
+
"""Remove Dimension object.
|
|
156
|
+
|
|
157
|
+
:param name: name of dimension to remove
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
if self.exists(name):
|
|
161
|
+
del self.__dimensions[name]
|
|
162
|
+
|
|
163
|
+
def tostructure(self) -> Dict[str, Dict[str, int]]:
|
|
164
|
+
"""Get data structure of Dimensions data for serialization.
|
|
165
|
+
|
|
166
|
+
:returns: dictionary of dimension names and sizes
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
dims = {}
|
|
170
|
+
for kk, vv in self.dimensions.items():
|
|
171
|
+
dims[kk] = {'size': vv.size}
|
|
172
|
+
return dims
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ParamDimensions(Dimensions):
|
|
176
|
+
"""Container for parameter dimensions.
|
|
177
|
+
|
|
178
|
+
This object adds tracking of dimension position and restricts the total number
|
|
179
|
+
of individual dimensions to 2.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self, metadata: Optional[MetaDataType] = None,
|
|
183
|
+
verbose: Optional[bool] = False,
|
|
184
|
+
strict: Optional[bool] = True):
|
|
185
|
+
|
|
186
|
+
super(ParamDimensions, self).__init__(metadata=metadata, verbose=verbose, strict=strict)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def xml(self) -> xmlET.Element:
|
|
190
|
+
"""Get xml for the dimensions.
|
|
191
|
+
|
|
192
|
+
:returns: XML element of the dimensions
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
# <dimensions>
|
|
196
|
+
# <dimension name = "nsegment" position = "1" size = "1434" />
|
|
197
|
+
# </ dimensions>
|
|
198
|
+
dims_xml = xmlET.Element('dimensions')
|
|
199
|
+
|
|
200
|
+
for kk, vv in self.dimensions.items():
|
|
201
|
+
dim_sub = xmlET.SubElement(dims_xml, 'dimension')
|
|
202
|
+
dim_sub.set('name', kk)
|
|
203
|
+
xmlET.SubElement(dim_sub, 'position').text = str(self.get_position(kk)+1)
|
|
204
|
+
xmlET.SubElement(dim_sub, 'size').text = str(vv.size)
|
|
205
|
+
|
|
206
|
+
# dim_sub.set('position', str(self.get_position(kk)+1))
|
|
207
|
+
# dim_sub.set('size', str(vv.size))
|
|
208
|
+
return dims_xml
|
|
209
|
+
|
|
210
|
+
def add(self, name: str, size: Optional[int] = None):
|
|
211
|
+
"""Add a new Dimension object.
|
|
212
|
+
|
|
213
|
+
:param name: Name of the dimension
|
|
214
|
+
:param size: Size of the dimension
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
if self.ndim == 2:
|
|
218
|
+
raise ValueError('A parameter cannot have more than two dimensions.')
|
|
219
|
+
|
|
220
|
+
# Restrict number of dimensions for parameters
|
|
221
|
+
super().add(name, size)
|
|
222
|
+
|
|
223
|
+
def get_dimsize_by_index(self, index: int) -> int:
|
|
224
|
+
"""Return size of dimension at the given index.
|
|
225
|
+
|
|
226
|
+
:param index: The 0-based position of the dimension
|
|
227
|
+
:returns: Size of the dimension
|
|
228
|
+
|
|
229
|
+
:raises ValueError: if index is greater than number dimensions for the parameter
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
if index < len(self.dimensions.items()):
|
|
233
|
+
return list(self.dimensions.items())[index][1].size
|
|
234
|
+
raise IndexError(f'Parameter has no dimension at index {index}')
|
|
235
|
+
|
|
236
|
+
def get_position(self, name: str) -> int:
|
|
237
|
+
"""Get 0-based index position of a dimension.
|
|
238
|
+
|
|
239
|
+
:param name: name of the dimension
|
|
240
|
+
|
|
241
|
+
:returns: Zero-based Index position of dimension
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
# TODO: method name should be index() ??
|
|
245
|
+
return list(self.dimensions.keys()).index(name)
|
|
246
|
+
|
|
247
|
+
def tostructure(self) -> Dict[str, Dict[str, int]]:
|
|
248
|
+
"""Get dictionary structure of the dimensions.
|
|
249
|
+
|
|
250
|
+
:returns: dictionary of Dimensions names, sizes, and positions
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
ldims = super(ParamDimensions, self).tostructure()
|
|
254
|
+
for kk, vv in ldims.items():
|
|
255
|
+
vv['position'] = self.get_position(kk)
|
|
256
|
+
return ldims
|