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.
- imas/__init__.py +34 -0
- imas/__main__.py +17 -0
- imas/_util.py +260 -0
- imas/_version.py +16 -0
- imas/assets/IDSDef2MDSpreTree.xsl +161 -0
- imas/assets/IDS_fake_toplevel.xml +318 -0
- imas/assets/IDS_minimal.xml +37 -0
- imas/assets/IDS_minimal_2.xml +37 -0
- imas/assets/IDS_minimal_struct_array.xml +43 -0
- imas/assets/IDS_minimal_types.xml +169 -0
- imas/assets/ITER_134173_106_core_profiles.ids +2438 -0
- imas/assets/ITER_134173_106_equilibrium.ids +3172 -0
- imas/assets/README.md +5 -0
- imas/assets/core_profiles.ids +2438 -0
- imas/assets/equilibrium.ids +3172 -0
- imas/backends/__init__.py +12 -0
- imas/backends/db_entry_impl.py +122 -0
- imas/backends/imas_core/__init__.py +4 -0
- imas/backends/imas_core/al_context.py +391 -0
- imas/backends/imas_core/db_entry_al.py +405 -0
- imas/backends/imas_core/db_entry_helpers.py +200 -0
- imas/backends/imas_core/imas_interface.py +216 -0
- imas/backends/imas_core/mdsplus_model.py +367 -0
- imas/backends/imas_core/uda_support.py +56 -0
- imas/backends/netcdf/__init__.py +4 -0
- imas/backends/netcdf/db_entry_nc.py +176 -0
- imas/backends/netcdf/ids2nc.py +306 -0
- imas/backends/netcdf/nc2ids.py +302 -0
- imas/backends/netcdf/nc_metadata.py +357 -0
- imas/backends/netcdf/nc_validate.py +53 -0
- imas/command/cli.py +239 -0
- imas/command/db_analysis.py +290 -0
- imas/command/helpers.py +45 -0
- imas/command/timer.py +77 -0
- imas/db_entry.py +802 -0
- imas/dd_helpers.py +300 -0
- imas/dd_zip.py +297 -0
- imas/exception.py +107 -0
- imas/ids_base.py +149 -0
- imas/ids_convert.py +920 -0
- imas/ids_coordinates.py +400 -0
- imas/ids_data_type.py +115 -0
- imas/ids_defs.py +171 -0
- imas/ids_factory.py +108 -0
- imas/ids_identifiers.py +125 -0
- imas/ids_metadata.py +307 -0
- imas/ids_path.py +254 -0
- imas/ids_primitive.py +510 -0
- imas/ids_struct_array.py +233 -0
- imas/ids_structure.py +331 -0
- imas/ids_toplevel.py +381 -0
- imas/setup_logging.py +64 -0
- imas/test/test_all_dd_versions.py +28 -0
- imas/test/test_cli.py +44 -0
- imas/test/test_dbentry.py +104 -0
- imas/test/test_dd_helpers.py +134 -0
- imas/test/test_dd_zip.py +29 -0
- imas/test/test_deepcopy.py +25 -0
- imas/test/test_exception.py +12 -0
- imas/test/test_get_sample.py +388 -0
- imas/test/test_hash.py +188 -0
- imas/test/test_helpers.py +354 -0
- imas/test/test_identifiers.py +105 -0
- imas/test/test_ids2nc.py +166 -0
- imas/test/test_ids_ascii_data.py +39 -0
- imas/test/test_ids_convert.py +415 -0
- imas/test/test_ids_coordinates.py +165 -0
- imas/test/test_ids_data_type.py +40 -0
- imas/test/test_ids_factory.py +35 -0
- imas/test/test_ids_metadata.py +66 -0
- imas/test/test_ids_mixin.py +20 -0
- imas/test/test_ids_path.py +192 -0
- imas/test/test_ids_primitive.py +109 -0
- imas/test/test_ids_struct_array.py +89 -0
- imas/test/test_ids_structure.py +70 -0
- imas/test/test_ids_toplevel.py +55 -0
- imas/test/test_ids_validate.py +343 -0
- imas/test/test_latest_dd_autofill.py +74 -0
- imas/test/test_latest_dd_manual.py +38 -0
- imas/test/test_latest_dd_resample.py +102 -0
- imas/test/test_lazy_loading.py +201 -0
- imas/test/test_list_occurrences.py +82 -0
- imas/test/test_minimal.py +37 -0
- imas/test/test_minimal_conversion.py +44 -0
- imas/test/test_minimal_io.py +21 -0
- imas/test/test_minimal_struct_array_io.py +60 -0
- imas/test/test_minimal_types.py +436 -0
- imas/test/test_minimal_types_io.py +70 -0
- imas/test/test_minimal_types_io_automatic.py +66 -0
- imas/test/test_nbc_change.py +408 -0
- imas/test/test_nc_autofill.py +13 -0
- imas/test/test_nc_entry.py +28 -0
- imas/test/test_nc_metadata.py +153 -0
- imas/test/test_nc_validation.py +197 -0
- imas/test/test_snippets.py +25 -0
- imas/test/test_static_ids.py +39 -0
- imas/test/test_str_1d.py +99 -0
- imas/test/test_time_slicing.py +182 -0
- imas/test/test_uda_support.py +12 -0
- imas/test/test_util.py +209 -0
- imas/training.py +45 -0
- imas/util.py +526 -0
- imas_python-0.1.dev1618.dist-info/LICENSE.txt +165 -0
- imas_python-0.1.dev1618.dist-info/METADATA +288 -0
- imas_python-0.1.dev1618.dist-info/RECORD +108 -0
- imas_python-0.1.dev1618.dist-info/WHEEL +5 -0
- imas_python-0.1.dev1618.dist-info/entry_points.txt +3 -0
- imas_python-0.1.dev1618.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from imas.ids_coordinates import IDSCoordinate
|
|
5
|
+
from imas.ids_defs import IDS_TIME_MODE_HETEROGENEOUS, IDS_TIME_MODE_HOMOGENEOUS
|
|
6
|
+
from imas.ids_factory import IDSFactory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_coordinate_cache():
|
|
10
|
+
coordinate = IDSCoordinate("1...N")
|
|
11
|
+
coordinate2 = IDSCoordinate("1...N")
|
|
12
|
+
assert coordinate is coordinate2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_coordinate_str_repr():
|
|
16
|
+
coordinate = IDSCoordinate("test_coordinate")
|
|
17
|
+
assert str(coordinate) == "test_coordinate"
|
|
18
|
+
assert repr(coordinate) == "IDSCoordinate('test_coordinate')"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_coordinate_index_unbounded():
|
|
22
|
+
coordinate = IDSCoordinate("1...N")
|
|
23
|
+
assert coordinate.size is None
|
|
24
|
+
assert not coordinate.references
|
|
25
|
+
assert not coordinate.has_alternatives
|
|
26
|
+
assert not coordinate.has_validation
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("size", range(1, 5))
|
|
30
|
+
def test_coordinate_index_bounded(size):
|
|
31
|
+
coordinate = IDSCoordinate(f"1...{size}")
|
|
32
|
+
assert coordinate.size == size
|
|
33
|
+
assert not coordinate.references
|
|
34
|
+
assert coordinate.has_validation
|
|
35
|
+
assert not coordinate.has_alternatives
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_coordinate_with_path():
|
|
39
|
+
coordinate = IDSCoordinate("time")
|
|
40
|
+
assert coordinate.size is None
|
|
41
|
+
assert len(coordinate.references) == 1
|
|
42
|
+
assert str(coordinate.references[0]) == "time"
|
|
43
|
+
assert coordinate.has_validation
|
|
44
|
+
assert not coordinate.has_alternatives
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_coordinate_with_multiple_paths():
|
|
48
|
+
coordinate = IDSCoordinate(
|
|
49
|
+
"distribution(i1)/profiles_2d(itime)/grid/r OR "
|
|
50
|
+
"distribution(i1)/profiles_2d(itime)/grid/rho_tor_norm"
|
|
51
|
+
)
|
|
52
|
+
assert coordinate.size is None
|
|
53
|
+
assert len(coordinate.references) == 2
|
|
54
|
+
assert coordinate.has_validation
|
|
55
|
+
assert coordinate.has_alternatives
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_coordinate_with_path_or_size():
|
|
59
|
+
coordinate = IDSCoordinate(
|
|
60
|
+
"coherent_wave(i1)/beam_tracing(itime)/beam(i2)/length OR 1...1"
|
|
61
|
+
)
|
|
62
|
+
assert coordinate.size == 1
|
|
63
|
+
assert len(coordinate.references) == 1
|
|
64
|
+
assert coordinate.has_validation
|
|
65
|
+
assert coordinate.has_alternatives
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.parametrize("spec", ["1...N_charge_states", "1..2"])
|
|
69
|
+
def test_coordinate_invalid(spec, caplog: pytest.LogCaptureFixture):
|
|
70
|
+
with caplog.at_level("DEBUG", "imas.ids_coordinates"):
|
|
71
|
+
caplog.clear()
|
|
72
|
+
IDSCoordinate._cache.pop(spec, None) # Remove spec from cache (if exists)
|
|
73
|
+
coordinate = IDSCoordinate(spec)
|
|
74
|
+
assert len(caplog.records) == 1
|
|
75
|
+
assert not coordinate.has_validation
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_coordinate_immutable():
|
|
79
|
+
coordinate = IDSCoordinate("1...N")
|
|
80
|
+
with pytest.raises(RuntimeError):
|
|
81
|
+
coordinate.has_validation = True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_format_refs():
|
|
85
|
+
core_profiles = IDSFactory(version="3.39.0").new("core_profiles")
|
|
86
|
+
core_profiles.profiles_1d.resize(2)
|
|
87
|
+
p1d = core_profiles.profiles_1d[1]
|
|
88
|
+
refs = p1d.magnetic_shear.metadata.coordinate1.format_refs(p1d)
|
|
89
|
+
assert refs == "`profiles_1d[1]/grid/rho_tor_norm`"
|
|
90
|
+
|
|
91
|
+
distributions = IDSFactory(version="3.39.0").new("distributions")
|
|
92
|
+
distributions.distribution.resize(2)
|
|
93
|
+
distributions.distribution[1].profiles_2d.resize(5)
|
|
94
|
+
p2d = distributions.distribution[1].profiles_2d[2]
|
|
95
|
+
refs1 = p2d.density.metadata.coordinate1.format_refs(p2d)
|
|
96
|
+
assert refs1 == (
|
|
97
|
+
"`distribution[1]/profiles_2d[2]/grid/r`, "
|
|
98
|
+
"`distribution[1]/profiles_2d[2]/grid/rho_tor_norm`"
|
|
99
|
+
)
|
|
100
|
+
refs2 = p2d.density.metadata.coordinate2.format_refs(p2d)
|
|
101
|
+
assert refs2 == (
|
|
102
|
+
"`distribution[1]/profiles_2d[2]/grid/z`, "
|
|
103
|
+
"`distribution[1]/profiles_2d[2]/grid/theta_geometric`, "
|
|
104
|
+
"`distribution[1]/profiles_2d[2]/grid/theta_straight`"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_coordinates(ids_minimal_types):
|
|
109
|
+
root = IDSFactory(xml_path=ids_minimal_types)
|
|
110
|
+
ids = root.new("minimal")
|
|
111
|
+
|
|
112
|
+
assert len(ids.flt_0d.coordinates) == 0
|
|
113
|
+
assert len(ids.flt_1d.coordinates) == 1
|
|
114
|
+
assert len(ids.flt_2d.coordinates) == 2
|
|
115
|
+
assert len(ids.flt_3d.coordinates) == 3
|
|
116
|
+
assert len(ids.flt_4d.coordinates) == 4
|
|
117
|
+
assert len(ids.flt_5d.coordinates) == 5
|
|
118
|
+
assert len(ids.flt_6d.coordinates) == 6
|
|
119
|
+
|
|
120
|
+
ids.flt_1d = [1, 2, 4]
|
|
121
|
+
assert ids.flt_1d.metadata.coordinates[0].size == 3
|
|
122
|
+
assert all(ids.flt_1d.coordinates[0] == np.arange(3))
|
|
123
|
+
|
|
124
|
+
ids.flt_3d = np.ones((3, 4, 2))
|
|
125
|
+
assert ids.flt_3d.coordinates[0] is ids.flt_1d
|
|
126
|
+
assert all(ids.flt_3d.coordinates[1] == np.arange(4))
|
|
127
|
+
assert all(ids.flt_3d.coordinates[2] == np.arange(2))
|
|
128
|
+
|
|
129
|
+
ids.cpx_1d = [1 - 1j]
|
|
130
|
+
assert ids.cpx_1d.coordinates[0] is ids.flt_1d
|
|
131
|
+
# if both flt_1d and int_1d are set, this should give an error
|
|
132
|
+
ids.int_1d = [1]
|
|
133
|
+
with pytest.raises(Exception):
|
|
134
|
+
ids.cpx_1d.coordinates[0]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_coordinates_with_core_profiles():
|
|
138
|
+
core_profiles = IDSFactory(version="3.39.0").new("core_profiles")
|
|
139
|
+
with pytest.raises(ValueError): # homogeneous_time not set
|
|
140
|
+
core_profiles.profiles_1d.coordinates[0]
|
|
141
|
+
|
|
142
|
+
core_profiles.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
143
|
+
assert core_profiles.profiles_1d.coordinates[0] is core_profiles.time
|
|
144
|
+
|
|
145
|
+
core_profiles.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
146
|
+
# Test logic for getting time coordinates from inside the core_profiles AoS
|
|
147
|
+
core_profiles.profiles_1d.resize(2)
|
|
148
|
+
core_profiles.profiles_1d[0].time = 1
|
|
149
|
+
core_profiles.profiles_1d[1].time = 2
|
|
150
|
+
assert np.array_equal(core_profiles.profiles_1d.coordinates[0], [1.0, 2.0])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_coordinates_with_equilibrium():
|
|
154
|
+
# Test error handling of a time-based coordinate outside our own timebasepath
|
|
155
|
+
# https://jira.iter.org/browse/IMAS-4675
|
|
156
|
+
equilibrium = IDSFactory(version="3.39.0").new("equilibrium")
|
|
157
|
+
equilibrium.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
158
|
+
equilibrium.grids_ggd.resize(1)
|
|
159
|
+
equilibrium.time_slice.resize(1)
|
|
160
|
+
with pytest.raises(RuntimeError):
|
|
161
|
+
equilibrium.time_slice[0].ggd.coordinates[0]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# TODO: test "<path> OR 1...1" coordinates: https://jira.iter.org/browse/IMAS-4661
|
|
165
|
+
# TODO: test complex amns_data coordinates: https://jira.iter.org/browse/IMAS-4666
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from imas.ids_data_type import IDSDataType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_legacy_type():
|
|
7
|
+
assert IDSDataType.parse("str_type") == (IDSDataType.STR, 0)
|
|
8
|
+
assert IDSDataType.parse("str_1d_type") == (IDSDataType.STR, 1)
|
|
9
|
+
assert IDSDataType.parse("flt_type") == (IDSDataType.FLT, 0)
|
|
10
|
+
assert IDSDataType.parse("flt_1d_type") == (IDSDataType.FLT, 1)
|
|
11
|
+
assert IDSDataType.parse("int_type") == (IDSDataType.INT, 0)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize("ndim", range(2)) # max string dimension is STR_1D
|
|
15
|
+
def test_str_types(ndim):
|
|
16
|
+
assert IDSDataType.parse(f"STR_{ndim}D") == (IDSDataType.STR, ndim)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("ndim", range(4)) # max integer dimension is INT_3D
|
|
20
|
+
def test_int_types(ndim):
|
|
21
|
+
assert IDSDataType.parse(f"INT_{ndim}D") == (IDSDataType.INT, ndim)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize("ndim", range(7)) # max floatt dimension is FLT_6D
|
|
25
|
+
def test_flt_types(ndim):
|
|
26
|
+
assert IDSDataType.parse(f"FLT_{ndim}D") == (IDSDataType.FLT, ndim)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("ndim", range(7)) # max complex dimension is CPX_6D
|
|
30
|
+
def test_cpx_types(ndim):
|
|
31
|
+
assert IDSDataType.parse(f"CPX_{ndim}D") == (IDSDataType.CPX, ndim)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_default_values():
|
|
35
|
+
assert IDSDataType.STR.default == ""
|
|
36
|
+
assert IDSDataType.INT.default == -999_999_999
|
|
37
|
+
assert IDSDataType.FLT.default == -9e40
|
|
38
|
+
assert IDSDataType.CPX.default == -9e40 - 9e40j
|
|
39
|
+
assert IDSDataType.STRUCT_ARRAY.default is None
|
|
40
|
+
assert IDSDataType.STRUCTURE.default is None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from imas.dd_zip import latest_dd_version
|
|
4
|
+
from imas.ids_factory import IDSFactory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_ids_factory_with_version():
|
|
8
|
+
IDSFactory(version="3.39.0")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_ids_factory_with_invalid_version():
|
|
12
|
+
# This raises a packaging.version.InvalidVersion exception, but any exception is ok
|
|
13
|
+
with pytest.raises(Exception):
|
|
14
|
+
IDSFactory(version="invalid")
|
|
15
|
+
# This is a valid version string, but we don't have it available
|
|
16
|
+
with pytest.raises(ValueError):
|
|
17
|
+
IDSFactory(version="0.1.2.3.4")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_ids_factory_with_xml_path(ids_minimal):
|
|
21
|
+
IDSFactory(xml_path=ids_minimal)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_ids_factory_latest(monkeypatch: pytest.MonkeyPatch):
|
|
25
|
+
monkeypatch.delenv("IMAS_VERSION", raising=False)
|
|
26
|
+
monkeypatch.delenv("IMAS_PREFIX", raising=False)
|
|
27
|
+
factory = IDSFactory()
|
|
28
|
+
assert factory._version == latest_dd_version()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_ids_factory_from_env(monkeypatch: pytest.MonkeyPatch):
|
|
32
|
+
version = "3.35.0"
|
|
33
|
+
monkeypatch.setenv("IMAS_VERSION", version)
|
|
34
|
+
factory = IDSFactory()
|
|
35
|
+
assert factory._version == version
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from imas.ids_factory import IDSFactory
|
|
6
|
+
from imas.ids_metadata import IDSType, get_toplevel_metadata
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_metadata_cache(fake_structure_xml):
|
|
10
|
+
meta = get_toplevel_metadata(fake_structure_xml)
|
|
11
|
+
meta2 = get_toplevel_metadata(fake_structure_xml)
|
|
12
|
+
assert meta is meta2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_metadata_init_structure_xml(fake_structure_xml):
|
|
16
|
+
meta = get_toplevel_metadata(fake_structure_xml)
|
|
17
|
+
assert fake_structure_xml.attrib["name"] == "gyrokinetics"
|
|
18
|
+
assert meta.name == "gyrokinetics"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_metadata_deepcopy(fake_structure_xml):
|
|
22
|
+
meta = get_toplevel_metadata(fake_structure_xml)
|
|
23
|
+
meta2 = deepcopy(meta)
|
|
24
|
+
|
|
25
|
+
# Test that deepcopy returns the same reference
|
|
26
|
+
assert meta is meta2
|
|
27
|
+
assert meta == meta2
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_metadata_immutable(fake_structure_xml):
|
|
31
|
+
meta = get_toplevel_metadata(fake_structure_xml)
|
|
32
|
+
with pytest.raises(RuntimeError):
|
|
33
|
+
meta.immutable = True
|
|
34
|
+
with pytest.raises(RuntimeError):
|
|
35
|
+
del meta.name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_ids_type():
|
|
39
|
+
assert not IDSType.NONE.is_dynamic
|
|
40
|
+
assert not IDSType.CONSTANT.is_dynamic
|
|
41
|
+
assert not IDSType.STATIC.is_dynamic
|
|
42
|
+
assert IDSType.DYNAMIC.is_dynamic
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_metadata_indexing():
|
|
46
|
+
core_profiles = IDSFactory("3.39.0").core_profiles()
|
|
47
|
+
metadata = core_profiles.metadata
|
|
48
|
+
assert metadata["ids_properties"] is core_profiles.ids_properties.metadata
|
|
49
|
+
assert (
|
|
50
|
+
metadata["ids_properties/version_put"]
|
|
51
|
+
is core_profiles.ids_properties.version_put.metadata
|
|
52
|
+
)
|
|
53
|
+
assert metadata["time"] is core_profiles.time.metadata
|
|
54
|
+
p1d_time_meta = metadata["profiles_1d/time"]
|
|
55
|
+
core_profiles.profiles_1d.resize(1)
|
|
56
|
+
assert p1d_time_meta is core_profiles.profiles_1d[0].time.metadata
|
|
57
|
+
|
|
58
|
+
# Test period (.) as separator:
|
|
59
|
+
assert (
|
|
60
|
+
metadata["profiles_1d/electrons/temperature"]
|
|
61
|
+
is metadata["profiles_1d.electrons.temperature"]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Test invalid path
|
|
65
|
+
with pytest.raises(KeyError):
|
|
66
|
+
metadata["DoesNotExist"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_toplevel(fake_filled_toplevel):
|
|
6
|
+
top = fake_filled_toplevel
|
|
7
|
+
assert top.wavevector._toplevel == top
|
|
8
|
+
assert top.wavevector[0].radial_component_norm._toplevel == top
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_path(fake_filled_toplevel):
|
|
12
|
+
top = fake_filled_toplevel
|
|
13
|
+
assert top.wavevector._path == "wavevector"
|
|
14
|
+
assert top.ids_properties.creation_date._path == "ids_properties/creation_date"
|
|
15
|
+
assert top.wavevector._path == "wavevector"
|
|
16
|
+
assert top.wavevector[0]._path == "wavevector[0]"
|
|
17
|
+
assert (
|
|
18
|
+
top.wavevector[0].radial_component_norm._path
|
|
19
|
+
== "wavevector[0]/radial_component_norm"
|
|
20
|
+
)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from imas.ids_factory import IDSFactory
|
|
4
|
+
from imas.ids_path import IDSPath
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_path_cache():
|
|
8
|
+
path = IDSPath("uniq")
|
|
9
|
+
path2 = IDSPath("uniq")
|
|
10
|
+
assert path is path2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_path_str_repr():
|
|
14
|
+
path = IDSPath("test/path")
|
|
15
|
+
assert str(path) == "test/path"
|
|
16
|
+
assert repr(path) == "IDSPath('test/path')"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_empty_path():
|
|
20
|
+
path = IDSPath("")
|
|
21
|
+
assert path.parts == ()
|
|
22
|
+
assert path.indices == ()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_path_without_slashes_and_indices():
|
|
26
|
+
path = IDSPath("ids_properties")
|
|
27
|
+
assert path.parts == ("ids_properties",)
|
|
28
|
+
assert path.indices == (None,)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_path_without_slashes():
|
|
32
|
+
path = IDSPath("profiles_1d(itime)")
|
|
33
|
+
assert path.parts == ("profiles_1d",)
|
|
34
|
+
assert path.indices == ("itime",)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_path_without_indices():
|
|
38
|
+
path = IDSPath("ids_properties/version_put/data_dictionary")
|
|
39
|
+
assert path.parts == ("ids_properties", "version_put", "data_dictionary")
|
|
40
|
+
assert path.indices == (None, None, None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_path_with_dummy_indices():
|
|
44
|
+
path = IDSPath(
|
|
45
|
+
"time_slice(itime)/ggd(i1)/grid/space(i2)/objects_per_dimension(i3)/"
|
|
46
|
+
"object(i4)/boundary(i5)"
|
|
47
|
+
)
|
|
48
|
+
assert path.parts == (
|
|
49
|
+
"time_slice",
|
|
50
|
+
"ggd",
|
|
51
|
+
"grid",
|
|
52
|
+
"space",
|
|
53
|
+
"objects_per_dimension",
|
|
54
|
+
"object",
|
|
55
|
+
"boundary",
|
|
56
|
+
)
|
|
57
|
+
assert path.indices == ("itime", "i1", None, "i2", "i3", "i4", "i5")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_path_with_path_index():
|
|
61
|
+
path = IDSPath("coordinate_system(process(i1)/coordinate_index)/coordinate(1)")
|
|
62
|
+
assert path.parts == ("coordinate_system", "coordinate")
|
|
63
|
+
assert isinstance(path.indices[0], IDSPath)
|
|
64
|
+
assert path.indices[0].parts == ("process", "coordinate_index")
|
|
65
|
+
assert path.indices[0].indices == ("i1", None)
|
|
66
|
+
assert path.indices[1] == 0
|
|
67
|
+
assert len(path.indices) == 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize(
|
|
71
|
+
"pth",
|
|
72
|
+
["distribution(1:3)/process(:)/nbi_unit", "distribution[0:3]/process[:]/nbi_unit"],
|
|
73
|
+
)
|
|
74
|
+
def test_path_with_slices(pth):
|
|
75
|
+
path = IDSPath(pth)
|
|
76
|
+
assert path.parts == ("distribution", "process", "nbi_unit")
|
|
77
|
+
assert path.indices == (slice(0, 3), slice(None), None)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_path_integer_indices():
|
|
81
|
+
py_path = IDSPath("a[1]/b[2]/c[:2]/d[1:]")
|
|
82
|
+
f_path = IDSPath("a(2)/b(3)/c(:2)/d(2:)")
|
|
83
|
+
assert py_path.indices[0] == f_path.indices[0] == 1
|
|
84
|
+
assert py_path.indices[1] == f_path.indices[1] == 2
|
|
85
|
+
assert py_path.indices[2] == f_path.indices[2] == slice(None, 2)
|
|
86
|
+
assert py_path.indices[3] == f_path.indices[3] == slice(1, None)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_path_immutable():
|
|
90
|
+
path = IDSPath("")
|
|
91
|
+
with pytest.raises(RuntimeError):
|
|
92
|
+
path.some_attr = True
|
|
93
|
+
with pytest.raises(RuntimeError):
|
|
94
|
+
path.parts = (1, 2)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_path_time():
|
|
98
|
+
assert not IDSPath("no_time").is_time_path
|
|
99
|
+
assert IDSPath("time").is_time_path
|
|
100
|
+
assert IDSPath("profiles_1d(itime)/time").is_time_path
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.parametrize(
|
|
104
|
+
"path",
|
|
105
|
+
[
|
|
106
|
+
"empty//part",
|
|
107
|
+
"(empty_part)",
|
|
108
|
+
"_invalid_node_name",
|
|
109
|
+
"another__invalid_node_name",
|
|
110
|
+
"CAPS_IS_ALSO_NOT_ALLOWED",
|
|
111
|
+
"nor are spaces",
|
|
112
|
+
"or.periods",
|
|
113
|
+
"or_commas,",
|
|
114
|
+
"unmatched(parentheses()",
|
|
115
|
+
"unmatched)(parentheses",
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
def test_invalid_paths(path):
|
|
119
|
+
with pytest.raises(ValueError):
|
|
120
|
+
IDSPath(path)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_path_goto(fake_toplevel_xml):
|
|
124
|
+
ids = IDSFactory(xml_path=fake_toplevel_xml).new("gyrokinetics")
|
|
125
|
+
|
|
126
|
+
version_put_path = IDSPath("ids_properties/version_put")
|
|
127
|
+
assert version_put_path.goto(ids) is ids.ids_properties.version_put
|
|
128
|
+
|
|
129
|
+
ids.wavevector.resize(2)
|
|
130
|
+
ids.wavevector[0].eigenmode.resize(2)
|
|
131
|
+
|
|
132
|
+
ids.wavevector[0].eigenmode[0].growth_rate_norm = 1.23
|
|
133
|
+
growth_rate_norm_path = IDSPath("wavevector(i1)/eigenmode(i2)/growth_rate_norm")
|
|
134
|
+
# We cannot address this path from the root (because of the dummy 'i1' and 'i2'):
|
|
135
|
+
with pytest.raises(ValueError):
|
|
136
|
+
growth_rate_norm_path.goto(ids)
|
|
137
|
+
# But we can when there is a common parent
|
|
138
|
+
assert (
|
|
139
|
+
growth_rate_norm_path.goto(ids.wavevector[0].eigenmode[0].frequency_norm)
|
|
140
|
+
is ids.wavevector[0].eigenmode[0].growth_rate_norm
|
|
141
|
+
)
|
|
142
|
+
# One common parent (of the two) is not enough
|
|
143
|
+
with pytest.raises(ValueError):
|
|
144
|
+
growth_rate_norm_path.goto(ids.wavevector[0])
|
|
145
|
+
|
|
146
|
+
# With explicit (1-based) indices we can resolve from ids
|
|
147
|
+
growth_rate_norm_path2 = IDSPath("wavevector(1)/eigenmode(1)/growth_rate_norm")
|
|
148
|
+
assert (
|
|
149
|
+
growth_rate_norm_path2.goto(ids)
|
|
150
|
+
is ids.wavevector[0].eigenmode[0].growth_rate_norm
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
radial_component_norm_path = IDSPath("wavevector(i1)/radial_component_norm")
|
|
154
|
+
assert (
|
|
155
|
+
radial_component_norm_path.goto(ids.wavevector[0].eigenmode[0].frequency_norm)
|
|
156
|
+
is ids.wavevector[0].radial_component_norm
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Use homogeneous_time (the only INT_0D in this fake IDS) as an indirect index
|
|
160
|
+
ids.ids_properties.homogeneous_time = 1
|
|
161
|
+
indirect_path = IDSPath("wavevector(ids_properties/homogeneous_time)")
|
|
162
|
+
assert indirect_path.goto(ids) is ids.wavevector[0]
|
|
163
|
+
ids.ids_properties.homogeneous_time = 2
|
|
164
|
+
assert indirect_path.goto(ids) is ids.wavevector[1]
|
|
165
|
+
|
|
166
|
+
# But it's not allowed to use a non INT_0D as indirect index
|
|
167
|
+
with pytest.raises(ValueError):
|
|
168
|
+
IDSPath("wavevector(ids_properties/comment)").goto(ids)
|
|
169
|
+
|
|
170
|
+
# Test that we can goto the same path
|
|
171
|
+
assert IDSPath("wavevector").goto(ids.wavevector[1]) is ids.wavevector[1]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_path_goto_metadata():
|
|
175
|
+
es = IDSFactory(version="3.42.0").new("edge_sources")
|
|
176
|
+
path = IDSPath("source/ggd/ion/energy")
|
|
177
|
+
energy_metadata = path.goto_metadata(es.metadata)
|
|
178
|
+
|
|
179
|
+
es.source.resize(1)
|
|
180
|
+
es.source[0].ggd.resize(1)
|
|
181
|
+
es.source[0].ggd[0].ion.resize(1)
|
|
182
|
+
energy = es.source[0].ggd[0].ion[0].energy
|
|
183
|
+
assert energy_metadata == energy.metadata
|
|
184
|
+
|
|
185
|
+
# Test when path does not exist in metadata
|
|
186
|
+
wrong_path = IDSPath("ggd/ion/energy")
|
|
187
|
+
with pytest.raises(ValueError):
|
|
188
|
+
wrong_path.goto_metadata(es.metadata)
|
|
189
|
+
|
|
190
|
+
sub_path = IDSPath("ggd/ion/energy")
|
|
191
|
+
energy_metadata = sub_path.goto_metadata(es.source.metadata)
|
|
192
|
+
assert energy_metadata == energy.metadata
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
import pprint
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from imas.ids_primitive import IDSPrimitive
|
|
8
|
+
from imas.util import visit_children
|
|
9
|
+
|
|
10
|
+
# As the IDSPrimitive class generally should not be used on its own. Instead we
|
|
11
|
+
# take a very well defined toplevel, initialize it, and do our tests on the
|
|
12
|
+
# tree structure that is generated. Keep the tests just to the functionality
|
|
13
|
+
# that is defined in ids_primitive.py though!
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
zero_to_two_pi = np.linspace(0, 2, num=10) * np.pi
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_pretty_print(fake_filled_toplevel):
|
|
20
|
+
eig = fake_filled_toplevel.wavevector[0].eigenmode[0]
|
|
21
|
+
assert pprint.pformat(fake_filled_toplevel).startswith("<IDSToplevel")
|
|
22
|
+
assert pprint.pformat(fake_filled_toplevel.wavevector[0].eigenmode).startswith(
|
|
23
|
+
"<IDSStructArray ("
|
|
24
|
+
)
|
|
25
|
+
assert pprint.pformat(fake_filled_toplevel.wavevector[0].eigenmode[0]).startswith(
|
|
26
|
+
"<IDSStructure ("
|
|
27
|
+
)
|
|
28
|
+
assert pprint.pformat(eig.time_norm).startswith("<IDSNumericArray")
|
|
29
|
+
assert pprint.pformat(eig.time_norm).endswith("empty FLT_1D)>")
|
|
30
|
+
assert pprint.pformat(eig.frequency_norm).startswith("<IDSFloat0D")
|
|
31
|
+
assert pprint.pformat(eig.frequency_norm).endswith("\nfloat(10.0)")
|
|
32
|
+
fake_filled_toplevel.ids_properties.comment = "A filled comment"
|
|
33
|
+
assert (
|
|
34
|
+
pprint.pformat(fake_filled_toplevel.ids_properties.comment)
|
|
35
|
+
== """<IDSString0D (IDS:gyrokinetics, ids_properties/comment, STR_0D)>
|
|
36
|
+
str('A filled comment')"""
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_value_attribute(fake_filled_toplevel):
|
|
41
|
+
"""Test if the value attribute acts as imas-python expects"""
|
|
42
|
+
eig = fake_filled_toplevel.wavevector[0].eigenmode[0]
|
|
43
|
+
assert isinstance(eig.frequency_norm, IDSPrimitive)
|
|
44
|
+
assert hasattr(eig.frequency_norm, "value")
|
|
45
|
+
|
|
46
|
+
# We should have a Python Primitive now:
|
|
47
|
+
assert eig.frequency_norm.data_type == "FLT_0D"
|
|
48
|
+
assert isinstance(eig.frequency_norm.value, float)
|
|
49
|
+
assert eig.frequency_norm.value == 10
|
|
50
|
+
|
|
51
|
+
# For arrays, we should get numpy arrays of the right type
|
|
52
|
+
# This one should be not-filled, e.g. default
|
|
53
|
+
assert not eig.phi_potential_perturbed_norm.has_value
|
|
54
|
+
assert eig.phi_potential_perturbed_norm.data_type == "CPX_2D"
|
|
55
|
+
assert isinstance(eig.phi_potential_perturbed_norm.value, np.ndarray)
|
|
56
|
+
assert np.array_equal(eig.phi_potential_perturbed_norm.value, np.ndarray((0, 0)))
|
|
57
|
+
|
|
58
|
+
# Finally, check a filled array
|
|
59
|
+
assert eig.poloidal_angle.has_value
|
|
60
|
+
assert eig.poloidal_angle.data_type == "FLT_1D"
|
|
61
|
+
assert isinstance(eig.poloidal_angle.value, np.ndarray)
|
|
62
|
+
assert np.array_equal(eig.poloidal_angle.value, zero_to_two_pi)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_visit_children(fake_filled_toplevel):
|
|
66
|
+
# This should visit leaf nodes only. Lets test that, but check only
|
|
67
|
+
# filled fields explicitly
|
|
68
|
+
nodes = []
|
|
69
|
+
|
|
70
|
+
def append_if_has_value(xx):
|
|
71
|
+
if xx.has_value:
|
|
72
|
+
nodes.append(xx)
|
|
73
|
+
|
|
74
|
+
visit_children(append_if_has_value, fake_filled_toplevel)
|
|
75
|
+
assert len(nodes) == 3
|
|
76
|
+
assert nodes[0] == 2
|
|
77
|
+
assert nodes[1] == 10
|
|
78
|
+
assert np.array_equal(nodes[2], zero_to_two_pi)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_visit_children_internal_nodes(fake_filled_toplevel):
|
|
82
|
+
nodes = []
|
|
83
|
+
|
|
84
|
+
def append_if_has_value(xx):
|
|
85
|
+
if xx.has_value:
|
|
86
|
+
nodes.append(xx)
|
|
87
|
+
|
|
88
|
+
visit_children(append_if_has_value, fake_filled_toplevel, leaf_only=False)
|
|
89
|
+
# We now visit the internal nodes too.
|
|
90
|
+
|
|
91
|
+
# We know we filled only endpoints frequency_norm and poloidal_angle
|
|
92
|
+
# We expect the following "mandatory" fields to be touched, which we check
|
|
93
|
+
# in the order visit_children visits
|
|
94
|
+
assert len(nodes) == 9
|
|
95
|
+
assert nodes[0] is fake_filled_toplevel
|
|
96
|
+
assert nodes[1] is fake_filled_toplevel.ids_properties
|
|
97
|
+
assert nodes[2] == 2
|
|
98
|
+
assert nodes[3] is fake_filled_toplevel.wavevector
|
|
99
|
+
assert nodes[4] is fake_filled_toplevel.wavevector[0]
|
|
100
|
+
assert nodes[5] is fake_filled_toplevel.wavevector[0].eigenmode
|
|
101
|
+
assert nodes[6] is fake_filled_toplevel.wavevector[0].eigenmode[0]
|
|
102
|
+
assert nodes[7] == 10
|
|
103
|
+
assert np.array_equal(nodes[8], zero_to_two_pi)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_assign_nan(fake_filled_toplevel, caplog):
|
|
107
|
+
with caplog.at_level("DEBUG"):
|
|
108
|
+
fake_filled_toplevel.wavevector[0].radial_component_norm = float("nan")
|
|
109
|
+
assert len(caplog.records) == 0
|