From f2620714b5a30ed5948b020dc6e2a4e15a517214 Mon Sep 17 00:00:00 2001 From: Maarten Sebregts Date: Wed, 25 Jun 2025 13:09:11 +0200 Subject: [PATCH] Add example for em_coupling conversion Fixes #22. --- docs/source/examples.rst | 14 ++ .../examples/custom_conversion_em_coupling.py | 168 ++++++++++++++++++ .../custom_conversion_em_coupling.rst | 13 ++ docs/source/index.rst | 1 + 4 files changed, 196 insertions(+) create mode 100644 docs/source/examples.rst create mode 100644 docs/source/examples/custom_conversion_em_coupling.py create mode 100644 docs/source/examples/custom_conversion_em_coupling.rst diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 00000000..41fa0388 --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,14 @@ +.. _`IMAS-Python Examples`: + +IMAS-Python Examples +==================== + +Most IMAS-Python usage examples can be found throughout the documentation pages. On this +page we collect some examples that are too big or too generic to include in specific +pages. Currently this is a short list, but we expect that it will grow over time. + +.. toctree:: + :caption: IMAS-Python examples + :maxdepth: 1 + + examples/custom_conversion_em_coupling diff --git a/docs/source/examples/custom_conversion_em_coupling.py b/docs/source/examples/custom_conversion_em_coupling.py new file mode 100644 index 00000000..5b5b8628 --- /dev/null +++ b/docs/source/examples/custom_conversion_em_coupling.py @@ -0,0 +1,168 @@ +"""IMAS-Python example for custom conversion logic. + +This example script loads a Data Entry (in Data Dictionary 3.38.1) created by +DINA and converts the em_coupling IDS to DD 4.0.0. +""" + +import imas +from imas.ids_defs import IDS_TIME_MODE_INDEPENDENT + +input_uri = "imas:hdf5?path=/work/imas/shared/imasdb/ITER_SCENARIOS/3/105013/1" +# An error is reported when there's already data at the output_uri! +output_uri = "imas:hdf5?path=105013-1-converted" +target_dd_version = "4.0.0" + + +# Mapping of DD 3.38.1 em_coupling data to DD 4.0.0 +# Map the name of the matrix in DD 3.38.1 to the identifier and coordinate URIs +COUPLING_MAPS = { + "field_probes_active": dict( + coupling_quantity=2, + rows_uri="#magnetics/b_field_pol_probe", + columns_uri="#pf_active/coil", + ), + "field_probes_grid": dict( + coupling_quantity=2, + rows_uri="#magnetics/b_field_pol_probe", + columns_uri="#pf_plasma/element", + ), + "field_probes_passive": dict( + coupling_quantity=2, + rows_uri="#magnetics/b_field_pol_probe", + columns_uri="#pf_passive/loop", + ), + "mutual_active_active": dict( + coupling_quantity=1, + rows_uri="#pf_active/coil", + columns_uri="#pf_active/coil", + ), + "mutual_grid_active": dict( + coupling_quantity=1, + rows_uri="#pf_plasma/element", + columns_uri="#pf_active/coil", + ), + "mutual_grid_grid": dict( + coupling_quantity=1, + rows_uri="#pf_plasma/element", + columns_uri="#pf_plasma/element", + ), + "mutual_grid_passive": dict( + coupling_quantity=1, + rows_uri="#pf_plasma/element", + columns_uri="#pf_passive/loop", + ), + "mutual_loops_active": dict( + coupling_quantity=1, + rows_uri="#magnetics/flux_loop", + columns_uri="#pf_active/coil", + ), + "mutual_loops_passive": dict( + coupling_quantity=1, + rows_uri="#magnetics/flux_loop", + columns_uri="#pf_passive/loop", + ), + "mutual_loops_grid": dict( + coupling_quantity=1, + rows_uri="#magnetics/flux_loop", + columns_uri="#pf_plasma/element", + ), + "mutual_passive_active": dict( + coupling_quantity=1, + rows_uri="#pf_passive/loop", + columns_uri="#pf_active/coil", + ), + "mutual_passive_passive": dict( + coupling_quantity=1, + rows_uri="#pf_passive/loop", + columns_uri="#pf_passive/loop", + ), +} + + +with ( + imas.DBEntry(input_uri, "r") as entry, + imas.DBEntry(output_uri, "x", dd_version=target_dd_version) as out, +): + print("Loaded IMAS Data Entry:", input_uri) + + print("This data entry contains the following IDSs:") + filled_idss = [] + for idsname in entry.factory.ids_names(): + occurrences = entry.list_all_occurrences(idsname) + if occurrences: + filled_idss.append(idsname) + print(f"- {idsname}, occurrences: {occurrences}") + print("") + + # Load and convert all IDSs (except em_coupling) with imas.convert_ids() + # N.B. we know that the input URI doesn't have multiple occurrences, so + # we do not need to worry about them: + for idsname in filled_idss: + if idsname == "em_coupling": + continue + + print(f"Loading IDS: {idsname}...") + ids = entry.get(idsname, autoconvert=False) + print(f"Converting IDS {idsname} to DD {target_dd_version}...") + ids4 = imas.convert_ids( + ids, + target_dd_version, + provenance_origin_uri=input_uri, + ) + print(f"Storing IDS {idsname} to output data entry...") + out.put(ids4) + + print("Conversion for em_coupling:") + emc = entry.get("em_coupling", autoconvert=False) + print("Using standard convert, this may log warnings about discarding data") + emc4 = imas.convert_ids( + emc, + target_dd_version, + provenance_origin_uri=input_uri, + ) + + print("Starting custom conversion of the coupling matrices") + for matrix_name, mapping in COUPLING_MAPS.items(): + # Skip empty matrices + if not emc[matrix_name].has_value: + continue + + # Allocate a new coupling_matrix AoS element + emc4.coupling_matrix.resize(len(emc4.coupling_matrix) + 1, keep=True) + # And fill it + + emc4.coupling_matrix[-1].name = matrix_name + # Assigning an integer to the identifier will automatically fill the + # index/name/description. See documentation: + # https://imas-python.readthedocs.io/en/latest/identifiers.html + emc4.coupling_matrix[-1].quantity = mapping["coupling_quantity"] + emc4.coupling_matrix[-1].rows_uri = [mapping["rows_uri"]] + emc4.coupling_matrix[-1].columns_uri = [mapping["columns_uri"]] + emc4.coupling_matrix[-1].data = emc[matrix_name].value + # N.B. the original data has no error_upper/error_lower so we skip these + # Store em_coupling IDS + out.put(emc4) + + print("Generating pf_plasma IDS...") + # N.B. This logic is specific to DINA + # Create a new pf_plasma IDS and set basic properties + pf_plasma = out.factory.pf_plasma() + pf_plasma.ids_properties.homogeneous_time = IDS_TIME_MODE_INDEPENDENT + pf_plasma.ids_properties.comment = "PF Plasma generated from equilibrium" + + equilibrium = entry.get("equilibrium", lazy=True, autoconvert=False) + r = equilibrium.time_slice[0].profiles_2d[0].grid.dim1 + z = equilibrium.time_slice[0].profiles_2d[0].grid.dim2 + nr, nz = len(r), len(z) + # Generate a pf_plasma element for each grid point: + pf_plasma.element.resize(nr * nz) + for ir, rval in enumerate(r): + for iz, zval in enumerate(z): + element = pf_plasma.element[ir * nr + iz] + element.geometry.geometry_type = 2 # rectangle + element.geometry.rectangle.r = rval + element.geometry.rectangle.z = zval + # Store pf_plasma IDS + out.put(pf_plasma) + +print("Conversion finished") diff --git a/docs/source/examples/custom_conversion_em_coupling.rst b/docs/source/examples/custom_conversion_em_coupling.rst new file mode 100644 index 00000000..96f6ec53 --- /dev/null +++ b/docs/source/examples/custom_conversion_em_coupling.rst @@ -0,0 +1,13 @@ +Custom conversion of the ``em_coupling`` IDS +============================================ + +The ``em_coupling`` IDS has had a big change between Data Dictionary 3.x and Data +Dictionary 4.x. These changes are not covered by the automatic conversions of +:py:meth:`imas.convert_ids ` because these are too +code-specific. + +Instead we show on this page an example to convert a DINA dataset from DD 3.38.1 to DD +4.0.0, which can be used as a starting point for converting output data from other codes +as well. + +.. literalinclude:: custom_conversion_em_coupling.py diff --git a/docs/source/index.rst b/docs/source/index.rst index 7aa06277..8388f5b5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -51,6 +51,7 @@ Manual cli netcdf changelog + examples .. toctree:: :caption: IMAS-Python training courses