Skip to content

Enhanced Field Metadata (was Discontinuous Galerkin)

Greg Sjaardema edited this page Jul 18, 2024 · 30 revisions

An exodus file stores transient "field" data on the entities (element blocks, nodeset, sidesets, ...) in an exodus file. Each of the fields is named and stores a scalar value. Conventions on the suffices of the names have been used in the past to guess on the actual "type" of the fields. For example, given the three fields "disp_x", "disp_y", and "disp_z", client codes have inferred that "disp" is a 3D vector field. This can work for general cases, but as is always the case, sometimes conventions fall short and misinterpretations can occur.

Another problem is that for fields stored at quadrature point locations or basis functions there is no way to interpret or map the fields to particular locations in the element.

The "enhanced field metadata" which is now available in exodus attempts to solve the above issues in a backwardly compatible manner. It is now possible to specify the "storage type" of the fields stored on an entity; either a "built-in" type such as "3D Vector" or a user-defined type such as a field stored at the 2x2x2 quadrature point locations in a hex element, or fields stored on locations defined by a particular basis function.

The "enhanced field metadata" API and implementation define several "built-in" standard types defined in the ex_field_type enumeration in the exodusII.h include file. They are listed in the first column of the table below along with the cardinality (number of components) of the field type and the list of suffices used to map from the field type to the scalar components.

Predefined Field Types

The following field types are predefined:

Enum Cardinality Suffices
EX_FIELD_TYPE_UNKNOWN invalid
EX_FIELD_TYPE_USER_DEFINED variable
EX_FIELD_TYPE_SEQUENCE variable 1, 2, ..., N
EX_BASIS specified by basis 1, 2, 3, ..., N ?
EX_QUADRATURE specified by quadrature 1, 2, 3, ..., N?
EX_SCALAR 1 -none-
EX_VECTOR_1D 1 x
EX_VECTOR_2D 2 x, y
EX_VECTOR_3D 3 x, y, z
EX_QUATERNION_2D 2 s, q
EX_QUATERNION_3D 4 x, y, z, q
EX_FULL_TENSOR_36 9 xx, yy, zz, xy, yz, zx, yx, zy, xz
EX_FULL_TENSOR_32 5 xx, yy, zz, xy, yx
EX_FULL_TENSOR_22 4 xx, yy, xy, yx
EX_FULL_TENSOR_16 7 xx, xy, yz, zx, yx, zy, xz
EX_FULL_TENSOR_12 3 xx, xy, yx
EX_SYM_TENSOR_33 6 xx, yy, zz, xy, yz, zx
EX_SYM_TENSOR_31 4 xx, yy, zz, xy
EX_SYM_TENSOR_21 3 xx, yy, xy
EX_SYM_TENSOR_13 4 xx, xy, yz, zx
EX_SYM_TENSOR_11 2 xx, xy
EX_SYM_TENSOR_10 1 xx
EX_ASYM_TENSOR_03 3 xy, yz, zx
EX_ASYM_TENSOR_02 2 xy, yz
EX_ASYM_TENSOR_01 1 xy
EX_MATRIX_22 4 11, 12, 21, 22
EX_MATRIX_33 9 11, 12, 13, 21, 22, 23, 31, 32, 33
EX_FIELD_TYPE_INVALID invalid

The types which do not have an explicit cardinality listed will have the component count (or cardinality) specified in the field metadata definition. The EX_BASIS and EX_QUADRATURE field types will reference a basis or quadrature type which is defined on the database. There can be multiple named basis and/or named quadrature types defined on the database.

The field metadata is defined via the ex_field struct:

