Use Interface Data Structures

The Interface Data Structures (IDSs) are the main way to interact with IMAS data. An IDS is a tree-like structure with one root element (the IDS) and several branches with data at the leave nodes.

Many types of IDSs exist: check out the documentation of the Data Dictionary for a complete overview.

Creating IDSs

IDSs can be created in multiple ways:

  1. Load an IDS from disk

  2. Create an empty IDS

  3. Create a copy of an IDS

Create an empty IDS

You can create an empty instance of an IDS by creating a new variable with type(ids_<ids name>), for example type(ids_core_profiles) :: cp creates an empty core_profiles IDS. This initializes all items in the IDS to their Default values.

Fortran example: create an empty IDS
program create_ids
    ! Make the Access Layer available
    use ids_routines
    implicit none

    ! Create empty core_profiles and equilibrium IDSs
    type(ids_core_profiles) :: core_profiles
    type(ids_equilibrium) :: equilibrium

    ! This outputs -999999999 (default INT_0D value)
    write(*,*) core_profiles%ids_properties%homogeneous_time

    ! This outputs F (by default array of structure is not associated)
    write(*,*) associated(equilibrium%time_slice)

end program create_ids

Create a copy of an IDS

You can create a copy of another IDS by calling ids_copy().

Fortran example: create a copy of an IDS
program copy_ids
    ! Make the Access Layer available
    use ids_routines
    implicit none

    integer :: i, j
    type(ids_core_profiles) :: core_profiles, core_profiles_copy

    core_profiles%ids_properties%homogeneous_time = IDS_TIME_MODE_HOMOGENEOUS;
    allocate(core_profiles%time(2))
    allocate(core_profiles%profiles_1d(2))
    do i = 1, 2
        core_profiles%time(i) = i
        allocate(core_profiles%profiles_1d(i)%grid%rho_tor_norm(5))
        allocate(core_profiles%profiles_1d(i)%j_total(5))
        do j = 1, 5
            core_profiles%profiles_1d(i)%grid%rho_tor_norm(j) = 0.25 * (j - 1)
            core_profiles%profiles_1d(i)%j_total(j) = 0.25 * (j - 1) + i
        end do
    end do

    call ids_copy(core_profiles, core_profiles_copy)

    ! Adjust some data
    core_profiles_copy%profiles_1d(1)%j_total = &
        2 * core_profiles_copy%profiles_1d(1)%j_total

    ! Verify that we didn't change the original IDS
    ! -> 2.000 2.500 3.000 3.500 4.000
    write(*,'(5(F0.3,TR1))') core_profiles_copy%profiles_1d(1)%j_total
    ! -> 1.000 1.250 1.500 1.750 2.000
    write(*,'(5(F0.3,TR1))') core_profiles%profiles_1d(1)%j_total
end program copy_ids

Deallocate an IDS

If you no longer need an IDS, you can deallocate it so it releases the (memory) resources in use by the data. You can call ids_deallocate() to deallocate an IDS.

Some attributes in an IDS are mandatory or recommended to always fill. Below list provides a short overview:

  1. ids_properties/homogeneous_time [mandatory]: see Time coordinates and time handling.

  2. ids_properties/comment [recommended]: a comment describing the content of this IDS.

  3. ids_properties/provider [recommended]: name of the person in charge of producing this data.

  4. ids_properties/creation_date [recommended]: date at which this data has been produced, recommended to use the ISO 8601 YYYY-MM-DD format.

Note

ids_properties/version_put is filled by the access layer when you put an IDS.

Understanding the IDS structure

An IDS is a tree structure. You can think of it similar as a directory structure with files: the IDS is the root “directory”, and inside it you can find “subdirectories” and “files” with data.

We will use the general Computer Science terminology for tree structures and call these “files” and “directories” of our IDSs nodes. IDSs can have a limited number of different types of nodes:

  1. Structure: think of these as the directories of your file system. Structures contain one or more child nodes (files and subdirectories). Child nodes can be of any node type again.

  2. Array of structures: this is an array of structures (see previous point).

  3. Data: this is a data element. Like files on your file system these nodes contain the actual data stored in the IDS.

Structure

Structure nodes in an IDS are a container for other nodes. In Fortran they are implemented as a derived type. You can address child nodes as data members, see the code sample below.

Fortran example: address the child node of an IDS structure node
program ids_structure
    ! Make the Access Layer available
    use ids_routines
    implicit none

    ! Create an empty core_profiles IDS
    type(ids_core_profiles) :: core_profiles

    ! core_profiles/ids_properties is a structure, which has multiple child
    ! nodes. One of the child nodes is homogeneous time:
    write(*,*) core_profiles%ids_properties%homogeneous_time
    ! Since we created an empty IDS, this will output "-999999999": the default
    ! value for integers.

end program ids_structure

Array of structures

Array of structure nodes in an IDS are one-dimensional arrays, containing structure nodes. In Fortran they are implemented as a a pointer (:) to a derived type. The default value (for example, when creating a new IDS) for these nodes is not associated (associated(node) .eq. .false.).

