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

"""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")

Last update: 2026-01-28