#define EX_MAX_FIELD_NESTING 2
typedef struct ex_field
{
  ex_entity_type entity_type;
  int64_t        entity_id;
  char           name[EX_MAX_NAME + 1]; /* Name of the field */
  int           nesting; /* Number of composite fields (vector at each quadrature point = 2) */
  ex_field_type type[EX_MAX_FIELD_NESTING];                /* ex_field_type of each nested field */

  // For basis, user, quadrature -- what is name of the subtype. This
  // is a comma-separated list of `nesting` names Use two consecutive
  // commas for an empty type_name. Leave empty if no type_names
  char          type_name[EX_MAX_NAME + 1];

  int           cardinality[EX_MAX_FIELD_NESTING];         /* 0 to calculate based on type */
  char          component_separator[EX_MAX_FIELD_NESTING]; /* empty defaults to '_'; */
  char          suffices[EX_MAX_NAME + 1]; /* Optional comma-separated list of suffices if type is
                                              EX_FIELD_TYPE_USER_DEFINED */
} ex_field;

For example, the following defines a symmetric tensor field named "Stress" on an element block with id 10 and a suffix separator of '$'.

    struct ex_field field = (ex_field){.entity_type            = EX_ELEM_BLOCK,
                                       .entity_id              = 10,
                                       .name                   = "Stress",
                                       .type                   = {EX_SYM_TENSOR_33},
                                       .nesting                = 1,
                                       .component_separator[0] = '$'};

This field would refer to the scalar fields on the database named "Stress$xx", "Stress$yy", "Stress$zz", "Stress$xy", "Stress$yz", and "Stress$zx".

The nesting entry on the field allows the definition of a field composed of 2 (or more) field types. For example, a field can represent a 3D vector of values at each integration point -- This would be a field with nesting of 2 and the type would be {EX_VECTOR_3D, EX_QUADRATURE}. Currnently, the nesting is limited to a maximum of 2, but that can be changed through the EX_MAX_FIELD_NESTING define if a higher nesting limit is needed.

Basis

Storing one or more Basis in an exodus file:

Multiple named basis can be defined on a database and referenced by zero or more fields.

  • The exodus API has a ex_basis structure defined as:
typedef struct ex_basis
{
  /*
   * subc_dim: dimension of the subcell associated with the specified DoF ordinal
   *      -- 0 node, 1 edge, 2 face, 3 volume [Range: 0..3]
   * subc_ordinal: ordinal of the subcell relative to its parent cell
   *      -- 0..n for each ordinal with the same subc dim [Range: <= DoF ordinal]
   * subc_dof_ordinal: ordinal of the DoF relative to the subcell
   * subc_num_dof: cardinality of the DoF set associated with this subcell.
   * xi, eta, mu (ξ, η, ζ): Parametric coordinate location of the DoF
   *      -- (Only first ndim values are valid)
   */

  char    name[EX_MAX_NAME + 1];
  int     cardinality; /* number of `basis` points == dimension of non-null subc_*, xi, eta, mu */
  int    *subc_dim;
  int    *subc_ordinal;
  int    *subc_dof_ordinal;
  int    *subc_num_dof;
  double *xi;
  double *eta;
  double *zeta;
} ex_basis;