Fortran example: address the child node of an IDS arrays of structure node
program ids_aos
    ! Make the Access Layer available
    use ids_routines
    implicit none

    ! Create an empty core_profiles IDS
    type(ids_core_profiles) :: core_profiles

    ! core_profiles/profiles_1d is an array of structures
    write(*,*) associated(core_profiles%profiles_1d)  ! -> F
    ! It currently has no elements, so let's allocate one
    allocate(core_profiles%profiles_1d(1))
    write(*,*) size(core_profiles%profiles_1d)  ! -> 1

    ! Use Fortran array indexation to get a specific structure from the array,
    ! then we can address child nodes as usual (for example, the average ion
    ! temperature, which is an unallocated data array):
    write(*,*) core_profiles%profiles_1d(1)%t_i_average ! -> <empty>

end program ids_aos

Resizing an array of structures

You can resize an array of structures with deallocate(node); allocate(node(n)). After calling this, the array of structures will have n elements.

Caution

Resizing an array of structures with deallocate(node); allocate(node(n)) will clear all data inside the array of structure! Use a temporary variable (as in below example) to keep existing data.

Fortran example: resizing an array of structures
program ids_aos
    ! Make the Access Layer available
    use ids_routines
    implicit none

    integer :: i
    type(ids_interferometer) :: interferometer
    type(ids_interferometer_channel), pointer :: tmp_channel(:)

    ! interferometer/channel is an array of structures, add some data
    allocate(interferometer%channel(1))
    write(*,*) size(interferometer%channel)  ! -> 1
    allocate(interferometer%channel(1)%name(1))
    interferometer%channel(1)%name(1) = 'test'

    ! resize, this will destroy the data that we just stored
    do i = 1,size(interferometer%channel)
        call ids_deallocate_struct(interferometer%channel(i), .false.)
    end do
    deallocate(interferometer%channel)
    allocate(interferometer%channel(2))
    write(*,*) associated(interferometer%channel(1)%name)  ! F

    allocate(interferometer%channel(1)%name(1))
    interferometer%channel(1)%name(1) = 'test'
    allocate(interferometer%channel(2)%name(1))
    interferometer%channel(2)%name(1) = 'test2'

    ! Resize, preserving existing data
    allocate(tmp_channel(3))
    tmp_channel(1:size(interferometer%channel)) = interferometer%channel
    deallocate(interferometer%channel)
    allocate(interferometer%channel(3))
    interferometer%channel = tmp_channel
    deallocate(tmp_channel)

    write(*,*) interferometer%channel(1)%name(1)  ! test
    write(*,*) interferometer%channel(2)%name(1)  ! test2
    write(*,*) associated(interferometer%channel(3)%name)  ! F

end program ids_aos

Data

Data nodes in an IDS contain numerical or textual data. The data type and dimensions of a node are defined in the Data Dictionary.

Fortran example: get the data contained in a data node of an IDS
program ids_structure
    ! Make the Access Layer available
    use ids_routines
    implicit none

    ! Create an empty core_profiles IDS
    type(ids_core_profiles) :: core_profiles

    ! core_profiles/time is a FLT_1D data node
    ! We can assign values to it:
    allocate(core_profiles%time(3))
    core_profiles%time = (/ 0.5, 1.0, 1.5 /)
    write(*,'(5(F0.3,TR1))') core_profiles%time ! -> .500 1.000 1.500

end program ids_structure

Data types

The following data types exist:

  • Textual data (character(len=132), dimension(:), pointer: STR_0D is implemented as a vector of strings of length 132 characters, allowing arbitrary long strings)

  • Whole numbers (integer(ids_int))

  • Floating point numbers (real(ids_real))

  • Complex floating point numbers (complex(ids_real))

Data nodes can be 0-dimensional, which means that the node accepts a single value of the specified type. Multi-dimensional data nodes also exist:

  • Textual data: at most 1 dimension (character(len=132), dimension(:), pointer: STR1D implementation is for the moment limited to a vector of 132 character strings)

  • Whole numbers: 1-3 dimensions (N-dimensional integer(ids_int), pointer)

  • Floating point numbers: 1-6 dimensions (N-dimensional real(ids_real), pointer)

  • Complex floating point numbers: 1-6 dimensions (N-dimensional complex(ids_real), pointer)

Default values

The default values for data fields (for example when creating an empty IDS) are indicated in the following table. You may use ids_is_valid() to test if a data node is not empty or unset.

0D

1+ dimension

Textual

data

not associated

not associated

Whole

numbers

-999_999_999, IDS_INT_INVALID

not associated

Floating

point

numbers

-9e40, IDS_REAL_INVALID

not associated

Complex

numbers

-9e40 -9e40i, IDS_COMPLEX_INVALID

not associated

Time coordinates and time handling

Some quantities (and array of structures) are time dependent. In the Data Dictionary documentation this is indicated by a coordinate that refers to a time quantity.

