Skip to content

Assemblies

Greg Sjaardema edited this page Feb 5, 2019 · 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. This grouping 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

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 nodes, and various sets and blocks consisting of nodes, edges, faces, and elements; however, the element block is the primary grouping mechanism.

As model complexity increases, a mesh (or exodus model) can contain hundreds to thousands of element blocks. As geometric model complexity increases, it may be necessary to split geometric volumes into multiple "meshable volumes" each of which becomes an individual element block. It can become very difficult for an analyst to remember the mapping from element blocks back to the geometric volume 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 bunch of element blocks.

The assembly concept described herein is an attempt to solve both of 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. [may be relaxed depending on representation chosen in file]. An assembly cannot contain itself, either directly or indirectly.

An assembly will have:

  • Member list. Either:
    • list of element blocks belonging to assembly
    • list of assemblies belonging to the assembly.
  • unique id (required)
  • unique name (required)
  • one or more named attributes (optional)
    • attribute is a single value per assembly, not a value for each entity in the assembly.
    • attribute can be of any supported NetCDF type (integer, char*, double, float)
  • transient data, one or more real variables which are output on each timestep. (optional)
    • The variables can be unique to a specific assembly
    • Not all variables must be on all assemblies.
    • Value is per assembly, not per entity in the assembly. Reduction variable
    • [future] Also add reduction variables to other blocks and sets.

Note that although the assembly feature will represent a directed acyclic graph of assemblies / element blocks, the exodus file might not know how to manipulate this graph or do any queries based on the graph. Exodus might 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 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...

Note. If the assembly structure defines a tree, then each assembly / element block will be in at most one assembly. We could then define an assembly tree just by specifying an optional parent for element block and assembly. Could require a single root or have a forest of trees with multiple roots. If we use a directed acyclic graph, then need to represent each assembly by its explicit membership list.

Undecided

  • Can an assembly or element block be in more than one assembly. Is this a tree or a graph?
  • Is there a benefit to supporting non-homogenous assemblies -- containing BOTH assemblies and element blocks.
  • Do we need to include other entity types (node sets, side sets, ...) in the assemblies [I would prefer just element blocks]

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 ex_init_params struct will be extended with an additional field int64_t num_assembly; The call to the ex_put_init_ext() function with that struct will then define the number of assemblies.

Note that possible that an older code will not know about added struct field and therefore will not initialize it so it is possible that uninitialized value will result in large number of unintended assemblies. Maybe add a function which enables use of assemblies (and future extensions). If not set, then the assembly field is ignored...

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

NOTE: Assembly names can also be set and queried via the ex_put_assemblies and ex_get_assemblies functions.

May only be called after call to ex_put_init_ext() for output file.

  • ex_put_name(exoid, EX_ASSEMBLY, assembly_id, name);
  • ex_put_names(exoid, EX_ASSEMBLY, names[]);
  • 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 */
  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_id_list.

   ex_init_params init;
   init.num_assembly = 3;
   ex_put_init_ext(exoid, init);

   /* conceptual; not real C code */
   ex_assembly assembly[3];
   assembly.id = {11, 22, 3};
   assembly.name = {"Fred";
   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].id = ids[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, num_assembly, 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, num_assembly, 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, it could have the value {1.0, 100.0}

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

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

  • 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) [Size of integer is based on int-size of the database]
  • define and output a text attribute....
  • Query number of attributes on an assembly
  • Query names of attributes on an assembly
  • Query names of a specific attribute on an assembly
  • Query types of attributes on an assembly
  • Query type of a specific attributed on an assembly
  • Query size / length of attributes on an assembly
  • Query size / length of a specific attribute on an assembly
  • Query value of a specific attribute on an assembly

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_variable_param(exoid, EX_ASSEMBLY, num_variables)
  • ex_put_all_var_param_ext(exoid, &variable_params)
  • ex_get_variable_param(exoid, EX_ASSEMBLY, &num_variables)

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. This is done so that we can access the chunk of num_vars * num_assemblies in a single write/read per timestep instead of doing num_vars * num_assemblies individual writes. [Is it possible / wanted to write / read the variables on an assembly by assembly basis...]

Define / Query names of variables on an assembly

  • ex_put_variable_names(exoid, EX_ASSEMBLY, num_variables, names)
  • ex_get_variable_names(exoid, EX_ASSEMBLY, num_variables, names)
  • ex_put_variable_name(exoid, EX_ASSEMBLY, index, name)
  • ex_get_variable_name(exoid, EX_ASSEMBLY, index, name)

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