The following code snippet shows the definition of a basis and outputting it to an already open exodus file:

    int    subc_dim[]         = {0, 0, 0, 0, 1, 1, 1, 1, 2};
    int    subc_ordinal[]     = {0, 1, 2, 3, 0, 1, 2, 3, 0};
    int    subc_dof_ordinal[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    int    subc_num_dof[]     = {1, 1, 1, 1, 1, 1, 1, 1, 1};
    double xi[]               = {-1.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0};
    double eta[]              = {-1.0, -1.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0};
    double zeta[]             = {1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0};

    struct ex_basis basis = (ex_basis){.name             = "HGRAD_QUAD_C2_FEM",
                                       .cardinality      = 9,
                                       .subc_dim         = subc_dim,
                                       .subc_ordinal     = subc_ordinal,
                                       .subc_dof_ordinal = subc_dof_ordinal,
                                       .subc_num_dof     = subc_num_dof,
                                       .xi               = xi,
                                       .eta              = eta,
                                       .zeta             = NULL};
    EXCHECK(ex_put_basis(exoid, basis));

Additional basis can be defined and output using code similar to above.

Reading one or more basis from an exodus file:

The following code snippets illustrate reading the basis definition(s) from an already open exodus file. There are two methods. In the first example, the ex_get_basis function allocates all memory to store the basis:

  int bas_cnt = 0;
  ex_basis *basis = NULL;
  ex_get_basis(exoid, &basis, &bas_cnt);

The function allocates memory for basis and for the array members inside the basis struct(s) and returns both basis and bas_cnt. In the next example, we allocate the memory for basis prior to calling the function. In this case, the function will still allocate memory for the array members inside the basis struct.

    auto bas_cnt = ex_get_basis_count(exoid);
    std::vector<ex_basis> exo_basis(bas_cnt);
    auto *pbasis = exo_basis.data();
    ex_get_basis(exoid, &pbasis, &bas_cnt);

At this point, the ex_basis structs are fully populated. When the information in the struct(s) is no longer needed, it can be free'd with another call to ex_initialize_basis_struct with the last argument set to -1 which indicates that memory should be freed (NOTE: Do not do this until the information in basis will no longer be accessed...) This function does not deallocate the memory for basis:

  ex_initialize_basis_struct(basis, bas_cnt, -1);
  free(basis);
  basis = NULL;

Additional Information

The basis implementation borrows heavily from Intrepid

Quadrature

In a manner very similar to the basis API described above, zero or more quadrature rules can be defined on a database for use by the fields.

Storing one or more Quadrature in an exodus file:

Multiple named basis can be defined on a database and referenced by zero or more fields.

  • The exodus API has a ex_quadrature structure defined as:
typedef struct ex_quadrature
{
  char    name[EX_MAX_NAME + 1];
  int     cardinality; // Number of quadrature points
  int     dimension;   // 1,2,3 -- spatial dimension of points
  double *xi;          // xi  (x) coordinate of points; dimension = cardinality  or NULL
  double *eta;         // eta (y) coordinate of points; dimension = cardinality if dimension = 2 or 3 or NULL
  double *zeta;        // zeta (z) coordinate of points; dimension = cardinality if dimension == 3. or NULL
  double *weight;      // weights for each point; dimension = cardinality or NULL
} ex_quadrature;

The following code snippet shows the definition of a quadrature and outputting it to an already open exodus file:

    double Q        = 1.0 / sqrt(3.0);
    double xi[]     = {-Q, Q, -Q, Q, -Q, Q, -Q, Q};
    double eta[]    = {-Q, -Q, Q, Q, -Q, -Q, Q, Q};
    double zeta[]   = {-Q, -Q, -Q, -Q, Q, Q, Q, Q};
    double weight[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};

    struct ex_quadrature quad = (ex_quadrature){
        .name = "2x2x2", .cardinality = 8, .xi = xi, .eta = eta, .zeta = zeta, .weight = weight};
    ex_put_quadrature(exoid, quad);

Additional quadratures can be defined and output using code similar to above.

Reading one or more quadratures from an exodus file:

The following code snippets illustrate reading the quadrature definition(s) from an already open exodus file. In the first example, all memory for both the quad structures and the array members inside the quad structures are allocated in the function and it returns both quad and quad_cnt:

  int quad_cnt = 0;
  ex_quadrature *quad = NULL;
  ex_get_quadrature(exoid, &quad, &quad_cnt);

In the second example, we allocate the memory external to the function and it only allocates the memory for the internal array members of the struct:

    auto quad_cnt = ex_get_quadrature_count(exoid);
    std::vector<ex_quadrature> exo_quadrature(quad_cnt);
    auto *pquad = exo_quadrature.data();
    ex_get_quadrature(exoid, &pquad, &quad_cnt);

At this point, the ex_quadrature structs are fully populated. When the information in the struct(s) is no longer needed, it can be free'd with another call to ex_initialize_quadrature_struct with the last argument set to -1 which indicates that memory should be freed (NOTE: Do not do this until the information in quad will no longer be accessed...):

  ex_initialize_quadrature_struct(quad, quad_cnt, -1);
  free(quad);
  quad = NULL;

Defining a field that uses a quadrature:

Here we will define a field named Strain which uses the above-defined quadrature. The field will be stored on an element block with an id of 10:

    struct ex_field field = (ex_field){.entity_type         = EX_ELEM_BLOCK,
                                       .entity_id           = 10,
                                       .name                = "Strain",
                                       .type_name           = {"2x2x2"},
                                       .type                = {EX_QUADRATURE},
                                       .nesting             = 1,
                                       .component_separator = {'-'}};
    EXCHECK(ex_put_field_metadata(exoid, field));

Note that the type field of the ex_field struct specifies that the field will be of type EX_QUADRATURE and the type_name field specifies the name (2x2x2) of the quadrature which will be used to determine the cardinality of the field and the location of each component of the field. This field will have 8 components and they will be named: Strain-1, ..., Strain-8

This field will store a scalar at each of the 8 quadrature locations.

Reading the Field metadata:

Reading the field metadata is similar to what was done for the basis:

    int fld_cnt = ex_get_field_metadata_count(exoid, EX_ELEM_BLOCK, 11);

    ex_field *fields = malloc(fld_cnt * sizeof(ex_field));

    fields[0].entity_id = 11;
    fields[0].entity_type = EX_ELEM_BLOCK;

    fields[1].entity_id = 11;
    fields[1].entity_type = EX_ELEM_BLOCK;

    fields[2].entity_id = 11;
    fields[2].entity_type = EX_ELEM_BLOCK;

    ex_get_field_metadata(exoid, fields);

This will populate most of the entries in the ex_field struct(s). Note that the cardinality entry will only be filled for the EX_FIELD_TYPE_USER_DEFINED and EX_FIELD_TYPE_SEQUENCE fields. If the type is EX_BASIS, then the cardinality is specified by the basis cardinality and for type EX_QUADRATURE by the quadrature cardinality. For all other types, the cardinality is based on the field type and can be determined via a call to ex_field_cardinality(field_type)

If the field type is EX_FIELD_USER_DEFINED, then the suffices struct entry will contain a comma-separated list of the suffices to be used with the field. For example, the following shows the contents of a field struct for a user-defined field:

    {.entity_type = EX_ELEM_BLOCK,
     .entity_id   = 143,
     .name        = "Species",
     .type        = {EX_FIELD_TYPE_USER_DEFINED},
     .type_name   = {},
     .nesting     = 1,
     .cardinality = {4},
     .component_separator = {'_'},
     .suffices={"h2o,gas,ch4,methane"}};

This represents a field named "Species" with 4 components. The components would be named "Species_h20", "Species_gas", "Species_ch4", and "Species_methane".

Exodus API Functions

Defines

#define EX_MAX_FIELD_NESTING 2

Enumerations

enum ex_field_type {
  EX_FIELD_TYPE_INVALID = 0,
  EX_FIELD_TYPE_USER_DEFINED,
  EX_FIELD_TYPE_SEQUENCE,
  EX_BASIS,
  EX_QUADRATURE,
  EX_SCALAR,
  EX_VECTOR_1D,
  EX_VECTOR_2D,
  EX_VECTOR_3D,
  EX_QUATERNION_2D,
  EX_QUATERNION_3D,
  EX_FULL_TENSOR_36,
  EX_FULL_TENSOR_32,
  EX_FULL_TENSOR_22,
  EX_FULL_TENSOR_16,
  EX_FULL_TENSOR_12,
  EX_SYM_TENSOR_33,
  EX_SYM_TENSOR_31,
  EX_SYM_TENSOR_21,
  EX_SYM_TENSOR_13,
  EX_SYM_TENSOR_11,
  EX_SYM_TENSOR_10,
  EX_ASYM_TENSOR_03,
  EX_ASYM_TENSOR_02,
  EX_ASYM_TENSOR_01,
  EX_MATRIX_2X2,
  EX_MATRIX_3X3
};
typedef enum ex_field_type ex_field_type;

Structures

typedef struct ex_field
{
  ex_entity_type entity_type;
  int64_t        entity_id;
  char           name[EX_MAX_NAME + 1]; /* Name of the field */
  /*
   * For basis, user, quadrature -- what is name of the subtype. This
   * is a comma-separated list of `nesting` names Use two consecutive
   * commas for an empty type_name. Leave empty if no type_names
   */
  int           nesting; /* Number of composite fields (vector at each quadrature point = 2) */
  char          type_name[EX_MAX_NAME + 1];
  ex_field_type type[EX_MAX_FIELD_NESTING];                /* ex_field_type of each nested field */
  int           cardinality[EX_MAX_FIELD_NESTING];         /* 0 to calculate based on type */
  char          component_separator[EX_MAX_FIELD_NESTING]; /* empty defaults to '_'; */
  char          suffices[EX_MAX_NAME + 1]; /* Optional comma-separated list of suffices if type is
                                              EX_FIELD_TYPE_USER_DEFINED */
} ex_field;
typedef struct ex_basis
{
  /*
   clang-format off
   *
   * subc_dim: dimension of the subcell associated with the specified DoF ordinal 
   *      -- 0 node, 1 edge, 2 face, 3 volume [Range: 0..3]
   * subc_ordinal: ordinal of the subcell relative to its parent cell
   *      -- 0..n for each ordinal with the same subc dim [Range: <= DoF ordinal] 
   * subc_dof_ordinal: ordinal of the DoF relative to the subcell 
   * subc_num_dof: cardinality of the DoF set associated with this subcell. 
   * xi, eta, mu (ξ, η, ζ): Parametric coordinate location of the DoF 
   *      -- (Only first ndim values are valid)
   *
   clang-format on
   */

  char    name[EX_MAX_NAME + 1];
  int     cardinality; /* number of `basis` points == dimension of non-null subc_*, xi, eta, mu */
  int    *subc_dim;
  int    *subc_ordinal;
  int    *subc_dof_ordinal;
  int    *subc_num_dof;
  double *xi;
  double *eta;
  double *zeta;
} ex_basis;
typedef struct ex_quadrature
{
  char    name[EX_MAX_NAME + 1];
  int     cardinality; /* Number of quadrature points */
  int     dimension;   /* 1,2,3 -- spatial dimension of points */
  double *xi;          /* xi  (x) coordinate of points; dimension = cardinality  or NULL */
  double *
      eta; /* eta (y) coordinate of points; dimension = cardinality if dimension = 2 or 3 or NULL */
  double
      *zeta; /* zeta (z) coordinate of points; dimension = cardinality if dimension == 3. or NULL */
  double *weight; /* weights for each point; dimension = cardinality or NULL */
} ex_quadrature;

Fields

int ex_put_field_metadata(int exoid, const ex_field field);
int ex_put_field_suffices(int exoid, const ex_field field, const char *suffices);
int ex_get_field_metadata(int exoid, ex_field *field);
int ex_get_field_metadata_count(int exoid, ex_entity_type obj_type, ex_entity_id id);
int ex_get_field_suffices(int exoid, const ex_field field, char *suffices);

Basis

int ex_get_basis_count(int exoid);
int ex_get_basis(int exoid, ex_basis **basis, int *num_basis);
int ex_put_basis(int exoid, const ex_basis basis);

Quadrature

int ex_get_quadrature_count(int exoid);
int ex_get_quadrature(int exoid, ex_quadrature **quad, int *num_quad);
int ex_put_quadrature(int exoid, const ex_quadrature quad);

Utility

int ex_initialize_basis_struct(ex_basis *basis, size_t num_basis, int mode);
int ex_initialize_quadrature_struct(ex_quadrature *quad, size_t num_quad, int mode);

const char *ex_component_field_name(ex_field *field, int component[EX_MAX_FIELD_NESTING]);
const char *ex_field_component_suffix(ex_field *field, int nest_level, int component);
int         ex_field_cardinality(const ex_field_type field_type);
const char *ex_field_type_name(const ex_field_type field_type);
ex_field_type ex_string_to_field_type_enum(const char *field_name);
const char   *ex_field_type_enum_to_string(const ex_field_type field_type);

IOSS API / Usage

Define a basis

      Ioss::Basis basis;
      for (int i = 0; i < cardinality; i++) {
        Ioss::BasisComponent bc{
            subc_dim[i],     subc_ordinal[i], subc_dof_ordinal[i],
            subc_num_dof[i], xi[i],           eta[i],
            zeta[i]};
        basis.basies.push_back(bc);
      }
      Ioss::VariableType::create_basis_type(name, basis);

Basis Variable Type

namespace Ioss {
  struct BasisComponent
  {
    int    subc_dim;
    int    subc_ordinal;
    int    subc_dof_ordinal;
    int    subc_num_dof;
    double xi;
    double eta;
    double zeta;
  };

  struct Basis
  {
    size_t                      size() const;
    std::vector<BasisComponent> basies;
  };

  class BasisVariableType : public VariableType
  {
  public:
    std::string label(int which, const char /* suffix_sep */) const override

    BasisVariableType(const std::string &my_name, const Ioss::Basis &basis, bool delete_me);
    BasisVariableType(const BasisVariableType &) = delete;

    VariableType::Type type() const override { return Type::BASIS; }
    std::string type_string() const override { return "Basis"; }

    const Ioss::Basis &get_basis() const;
    const Ioss::BasisComponent &get_basis_component(int which) const;
    void print() const override final;
  };
}

Define a quadrature

The IOSS QuadratureVariableType is a class containing the following information:

Quadrature Variable Type

namespace Ioss {
  struct QuadraturePoint
  {
    double xi;
    double eta;
    double zeta;
    double weight;
  };

  class QuadratureVariableType : public VariableType
  {
  public:
    std::string label(int which, const char /* suffix_sep */) const override

    QuadratureVariableType(const std::string &my_name, const std::vector<Ioss::QuadraturePoint> &quad_points, bool delete_me);
    QuadratureVariableType(const QuadratureVariableType &) = delete;

    VariableType::Type type() const override { return Type::QUADRATURE; }
    std::string type_string() const override { return "Quadrature"; }

    std::vector<Ioss::QuadraturePoint> get_quadrature() const;
    Ioss::QuadraturePoint get_quadrature_component(int which) const;
    
    void print() const override final;
  };
}

When an exodus file that contains quadrature definitions is read by the IOSS library, there will be a QuadratureVariableType created for each quadrature definition on the exodus database.

To create a quadrature type in IOSS, you can use:

      std::vector<Ioss::QuadraturePoint> quadrature;
      quadrature.reserve(cardinality_of_quadrature);
      for (int i = 0; i < cardinality_of_quadrature; i++) {
        Ioss::QuadraturePoint q{xi[i], eta[i], zeta[i], weight[i]};
        quadrature.push_back(q);
      }
      Ioss::VariableType::create_quadrature_type(quadrature_name, quadrature);

If this is on an output exodus database, this quadrature definition will be defined on the output database.

Define a field that uses a basis or quadrature

To define a single-level (non-composite) field that uses a basis or quadrature type, you pass in the name of the basis or quadrature in the storage type field of the Ioss::Field constructor. For example, to create a field named curl that is defined as a basis type HGRAD_QUAD_C2_FEM, the following code would be used:

     auto my_field = Ioss::Field("curl", Ioss::Field::REAL, "HGRAD_QUAD_C2_FEM", Ioss::Field::TRANSIENT, entity->entity_count());

The Basis type HGRAD_QUAD_C2_FEM must already be defined prior to this call.

A composite field, for example a field which defines a 3D vector of values at each quadrature point of a 2x2x2 quadrature rule would be defined as:

     auto comp_field = Ioss::Field(name, Ioss::Field::REAL, "vector3d", "2x2x2", Ioss::Field::TRANSIENT, entity->entity_count());

This would define a field with 3 vector components at each of the 8 quadrature points.

Define a user-defined field

A user-defined field is a field in which the cardinality and the suffices are defined by the user. The IOSS library represents this as a field which uses a Ioss::NamedSuffixVariableType storage. The storage type is defined via:

          std::vector<std::string> suffices = {"earth", "wind", "fire", "water");
          auto *nst = Ioss::VariableType::create_named_suffix_type("My_Type", suffices);

This creates a storage type named nst which can be used in the definition of an Ioss::Field. If the field is named Elements, then it will have 4 components named: Elements_earth, Elements_wind, Elements_fire, and Elements_water.

Query what non-internal types are defined

The external_types function can be used to determine the non-internal types that are defined on an IOSS Region:

std::vector<Ioss::VariableType*> Ioss::VariableType::external_types(Ioss::VariableType::Type type);

The valid values for the type argument are:

Variable Types:

    enum class Type {
      UNKNOWN,
      SCALAR,
      STANDARD,
      COMPOSED,
      COMPOSITE,
      CONSTRUCTED,
      ELEMENT,
      NAMED_SUFFIX,
      BASIS,
      QUADRATURE
    };

If UNKNOWN is passed as the argument, then all defined types will be returned; otherwise, only types matching the argument type will be returned. The types in the returned vector can be dynamically cast to their type in order to query their definitions. For example, the following will retrieve all quadrature types and print their internal details:

    auto var_list = Ioss::VariableType::external_types(Ioss::VariableType::Type::QUADRATURE);
    for (auto &var : var_list) {
      quad_type = dymamic_cast<Ioss::QuadratureVariableType*>(var);
      if (quad_type != nullptr) {
         quad_type->print();
      }
    }

NOTE: For this example, the dynamic_cast is not needed since print() is a virtual function. It is just used to illustrate getting a variable of type QuadratureVariableType

If you have an Ioss::VariableType and need to know what type it is, you can call the type() function and it will return one of the types in the Type enum above.

Query contents of a specific basis

Query contents of a specific quadrature

Determine the storage type used by a field

    Ioss::VariableType *storage = fld.raw_storage();
    if (storage->type() == Ioss::VaribleType::Type::COMPOSED) {
       // This is a composed field consisting of two storage types...
       // The two types are:
       auto *composed = dynamic_cast<Ioss::ComposedVariableType*>(storage);
       auto *primary_type = composed->get_base_type();
       auto *secondary_type = composed->get_secondary_type();
       // The types of these can be determined following similar logic...
    }
    else if (storage->type() == Ioss::VariableType::Type::COMPOSITE) {
       // This is a `composite` type which means that it consists of multiple copies of the primary storage type...
       auto *composite = dynamic_cast<Ioss::CompositeVariableType*>(storage);
       auto *primary_type = composite->get_base_type();
       int num_copies = composite->get_num_copies();
       // The type of `primary_type` can be determined following similar logic...
    } 
    else if (storage->type() == Ioss::VariableType::Type::QUADRATURE) {
       // The field is a "single-level" field defined on a quadrature type
       // `storage->name()` will give the name of the quadrature type.
    }
    else if (storage->type() == Ioss::VariableType::Type::BASIS) {
       // The field is a "single-level" field defined on a basis type
       // `storage->name()` will give the name of the basis type.
    }
    else if (storage->type() == Ioss::VariableType::Type::NAMED_SUFFIX) {
       // The field is a "single-level" field defined on a named-suffix type
       // `storage->name()` will give the name of the type
    }

What currently supports enhanced field metadata...

The following support the enhanced field metadata if you checkout the discontinuous-galerkin branch on the seacas github repository.

  • Exodus library
  • IOSS library
  • io_info executable will show fields and whether they use basis or quadrature if the --detailed_field option is used
    • Need to add an option which will display the quadrature and/or basis on a database
  • io_shell will read and write the enhanced field metadata information.