Skip to content

Assemblies

Greg Sjaardema edited this page Jan 10, 2020 · 40 revisions

NOTE: The logical name of this concept would be group, but there is already an experimental group use in Exodus which use the NetCDF group implementation.

The existing experimental grouping concept is basically a hierarchy of Exodus (NetCDF) files inside an Exodus file. This group has never been officially released and I am probably the only one to use it, so an option would be to rename the existing Exodus experimental group to be file_group or db_group and then use group instead of assembly.

For now, the exodus grouping concept will be called assembly ** NOTE: In the description below, object type refers to a node, edge, face, side, or element set; or an edge face, or element block.

Motivation

Currently, the primary "grouping" entity in an exodus model is an element block which describes a collection of one or more elements of homogenous topology (i.e., all hexes or all tets or all quads). The model also contains a global "block" of nodes, and various sets and blocks consisting of nodes (sets only), edges, faces, and elements; however, the element block is the primary grouping mechanism.

As model complexity increases, a mesh (or exodus model) can contain tens to hundreds to thousands of element blocks. As geometric model complexity increases, it may be necessary to split geometric volumes or "parts" into multiple "meshable volumes" each of which becomes an individual element block (for example a complicated volume may be mostly meshable using hexes, but may in addition have a few areas that require tets and then some transition wedges from the tets to hexes. Since element blocks are homogenous topology, that part would consist of at least three element blocks). It can become very difficult for an analyst to remember the mapping from element blocks back to the geometric part in the solid model.

Another issue is that the assembly hierarchy in the original solid model or geometry is lost in the exodus file where there is simply a flat list of element blocks.

The assembly concept described herein is an attempt to solve these issues in the exodus model. An assembly will be a homogenous collection of one or more element blocks OR one or more assemblies. Note that a mixed collection of element blocks AND assemblies is not allowed. An assembly cannot contain itself, either directly or indirectly.

An assembly will have:

  • Member list. Either:
    • non-empty list of element blocks belonging to assembly
    • non-empty list of assemblies belonging to the assembly.
    • non-empty homogeneous list of node, edge, face, side, or element sets is also allowed.
    • non-empty homogeneous list of edge or face blocks is also allowed.
  • unique id (required, positive, unique among assemblies)
  • unique name (required, unique among all blocks/sets/assemblies)
  • zero or more named attributes (optional)
    • attribute is per assembly, not a value for each entity in the assembly.
    • attribute can be of type--integer, char*, double.
    • if type is integer or double, then there can be multiple values per attribute.
    • NOTE: Named entity attributes can also be applied to other blocks and sets and GLOBAL.
  • transient data, zero or more named real variables which are output on each timestep. (optional)
    • The variables are termed "reduction variables" since they are a single value per assembly.
    • [UNDECIDED] The reduction variables can be unique to a specific assembly
    • [UNDECIDED] Not all reduction variables must be on all assemblies.
    • Names should be unique across all transient reduction variable names in the model.
    • Value is per assembly, not per entity in the assembly.
    • NOTE: transient reduction variables are also supported on all block and set objects.

Note that although the assembly feature will represent a directed acyclic graph of assemblies, blocks, and sets, the exodus file does not know how to manipulate this graph, do any queries based on the graph, or validate the graph. Exodus does not know about parent-child relations or roots or leaves of the tree/graph. The structure of this graph; however, will be able to be constructed based on the data stored in the exodus file.

Note that, conceptually, there is a "global assembly" which contains all element blocks in the model. The current "global variables" are in essence the reduction variables for this global assembly. Not sure yet if this provides any benefit in the implementation...

  • An assembly or object type can be in multiple assemblies. The structure is a directed acyclic graph
  • Assemblies will be homogenous -- all members of a specific assembly will be all element blocks or all assemblies or all same object type.
  • [UNDECIDED] An assembly or object type can appear multiple times in a single assembly. Potentially useful for a part containing multiple copies of a part -- perhaps offset for each instance. Example is a bolt circle of multiple instances of the same bolt each offset by a specified angle from a specified center point.

Parallel

The assembly data will be replicated on all processors--Each processor will have the same data.

