-
Notifications
You must be signed in to change notification settings - Fork 79
Enhanced Field Metadata (was Discontinuous Galerkin)
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 implememtation 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.
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.
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.
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;
The basis implementation borrows heavily from Intrepid
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.
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.
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;
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 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".
#define EX_MAX_FIELD_NESTING 2
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;
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;
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);
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);
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);
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::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);
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;
};
}
The IOSS QuadratureVariableType
is a class containing the following information:
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. A list of all quadrature types that have been defined can be retrieved via the Ioss::VariableType::external_types(Ioss::VariableType::Type::QUADRATURE);
function call which will return a vector of VariableType*
which can be dynamically cast to QuadratureVariableType*
.
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.
enum class Type {
UNKNOWN,
SCALAR,
STANDARD,
COMPOSED,
COMPOSITE,
CONSTRUCTED,
ELEMENT,
NAMED_SUFFIX,
BASIS,
QUADRATURE
};
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.