All variables for all assemblies at one time:

  • ex_put_var(exoid, step, EX_ASSEMBLY, 0, 0, num_assem_vars, values)
  • ex_get_var(exoid, step, EX_ASSEMBLY, 0, 0, num_assem_vars, values)

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

  • ex_put_var(exoid, step, EX_ASSEMBLY, id, id, num_assem_vars, values)
  • ex_put_var(exoid, step, EX_ASSEMBLY, id, id, num_assem_vars, values)

[Not sure which or both of these should be supported. Need to distinguish somehow whether client is specifying all assemblies, or only a specific assembly. For blocks and sets, the first argument following type is variable index and the second is the entity id. Above I am using convention that if index is 0, then we are accessing all variables on all assemblies. In second use, using the convention that if index and id match and are non-zero, then specifying all values on a certain assembly. Is this flexibility worth the complexity...]

CDF Representation options

dimensions:
	num_assembly = 3 ;
	num_assembly_members = 11 ; # num_assembly + num_elem_blk

variables:
# How do we represent an assembly in the file:
# Note that assemblies are homogenous, so members are either all element blocks or all assemblies
# 1. Define each assembly individually.  Either a set of element blocks or a set of assemblies
#    Similar to option 3, but not as many NetCDF dimensions needed, but a little wasteful in space.
	int assembly1(num_elem_blk) ;
	    assembly1:name = "Fireset" ;
	    assembly1:type = "element" ;

	int assembly2(num_assembly) ;
	    assembly2:name = "Stronglink" ;
	    assembly2:type = "assembly" ;
	    
# 2. Define all assemblies at one time.
#    + can access all assemblies and all assembly names with single calls
     	int assembly(num_assembly, num_assembly_members) ;  # `num_assembly_members = num_assembly + num_elem_blk`
        char assembly_names(num_assembly, len_name) ;
	char name_assembly(num_assembly, len_name) ;
		name_assembly:_FillValue = "" ;

# Option 3: Each assembly individually with a dimension for each giving member count.
# o efficient if define all at once; efficient in space.
# + easier to define per-assembly attributes.
        int assembly_1(num_entities_1);
                assembly_1:entity_type = "element_block" ;
                assembly_1:name = "My Name" ;
                assembly_1:my_double_attribute = 3.14159, 42.0 ;
                assembly_1:part_number = 1239484764 ;

	# Option 1 -- assembly vars per assembly per variable
	# + Can specify only vars that exist on an assembly
	# + best granularity for type, dimension, unit [if ever supported in exodus]
	# - Verbose O(#assembly * #var) variables
	# - small output size -- double / timestep
	double vals_assembly_var1g1(time_step) ;
	double vals_assembly_var2g1(time_step) ;
	double vals_assembly_var3g1(time_step) ;
	double vals_assembly_var1g2(time_step) ;
	double vals_assembly_var3g2(time_step) ;
	double vals_assembly_var1g3(time_step) ;
	double vals_assembly_var2g3(time_step) ;

	# Option 2 -- assembly vars blob of all vars on all assemblies -- zero-fill if not valid for particular assembly
	# - Data for a variable even if doesn't exist on a specific assembly
	# + Compact O(1) variable
	# - All vars on all assemblies are of same type, dimension, unit [if ever supported in exodus]
	# + largest output size -- num_var * num_assembly doubles / timestep
	double vals_assembly(time_step, num_assembly, num_var) ;

	# Option 3 -- assembly vars -- all vars on a particular assembly.
	# + Can specify only vars that exist on an assembly
	# - All vars are of same type, dimension, unit [if ever supported in exodus]
	# + can have different variables on each assembly
	# o Somewhat Compact O(#assemblies) variables
	# o smallish output size -- num_var / timestep
	double vals_assembly1(time_step, num_vars_assembly1) ;

	# Option 3 -- assembly vars -- all vars on a particular assembly.
	# + Can specify only vars that exist on an assembly
	# + var for all assemblies are of same type, dimension, unit [if ever supported in exodus]
	# o Somewhat Compact O(#assemblies) variables
	# o smallish output size -- num_assembly / timestep
	double vals_assembly_var1(time_step, num_assemblies) ;