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
@@ -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