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
imas/test/test_ids2nc.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import netCDF4
|
|
2
|
+
import numpy
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from imas.backends.netcdf.ids2nc import IDS2NC, default_fillvals
|
|
6
|
+
from imas.ids_data_type import IDSDataType
|
|
7
|
+
from imas.ids_defs import IDS_TIME_MODE_HOMOGENEOUS
|
|
8
|
+
from imas.ids_factory import IDSFactory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def group(tmp_path):
|
|
13
|
+
with netCDF4.Dataset(tmp_path / "test.nc", "w") as group:
|
|
14
|
+
yield group
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_tensorization(group):
|
|
18
|
+
ids = IDSFactory("3.39.0").core_profiles()
|
|
19
|
+
|
|
20
|
+
ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
21
|
+
ids.time = [1.0, 2.0, 3.0]
|
|
22
|
+
ids.profiles_1d.resize(3)
|
|
23
|
+
for p1d in ids.profiles_1d:
|
|
24
|
+
p1d.ion.resize(2)
|
|
25
|
+
p1d.ion[0].label = "D"
|
|
26
|
+
p1d.ion[0].z_ion = 1.0
|
|
27
|
+
p1d.ion[0].element.resize(1)
|
|
28
|
+
p1d.ion[0].element[0].a = 2.0
|
|
29
|
+
p1d.ion[0].element[0].z_n = 1.0
|
|
30
|
+
p1d.ion[0].element[0].atoms_n = 1
|
|
31
|
+
|
|
32
|
+
p1d.ion[1].label = "OH-"
|
|
33
|
+
p1d.ion[1].z_ion = -1.0
|
|
34
|
+
p1d.ion[1].element.resize(2)
|
|
35
|
+
p1d.ion[1].element[0].a = 1.0
|
|
36
|
+
p1d.ion[1].element[0].z_n = 1.0
|
|
37
|
+
p1d.ion[1].element[0].atoms_n = 1
|
|
38
|
+
p1d.ion[1].element[1].a = 16.0
|
|
39
|
+
p1d.ion[1].element[1].z_n = 8.0
|
|
40
|
+
p1d.ion[1].element[1].atoms_n = 1
|
|
41
|
+
|
|
42
|
+
IDS2NC(ids, group).run()
|
|
43
|
+
# Test tensorized values
|
|
44
|
+
expected = [["D", "OH-"]] * 3
|
|
45
|
+
assert numpy.array_equal(group["profiles_1d.ion.label"], expected)
|
|
46
|
+
assert not hasattr(group["profiles_1d.ion.label"], "sparse")
|
|
47
|
+
|
|
48
|
+
expected = [[1.0, -1.0]] * 3
|
|
49
|
+
assert numpy.array_equal(group["profiles_1d.ion.z_ion"], expected)
|
|
50
|
+
assert not hasattr(group["profiles_1d.ion.z_ion"], "sparse")
|
|
51
|
+
|
|
52
|
+
expected = [[[2.0, netCDF4.default_fillvals["f8"]], [1.0, 16.0]]] * 3
|
|
53
|
+
assert numpy.array_equal(group["profiles_1d.ion.element.a"], expected)
|
|
54
|
+
assert hasattr(group["profiles_1d.ion.element.a"], "sparse")
|
|
55
|
+
|
|
56
|
+
expected = [[[1.0, netCDF4.default_fillvals["f8"]], [1.0, 8.0]]] * 3
|
|
57
|
+
assert numpy.array_equal(group["profiles_1d.ion.element.z_n"], expected)
|
|
58
|
+
assert hasattr(group["profiles_1d.ion.element.z_n"], "sparse")
|
|
59
|
+
|
|
60
|
+
expected = [[[1, netCDF4.default_fillvals["i4"]], [1, 1]]] * 3
|
|
61
|
+
assert numpy.array_equal(group["profiles_1d.ion.element.atoms_n"], expected)
|
|
62
|
+
assert hasattr(group["profiles_1d.ion.element.atoms_n"], "sparse")
|
|
63
|
+
|
|
64
|
+
# Test :shape arrays
|
|
65
|
+
assert "profiles_1d:shape" not in group.variables
|
|
66
|
+
assert not hasattr(group["profiles_1d"], "sparse")
|
|
67
|
+
assert "profiles_1d.ion:shape" not in group.variables
|
|
68
|
+
assert not hasattr(group["profiles_1d.ion"], "sparse")
|
|
69
|
+
assert "profiles_1d.ion.element:shape" in group.variables
|
|
70
|
+
# The shape array should be mentioned in the sparse attribute:
|
|
71
|
+
assert "profiles_1d.ion.element:shape" in group["profiles_1d.ion.element"].sparse
|
|
72
|
+
expected = [[[1], [2]]] * 3
|
|
73
|
+
assert numpy.array_equal(group["profiles_1d.ion.element:shape"], expected)
|
|
74
|
+
assert group["profiles_1d.ion.element:shape"].documentation != ""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_metadata(group):
|
|
78
|
+
ids = IDSFactory("3.39.0").core_profiles()
|
|
79
|
+
|
|
80
|
+
ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
81
|
+
ids.ids_properties.comment = "Test string variable"
|
|
82
|
+
ids.time = [1.0, 2.0, 3.0]
|
|
83
|
+
ids.profiles_1d.resize(3)
|
|
84
|
+
for p1d in ids.profiles_1d:
|
|
85
|
+
p1d.grid.rho_tor_norm = [0.0, 0.5, 1.0]
|
|
86
|
+
p1d.j_tor = [1e3, 1e4, 1e5]
|
|
87
|
+
|
|
88
|
+
IDS2NC(ids, group).run()
|
|
89
|
+
|
|
90
|
+
for var in [
|
|
91
|
+
"ids_properties",
|
|
92
|
+
"ids_properties.homogeneous_time",
|
|
93
|
+
"ids_properties.comment",
|
|
94
|
+
"time",
|
|
95
|
+
"profiles_1d",
|
|
96
|
+
"profiles_1d.grid.rho_tor_norm",
|
|
97
|
+
"profiles_1d.j_tor",
|
|
98
|
+
]:
|
|
99
|
+
assert group[var].documentation == ids.metadata[var].documentation
|
|
100
|
+
units = ids.metadata[var].units
|
|
101
|
+
if units:
|
|
102
|
+
assert group[var].units == units
|
|
103
|
+
fillvalue = default_fillvals.get(ids.metadata[var].data_type)
|
|
104
|
+
if fillvalue is None:
|
|
105
|
+
assert ids.metadata[var].data_type in [
|
|
106
|
+
IDSDataType.STRUCTURE,
|
|
107
|
+
IDSDataType.STRUCT_ARRAY,
|
|
108
|
+
]
|
|
109
|
+
else:
|
|
110
|
+
assert group[var]._FillValue == fillvalue
|
|
111
|
+
|
|
112
|
+
assert (
|
|
113
|
+
group["profiles_1d.j_tor"].coordinates == "time profiles_1d.grid.rho_tor_norm"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_filter_coordinates(group):
|
|
118
|
+
ids = IDSFactory("3.39.0").pf_active()
|
|
119
|
+
|
|
120
|
+
ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
121
|
+
ids.time = [1.0]
|
|
122
|
+
ids.coil.resize(2)
|
|
123
|
+
ids.coil[0].resistance = 1.0
|
|
124
|
+
ids.coil[1].resistance = 2.0
|
|
125
|
+
|
|
126
|
+
# Using sub-groups: IDS2NC expects an empty group to store the IDS in
|
|
127
|
+
IDS2NC(ids, group.createGroup("1")).run()
|
|
128
|
+
# coil.name or coil.identifier could be a coordinate, but they're not filled
|
|
129
|
+
assert not hasattr(group["1/coil.resistance"], "coordinates")
|
|
130
|
+
|
|
131
|
+
ids.coil[0].name = "coil 0"
|
|
132
|
+
ids.coil[1].name = "coil 1"
|
|
133
|
+
IDS2NC(ids, group.createGroup("2")).run()
|
|
134
|
+
assert group["2/coil.resistance"].coordinates == "coil.name"
|
|
135
|
+
|
|
136
|
+
ids.coil[0].identifier = "aab7b42a-5646-4f0a-8173-03dd5e4ad386"
|
|
137
|
+
ids.coil[1].identifier = "aab7b42a-5646-4f0a-8173-283a57b3a801"
|
|
138
|
+
IDS2NC(ids, group.createGroup("3")).run()
|
|
139
|
+
assert group["3/coil.resistance"].coordinates == "coil.name coil.identifier"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_ancillary_variables(group):
|
|
143
|
+
ids = IDSFactory("3.39.0").core_profiles()
|
|
144
|
+
|
|
145
|
+
ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
146
|
+
ids.time = [1.0]
|
|
147
|
+
ids.profiles_1d.resize(1)
|
|
148
|
+
|
|
149
|
+
ids.profiles_1d[0].grid.rho_tor_norm = [0.0, 0.5, 1.0]
|
|
150
|
+
ids.profiles_1d[0].j_tor = [1e2, 1e3, 1e4]
|
|
151
|
+
ids.profiles_1d[0].j_tor_error_upper = [1e-1, 1e0, 1e1]
|
|
152
|
+
ids.profiles_1d[0].j_total = [1e2, 1e3, 1e4]
|
|
153
|
+
ids.profiles_1d[0].j_total_error_upper = [2e-1, 2e0, 2e1]
|
|
154
|
+
ids.profiles_1d[0].j_total_error_lower = [1e-1, 1e0, 1e1]
|
|
155
|
+
|
|
156
|
+
IDS2NC(ids, group).run()
|
|
157
|
+
|
|
158
|
+
assert not hasattr(group["profiles_1d.grid.rho_tor_norm"], "ancillary_variables")
|
|
159
|
+
assert (
|
|
160
|
+
group["profiles_1d.j_tor"].ancillary_variables
|
|
161
|
+
== "profiles_1d.j_tor_error_upper"
|
|
162
|
+
)
|
|
163
|
+
assert (
|
|
164
|
+
group["profiles_1d.j_total"].ancillary_variables
|
|
165
|
+
== "profiles_1d.j_total_error_upper profiles_1d.j_total_error_lower"
|
|
166
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# This file is part of imas-python.
|
|
2
|
+
# You should have received the imas-python LICENSE file with this project.
|
|
3
|
+
from pathlib import PosixPath
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from importlib.resources import files
|
|
9
|
+
except ImportError: # Python 3.8 support
|
|
10
|
+
from importlib_resources import files
|
|
11
|
+
|
|
12
|
+
import imas.training
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_data_exists():
|
|
16
|
+
data_file: PosixPath = files(imas) / "assets/ITER_134173_106_equilibrium.ids"
|
|
17
|
+
assert data_file.exists()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def test_data(requires_imas):
|
|
22
|
+
db_entry = imas.training.get_training_db_entry()
|
|
23
|
+
yield db_entry
|
|
24
|
+
db_entry.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_data_is_sane(test_data):
|
|
28
|
+
assert isinstance(test_data, imas.DBEntry)
|
|
29
|
+
eq = test_data.get("equilibrium")
|
|
30
|
+
assert len(eq.time_slice) == 3
|
|
31
|
+
ts = eq.time_slice[0]
|
|
32
|
+
r = ts.boundary.outline.r
|
|
33
|
+
z = ts.boundary.outline.z
|
|
34
|
+
|
|
35
|
+
# Test a few relevant numbers to check if data loading went okay
|
|
36
|
+
assert r.value[0] == pytest.approx(7.2896011)
|
|
37
|
+
assert r.value[-1] == pytest.approx(7.29120937)
|
|
38
|
+
assert z.value[0] == pytest.approx(-1.00816660e-01)
|
|
39
|
+
assert z.value[-1] == pytest.approx(-9.60027185e-14)
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Unit tests for ids_convert.py.
|
|
2
|
+
# See also integration tests for conversions in test_nbc_change.py
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from unittest.mock import MagicMock
|
|
8
|
+
|
|
9
|
+
import numpy
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from imas import identifiers
|
|
13
|
+
from imas.ids_convert import (
|
|
14
|
+
_get_ctxpath,
|
|
15
|
+
_get_tbp,
|
|
16
|
+
convert_ids,
|
|
17
|
+
dd_version_map_from_factories,
|
|
18
|
+
iter_parents,
|
|
19
|
+
)
|
|
20
|
+
from imas.ids_defs import (
|
|
21
|
+
ASCII_BACKEND,
|
|
22
|
+
IDS_TIME_MODE_HETEROGENEOUS,
|
|
23
|
+
IDS_TIME_MODE_HOMOGENEOUS,
|
|
24
|
+
IDS_TIME_MODE_INDEPENDENT,
|
|
25
|
+
MEMORY_BACKEND,
|
|
26
|
+
)
|
|
27
|
+
from imas.ids_factory import IDSFactory
|
|
28
|
+
from imas.ids_struct_array import IDSStructArray
|
|
29
|
+
from imas.ids_structure import IDSStructure
|
|
30
|
+
from imas.test.test_helpers import compare_children, open_dbentry
|
|
31
|
+
|
|
32
|
+
UTC = timezone.utc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_iter_parents():
|
|
36
|
+
assert list(iter_parents("a/b/c/d/e")) == ["a", "a/b", "a/b/c", "a/b/c/d"]
|
|
37
|
+
assert list(iter_parents("abc/def/g")) == ["abc", "abc/def"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_dd_version_map_from_factories_invalid_version():
|
|
41
|
+
factory1 = IDSFactory(version="3.39.0")
|
|
42
|
+
factory2 = MagicMock()
|
|
43
|
+
factory2._version = "3.30.0-123-12345678"
|
|
44
|
+
factory2._etree = factory1._etree
|
|
45
|
+
|
|
46
|
+
version_map, factory1_is_oldest = dd_version_map_from_factories(
|
|
47
|
+
"core_profiles", factory1, factory2
|
|
48
|
+
)
|
|
49
|
+
assert not factory1_is_oldest
|
|
50
|
+
# maps should be empty, since we set the same etree on factory2
|
|
51
|
+
assert not version_map.new_to_old.path
|
|
52
|
+
assert not version_map.old_to_new.path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture()
|
|
56
|
+
def factory():
|
|
57
|
+
return IDSFactory(version="3.38.0")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture()
|
|
61
|
+
def core_profiles_paths(factory):
|
|
62
|
+
etree = factory._etree
|
|
63
|
+
cp = etree.find("IDS[@name='core_profiles']")
|
|
64
|
+
return {field.get("path", ""): field for field in cp.iterfind(".//field")}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_aos_and_ctxpath(core_profiles_paths):
|
|
68
|
+
paths = core_profiles_paths
|
|
69
|
+
f = _get_ctxpath
|
|
70
|
+
assert f("time", paths) == "time"
|
|
71
|
+
assert f("profiles_1d", paths) == "profiles_1d"
|
|
72
|
+
assert f("profiles_1d/time", paths) == "time"
|
|
73
|
+
assert f("profiles_1d/grid/rho_tor_norm", paths) == "grid/rho_tor_norm"
|
|
74
|
+
assert f("profiles_1d/ion", paths) == "ion"
|
|
75
|
+
assert f("profiles_1d/ion/element", paths) == "element"
|
|
76
|
+
assert f("profiles_1d/ion/element/z_n", paths) == "z_n"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_timebasepath(core_profiles_paths):
|
|
80
|
+
paths = core_profiles_paths
|
|
81
|
+
f = _get_tbp
|
|
82
|
+
assert f(paths["time"], paths) == "time"
|
|
83
|
+
assert f(paths["profiles_1d"], paths) == "profiles_1d/time"
|
|
84
|
+
assert f(paths["profiles_1d/grid"], paths) == ""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_compare_timebasepath_functions(ids_name):
|
|
88
|
+
# Ensure that the two timebasepath implementations are consistent
|
|
89
|
+
ids = IDSFactory().new(ids_name)
|
|
90
|
+
ids_element = ids.metadata._structure_xml
|
|
91
|
+
paths = {field.get("path", ""): field for field in ids_element.iterfind(".//field")}
|
|
92
|
+
|
|
93
|
+
def recurse(structure: IDSStructure, ctx_path: str):
|
|
94
|
+
for item in structure:
|
|
95
|
+
name = item.metadata.name
|
|
96
|
+
new_path = f"{ctx_path}/{name}" if ctx_path else name
|
|
97
|
+
|
|
98
|
+
tbp1 = _get_tbp(item.metadata._structure_xml, paths)
|
|
99
|
+
tbp2 = item.metadata.timebasepath
|
|
100
|
+
assert tbp1 == tbp2
|
|
101
|
+
|
|
102
|
+
if isinstance(item, IDSStructure):
|
|
103
|
+
recurse(item, new_path)
|
|
104
|
+
else:
|
|
105
|
+
if isinstance(item, IDSStructArray):
|
|
106
|
+
item.resize(1)
|
|
107
|
+
recurse(item[0], "")
|
|
108
|
+
|
|
109
|
+
recurse(ids, "")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_dbentry_autoconvert1(backend, worker_id, tmp_path):
|
|
113
|
+
entry_331 = open_dbentry(backend, "w", worker_id, tmp_path, dd_version="3.31.0")
|
|
114
|
+
old_factory = entry_331.factory
|
|
115
|
+
old_ids = old_factory.new("core_profiles")
|
|
116
|
+
old_ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
117
|
+
|
|
118
|
+
# Put without conversion:
|
|
119
|
+
entry_331.put(old_ids)
|
|
120
|
+
assert old_ids.ids_properties.version_put.data_dictionary == "3.31.0"
|
|
121
|
+
if backend != MEMORY_BACKEND:
|
|
122
|
+
entry_331.close()
|
|
123
|
+
|
|
124
|
+
entry_342 = open_dbentry(backend, "r", worker_id, tmp_path, dd_version="3.42.0")
|
|
125
|
+
|
|
126
|
+
# Get without conversion
|
|
127
|
+
old_ids_get = entry_342.get("core_profiles", autoconvert=False)
|
|
128
|
+
assert old_ids_get.ids_properties.version_put.data_dictionary == "3.31.0"
|
|
129
|
+
assert old_ids_get._dd_version == "3.31.0"
|
|
130
|
+
|
|
131
|
+
# Work around ASCII backend bug...
|
|
132
|
+
if backend == ASCII_BACKEND:
|
|
133
|
+
entry_342.close()
|
|
134
|
+
entry_342 = open_dbentry(backend, "r", worker_id, tmp_path, dd_version="3.42.0")
|
|
135
|
+
|
|
136
|
+
# Get with conversion
|
|
137
|
+
new_ids_get = entry_342.get("core_profiles")
|
|
138
|
+
assert new_ids_get.ids_properties.version_put.data_dictionary == "3.31.0"
|
|
139
|
+
assert new_ids_get._dd_version == "3.42.0"
|
|
140
|
+
|
|
141
|
+
entry_342.close()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_dbentry_autoconvert2(backend, worker_id, tmp_path):
|
|
145
|
+
entry_342 = open_dbentry(backend, "w", worker_id, tmp_path, dd_version="3.42.0")
|
|
146
|
+
new_ids = entry_342.factory.new("core_profiles")
|
|
147
|
+
new_ids.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
148
|
+
|
|
149
|
+
# Put without conversion:
|
|
150
|
+
entry_342.put(new_ids)
|
|
151
|
+
assert new_ids.ids_properties.version_put.data_dictionary == "3.42.0"
|
|
152
|
+
if backend != MEMORY_BACKEND:
|
|
153
|
+
entry_342.close()
|
|
154
|
+
|
|
155
|
+
entry_331 = open_dbentry(backend, "r", worker_id, tmp_path, dd_version="3.31.0")
|
|
156
|
+
|
|
157
|
+
# Get without conversion
|
|
158
|
+
new_ids_get = entry_331.get("core_profiles", autoconvert=False)
|
|
159
|
+
assert new_ids_get.ids_properties.version_put.data_dictionary == "3.42.0"
|
|
160
|
+
assert new_ids_get._dd_version == "3.42.0"
|
|
161
|
+
|
|
162
|
+
# Work around ASCII backend bug...
|
|
163
|
+
if backend == ASCII_BACKEND:
|
|
164
|
+
entry_331.close()
|
|
165
|
+
entry_331 = open_dbentry(backend, "r", worker_id, tmp_path, dd_version="3.31.0")
|
|
166
|
+
|
|
167
|
+
# Get with conversion
|
|
168
|
+
old_ids_get = entry_331.get("core_profiles")
|
|
169
|
+
assert old_ids_get.ids_properties.version_put.data_dictionary == "3.42.0"
|
|
170
|
+
assert old_ids_get._dd_version == "3.31.0"
|
|
171
|
+
|
|
172
|
+
entry_331.close()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_provenance_entry(factory):
|
|
176
|
+
cp = factory.core_profiles()
|
|
177
|
+
# Note: DD 3.31.0 doesn't have the provenance data structure, test that it doesn't
|
|
178
|
+
# report an error:
|
|
179
|
+
cp2 = convert_ids(cp, "3.31.0", provenance_origin_uri="<testdata>")
|
|
180
|
+
# Convert back to 3.38.0
|
|
181
|
+
cp3 = convert_ids(cp2, "3.38.0", provenance_origin_uri="<testdata>")
|
|
182
|
+
assert len(cp3.ids_properties.provenance.node) == 1
|
|
183
|
+
assert cp3.ids_properties.provenance.node[0].path == ""
|
|
184
|
+
assert len(cp3.ids_properties.provenance.node[0].sources) == 1
|
|
185
|
+
provenance_txt = cp3.ids_properties.provenance.node[0].sources[0]
|
|
186
|
+
# Check that the provided origin URI is in the text
|
|
187
|
+
assert "<testdata>" in provenance_txt
|
|
188
|
+
# Check that origin and destination DD versions are included
|
|
189
|
+
assert "3.31.0" in provenance_txt
|
|
190
|
+
assert "3.38.0" in provenance_txt
|
|
191
|
+
# Check that imas-python is mentioned
|
|
192
|
+
assert "imas-python" in provenance_txt
|
|
193
|
+
|
|
194
|
+
# Test logic branch for node.reference implemented with IMAS-5304
|
|
195
|
+
cp4 = convert_ids(cp2, "3.42.0", provenance_origin_uri="<testdata>")
|
|
196
|
+
assert len(cp4.ids_properties.provenance.node) == 1
|
|
197
|
+
assert cp4.ids_properties.provenance.node[0].path == ""
|
|
198
|
+
assert len(cp4.ids_properties.provenance.node[0].reference) == 1
|
|
199
|
+
assert "<testdata>" in cp4.ids_properties.provenance.node[0].reference[0].name
|
|
200
|
+
timestamp = str(cp4.ids_properties.provenance.node[0].reference[0].timestamp)
|
|
201
|
+
# Check that timestamp adheres to the format YYYY-MM-DDTHH:MM:SSZ
|
|
202
|
+
assert re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", timestamp)
|
|
203
|
+
dtime = datetime.now(UTC) - datetime.fromisoformat(timestamp)
|
|
204
|
+
assert timedelta(seconds=0) <= dtime < timedelta(seconds=2)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.fixture
|
|
208
|
+
def dd4factory():
|
|
209
|
+
return IDSFactory("4.0.0")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_3to4_ggd_space_identifier(dd4factory):
|
|
213
|
+
ep = IDSFactory("3.39.0").edge_profiles()
|
|
214
|
+
ep.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
215
|
+
ep.grid_ggd.resize(1)
|
|
216
|
+
ep.grid_ggd[0].time = 0.0
|
|
217
|
+
ep.grid_ggd[0].space.resize(1)
|
|
218
|
+
ep.grid_ggd[0].space[0].coordinates_type = numpy.array([1, 2], dtype=numpy.int32)
|
|
219
|
+
|
|
220
|
+
ep4 = convert_ids(ep, None, factory=dd4factory)
|
|
221
|
+
cid = identifiers.coordinate_identifier
|
|
222
|
+
assert ep4.grid_ggd[0].time == 0.0
|
|
223
|
+
coordinates_type = ep4.grid_ggd[0].space[0].coordinates_type
|
|
224
|
+
assert len(coordinates_type) == 2
|
|
225
|
+
for i in range(2):
|
|
226
|
+
# Test that the full identifier structure is filled:
|
|
227
|
+
identifier = cid(i + 1)
|
|
228
|
+
assert coordinates_type[i].index == identifier.index
|
|
229
|
+
assert coordinates_type[i].name == identifier.name
|
|
230
|
+
assert coordinates_type[i].description == identifier.description
|
|
231
|
+
|
|
232
|
+
ep3 = convert_ids(ep4, "3.39.0")
|
|
233
|
+
compare_children(ep, ep3)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_3to4_repeat_children_first_point_conditional(dd4factory):
|
|
237
|
+
# The wall IDS contains all (four!) cases with conditional repeats
|
|
238
|
+
wall = IDSFactory("3.39.0").wall()
|
|
239
|
+
wall.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
240
|
+
wall.description_2d.resize(2)
|
|
241
|
+
|
|
242
|
+
# Case 1: repeat_children_first_point_conditional
|
|
243
|
+
wall.description_2d[0].vessel.unit.resize(2)
|
|
244
|
+
for i in range(2):
|
|
245
|
+
outline_inner = wall.description_2d[0].vessel.unit[i].annular.outline_inner
|
|
246
|
+
outline_inner.closed = i # first is open, second is closed
|
|
247
|
+
outline_inner.r = [1.0, 2.0, 3.0]
|
|
248
|
+
outline_inner.z = [-1.0, -2.0, -3.0]
|
|
249
|
+
|
|
250
|
+
# Case 2: repeat_children_first_point_conditional_sibling
|
|
251
|
+
wall.description_2d[0].limiter.unit.resize(2)
|
|
252
|
+
for i in range(2):
|
|
253
|
+
unit = wall.description_2d[0].limiter.unit[i]
|
|
254
|
+
unit.closed = i # first is open, second is closed
|
|
255
|
+
unit.outline.r = [1.0, 2.0, 3.0]
|
|
256
|
+
unit.outline.z = [-1.0, -2.0, -3.0]
|
|
257
|
+
|
|
258
|
+
# Case 3: repeat_children_first_point_conditional_sibling_dynamic
|
|
259
|
+
wall.description_2d[0].mobile.unit.resize(2)
|
|
260
|
+
for i in range(2):
|
|
261
|
+
unit = wall.description_2d[0].mobile.unit[i]
|
|
262
|
+
unit.closed = i # first is open, second is closed
|
|
263
|
+
unit.outline.resize(3)
|
|
264
|
+
for j in range(3):
|
|
265
|
+
unit.outline[j].r = [1.0, 2.0, 3.0]
|
|
266
|
+
unit.outline[j].z = [-1.0, -2.0, -3.0]
|
|
267
|
+
unit.outline[j].time = j / 5
|
|
268
|
+
|
|
269
|
+
# Case 4: repeat_children_first_point_conditional_centreline
|
|
270
|
+
# (see https://jira.iter.org/browse/IMAS-5541)
|
|
271
|
+
wall.description_2d[1].vessel.unit.resize(2)
|
|
272
|
+
for i in range(2):
|
|
273
|
+
centreline = wall.description_2d[1].vessel.unit[i].annular.centreline
|
|
274
|
+
centreline.closed = i # first is open, second is closed
|
|
275
|
+
centreline.r = [1.0, 2.0, 3.0]
|
|
276
|
+
centreline.z = [-1.0, -2.0, -3.0]
|
|
277
|
+
# if it was open there were too many thickness values!
|
|
278
|
+
# The last one will be dropped and repeated
|
|
279
|
+
wall.description_2d[1].vessel.unit[i].annular.thickness = [1, 0.9, 0.9]
|
|
280
|
+
|
|
281
|
+
wall4 = convert_ids(wall, None, factory=dd4factory)
|
|
282
|
+
assert len(wall4.description_2d) == 2
|
|
283
|
+
|
|
284
|
+
# Test conversion for case 1:
|
|
285
|
+
assert len(wall4.description_2d[0].vessel.unit) == 2
|
|
286
|
+
for i in range(2):
|
|
287
|
+
outline_inner = wall4.description_2d[0].vessel.unit[i].annular.outline_inner
|
|
288
|
+
if i == 0: # open outline, first point not repeated:
|
|
289
|
+
assert numpy.array_equal(outline_inner.r, [1.0, 2.0, 3.0])
|
|
290
|
+
assert numpy.array_equal(outline_inner.z, [-1.0, -2.0, -3.0])
|
|
291
|
+
else: # closed outline, first point repeated:
|
|
292
|
+
assert numpy.array_equal(outline_inner.r, [1.0, 2.0, 3.0, 1.0])
|
|
293
|
+
assert numpy.array_equal(outline_inner.z, [-1.0, -2.0, -3.0, -1.0])
|
|
294
|
+
|
|
295
|
+
# Test conversion for case 2:
|
|
296
|
+
assert len(wall4.description_2d[0].limiter.unit) == 2
|
|
297
|
+
for i in range(2):
|
|
298
|
+
unit = wall4.description_2d[0].limiter.unit[i]
|
|
299
|
+
if i == 0: # open outline, first point not repeated:
|
|
300
|
+
assert numpy.array_equal(unit.outline.r, [1.0, 2.0, 3.0])
|
|
301
|
+
assert numpy.array_equal(unit.outline.z, [-1.0, -2.0, -3.0])
|
|
302
|
+
else: # closed outline, first point repeated:
|
|
303
|
+
assert numpy.array_equal(unit.outline.r, [1.0, 2.0, 3.0, 1.0])
|
|
304
|
+
assert numpy.array_equal(unit.outline.z, [-1.0, -2.0, -3.0, -1.0])
|
|
305
|
+
|
|
306
|
+
# Test conversion for case 3:
|
|
307
|
+
assert len(wall4.description_2d[0].mobile.unit) == 2
|
|
308
|
+
for i in range(2):
|
|
309
|
+
unit = wall4.description_2d[0].mobile.unit[i]
|
|
310
|
+
for j in range(3):
|
|
311
|
+
if i == 0: # open outline, first point not repeated:
|
|
312
|
+
assert numpy.array_equal(unit.outline[j].r, [1.0, 2.0, 3.0])
|
|
313
|
+
assert numpy.array_equal(unit.outline[j].z, [-1.0, -2.0, -3.0])
|
|
314
|
+
else: # closed outline, first point repeated:
|
|
315
|
+
assert numpy.array_equal(unit.outline[j].r, [1.0, 2.0, 3.0, 1.0])
|
|
316
|
+
assert numpy.array_equal(unit.outline[j].z, [-1.0, -2.0, -3.0, -1.0])
|
|
317
|
+
assert unit.outline[j].time == pytest.approx(j / 5)
|
|
318
|
+
|
|
319
|
+
# Test conversion for case 4:
|
|
320
|
+
assert len(wall4.description_2d[1].vessel.unit) == 2
|
|
321
|
+
for i in range(2):
|
|
322
|
+
thickness = wall4.description_2d[1].vessel.unit[i].annular.thickness
|
|
323
|
+
if i == 0: # open outline, there was one value too many, drop the last one
|
|
324
|
+
assert numpy.array_equal(thickness, [1, 0.9])
|
|
325
|
+
else: # closed outline, thickness values kept
|
|
326
|
+
assert numpy.array_equal(thickness, [1, 0.9, 0.9])
|
|
327
|
+
|
|
328
|
+
# Test conversion back
|
|
329
|
+
wall3 = convert_ids(wall4, "3.39.0")
|
|
330
|
+
compare_children(wall, wall3)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_3to4_repeat_children_first_point(dd4factory):
|
|
334
|
+
iron_core = IDSFactory("3.39.0").iron_core()
|
|
335
|
+
iron_core.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
336
|
+
iron_core.segment.resize(1)
|
|
337
|
+
iron_core.segment[0].geometry.outline.r = [1.0, 2.0, 3.0]
|
|
338
|
+
iron_core.segment[0].geometry.outline.z = [-1.0, -2.0, -3.0]
|
|
339
|
+
|
|
340
|
+
iron_core4 = convert_ids(iron_core, None, factory=dd4factory)
|
|
341
|
+
geometry = iron_core4.segment[0].geometry
|
|
342
|
+
assert numpy.array_equal(geometry.outline.r, [1.0, 2.0, 3.0, 1.0])
|
|
343
|
+
assert numpy.array_equal(geometry.outline.z, [-1.0, -2.0, -3.0, -1.0])
|
|
344
|
+
|
|
345
|
+
iron_core3 = convert_ids(iron_core4, "3.39.0")
|
|
346
|
+
compare_children(iron_core, iron_core3)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def test_3to4_cocos_change(dd4factory):
|
|
350
|
+
cp = IDSFactory("3.39.0").core_profiles()
|
|
351
|
+
cp.ids_properties.homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS
|
|
352
|
+
cp.time = [1.0]
|
|
353
|
+
cp.profiles_1d.resize(1)
|
|
354
|
+
cp.profiles_1d[0].grid.rho_tor_norm = numpy.linspace(0, 1, 11)
|
|
355
|
+
cp.profiles_1d[0].grid.psi = numpy.linspace(10, 20, 11)
|
|
356
|
+
|
|
357
|
+
cp4 = convert_ids(cp, None, factory=dd4factory)
|
|
358
|
+
assert numpy.array_equal(
|
|
359
|
+
cp4.profiles_1d[0].grid.rho_tor_norm,
|
|
360
|
+
cp.profiles_1d[0].grid.rho_tor_norm,
|
|
361
|
+
)
|
|
362
|
+
assert numpy.array_equal(
|
|
363
|
+
cp4.profiles_1d[0].grid.psi,
|
|
364
|
+
-cp.profiles_1d[0].grid.psi,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
cp3 = convert_ids(cp4, "3.39.0")
|
|
368
|
+
compare_children(cp, cp3)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_3to4_circuit_connections(dd4factory, caplog):
|
|
372
|
+
pfa = IDSFactory("3.39.0").pf_active()
|
|
373
|
+
pfa.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS
|
|
374
|
+
pfa.circuit.resize(1)
|
|
375
|
+
pfa.circuit[0].connections = [
|
|
376
|
+
[0, 1, 0, 0, 1, 0],
|
|
377
|
+
[0, 0, 1, 0, 0, 1],
|
|
378
|
+
[1, 0, 0, 1, 0, 0],
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
pfa4 = convert_ids(pfa, None, factory=dd4factory)
|
|
382
|
+
assert numpy.array_equal(
|
|
383
|
+
pfa4.circuit[0].connections, [[-1, 0, 1], [0, 1, -1], [1, -1, 0]]
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
pfa3 = convert_ids(pfa4, "3.39.0")
|
|
387
|
+
compare_children(pfa, pfa3)
|
|
388
|
+
|
|
389
|
+
# Test invalid connections shape
|
|
390
|
+
pfa.circuit[0].connections = [
|
|
391
|
+
[0, 1, 0, 0, 1, 0, 1],
|
|
392
|
+
[0, 0, 1, 0, 0, 1, 1],
|
|
393
|
+
[1, 0, 0, 1, 0, 0, 1],
|
|
394
|
+
]
|
|
395
|
+
caplog.clear()
|
|
396
|
+
with caplog.at_level(logging.ERROR):
|
|
397
|
+
pfa4 = convert_ids(pfa, None, factory=dd4factory)
|
|
398
|
+
# Incorrect shape, data is not converted:
|
|
399
|
+
assert numpy.array_equal(pfa.circuit[0].connections, pfa4.circuit[0].connections)
|
|
400
|
+
# Check that a message with ERROR severity was logged
|
|
401
|
+
assert len(caplog.record_tuples) == 1
|
|
402
|
+
assert caplog.record_tuples[0][1] == logging.ERROR
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_3to4_cocos_magnetics_workaround(dd4factory):
|
|
406
|
+
mag = IDSFactory("3.39.0").magnetics()
|
|
407
|
+
mag.ids_properties.homogeneous_time = IDS_TIME_MODE_INDEPENDENT
|
|
408
|
+
mag.flux_loop.resize(1)
|
|
409
|
+
mag.flux_loop[0].flux.data = [1.0, 2.0]
|
|
410
|
+
|
|
411
|
+
mag4 = convert_ids(mag, None, factory=dd4factory)
|
|
412
|
+
assert numpy.array_equal(mag4.flux_loop[0].flux.data, [-1.0, -2.0])
|
|
413
|
+
|
|
414
|
+
mag3 = convert_ids(mag4, "3.39.0")
|
|
415
|
+
compare_children(mag, mag3)
|