Working with data in GEOS
In GEOS
, data is typically registered in the Data Repository.
This allows for the writing/reading of data to/from restart and plot files.
Any object that derives from Group may have data registered on it
through the methods described in Group.
Similarly, accessing data from outside the scope of an object is possible
through one of the various Group::get()
.
Of course, for temporary data that does not need to persist between cycles,
or across physics packages, you may simply define member or local variable which
will not be registered with the Data Repository.
Working with data on the Mesh objects
The mesh objects in GEOS
such as the FaceManager
or NodeManager
,
are derived from ObjectManagerBase
, which in turn derives from Group.
The important distinction is that ObjectManagerBase
contains various members
that are useful when defining mesh object managers.
When considering data that is attached to a mesh object, we group the data into
two categories:
Intrinsic data is data that is required to describe the object. For instance, to define a
Node
, theNodeManager
contains an array of positions corresponding to eachNode
it contains. Thus theReferencePosition
isIntrinsic
data.Intrinsic
data is almost always a member of the mesh object, and is registered on the mesh object in the constructor of mesh object itself.Field data (or Extrinsic data) is data that is not required to define the object. For instance, a physics package may request that a
Velocity
value be stored on the nodes. Appropriately the data will be registered on theNodeManager
. However, this data is not required to define aNode
, and is viewed asFields
orExtrinsic
.Field
data is never a member of the mesh object, and is typically registered on the mesh object outside of the definition of the mesh object (i.e. from a physics solver).
Registering Intrinsic data on a Mesh Object
As mentioned above, Intrinsic
data is typically a member of the mesh object,
and is registered in the constructor of the mesh Object.
Taking the NodeManager
and the referencePosition
as an example, we
point out that the reference position is actually a member in the
NodeManager
.
/**
* @brief Get the mutable reference position array. This table will contain all the node coordinates.
* @return reference position array
*/
array2d< real64, nodes::REFERENCE_POSITION_PERM > & referencePosition() { return m_referencePosition; }
/**
* @brief Provide an immutable arrayView of the reference position. This table will contain all the node coordinates.
* @return an immutable arrayView of the reference position.
*/
arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > referencePosition() const
{ return m_referencePosition; }
This member is registered in the constructor for the NodeManager
.
NodeManager::NodeManager( string const & name,
Group * const parent ):
ObjectManagerBase( name, parent ),
m_referencePosition( 0, 3 )
{
registerWrapper( viewKeyStruct::referencePositionString(), &m_referencePosition );
Finally in order to access this data, the NodeManager
provides explicit
accessors.
/**
* @brief Get the mutable reference position array. This table will contain all the node coordinates.
* @return reference position array
*/
array2d< real64, nodes::REFERENCE_POSITION_PERM > & referencePosition() { return m_referencePosition; }
/**
* @brief Provide an immutable arrayView of the reference position. This table will contain all the node coordinates.
* @return an immutable arrayView of the reference position.
*/
arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > referencePosition() const
{ return m_referencePosition; }
Thus the interface for Intrinsic
data is set by the object that it is a part
of, and the developer may only access the data through the accesssors from
outside of the mesh object class scope.
Registering Field data on a Mesh Object
To register Field
data, there are many ways a developer may proceed.
We will use the example of registering a totalDisplacement
on the NodeManager
from the SolidMechanics
solver.
The most general approach is to define a string key and call one of the
Group::registerWrapper()
functions from PhysicsSolverBase::registerDataOnMesh()
.
Then when you want to use the data, you can call Group::getReference()
.
For example this would look something like:
void SolidMechanicsLagrangianFEM::registerDataOnMesh( Group * const MeshBodies )
{
for( auto & mesh : MeshBodies->GetSubGroups() )
{
NodeManager & nodes = mesh.second->groupCast< MeshBody * >()->getMeshLevel( 0 ).getNodeManager();
nodes.registerWrapper< array2d< real64, nodes::TOTAL_DISPLACEMENT_PERM > >( keys::totalDisplacement ).
setPlotLevel( PlotLevel::LEVEL_0 ).
setRegisteringObjects( this->getName()).
setDescription( "An array that holds the total displacements on the nodes." ).
reference().resizeDimension< 1 >( 3 );
}
}
and
arrayView2d< real64, nodes::TOTAL_DISPLACEMENT_USD > const & u = nodes.getReference< array2d< real64, nodes::TOTAL_DISPLACEMENT_PERM > >( keys::totalDisplacement );
... do something with u
This approach is flexible and extendible, but is potentially error prone due to
its verbosity and lack of information centralization.
Therefore we also provide a more controlled/uniform method by which to register
and extract commonly used data on the mesh.
The trait approach
requires the definition of a traits struct
for each
data object that will be supported.
To apply the trait approach
to the example use case shown above, there
should be the following definition somewhere in a header file:
namespace fields
{
struct totalDisplacement
{
static constexpr auto key = "totalDisplacement";
using DataType = real64;
using Type = array2d< DataType, nodes::TOTAL_DISPLACEMENT_PERM >;
static constexpr DataType defaultValue = 0;
static constexpr auto plotLevel = dataRepository::PlotLevel::LEVEL_0;
/// Description of the data associated with this trait.
static constexpr auto description = "An array that holds the total displacements on the nodes.";
};
}
Also note that you should use the DECLARE_FIELD
C++ macro that will perform this tedious task for you.
Then the registration is simplified as follows:
void SolidMechanicsLagrangianFEM::registerDataOnMesh( Group * const MeshBodies )
{
for( auto & mesh : MeshBodies->GetSubGroups() )
{
NodeManager & nodes = mesh.second->groupCast< MeshBody * >()->getMeshLevel( 0 ).getNodeManager();
nodes.registerField< fields::totalDisplacement >( this->getName() ).resizeDimension< 1 >( 3 );
}
}
And to extract the data, the call would be:
arrayView2d< real64, nodes::TOTAL_DISPLACEMENT_USD > const & u = nodes.getField< fields::totalDisplacement >();
... do something with u
The end result of the trait approach
to this example is that the developer
has defined a standard specification for totalDisplacement
, which may be
used uniformly across the code.