imas-python 0.1.dev1618__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.

Potentially problematic release.


This version of imas-python might be problematic. Click here for more details.

Files changed (108) hide show
  1. imas/__init__.py +34 -0
  2. imas/__main__.py +17 -0
  3. imas/_util.py +260 -0
  4. imas/_version.py +16 -0
  5. imas/assets/IDSDef2MDSpreTree.xsl +161 -0
  6. imas/assets/IDS_fake_toplevel.xml +318 -0
  7. imas/assets/IDS_minimal.xml +37 -0
  8. imas/assets/IDS_minimal_2.xml +37 -0
  9. imas/assets/IDS_minimal_struct_array.xml +43 -0
  10. imas/assets/IDS_minimal_types.xml +169 -0
  11. imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
  12. imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
  13. imas/assets/README.md +5 -0
  14. imas/assets/core_profiles.ids +2438 -0
  15. imas/assets/equilibrium.ids +3172 -0
  16. imas/backends/__init__.py +12 -0
  17. imas/backends/db_entry_impl.py +122 -0
  18. imas/backends/imas_core/__init__.py +4 -0
  19. imas/backends/imas_core/al_context.py +391 -0
  20. imas/backends/imas_core/db_entry_al.py +405 -0
  21. imas/backends/imas_core/db_entry_helpers.py +200 -0
  22. imas/backends/imas_core/imas_interface.py +216 -0
  23. imas/backends/imas_core/mdsplus_model.py +367 -0
  24. imas/backends/imas_core/uda_support.py +56 -0
  25. imas/backends/netcdf/__init__.py +4 -0
  26. imas/backends/netcdf/db_entry_nc.py +176 -0
  27. imas/backends/netcdf/ids2nc.py +306 -0
  28. imas/backends/netcdf/nc2ids.py +302 -0
  29. imas/backends/netcdf/nc_metadata.py +357 -0
  30. imas/backends/netcdf/nc_validate.py +53 -0
  31. imas/command/cli.py +239 -0
  32. imas/command/db_analysis.py +290 -0
  33. imas/command/helpers.py +45 -0
  34. imas/command/timer.py +77 -0
  35. imas/db_entry.py +802 -0
  36. imas/dd_helpers.py +300 -0
  37. imas/dd_zip.py +297 -0
  38. imas/exception.py +107 -0
  39. imas/ids_base.py +149 -0
  40. imas/ids_convert.py +920 -0
  41. imas/ids_coordinates.py +400 -0
  42. imas/ids_data_type.py +115 -0
  43. imas/ids_defs.py +171 -0
  44. imas/ids_factory.py +108 -0
  45. imas/ids_identifiers.py +125 -0
  46. imas/ids_metadata.py +307 -0
  47. imas/ids_path.py +254 -0
  48. imas/ids_primitive.py +510 -0
  49. imas/ids_struct_array.py +233 -0
  50. imas/ids_structure.py +331 -0
  51. imas/ids_toplevel.py +381 -0
  52. imas/setup_logging.py +64 -0
  53. imas/test/test_all_dd_versions.py +28 -0
  54. imas/test/test_cli.py +44 -0
  55. imas/test/test_dbentry.py +104 -0
  56. imas/test/test_dd_helpers.py +134 -0
  57. imas/test/test_dd_zip.py +29 -0
  58. imas/test/test_deepcopy.py +25 -0
  59. imas/test/test_exception.py +12 -0
  60. imas/test/test_get_sample.py +388 -0
  61. imas/test/test_hash.py +188 -0
  62. imas/test/test_helpers.py +354 -0
  63. imas/test/test_identifiers.py +105 -0
  64. imas/test/test_ids2nc.py +166 -0
  65. imas/test/test_ids_ascii_data.py +39 -0
  66. imas/test/test_ids_convert.py +415 -0
  67. imas/test/test_ids_coordinates.py +165 -0
  68. imas/test/test_ids_data_type.py +40 -0
  69. imas/test/test_ids_factory.py +35 -0
  70. imas/test/test_ids_metadata.py +66 -0
  71. imas/test/test_ids_mixin.py +20 -0
  72. imas/test/test_ids_path.py +192 -0
  73. imas/test/test_ids_primitive.py +109 -0
  74. imas/test/test_ids_struct_array.py +89 -0
  75. imas/test/test_ids_structure.py +70 -0
  76. imas/test/test_ids_toplevel.py +55 -0
  77. imas/test/test_ids_validate.py +343 -0
  78. imas/test/test_latest_dd_autofill.py +74 -0
  79. imas/test/test_latest_dd_manual.py +38 -0
  80. imas/test/test_latest_dd_resample.py +102 -0
  81. imas/test/test_lazy_loading.py +201 -0
  82. imas/test/test_list_occurrences.py +82 -0
  83. imas/test/test_minimal.py +37 -0
  84. imas/test/test_minimal_conversion.py +44 -0
  85. imas/test/test_minimal_io.py +21 -0
  86. imas/test/test_minimal_struct_array_io.py +60 -0
  87. imas/test/test_minimal_types.py +436 -0
  88. imas/test/test_minimal_types_io.py +70 -0
  89. imas/test/test_minimal_types_io_automatic.py +66 -0
  90. imas/test/test_nbc_change.py +408 -0
  91. imas/test/test_nc_autofill.py +13 -0
  92. imas/test/test_nc_entry.py +28 -0
  93. imas/test/test_nc_metadata.py +153 -0
  94. imas/test/test_nc_validation.py +197 -0
  95. imas/test/test_snippets.py +25 -0
  96. imas/test/test_static_ids.py +39 -0
  97. imas/test/test_str_1d.py +99 -0
  98. imas/test/test_time_slicing.py +182 -0
  99. imas/test/test_uda_support.py +12 -0
  100. imas/test/test_util.py +209 -0
  101. imas/training.py +45 -0
  102. imas/util.py +526 -0
  103. imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
  104. imas_python-0.1.dev1618.dist-info/METADATA +288 -0
  105. imas_python-0.1.dev1618.dist-info/RECORD +108 -0
  106. imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
  107. imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
  108. imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