This time-dependent coordinate is treated specially in the access layer, and it depends on the value of ids_properties/homogeneous_time. There are three valid values for this property:

  1. IDS_TIME_MODE_HETEROGENEOUS (=0): time-dependent quantities in the IDS may have different time coordinates. The time coordinates are stored as indicated by the path in the documentation. This is known as heterogeneous time.

  2. IDS_TIME_MODE_HOMOGENEOUS (=1): All time-dependent quantities in this IDS use the same time coordinate. This is known as homogeneous time. This time coordinate is located in the root of the IDS, for example core_profiles/time. The paths time paths indicated in the documentation are unused in this case.

  3. IDS_TIME_MODE_INDEPENDENT (=2): The IDS stores no time-dependent data.

IDS validation

The IDSs you fill should be consistent. To help you in validating that, the Access Layer provides a validation method (by calling ids_validate()) that executes the following checks.

If you call this method and your IDS fails validation, the Access Layer returns a nonzero status and error message explaining the problem. See the following example:

Fortran example: call IDS validation
type(ids_pf_active) :: ids
character(:), allocatable :: err_msg
integer :: status

// get or populate the IDS
// ...

ids_validate(ids, status, err_msg)

The Access Layer automatically validates an IDS every time you do a put or put_slice. To disable this feature, you must set the environment variable IMAS_AL_DISABLE_VALIDATE to 1.

See also

API documentation: by calling ids_validate()

Validate the time mode

The time mode of an IDS is stored in ids_properties.homogeneous_time. This property must be filled with a valid time mode (IDS_TIME_MODE_HOMOGENEOUS, IDS_TIME_MODE_HETEROGENEOUS or IDS_TIME_MODE_INDEPENDENT). When the time mode is IDS_TIME_MODE_INDEPENDENT, all time-dependent quantities must be empty.

Validate coordinates

If a quantity in your IDS has coordinates, then these coordinates must be filled. The size of your data must match the size of the coordinates:

  1. Some dimensions must have a fixed size. This is indicated by the Data Dictionary as, for example, 1...3.

    For example, in the magnetics IDS, b_field_pol_probe(i1)/bandwidth_3db has 1...2 as coordinate 1. This means that, if you fill this data field, the first (and only) dimension of this field must be of size 2.

  2. If the coordinate is another quantity in the IDS, then that coordinate must be filled and have the same size as your data.

    For example, in the pf_active IDS, coil(i1)/current_limit_max is a two-dimensional quantity with coordinates coil(i1)/b_field_max and coil(i1)/temperature. This means that, if you fill this data field, their coordinate fields must be filled as well. The first dimension of current_limit_max must have the same size as b_field_max and the second dimension the same size as temperature.

    Time coordinates are handled depending on the value of ids_properties/homogeneous_time:

    • When using IDS_TIME_MODE_HOMOGENEOUS, all time coordinates look at the root time node of the IDS.

    • When using IDS_TIME_MODE_HETEROGENEOUS, all time coordinates look at the time path specified as coordinate by the Data Dictionary.

      For dynamic array of structures, the time coordinates is a FLT_0D inside the AoS (see, for example, profiles_1d in the core_profiles IDS). In such cases the time node must be set to something different than EMPTY_FLOAT. This is the only case in which values of the coordinates are verified, in all other cases only the sizes of coordinates are validated.

    Alternative coordinates

    Version 4 of the Data Dictionary introduces alternative coordinates. An example of this can be found in the core_profiles IDS in profiles_1d(itime)/grid/rho_tor_norm. Alternatives for this coordinate are:

    • profiles_1d(itime)/grid/rho_tor

    • profiles_1d(itime)/grid/psi

    • profiles_1d(itime)/grid/volume

    • profiles_1d(itime)/grid/area

    • profiles_1d(itime)/grid/surface

    • profiles_1d(itime)/grid/rho_pol_norm

    Multiple alternative coordinates may be filled (for example, an IDS might fill both the normalized and non-normalized toroidal flux coordinate). In that case, the size must be the same.

    When a quantity refers to this set of alternatives (for example profiles_1d(itime)/electrons/temperature), at least one of the alternative coordinates must be set and its size match the size of the quantity.

  3. The Data Dictionary can indicate exclusive alternative coordinates. See for example the distribution(i1)/profiles_2d(itime)/density(:,:) quantity in the distributions IDS, which has as first coordinate distribution(i1)/profiles_2d(itime)/grid/r OR distribution(i1)/profiles_2d(itime)/grid/rho_tor_norm. This means that either r or rho_tor_norm can be used as coordinate.

    Validation works the same as explained in the previous point, except that exactly one of the alternative coordinate must be filled. Its size must, of course, still match the size of the data in the specified dimension.

  4. Some quantites indicate a coordinate must be the same size as another quantity through the property coordinateX_same_as. In this case, the other quantity is not a coordinate, but their data is related and must be of the same size.

    An example can be found in the edge_profiles IDS, quantity ggd(itime)/neutral(i1)/velocity(i2)/diamagnetic. This is a two-dimensional field for which the first coordinate must be the same as ggd(itime)/neutral(i1)/velocity(i2)/radial. When the diamagnetic velocity component is filled, the radial component must be filled as well, and have a matching size.