Skip to content

Commit 7659e54

Browse files
authored
Migrate deprecated magnetics fields (#119)
1 parent abd7b6c commit 7659e54

2 files changed

Lines changed: 102 additions & 34 deletions

File tree

imas/ids_convert.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -274,16 +274,7 @@ def get_old_path(path: str, previous_name: str) -> str:
274274
self.version_old,
275275
)
276276
elif self._check_data_type(old_item, new_item):
277-
# use class helper to register simple renames and
278-
# reciprocal mappings
279277
self._add_rename(old_path, new_path)
280-
if old_item.get("data_type") in DDVersionMap.STRUCTURE_TYPES:
281-
# Add entries for common sub-elements
282-
for path in old_paths:
283-
if path.startswith(old_path):
284-
npath = path.replace(old_path, new_path, 1)
285-
if npath in new_path_set:
286-
self._add_rename(path, npath)
287278
elif nbc_description == "type_changed":
288279
pass # We will handle this (if possible) in self._check_data_type
289280
elif nbc_description == "repeat_children_first_point":
@@ -334,28 +325,40 @@ def get_old_path(path: str, previous_name: str) -> str:
334325
# Additional conversion rules for DDv3 to DDv4
335326
if self.version_old.major == 3 and new_version and new_version.major == 4:
336327
self._apply_3to4_conversion(old, new)
328+
# 3to4 rules may have introduced additional missing items in self.old_to_new
329+
self._map_missing(
330+
False, old_path_set.difference(new_path_set, self.old_to_new)
331+
)
337332

338-
def _add_rename(self, old_path: str, new_path: str) -> None:
333+
def _add_rename(
334+
self, old_path: str, new_path: str, reciprocal: bool = True
335+
) -> None:
339336
"""Register a simple rename from old_path -> new_path using the
340337
path->Element maps stored on the instance (self.old_paths/self.new_paths).
341338
This will also add the reciprocal mapping when possible.
342339
"""
343340
old_item = self.old_paths[old_path]
344341
new_item = self.new_paths[new_path]
345-
346-
# forward mapping
342+
# Forward mapping
347343
self.old_to_new[old_path] = (
348344
new_path,
349345
_get_tbp(new_item, self.new_paths),
350346
_get_ctxpath(new_path, self.new_paths),
351347
)
352-
353-
# reciprocal mapping
354-
self.new_to_old[new_path] = (
355-
old_path,
356-
_get_tbp(old_item, self.old_paths),
357-
_get_ctxpath(old_path, self.old_paths),
358-
)
348+
# Reciprocal mapping
349+
if reciprocal:
350+
self.new_to_old[new_path] = (
351+
old_path,
352+
_get_tbp(old_item, self.old_paths),
353+
_get_ctxpath(old_path, self.old_paths),
354+
)
355+
# Apply to descendent nodes as well if the item is a struct or AoS
356+
for item in old_item.findall("field"):
357+
path = item.get("path")
358+
assert path is not None and path.startswith(old_path)
359+
npath = path.replace(old_path, new_path, 1)
360+
if npath in self.new_paths:
361+
self._add_rename(path, npath, reciprocal)
359362

360363
def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
361364
# Postprocessing for COCOS definition change:
@@ -421,6 +424,13 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
421424
to_update[p] = v
422425
self.old_to_new.path.update(to_update)
423426

427+
# Migrate additional obsolescent nodes
428+
# TODO: define migrations in a separate variable (as with the sign flips)?
429+
if self.ids_name == "magnetics":
430+
self._add_rename("bpol_probe", "b_field_pol_probe", reciprocal=False)
431+
self._add_rename("method", "ip", reciprocal=False)
432+
self.old_to_new.type_change["method"] = _magnetics_method_to_ip
433+
424434
# GH#59: To improve further the conversion of DD3 to DD4, especially the
425435
# Machine Description part of the IDSs, we would like to add a 3to4 specific
426436
# rule to convert any siblings name + identifier (that are not part of an
@@ -431,19 +441,20 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
431441
# Only perform the mapping if the corresponding target fields exist in the
432442
# new DD and if we don't already have a mapping for the involved paths.
433443
# use self.old_paths and self.new_paths set in _build_map
434-
for p in self.old_paths:
444+
for name_path in self.old_paths:
435445
# look for name children
436-
if not p.endswith("/name"):
446+
if not name_path.endswith("/name"):
437447
continue
438-
parent = p.rsplit("/", 1)[0]
439-
name_path = f"{parent}/name"
448+
parent = name_path.rsplit("/", 1)[0]
440449
id_path = f"{parent}/identifier"
441450
index_path = f"{parent}/index"
442-
desc_path = f"{parent}/description"
443-
new_name_path = name_path
451+
# Follow renames of parent structure
452+
new_parent = self.old_to_new.path.get(parent) or parent
453+
desc_path = f"{new_parent}/description"
454+
new_name_path = f"{new_parent}/name"
444455

445-
# If neither 'name' nor 'identifier' existed in the old DD, skip this parent
446-
if name_path not in self.old_paths or id_path not in self.old_paths:
456+
# If 'identifier' doesn't exist in the old DD, skip this parent
457+
if id_path not in self.old_paths:
447458
continue
448459
# exclude identifier-structure (has index sibling)
449460
if index_path in self.old_paths:
@@ -454,14 +465,11 @@ def _apply_3to4_conversion(self, old: Element, new: Element) -> None:
454465
continue
455466