Undecided

  • As implemented, any object type can be in an assembly. Must be homogenous type within an assembly.
    • There are benefits to allowing assemblies of side sets, especially if move to using the Block Dominant models.
    • What is homogenous in this case. Can an assembly contain an "assembly of element blocks" and an "assembly of sidesets" or is that a non-homogenous collection...? As implemented, can have an assembly containing an assembly of element blocks and an assembly of sidesets.

C API

  • [NOT YET IMPLEMENTED] Will also have to provide wrapper functions for the fortran API.

Determine number of assemblies on an exodus file:

 int num_assembly = ex_inquire_int(exoid, EX_INQ_ASSEMBLY);

Define number of assemblies which will be defined in an exodus file:

The number of assemblies does not need to be specified prior to defining an assembly.

Get / Put names of all assemblies or an individual assembly:

Assembly names are set and queried via the ex_put_assemblies and ex_get_assemblies functions. They are somewhat different from other object types in that the names are not defined via the ex_put_name calls. However, the names can be queried using ex_get_name() although it is recommended to use the ex_get_assembly() function.

 ex_get_name(exoid, EX_ASSEMBLY, assembly_id, name);
 ex_get_names(exoid, EX_ASSEMBLY,  names[]);

Define and output assembly

typedef struct ex_assembly
{
  int64_t        id;
  char           *name;
  ex_entity_type type; /* EX_ELEM_BLOCK or EX_ASSEMBLY or other valid object type */
  int64_t        entity_count;
  void_int       *entity_list;
} ex_assembly;

ex_put_assembly(exoid, ex_assembly assembly);
ex_put_assemblies(exoid, num_assembly, ex_assembly *assemblies);

Defines and outputs an assembly with id id and name name consisting of entity_count entities of type entity_type (EX_ASSEMBLY or EX_ELEM_BLOCK). The list of entity_count entities is given by the ids in entity_list. If entity_list is NULL, then only the "metadata" of the assembly is defined. A subsequent call to ex_put_assembly or ex_put_assemblies with a non-NULL entity_list is required to fully define the assembly.

NOTE: Efficiency -- it is more efficient to define multiple assemblies via a call to ex_put_assemblies() then to call ex_put_assembly() multiple times.

   /* conceptual; not real C code */
   ex_assembly assembly[3];
   assembly.id = {11, 22, 3};
   assembly.name = {"Fred", "Wilma", "BamBam"};
   assembly.type = {EX_ELEM_BLOCK, EX_ELEM_BLOCK, EX_ASSEMBLY};
   assembly.entity_count = {2, 2, 2};
   assembly[0].entity_list = {10, 20};
   assembly[1].entity_list = {30, 40};
   assembly[2].entity_list = {11, 22};
   ex_put_assemblies(exoid, 3, assembly);

Read all assemblies

   int num_assembly = ex_inquire_int(exoid, EX_INQ_ASSEMBLY);
   int64_t ids[num_assembly];
   ex_get_ids(exoid, EX_ASSEMBLY, ids);

   int name_len = ex_inquire_int(exoid, EX_INQ_DB_MAX_USED_NAME_LENGTH);
   struct ex_assembly assemblies[num_assembly];
   for (int i=0; i < num_assembly; i++) {
      assemblies[i].entity_list = NULL;
      assemblies[i].name = malloc(name_len + 1);
   } 
   /* Get parameters for all assemblies, but not entity_list */
   ex_get_assemblies(exoid, assemblies);

   for (i = 0; i < num_assembly; i++) {
      int entity_count = assemblies[i].entity_count;
      assemblies[i].entity_list = malloc(entity_count * sizeof(INT));
   }

   /* Now get the entity_lists */
   ex_get_assemblies(exoid, assemblies);

Define/Query assembly attributes and attribute names

An assembly attribute is similar to an IOSS property consisting of a name, a type, and a value or values. It is not a value per entity in the assembly, but a value for the assembly. For now, they types will be limited to text, integer, and double to provide capability without the complexity of supporting the many types available in NetCDF-4 including user-defined types. Note that an attribute can have multiple values, for example if the attribute is a range, a single attributed could have two double values -- {1.0, 100.0}

NOTE: This type of attribute (value on entity instead of value per entities members, for example nodes in a nodeset) has also be added to the other entity types (blocks and sets).

