SCI Home Software Documentation Installation User's Guide Developer's Guide

CIBC:Development:Virtual:TechDetails

From SCIRun Documentation Wiki

Jump to: navigation, search

Contents

Technical Details Virtual branch

How virtualization is accomplished

For virtualization on the virtual branch we use a mechanism through which an additional class is generated that contains all the access points of the virtual functions provided with each field.

For example for a Mesh the virtual access point class is called VMesh. And one access this VMesh by calling the vmesh() function on the mesh class.

 MeshHandle mesh;
 mesh = CreateMesh("LatVolMesh","Linear","double");
 VMesh* vmesh = mesh->vmesh();

The reason for having a separate virtual access point class is to allow both efficient dynamic compilation as well as efficient access to the virtual access points. The problem with having virtual functions inside the main class is that each dynamic compile will instantiate all virtual functions in the class, as it is not clear whether they exists in another library and the compiler cannot check for that, hence every time a compile is done all those virtual functions are instantiated leading to large files and downloads.

To prevent this from happening the constructor of each field calls a function:

 LatVolMesh()
 {
    ...
  vmesh_ = CreateVLatVolMesh(this);
 }

In order to assure that the virtual interface is fully precompiled we declare these instantiations of the virtual access points for each mesh we want to have virtual support for

 template<MESH>
 VMesh* = CreateVLatVolMesh(MESH *) { return 0; } // default case
 
 template<>
 VMesh* = CreateVLatVolMesh(LatVolMesh<HexTrilinearLgn<Point> >* mesh); // this one is defined in a .cc file and will thus be compiled only once.


The virtual interface is created as soon as the mesh is created and a virtual access point function is available. Hence if one detaches a field or clones it, one has to call 'vmesh()' again to get the most current pointer to the virtual interface.

Internally the virtual interface stores a series of constants in the VMesh structure like basis order, the topological layout of elements, and the dimensions used. As these are constants per mesh, we grab them once and then inline them into the algorithms to speed up execution:

 class VMesh {
   ...
   
   inline basis_order() { return (basis_order_); }
 proteced:
   int unsigned int basis_order_;   // this one is set by the class derived from this one
   ...
 };

In order for these short cuts to work all the classes derived from VMesh should set that information.

In order to have the virtual interface mimic the algorithms used in dynamic compilation, we use both inline and virtual functions in the VMesh class. Over the years functions for the same functionality have been added to SCIRun under different names, e.g. set_node and set_point

In order to limit maintance and the amount of virtual functions we need to compile these duplicate calls have been create by inlining the other one:

 class VMesh {
   ...
   virtual void set_point(const Point& p, VMesh::VNode::index_type idx);
   inline  void set_node(const Point& p, VMesh::VNode::index_type idx) { set_point(p,idx); }
   ...
 };


The VField class

The VField class is generated in GenericField and wraps the virtual functions together needed for virtual support. However in order NOT to have to generate a virtual access function like we do for the meshes, the functions inside the VField class are combination of different interface classes with one common outside interface. Currently the interface is split in three: (1) mesh class are rerouted through vmesh of the mesh and the interface keeps an internal pointer to the virtual mesh class, (2) some functions like synchronize() are implemented directly into the field classes and rely on the Pio system to be instantiated. Most function however are implemented in the VFData class, which is a wrapper around the data pointers inside the FData container and the Basis class. Hence it combines all the data into one class, both the linear data as well as the additional data needed for the higher order basis functions.

Unfortunately the virtual interface needs about 40 instantiations of this class to cope with the FData2d and FData3d and vector classes in the container of GenericField. In the future would replace these containers with one, which will reduce complexity. The current complexity is hidden inside the VField class. Currently it is not the cleanest of solutions on the inside but on the outside it is consistent interface.

For instance in VField the get_value method is declared as following:


 classd VField {
 public:
 ...
   template<class T> 
   inline void get_value(T& val, VMesh::index_type idx)
   {  vfdata_->get_value(val,idx); }
 ...
 
 private:
   VFData* vfdata_; 
 };


This way a function call is forwarded to the actual virtual access point which is the Virtual FData class. As that class doesn't care about where data is stored, the function calls that are distinct on VField for compatibility are the same internally:


 classd VField {
 public:
 ...
   template<class T> 
   inline void get_value(T& val, VMesh::index_type idx)
   {  vfdata_->get_value(val,idx); }
    
   template<class T> 
   inline void get_value(T& val, VMesh::Node::index_type idx)
   {  vfdata_->get_value(val,VMesh::index_type(idx)); }
  
   template<class T> 
   inline void get_value(T& val, VMesh::Edge::index_type idx)
   {  vfdata_->get_value(val,VMesh::index_type(idx)); }
 
 ...
 private:
   VFData* vfdata_; 
 };

This is helps to maintain the same abstract interface for fields while reducing the number of virtual function calls that are actually in the table.


Virtual Support in field classes

There are a few major changes between the virtual support and dynamic compilation support:

[1] Index types in the virtual support are all uniquely, consecutively numbered from 0 until size-1.

[2] Index type VElem and VDElem are distinctively different from the others and are not a typedef like in the dynamic compilation

[3] Most functions will use VElem over a specific other index like VFace or VEdge. However one can convert them easily by casting between them.

[4] Index types now have a ++ and += operator, so one cana use them in for loops directly.

[5] In the dynamic compiled world one could not create a mesh until inside the dynamic algorithm. With virtual support one can instantiate a mesh or field directly with the CreateField and CreateMesh functions.

[6] There is no support for virtual data typing, this is accomplised by having casting operators built into to virtual functions for retrieving data. For instance if the field is of a char type and the algorithm does a get_value(double& val, VMesh::index_type idx) on the virtual interface the data will be cast on the fly. Hence one only needs to implement the algorithm for the value types one really cares for.

[7] Casting Vector and Tensors to scalar will result in zero values, the same is true for the other direction. Hence one wants to provide separate algorithms for Vector and Tensor the reasoning behind this is that virtualizing that would require a lot of virtual function calls (every algebraic operation would have to be one).

[8] The virtual interface provides support for "enodes" (edge nodes), these are nodes located on the edge of a element and are used in the quadratic support. In the virtual world it does not matter whether a field is of linear or higher order, the enode is always defined. In case we have a linear field it is the center of the edge. There is a separate iterator called VEnode that deals with these. And topology functions like get_enodes() are supported. Like wise one can do set_evalue/get_evalue etc.

[9] Virtual support is by default already compiled for linear, quadratic and cubic meshes. Note that regularly structured mesh do not have this support yet, only the unstructured versions have support for this built in.

[10] Element topology queries have been added to the interface (hence one does not need to access the basis class). Functions for number of edges per element, number of faces per element etc, are all provided for in the interface.

[11] Local element coordinates needed for get_coords, interpolate etc, have been replaced from a vector<double> type to a VMesh::coords_type. The latter one resides on the stack for improved efficiency.

[12] The order of the arguments of derivate has been altered to match interpolate.

[13] get_weights is no longer supported instead one has to choose from get_linear_weights(), get_constant_weights(), get_quadratic_weights() and get_cubic_weights, this is to distinguish for h.o. support in the future.

[14] similarly added support for derivatives get_linear_derivate_weights() etc.

Personal tools