imas/ids_path.py ADDED
@@ -0,0 +1,254 @@
1
+ # This file is part of imas-python.
2
+ # You should have received the imas-python LICENSE file with this project.
3
+ """Logic for interpreting paths to elements in an IDS
4
+ """
5
+
6
+ import logging
7
+ import re
8
+ from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Tuple, Union
9
+
10
+ if TYPE_CHECKING: # Prevent circular imports
11
+ from imas.ids_base import IDSBase
12
+ from imas.ids_metadata import IDSMetadata
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+ _IndexType = Union[None, str, int, "IDSPath", slice]
17
+ """Type of an index in a path."""
18
+
19
+
20
+ def _split_on_matching_parens(path: str, open: str, close: str) -> List[str]:
21
+ """Split a string on matching parentheses ``(``, ``)``.
22
+
23
+ Return a list [before_first_(, between(), between)(, ..., after_last)]
24
+ """
25
+ cur_index = 0
26
+ ret = []
27
+ while True:
28
+ next_open = path.find(open, cur_index)
29
+ next_close = path.find(close, cur_index)
30
+ if next_open != -1:
31
+ if next_open > next_close:
32
+ raise ValueError(f"Unmatched parentheses in: {path}")
33
+ ret.append(path[cur_index:next_open])
34
+ # find matching closing bracket
35
+ cur_index = next_open + 1
36
+ next_open = path.find(open, next_open + 1)
37
+ while next_open != -1 and next_open < next_close:
38
+ next_close = path.find(close, next_close + 1)
39
+ if next_close == -1:
40
+ raise ValueError(f"Unmatched parentheses in: {path}")
41
+ next_open = path.find(open, next_open + 1)
42
+ ret.append(path[cur_index:next_close])
43
+ cur_index = next_close + 1
44
+ else:
45
+ ret.append(path[cur_index:])
46
+ break
47
+ return ret
48
+
49
+
50
+ _number = re.compile(r"\d+", re.ASCII)
51
+ _slice = re.compile(r"\d*:\d*", re.ASCII)
52
+ _generic_index = re.compile(r"itime|i\d", re.ASCII)
53
+ # Naming conventions of DD nodes: start with a-z, a-z, 0-9 and _ allowed, but no
54
+ # consecutive double underscores.
55
+ _valid_field = re.compile(r"[a-z]([a-z0-9]|_(?!_))*", re.ASCII)
56
+
57
+
58
+ def _parse_path(
59
+ path: str,
60
+ ) -> Tuple[Tuple[str, ...], Tuple[_IndexType, ...]]:
61
+ """Parse an IDS path into its constituent parts.
62
+
63
+ Args:
64
+ path: an IDS path (e.g. ``"profiles_1d(itime)/zeff"``)
65
+
66
+ Returns:
67
+ path_parts: all field names, e.g. ``("profiles_1d", "zeff")``
68
+ path_indices: all field indices, e.g. ``("itime", None)``
69
+ """
70
+ path_parts: List[str] = []
71
+ path_indices: List[Union[str, int, "IDSPath", slice]] = []
72
+ fortran_style = "(" in path
73
+ if fortran_style and "[" in path:
74
+ raise ValueError(
75
+ f"Cannot mix Fortran-style indexation with Python-style indexation: {path}"
76
+ )
77
+ split_path = _split_on_matching_parens(
78
+ path, "[("[fortran_style], "])"[fortran_style]
79
+ )
80
+ # split_path always has an odd number of items, iterate over pairs first
81
+ for i in range(0, len(split_path) - 1, 2):
82
+ part, index = split_path[i : i + 2]
83
+ if not part:
84
+ raise ValueError(f"Invalid empty node name in path: {path}")
85
+ parts = (part[1:] if part[0] == "/" else part).split("/")
86
+ path_parts.extend(parts)
87
+ path_indices.extend([None] * len(parts))
88
+ if _generic_index.fullmatch(index):
89
+ path_indices[-1] = index # keep as string
90
+ elif _number.fullmatch(index):
91
+ # if fortran-style (1-based index), decrease index by one
92
+ path_indices[-1] = int(index) - fortran_style
93
+ elif _slice.fullmatch(index):
94
+ start, stop = index.split(":")
95
+ # When using Fortran style, we should decrement the start index by one
96
+ # e.g. Fortran-style (2:3) == [1:3] == range(1, 3)
97
+ path_indices[-1] = slice(
98
+ int(start) - fortran_style if start else None,
99
+ int(stop) if stop else None,
100
+ )
101
+ else: # it must be another path, parse it:
102
+ path_indices[-1] = IDSPath(index)
103
+ if split_path[-1]:
104
+ part = split_path[-1]
105
+ parts = (part[1:] if part[0] == "/" else part).split("/")
106
+ path_parts.extend(parts)
107
+ path_indices.extend([None] * len(parts))
108
+ for part in path_parts:
109
+ if not _valid_field.fullmatch(part):
110
+ raise ValueError(f"Invalid node name '{part}' in path: {path}")
111
+ return tuple(path_parts), tuple(path_indices)
112
+
113
+
114
+ class IDSPath:
115
+ """Represent a path in an IDS.
116
+
117
+ An IDS Path indicates the relative position of an element in an IDS and is similar
118
+ to a directory structure.
119
+
120
+ Paths consist of elements with an optional index, separated by slashes. For example:
121
+
122
+ - ``ids_properties/version_put/data_dictionary``, no indices
123
+ - ``profiles_1d(itime)/zeff``, using a dummy ``itime`` index
124
+ - ``distribution(1)/process(:)/nbi_unit``, using a Fortran-style explicit index (1)
125
+ and Fortran-style range selector (:)
126
+ - ``distribution[0]/process[:]/nbi_unit``, using a Python-style explicit index [0]
127
+ and Python-style range selector [:]
128
+ - ``coordinate_system(process(i1)/coordinate_index)/coordinate(1)`` using another
129
+ IDSPath as index.
130
+ """
131
+
132
+ _cache: Dict[str, "IDSPath"] = {}
133
+
134
+ def __new__(cls, path: str) -> "IDSPath":
135
+ if path not in cls._cache:
136
+ cls._cache[path] = super().__new__(cls)
137
+ return cls._cache[path]
138
+
139
+ def __init__(self, path: str) -> None:
140
+ if hasattr(self, "_init_done"):
141
+ return # Already initialized, __new__ returned from cache
142
+ self._path = path
143
+ self.parts, self.indices = _parse_path(path)
144
+ self.is_time_path = self.parts and self.parts[-1] == "time"
145
+ # Prevent accidentally modifying attributes
146
+ self._init_done = True
147
+
148
+ def __setattr__(self, name: str, value: Any):
149
+ if hasattr(self, "_init_done"):
150
+ raise RuntimeError("Cannot set attribute: IDSPath is read-only.")
151
+ super().__setattr__(name, value)
152
+
153
+ def __len__(self) -> int:
154
+ return len(self.parts)
155
+
156
+ def __str__(self) -> str:
157
+ return self._path
158
+
159
+ def __repr__(self) -> str:
160
+ return f"IDSPath({self._path!r})"
161
+
162
+ def __hash__(self) -> int:
163
+ """IDSPaths are immutable, we can be used e.g. as dict key."""
164
+ return hash(self._path)
165
+
166
+ def items(self) -> Iterator[Tuple[str, _IndexType]]:
167
+ """``(part, index)`` iterator"""
168
+ return zip(self.parts, self.indices)
169
+
170
+ def goto(self, from_element: "IDSBase", *, from_root: bool = True) -> "IDSBase":
171
+ """Go to this path, taking from_element as reference.
172
+
173
+ This returns the IDS node at the specified path, or raises an error when that
174
+ path cannot be found.
175
+
176
+ Args:
177
+ from_element: IDS element to start the goto from
178
+ from_root: True iff we should start at the root, otherwise we start from the
179
+ provided IDS element (see example)
180
+
181
+ Example:
182
+ .. code-block:: python
183
+
184
+ cp = imas.IDSFactory().core_profiles()
185
+ cp.profiles_1d.resize(1)
186
+ element = cp.profiles_1d[0]
187
+ path1 = IDSPath("ids_properties/homogeneous_time")
188
+ assert path1.goto(element, True) is cp.ids_properties.homogeneous_time
189
+ path2 = IDSPath("electrons/temperature")
190
+ assert path2.goto(element, False) is element.electrons.temperature
191
+ """
192
+ from_path = from_element.metadata.path # this path doesn't have indices
193
+ element = None if from_root else from_element
194
+ for i, (part, index) in enumerate(self.items()):
195
+ if isinstance(index, slice):
196
+ raise NotImplementedError("Cannot go to slices")
197
+ if element is None:
198
+ if i < len(from_path) and part == from_path.parts[i]:
199
+ if index is not None and not isinstance(index, str):
200
+ logger.warning(
201
+ "Ignoring index %s while resolving path %s.", index, self
202
+ )
203
+ else:
204
+ # last common parent found, set element to it
205
+ element = from_element
206
+ for _ in range(len(from_path) - i):
207
+ element = element._dd_parent
208
+ # Not using `else` on the next line, because element might be set above
209
+ if element is not None:
210
+ element = getattr(element, part)
211
+ if index is not None:
212
+ if isinstance(index, str):
213
+ raise ValueError(f"Unexpected index {index} in path '{self}'.")
214
+ if isinstance(index, IDSPath):
215
+ val = index.goto(from_element).value
216
+ if not isinstance(val, int):
217
+ raise ValueError(f"Invalid index {index} in path '{self}'.")
218
+ index = val - 1 # Stored as 1-based index
219
+ element = element[index]
220
+ if element is None: # We point to from_element or a direct ancestor
221
+ element = from_element
222
+ for _ in range(len(from_path) - len(self)):
223
+ element = element._dd_parent
224
+ return element
225
+
226
+ def goto_metadata(self, from_metadata: "IDSMetadata") -> "IDSMetadata":
227
+ """Go to the metadata of this path, taking from_metadata as reference.
228
+
229
+ This returns the metadata of the metadata IDS node at the specified path,
230
+ or raises an error when that path cannot be found. It is useful when you want to
231
+ retrieve the metadata of an IDS node without having to traverse the IDS nodes.
232
+
233
+ Args:
234
+ from_metadata: IDS metadata node to start the goto from
235
+
236
+ Example:
237
+ .. code-block:: python
238
+
239
+ es = imas.IDSFactory().edge_sources()
240
+ path = IDSPath("source/ggd/ion/energy")
241
+ energy_metadata = path.goto_metadata(es.metadata)
242
+ """
243
+ metadata_node = from_metadata
244
+ for part, _ in self.items():
245
+ if part in metadata_node._children:
246
+ metadata_node = metadata_node[part]
247
+ else:
248
+ raise ValueError(f"Could not find {part} in {metadata_node}.")
249
+ return metadata_node
250
+
251
+ def is_ancestor_of(self, other_path: "IDSPath") -> bool:
252
+ """Test if this path is an ancestor of the other path."""
253
+ len_self = len(self)
254
+ return len_self < len(other_path) and other_path.parts[:len_self] == self.parts