NOTE: Need a better name or way of distinguishing from the attributes which are currently supported in Exodus.

  • define and output an attribute
   ex_attribute attribute = {EX_ASSEMBLY, 100, "Offset", EX_DOUBLE, 3, {1.1, 2.2, 3.3}};
   ex_put_attribute(exoid, attribute);
  • define and output multiple attributes
   ex_attribute attributes[10];
   /* ... Initialize `attributes` */
   ex_put_attributes(exoid, 10, attributes);
  • define and output a double attribute
   ex_put_double_attribute(exoid, EX_ASSEMBLY, id, name, num_values, values);
  • define and output an integer attribute
   ex_put_integer_attribute(exoid, EX_ASSEMBLY, id, name, num_values, values);
  • [The size of the integers used in values is based on int-size of the database]
  • define and output a text attribute....
   ex_put_text_attribute(exoid, EX_ASSEMBLY, id, name, char *values);
  • Query number of attributes on an assembly
   int num_attr = ex_get_attribute_count(exoid, EX_ASSEMBLY, id);
  • Query names, types, size/length, values of all attributes on an assembly
   int num_attr = ex_get_attribute_count(exoid, EX_ASSEMBLY, id);
   ex_attribute attributes[num_attr];
   ex_get_attributes(exoid, EX_ASSEMBLY, id, attributes);
  • Get values of all attributes on the specified entity (ASSEMBLY 100)
   int count = ex_get_attribute_count(exoid, EX_ASSEMBLY, 100)
   ex_attribute attributes[count];
   memset(attributes, 0, sizeof(ex_attribute)*count);
   ex_get_attribute_param(exoid, EX_ASSEMBLY, 100, attributes);
   /* Get attribute values for all attributes listed in `attributes`
      `ex_get_attributes()` will allocate memory for `attributes[i].values` 
      which must be freed by caller.
    */
   ex_get_attributes(exoid, count, attributes);
  • Get value of specific attribute on the specified entity (ASSEMBLY 100)
   int count = ex_get_attribute_count(exoid, EX_ASSEMBLY, 100)
   ex_attribute attributes[count];
   ex_get_attribute_param(exoid, EX_ASSEMBLY, 100, attributes);
   /* ... allocate space on specific attribute to store values. If
    *     not allocated, it will be allocated by `ex_get_attribute()`
    */
   attributes[3].values = calloc(attributes[3].value_count, sizeof(double));
   /* Get attribute values for attribute at index 3 */
   ex_get_attribute(exoid, attributes[3]);

The ex_attribute argument is the struct:

typedef struct ex_attribute
{
  ex_entity_type entity_type,
  int64_t   entity_id,
  char  name[EX_MAX_NAME];
  ex_type type; /* EX_INTEGER, EX_DOUBLE, EX_CHAR */
  size_t   value_count;
  void* values; /* not accessed if NULL */
} ex_attribute;

NOTE: is there benefit to getting all attribute names/types/size in single call, or can we simplify API and just provide the query of individual attribute. Can do this in single call if only fill in non-NULL arguments

Define/Query number of variables on an assembly

 ex_put_reduction_variable_param(exoid, EX_ASSEMBLY, num_variables);
 ex_get_reduction_variable_param(exoid, EX_ASSEMBLY, &num_variables);

[UNDECIDED] Can also define and query the assembly truth table which will specify which variables are defined on which assemblies.

Note that there will be a value written to and read from the database for all variables on all assemblies, but only the values corresponding to a defined variable will be valid values.

Define / Query names of variables on an assembly

 ex_put_reduction_variable_names(exoid, EX_ASSEMBLY, num_variables, names);
 ex_get_reduction_variable_names(exoid, EX_ASSEMBLY, num_variables, names);
 ex_put_reduction_variable_name(exoid, EX_ASSEMBLY, index, name);
 ex_get_reduction_variable_name(exoid, EX_ASSEMBLY, index, name);

Write / Read data for assembly variable(s) at a time step

All variables for a single assembly (with id id):

  ex_get_reduction_vars(exoid, step, EX_ASSEMBLY, id, num_assem_vars, values);
  ex_put_reduction_vars(exoid, step, EX_ASSEMBLY, id, num_assem_vars, values);

Representation options