456467
# Map DD3 name -> DD4 description
457-
if name_path not in self.old_to_new.path:
458-
self._add_rename(name_path, desc_path)
459-
# GH#114: Also preserve name in DD4 name when identifier is empty
460-
self.old_to_new.type_change[name_path] = _name_identifier_3to4
461-
468+
self._add_rename(name_path, desc_path)
469+
# GH#114: Also preserve name in DD4 name when identifier is empty
470+
self.old_to_new.type_change[name_path] = _name_identifier_3to4
462471
# Map DD3 identifier -> DD4 name
463-
if id_path in self.old_to_new.path:
464-
self._add_rename(id_path, new_name_path)
472+
self._add_rename(id_path, new_name_path)
465473

466474
def _map_missing(self, is_new: bool, missing_paths: Set[str]):
467475
rename_map = self.new_to_old if is_new else self.old_to_new
@@ -1329,3 +1337,14 @@ def _equilibrium_boundary_3to4(eq3: IDSToplevel, eq4: IDSToplevel, deepcopy: boo
13291337
node[2].psi = -ts3.boundary_secondary_separatrix.psi # COCOS change
13301338
node[2].levelset.r = copy(ts3.boundary_secondary_separatrix.outline.r)
13311339
node[2].levelset.z = copy(ts3.boundary_secondary_separatrix.outline.z)
1340+
1341+
1342+
def _magnetics_method_to_ip(method: IDSBase, ip: IDSBase) -> None:
1343+
"""Convert obsolescent method(:) to ip(:) in the magnetics IDS."""
1344+
if not len(method):
1345+
return
1346+
ip.resize(len(method))
1347+
for old_item, new_item in zip(method, ip, strict=True):
1348+
new_item.method_name.value = old_item.name.value
1349+
new_item.data.value = old_item.ip.data.value
1350+
new_item.time.value = old_item.ip.time.value

imas/test/test_ids_convert.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,55 @@ def test_3to4_cocos_magnetics_workaround(dd4factory):
439439
compare_children(mag, mag3)
440440

441441

442+
def test_3to4_deprecated_magnetics(dd4factory):
443+
# Test migrating deprecated bpol_probe
444+
mag = IDSFactory("3.39.0").magnetics()
445+
mag.bpol_probe.resize(2)
446+
mag.bpol_probe[0].name = "name1"
447+
mag.bpol_probe[0].identifier = "identifier1"
448+
mag.bpol_probe[0].position.r = 1
449+
mag.bpol_probe[0].field.data = [0.1, 0.2, 0.3]
450+
mag.bpol_probe[1].name = "name2"
451+
mag.bpol_probe[1].voltage.data = [0.1, 0.2, 0.3]
452+
453+
mag.method.resize(2)
454+
for i, method in enumerate(mag.method):
455+
method.name = f"name{i}"
456+
method.ip.data = [i, 1.0, 2.0]
457+
method.ip.time = [i + 1, 2.0, 3.0]
458+
459+
mag4 = convert_ids(mag, None, factory=dd4factory)
460+
assert len(mag4.b_field_pol_probe) == 2
461+
assert mag4.b_field_pol_probe[0].name == "identifier1"
462+
assert mag4.b_field_pol_probe[0].description == "name1"
463+
assert mag4.b_field_pol_probe[0].position.r == 1
464+
assert array_equal(mag4.b_field_pol_probe[0].field.data, [0.1, 0.2, 0.3])
465+
assert mag4.b_field_pol_probe[1].name == "name2"
466+
assert mag4.b_field_pol_probe[1].description == "name2"
467+
assert array_equal(mag4.b_field_pol_probe[1].voltage.data, [0.1, 0.2, 0.3])
468+
469+
assert len(mag4.ip) == 2
470+
assert mag4.ip[0].method_name == "name0"
471+
assert array_equal(mag4.ip[0].data, [0.0, 1.0, 2.0])
472+
assert array_equal(mag4.ip[0].time, [1.0, 2.0, 3.0])
473+
assert mag4.ip[1].method_name == "name1"
474+
assert array_equal(mag4.ip[1].data, [1.0, 1.0, 2.0])
475+
assert array_equal(mag4.ip[1].time, [2.0, 2.0, 3.0])
476+
477+
# If both the deprecated and the "correct" quantity exist, we expect only the
478+
# correct one to be converted to DD4:
479+
mag.b_field_pol_probe.resize(1)
480+
mag.b_field_pol_probe[0].name = "test"
481+
mag.ip.resize(1)
482+
mag.ip[0].method_name = "ip"
483+
484+
mag4 = convert_ids(mag, None, factory=dd4factory)
485+
assert len(mag4.b_field_pol_probe) == 1
486+
assert mag4.b_field_pol_probe[0].name == "test"
487+
assert len(mag4.ip) == 1
488+
assert mag4.ip[0].method_name == "ip"
489+
490+
442491
def test_3to4_pulse_schedule():
443492
ps = IDSFactory("3.39.0").pulse_schedule()
444493
ps.ids_properties.homogeneous_time = IDS_TIME_MODE_HETEROGENEOUS

0 commit comments

Comments
 (0)