From a790a1c62eeefd97e5f718241dd372aa99a4cf36 Mon Sep 17 00:00:00 2001 From: Maarten Sebregts Date: Thu, 25 Sep 2025 10:53:30 +0200 Subject: [PATCH 1/2] Add new 3to4 conversion rules to change sign of poloidal flux Add a list of quantities that should get a sign flip on conversion, but are not covered by the generic rule that looks at DD3 `cocos_label_transformation` metadata. --- imas/ids_convert.py | 128 ++++++++++++++++++++++++++++++++-- imas/test/test_ids_convert.py | 21 ++++++ 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/imas/ids_convert.py b/imas/ids_convert.py index 75359f8..48b105d 100644 --- a/imas/ids_convert.py +++ b/imas/ids_convert.py @@ -336,13 +336,18 @@ def add_rename(old_path: str, new_path: str): def _apply_3to4_conversion(self, old: Element, new: Element) -> None: # Postprocessing for COCOS definition change: + cocos_paths = [] for psi_like in ["psi_like", "dodpsi_like"]: xpath_query = f".//field[@cocos_label_transformation='{psi_like}']" for old_item in old.iterfind(xpath_query): - old_path = old_item.get("path") - new_path = self.old_to_new.path.get(old_path, old_path) - self.new_to_old.post_process[new_path] = _cocos_change - self.old_to_new.post_process[old_path] = _cocos_change + cocos_paths.append(old_item.get("path")) + # Sign flips not covered by the generic rule: + cocos_paths.extend(_3to4_sign_flip_paths.get(self.ids_name, [])) + for old_path in cocos_paths: + new_path = self.old_to_new.path.get(old_path, old_path) + self.new_to_old.post_process[new_path] = _cocos_change + self.old_to_new.post_process[old_path] = _cocos_change + # Definition change for pf_active circuit/connections if self.ids_name == "pf_active": path = "circuit/connections" @@ -676,6 +681,121 @@ def _copy_structure( callback(item, target_item) +_3to4_sign_flip_paths = { + "core_instant_changes": [ + "change/profiles_1d/grid/psi_magnetic_axis", + "change/profiles_1d/grid/psi_boundary", + ], + "core_profiles": [ + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + ], + "core_sources": [ + "source/profiles_1d/grid/psi_magnetic_axis", + "source/profiles_1d/grid/psi_boundary", + ], + "core_transport": [ + "model/profiles_1d/grid_d/psi_magnetic_axis", + "model/profiles_1d/grid_d/psi_boundary", + "model/profiles_1d/grid_v/psi_magnetic_axis", + "model/profiles_1d/grid_v/psi_boundary", + "model/profiles_1d/grid_flux/psi_magnetic_axis", + "model/profiles_1d/grid_flux/psi_boundary", + ], + "disruption": [ + "global_quantities/psi_halo_boundary", + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + ], + "ece": [ + "channel/beam_tracing/beam/position/psi", + "psi_normalization/psi_magnetic_axis", + "psi_normalization/psi_boundary", + ], + "edge_profiles": [ + "profiles_1d/grid/psi", + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + ], + "equilibrium": [ + "time_slice/boundary/psi", + "time_slice/global_quantities/q_min/psi", + "time_slice/ggd/psi/values", + "time_slice/ggd/psi/coefficients", + ], + "mhd": [ + "ggd/psi/values", + "ggd/psi/coefficients", + ], + "pellets": ["time_slice/pellet/path_profiles/psi"], + "plasma_profiles": [ + "profiles_1d/grid/psi", + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + "ggd/psi/values", + "ggd/psi/coefficients", + ], + "plasma_sources": [ + "source/profiles_1d/grid/psi", + "source/profiles_1d/grid/psi_magnetic_axis", + "source/profiles_1d/grid/psi_boundary", + ], + "plasma_transport": [ + "model/profiles_1d/grid_d/psi", + "model/profiles_1d/grid_d/psi_magnetic_axis", + "model/profiles_1d/grid_d/psi_boundary", + "model/profiles_1d/grid_v/psi", + "model/profiles_1d/grid_v/psi_magnetic_axis", + "model/profiles_1d/grid_v/psi_boundary", + "model/profiles_1d/grid_flux/psi", + "model/profiles_1d/grid_flux/psi_magnetic_axis", + "model/profiles_1d/grid_flux/psi_boundary", + ], + "radiation": [ + "process/profiles_1d/grid/psi_magnetic_axis", + "process/profiles_1d/grid/psi_boundary", + ], + "reflectometer_profile": [ + "psi_normalization/psi_magnetic_axis", + "psi_normalization/psi_boundary", + ], + "reflectometer_fluctuation": [ + "psi_normalization/psi_magnetic_axis", + "psi_normalization/psi_boundary", + ], + "runaway_electrons": [ + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + ], + "sawteeth": [ + "profiles_1d/grid/psi_magnetic_axis", + "profiles_1d/grid/psi_boundary", + ], + "summary": [ + "global_quantities/psi_external_average/value", + "local/magnetic_axis/position/psi", + ], + "transport_solver_numerics": [ + "solver_1d/grid/psi_magnetic_axis", + "solver_1d/grid/psi_boundary", + "derivatives_1d/grid/psi_magnetic_axis", + "derivatives_1d/grid/psi_boundary", + ], + "wall": [ + "description_ggd/ggd/psi/values", + "description_ggd/ggd/psi/coefficients", + ], + "waves": [ + "coherent_wave/profiles_1d/grid/psi_magnetic_axis", + "coherent_wave/profiles_1d/grid/psi_boundary", + "coherent_wave/profiles_2d/grid/psi", + "coherent_wave/beam_tracing/beam/position/psi", + ], +} +"""List of paths per IDS that require a COCOS sign change, but aren't covered by the +generic rule.""" + + ######################################################################################## # Type changed handlers and post-processing functions # ######################################################################################## diff --git a/imas/test/test_ids_convert.py b/imas/test/test_ids_convert.py index 826a797..af6f3a5 100644 --- a/imas/test/test_ids_convert.py +++ b/imas/test/test_ids_convert.py @@ -12,12 +12,14 @@ from imas import identifiers from imas.ids_convert import ( + _3to4_sign_flip_paths, _get_ctxpath, _get_tbp, convert_ids, dd_version_map_from_factories, iter_parents, ) +from imas.ids_data_type import IDSDataType from imas.ids_defs import ( ASCII_BACKEND, IDS_TIME_MODE_HETEROGENEOUS, @@ -529,3 +531,22 @@ def test_3to4_migrate_deprecated_fields(): # GH#55 del cp342.profiles_1d[0].ion[0].label cp4 = convert_ids(cp342, "4.0.0") assert cp4.profiles_1d[0].ion[0].name == "y" + + +def test_3to4_cocos_hardcoded_paths(): + # Check for existence in 3.42.0 + factory = IDSFactory("3.42.0") + for ids_name, paths in _3to4_sign_flip_paths.items(): + ids = factory.new(ids_name) + for path in paths: + # Check path exists and is not a FLT + metadata = ids.metadata[path] + assert metadata.data_type is IDSDataType.FLT + + # Test a conversion + eq = factory.equilibrium() + eq.time_slice.resize(1) + eq.time_slice[0].boundary.psi = 3.141 + + eq4 = convert_ids(eq, "4.0.0") + assert eq4.time_slice[0].boundary.psi == -3.141 From d98be19edc66216fc106956b598536db6b6f1c97 Mon Sep 17 00:00:00 2001 From: Maarten Sebregts Date: Fri, 26 Sep 2025 13:08:20 +0200 Subject: [PATCH 2/2] Remove psi/ggd/coefficients from COCOS sign flip conversion rule --- imas/ids_convert.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/imas/ids_convert.py b/imas/ids_convert.py index 48b105d..559e8a3 100644 --- a/imas/ids_convert.py +++ b/imas/ids_convert.py @@ -721,19 +721,14 @@ def _copy_structure( "time_slice/boundary/psi", "time_slice/global_quantities/q_min/psi", "time_slice/ggd/psi/values", - "time_slice/ggd/psi/coefficients", - ], - "mhd": [ - "ggd/psi/values", - "ggd/psi/coefficients", ], + "mhd": ["ggd/psi/values"], "pellets": ["time_slice/pellet/path_profiles/psi"], "plasma_profiles": [ "profiles_1d/grid/psi", "profiles_1d/grid/psi_magnetic_axis", "profiles_1d/grid/psi_boundary", "ggd/psi/values", - "ggd/psi/coefficients", ], "plasma_sources": [ "source/profiles_1d/grid/psi", @@ -781,10 +776,7 @@ def _copy_structure( "derivatives_1d/grid/psi_magnetic_axis", "derivatives_1d/grid/psi_boundary", ], - "wall": [ - "description_ggd/ggd/psi/values", - "description_ggd/ggd/psi/coefficients", - ], + "wall": ["description_ggd/ggd/psi/values"], "waves": [ "coherent_wave/profiles_1d/grid/psi_magnetic_axis", "coherent_wave/profiles_1d/grid/psi_boundary",