In the examples that follow, assume for illustration that there are 3 assemblies and 4 element blocks. Assembly 11 is blocks 10 and 30; Assembly 22 is blocks 20 and 40; Assembly 33 is assemblies 11 and 22.

The assembly is represented with an explicit listing of the entity type (EX_ELEM_BLOCK or EX_ASSEMBLY), count, and a list of the ids of the entities.

type count members
A1 element 2 10, 30
A2 element 2 20, 40
A3 assembly 2 11, 22

CDF Representation options

Assembly definition

variables:
# How are assemblies represented in the file:
# Note that assemblies are homogenous, so members are either all element blocks or all assemblies
# Each assembly is defined individually.  Either a set of element blocks or a set of assemblies
# Currently the assembly id is used in the names of the `dim` and `var` representing assemblies.
# This makes it easier to add new assemblies to an existing file.
# NOTE: Currently redundancy in `id` and in `_type` and `_typename`
	int assembly_entity100(num_entity_assembly100) ;
		assembly_entity100:_id = 100 ;
		assembly_entity100:_type = 16 ;
		assembly_entity100:_name = "Root" ;
		assembly_entity100:_typename = "assembly" ;

Assembly Attributes

variables:
	int assembly_entity100(num_entity_assembly100) ;
		assembly_entity100:_id = 100 ;
		assembly_entity100:_type = 16 ;
		assembly_entity100:_name = "Root" ;
		assembly_entity100:_typename = "assembly" ;
		assembly_entity100:Scale = 1.5 ;
		assembly_entity100:Units = 1, 0, 0, -1 ;

In the above, there are two user-defined attributes Scale which is a single double value and Units which is a set of 4 integers.

Assembly Reduction Variables

dimensions:
	num_assembly_red_var = 4 ;
variables:
	char name_assembly_red_var(num_assembly_red_var, len_name) ;
		name_assembly_red_var:_FillValue = "" ;
	double vals_red_var_assembly100(time_step, num_assembly_red_var) ;
data:
 vals_red_var_assembly100 =
  0.02, 0.03, 0.04, 0.05,
  0.04, 0.06, 0.08, 0.1,
  0.06, 0.09, 0.12, 0.15,
  0.08, 0.12, 0.16, 0.2,
  0.1, 0.15, 0.2, 0.25,
  0.12, 0.18, 0.24, 0.3,
  0.14, 0.21, 0.28, 0.35,
  0.16, 0.24, 0.32, 0.4,
  0.18, 0.27, 0.36, 0.45,
  0.2, 0.3, 0.4, 0.5 ;

Example Assembly Output/Input Code

See the files testwt-assembly.c, testrd-assembly.c and test-add-assembly.c in the packages/seacas/libraries/exodus/test directory.

Additional Changes

IOSS

  • Add an Assembly Ioss::GroupingEntity class
  • Read assembly information from Exodus file and create the assembly graph in an Ioss Region.
  • IOSS will know parent/child information so could support graph-structure queries.
  • Client can modify assembly structure which will be persisted to the output database.
    • Modifying assembly structure of an input database will not modify structure in existing exodus file.

SEACAS Applications

Applications will need to be modified to support assemblies. Unless noted below, a SEACAS application will not know about assemblies or replicate them to an output file the application creates (i.e., assembly information will be lost).

  • EPU: Assume assembly structure the same on all processor files; replicate to the output file(s)

  • CONJOIN: Assembly structure in the first database will be replicated to output file. If any part subsetting, then the part will be removed from any assemblies it was in.

  • EJOIN: Output file will have merged assemblies from input files.

    • Maybe create new assembly consisting of element blocks from each input file to make it easy to see where blocks came from...
  • EXODIFF: Can do diff of assembly attributes and transient data.

  • [p] IO_INFO: Show assembly graph. (Understands assemblies; shows assembly contents)

  • IO_SHELL: replicate to output file.

  • Scoping Needed:

    • EXPLORE: Scope what is needed to show assembly structure.
    • GREPOS: what is needed to replicate assembly structure to output file.
    • APREPRO: Is there a benefit to having aprepro access to assembly data.

Creation / Modification of Assemblies

  • Could modify grepos to add assembly information to the output file
  • Could modify io_shell
  • Create new application which adds assembly information to existing file without rewriting.