1.0 Introduction
1.1 Uses of the Result Library
1.2 Concepts and Terminology
1.3 Class Relationship
1.4 Top Level view of Result Library
2.0 Basics of Result Library
2.1 Opening a Result File
2.2 Getting Mesh Information
2.3 Nodes and Elements
2.4 Families
2.5 Getting Result Information and Values
2.6 Example ICEM CFD Translator
3.0 Application Programming
3.1 Starting a Result Library Session
3.2 Structured Meshes
3.3 Unstructured Meshes
3.4 Families in Mesh Definition
3.5 Completed Mesh
3.6 Writing Domain Files
3.7 Readers
3.7.1 Reader Main Program
3.7.2 Reader Methods
3.7.3 Reader Methods as C/FORTRAN Functions
3.7.4 Adding Results
3.7.5 Advanced Reader Topics
3.7.6 Example Reader
4.0 Advanced Usage
4.1 Mesh Iteration Methods
4.2 Mesh Representations
4.2.1 Handles for Representations
4.2.2 Top Level Mesh Representation
4.2.3 Implementation of Structured Domains
4.2.4 Implementation of Vertices
4.2.5 Implementation of Unstructured Elements
4.3 Visual3 Evaluator
5.0 Conclusions
Introduction
The result library solves a number of problems related to reading different result file formats along with manipulating meshes but maintaining the mapping to the results. Users define meshes in the ordering and numbering most appropriate to their result file format or their application's data structures. The result library maps the user specified ordering into it's own internal ordering.
The result library is bi-directional which means it can be used to program both in mesh applications along with result format readers. In fact, a reader is a specialized mesh application written using the result library.
There are a number of API interfaces that can be defined or used by developers to fit their specific needs. These include
These interfaces can be used for most applications. However, there are interfaces available from ICEM CFD Engineering a subsidiary of ANSYS that add additional functionality
A distinct advantage of the result library is that the result library interfaces provide a consistent interface to manipulate the mesh for different result file formats. Hence an application which manipulates the fluent format can be used without modification to modify CGNS format meshes simply by changing the file name and the reader specification.
The result library can be an in-memory object representation for a mesh and results which may ease interface problems for application developers who need to read different result file formats and pass them via an API to different routines/applications.
Each of the uses of the result library has a coding example. There are examples of opening, querying, iterating over mesh files using the result file interface (openfile.cxx, info.cxx, query.cxx, and iteration.cxx). There are examples to build both unstructured and structured meshes for use by an application (example_struct.cxx, example_uns.cxx). There is an example reader for both internal and external use (exuns_impl.cxx) along with an output translator (translator.c). There are several examples that use the FORTRAN interface for both as a reader (readexuns_ftn.f) and as an application (example_struct_ftn.f and example_uns_ftn.f).
The examples can be built on UNIX by executing the command "make". The executable is built with the file extension .out. The examples also can be built on Windows platforms by executing the command line
nmake -f makefile.vc
The file makefile.vc needs to be edited because the location of the MSDEV distribution is not easily determined. The executable extension on Windows is .exe instead of .out.
The action examples (info, iteration, nodetonode, openfile, query and translator) read the domain file prism_domain.1 by default. The user however can specify any file format for which we have a reader. The command line to change the file and reader is
XXX.out <reader> <file>
where XXX is any example action executable. The line
XXX.out ./readexuns.out
will run the example executable reading with the example reader. The example reader does not actually read a file so no file needs to be specified. The user can also specify readexuns_c.out or readexuns_ftn.out as the reader to use.
Interfaces will be deprecated from time to time. Deprecated interfaces are guaranteed to persist at least 2 release cycles beyond when they were deprecated. The file DeprecatedInterfaces.html lists the deprecated interfaces and the release they were deprecated. If you compile your code with the macro RF_NO_DEPRECATED defined, you can check that your code has been upgraded to conform to the most current standard.
Uses of the Result Library
The result library is used in a number of applications
The result library, at its heart, works by users specifying the implementation of the result interfaces at runtime. The use of interfaces as opposed to directly subclassing classes improves the usability and simplifies maintenance of result library applications.
The usability of the system is improved because users can supply their own private mesh implementation that directly uses or creates their own existing data structures. The sources for both the result library and the 3rd party can remain privately held since only the interface is required to specify the interaction of the result library with the 3rd party application.
The maintenance of result library applications is improved because there is no need for result library applications to be recompiled after changes to the specific implementations of interfaces. Obviously, if the interface changes, then all applications need to be recompiled, but the interface changes slowly with respect to changes in the implementations.
Finally, the interface/implementation design means that applications can choose which implementations are of interest to the specific application. Hence, fewer libraries are required to link the application. Generally, the user just links their application against the basic result library.
Optimally, an IDL compiler/application development system would be used to compile and maintain the interfaces and the implementation of those interfaces. Unfortunately the scope of this project is to narrow to justify using an IDL compiler/development system given the cross platform requirements for the result library. Instead, we maintain the interface by hand. The drawback to this method of maintenance is that extending the set of result interfaces is not as easy as it should be -- neither for us or for users. However, since the result library just handles mesh and results and the form of inquires is limited, this has not proved a hindrance to date.
| Name
| What is it?
| Description
|
| backend
| Object or executable
| Synonym for reader.
|
| handle
| Object
| All interfaces have a reference count. When reference count goes to 0, interface is released. If a routine needs to own an interface, or if a request requires reference count be incremented, the input/request is a handle of the interface instead of the interface itself. Reference count automatically incremented on instantiation, copy, or equal. It is automatically decremented on destruction. Constructor provided to convert from interface to handle which is useful during routine invocation.
|
| med_impl
| Implementation of rf_mesh_interface; object
| Internal ICEM CFD mesh represented by this class.
|
| openMesh
| Implementation of rf_mesh_interface; object
| Default mesh representation. Can share node coordinate and unstructured element connectivity with result library application.
|
| reader
| Object or executable
| Reads a specific result file format. Is a result library application.
|
| result library application
| Application
| Any program, or routine that uses the result library API.
|
| results.h
| include file
| C callable API for the results library.
|
| rf_backend
| Specification of rf_backend_impl; object
| All readers are specifications of rf_backend. Redirects load, node, cell and group requests to proper methods in rfinfo_base appropriate to readers.
|
| rf_backend_interface
| Interface class
| Basic interaction interface for result applications. Interface represents mesh and results.
|
| rf_backend_impl
| Implementation of rf_backend_interface; class
| Abstract implementation of rf_backend_interface. Specifications of rf_backend_impl used by applications.
|
| rf_cback_impl
| Specification of rf_backend_impl; object
| Readers can be written in C by specifying functions as callbacks for this backend object. C call can be used to instantiate and initialize this object.
|
| rf_mesh_interface
| Interface class
| Mesh representation interface. Implementations of this interface handle all mesh related requests for the result library.
|
| rfinfo_base
| Specification of rf_backend_impl; object
| Object that actually handles rf_backend_interface requests. Stores all internal maps and lists used for result library applications. Defines usage of external reader executables to read result files.
|
Figure 1 shows the basic relationship between the result interface and the implementations of the interface. The result interface (rf_backend_interface) holds one result implementation object. A method call to the result interface, simply invokes the corresponding method in the implementation. The abstract implementation holds one rfinfo_base class object. Every method invoked in the implementation also invokes a corresponding method in the rfinfo_base object.
Since rfinfo_base is itself an implementation of rf_backend_interface, the second layer the indirection is somewhat redundant. However, we distinguish a backend reader from an application that uses a reader by use of rf_backend -- a specification of rf_backend_impl. A reader is a specification of rf_backend whereas an application uses rfinfo_base via rf_backend_impl. A reader also needs an rfinfo_base object, but it uses rfinfo_base slightly differently than an application. At minimum, the methods load_mesh, nodes_local, cells_local and group_local are all redirected in a reader due to rf_backend.
Figure 1. Relationship between the object classes in the result library.
Top Level view of Result Library
Figure 2 is a representation of how an application (including a reader) can use the result library. As input, the result library requires implementations of both rf_backend_interface and rf_mesh_interface. If these implementations are not specified, they default to rfinfo_base or openMesh respectively. The application can either open a result file using an available reader (also known as a backend) or it can build the mesh itself using methods in rf_backend_interface.
Figure 2. Interaction between Application and Result Library.
The application write a domain or CGNS format file (CGNS not available until Q2 2003). The user can specify either a callback to write out the results or simply use any available results read from a result file.
Basics of Result Library
The result library API can be used to extract both the mesh and results from a result file, construct a mesh, or write out a ICEM CFD domain or CGNS file.
The user can query the result library for the following mesh and result information:
Users can then write out the defined mesh into ICEM CFD domain or CGNS file formats.
Result file readers can either be internal (result reader object linked directly into application code),
or an external program. The benefits of using internal readers instead of running an external executable are
Unstructured meshes come in a number of different flavors. Nodes can be numbered sequentially or randomly. Sequential ode numbers can start at an offset. Unstructured elements of the same type can be defined together or the definition of different element types can be mixed together. Users declare the style of the unstructured mesh before defining the mesh. The order of the definition of unstructured elements should match the order of results on elements.
On the other hand, once the mesh is defined, the result library references the mesh in a fixed internal format. Unstructured nodes are numbered sequentially starting at zero in the C and C++ interfaces but one in the FORTRAN interface (enumeration routines and rf_cells_ftn() for instance). Structured nodes follow the unstructured ones.
Unstructured elements are readout in blocks of the same type. Applications can specify the order of the element type blocks or simply accept the default element block order. The default order is simply the order of how different element types were defined or read into result library. The result library takes care of mapping the order in which unstructured nodes or cells are defined or read into the order the application recieves information from the result library automatically. The unstructured element node numbering is mesh order. Elements are also numbered sequentially starting from zero in the C and C++ interfaces, but starting from one in the FORTRAN interface.
Applications can share memory with the result library if they use mesh representations capable of supporting shared memory (openMesh for instance). Applications can configure the starting sequential node number, and the connection order of the unstructured elements. Internally, the result library routines will continue to use the internal order (node numbering starting from zero, unstructured element connectivity in mesh order), but the memory storing the connectivity being shared with the application will be in the configuration specified by the application.
Users can open a result file by specifying the result file and the reader format to be used. The basic call is
RFInterface *rfint = rf_open(file_name, reader, n_config_pars, config_pars_list)
where file_name is the name of the file to open, reader is the backend reader specification, n_config_pars is the number of configuration parameters and config_pars_list is the actual list of configuration parameters. The reader specification is an ASCII name of the reader. It can specify the path to an external reader executable or it can be the tag name for an internal reader.
The user can use the returned interface, rfint, in calls to the C or FORTRAN callable interface or it can be cast to rf_backend_interface and treated as a C++ object. The implementation of rf_backend_interface will be rfinfo_base and the implementation of rf_mesh_interface will be openMesh. See the top level description and mesh representation for further information. One can change either representation along with specifying a factory to create a backend interface by using the interface
where nfiles is the number of file specifications in file_list, file_list is a list of file specifications for the specific reader, n_config_pars is the number of configuration parameters, config_pars_list is the list of configuration parameters, force is to force the use of an external executable instead of using any internal reader, factory is a special implementation of rf_backend_interface that responds to the method backend_for, rfimpl is the implementation of rf_backend_interface that actually manages the mesh (such as rfinfo_base, rfinfo_domain, etc.) and mi is the mesh implementation to use.
The special factory object intercepts the request for backend and if it can process the request, instantiates the reader object specified.
Applications then force the reading of a particular time step available in the mesh by the invocation of the method
status = rfint->read_mesh(prec, tstep)
where prec is the precision the nodes are to be read with and tstep is the time step to be read. The first time step for C and C++ interfaces is 0. The first time step for the FORTRAN interface is 1.
| C++
| C
| FORTRAN
|
| See below
| rf_open() rfint = rf_open(file, reader, npars, pars)
| rf_open_ftn() call rf_open_ftn(rfint, file, reader)
|
| rf_backend_impl::backend_for() rf_backend_interface::open() rfint = factory->backend_for(reader, force, rfobj, mi); rfint->open(file, reader, npars, pars)
| rfmi_open_full() rfint = rfmi_open_full(nfiles, files, reader, npars, pars, force, factory, rfimpl, mi)
| rfmi_open_ftn() call rfmi_open_ftn(rfint, file, reader, force, mi)
|
| rf_backend_interface::read_mesh() status = rfint->read_mesh(prec, tstep);
| rf_read_mesh() status = rf_read_mesh(rfint, prec, tstep)
| rf_read_mesh_ftn() call rf_read_mesh_ftn(status, rfint, prec, tstep) |
Applications can request the following information from the result library:
The following sections discuss querying the result library for mesh and result information. Users should consult the examples and the detailed interface documentation for further information about time step and parameters.
There are two views of the mesh stored by the result library. One view is the view of the backend that builds a mesh. This view is the view before the mesh has been edited. The consumer of the result library has a different view. This view includes the effects of editing the mesh. Note that since readers can be internal or applications themselves can build the mesh, the two views can be held simultaneously in one result object. However, the two views are still required because the result operation, in particular, depends on knowing which elements have been added or deleted during mesh editing.
The form of the query in C++ is rfint->view_nXXX where view is be for backend or reader view or v3 for application view (visual3 was the first application for the result library). XXX is the specified query name.
| What Query
| C++
| C
| FORTRAN
|
| Number of unstructured nodes
| rf_backend_interface::v3_nunodes()
| rf_v3_nunodes()
| rf_v3_nunodes_ftn()
|
| Number of specified unstructured element type
| rf_backend_interface::v3_nelements()
| rf_v3_nelements()
| rf_v3_nelements_ftn()
|
| First element number of specified unstructured element type
| rf_backend_interface::v3_elementstart()
| rf_v3_elementstart()
| rf_v3_elementstart_ftn()
|
| Total unstructured volume elements in mesh
| rf_backend_interface::v3_ntot_unstrvolele()
| rf_v3_ntot_unstrvolele()
| rf_v3_ntot_unstrvolele_ftn()
|
| Total unstructured surface elements in mesh
| rf_backend_interface::v3_ntot_unstrfaceele()
| rf_v3_ntot_unstrfaceele()
| rf_v3_ntot_unstrfaceele_ftn()
|
| Total of all unstructured elements in mesh (volume, surface, bar, and NODE)
| rf_backend_interface::v3_ntot_unstrele()
| rf_v3_ntot_unstrele()
| rf_v3_ntot_unstrele_ftn() |
Since no editing of structured meshes is allowed currently, the backend view is the same as the application view. Users are encouraged to use the proper interface in case editing of structured meshes is allowed.
| What Query
| C++
| C
| FORTRAN
|
| Number of structured domains
| rf_backend_interface::n_domains()
| rf_n_domains()
| rf_n_domains_ftn()
|
| Number of structured nodes
| rf_backend_interface::v3_nsnodes()
| rf_v3_nsnodes()
| rf_v3_nsnodes_ftn()
|
| Number of nodes in a particular structured domain
| rf_backend_interface::v3_nsnodes_dom()
| rf_v3_nsnodesdom()
| rf_v3_nsnodesdom_ftn()
|
| Number of cells in a particular structured domain
| rf_backend_interface::v3_nscells_dom()
| rf_v3_nscellsdom()
| rf_v3_nscellsdom_ftn()
|
| Number of structured volume cells in mesh
| rf_backend_interface::v3_ntot_strvolele()
| rf_v3_ntot_strvolele()
| rf_v3_ntot_strvolele_ftn()
|
| Number of structured faces in mesh
| rf_backend_interface::v3_ntot_strfaceele()
| rf_v3_ntot_strfaceele()
| rf_v3_ntot_strfaceele_ftn()
|
| Total of structured volume cells and faces in mesh
| rf_backend_interface::v3_ntot_strele()
| rf_v3_ntot_strele()
| rf_v3_ntot_strele_ftn() |
Typically applications only use the application view as opposed to the backend view. The standard usage for these methods is to determine the amount of dynamic storage required to hold the queried mesh entity. For example, the following code segment creates storage to hold all of the nodes in the mesh:
size_t ncoords = rfint->v3_nunodes() + rfint->v3_nsnodes();
float *coords = (float *) malloc(3*ncoords*sizeof(float));
Remember to include the number of vertices in an element when allocating memory for the connectivity of an element. Users can use the routine rf_nodes_per_element() to get the number of nodes in an element of a particular type.
Once one has enough memory to hold the entity, one can query the result library for the information. The method
status = rfint->nodes(crds, bbmin, bbmax, prec, tstep)
returns both the node coordinates and the bounding box of the mesh where crds is an array large enough to hold the coordinates of all nodes in the mesh, bbmin and bbmax are arrays dimensioned size 3 that hold the minimum bounding coordinate and the maximum bounding coordinate respectively, prec are the precision of the coordinate and bounding box arrays, and tstep is the time step of the result file to read.
Similarly
status = rfint->cells(eltype, eles, tstep)
returns the connectivity of all elements of the specified element type where eltype is the element type desired, eles is an array large enough to hold the connectivity of all elements of the specified type and tstep is the time step to return. If eltype is MIXED, then all elements in the mesh will be returned.
There are two additional routines that can be used by the user to return the connectivity. Different applications could require different sequential vertex numbers or different element connectivity order. The method
status = rfint->cells_order(eltype, eles, tstep, voff, order)
allows the user to specify a specific starting vertex sequential number (1 for example) and/or a different connectivity order where voff is the vertex offset for the connectivities and order is an allowed connectivity order.
Applications may also want family information for the elements. The method
status = rfint->domain_cells(eltype, eles, tstep)
will return a family id along with the connectivity for every element of the specified type. The family id is first followed by the connectivity. The method rf_backend_interface::domain_cells_order() can be use to change the starting vertex number and/or the connectivity order.
| C++
| C
| FORTRAN
|
| rf_backend_interface::nodes() status = rfint->nodes(crds, bbmin, bbmax, prec, tstep)
| rf_nodes() status = rf_nodes(rfint, crds, bbmin, bbmax, prec, tstep)
| rf_nodes_ftn() call rf_nodes_ftn(status, rfint, crds, bbmin, bbmax, prec, tstep)
|
| rf_backend_interface::cells() status = rfint->cells(eltype, eles, tstep)
| rf_cells() status = rf_cells(rfint, eltype, eles, tstep)
| rf_cells_ftn() call rf_cells_ftn(status, rfint, eltype, eles, tstep)
|
| rf_backend_interface::cells_order() status = rfint->cells_order(eltype, eles, tstep, voff, order)
| rf_cells_order() status = rf_cells_order(rfint, eltype, eles, tstep, voff, order)
| rf_cells_order_ftn() call rf_cells_order_ftn(status, rfint, eltype, eles, tstep, voff, order)
|
| rf_backend_interface::domain_cells() status = rfint->domain_cells(eltype, eles, tstep)
| rf_domain_cells() status = rf_domain_cells(rfint, eltype, eles, tstep)
| rf_domain_cells_ftn() call rf_domain_cells_ftn(status, rfint, eltype, eles, tstep)
|
| rf_backend_interface::domain_cells_order() status = rfint->domain_cells_order(eltype, eles, tstep, voff, order)
| rf_domain_cells_order() status = rf_domain_cells_order(rfint, eltype, eles, tstep, voff, order)
| rf_domain_cells_order_ftn() call rf_domain_cells_order_ftn(status, rfint, eltype, eles, tstep, voff, order) |
Result library applications can also define families or groups of elements. Families form a partition of the mesh -- each element can belong to one family. Generally this is used in connection with the mechanism to assign boundary conditions for use of the mesh by solvers.
Each element is stored with a family id. The id for the ORFN family is 0. Elements can either be assigned a family id at element creation time or at some later time in the mesh definition process.
The result library maintains a set of descriptors about the families including number of mesh entities of various types in the family. Users can validate the family descriptors by invoking the method
status=create_family(-1, -1, 0)
which clears all family descriptors and then iterates over all elements in the mesh forming a definitive count of elements in each family.
| C++
| C
| FORTRAN
|
| rf_backend_interface::create_family() status = rfint->create_family(bid, famid, olf);
| rf_create_groups() status = rf_create_groups(rfint, bid, famid, olf);
| rf_create_groups_ftn() call rf_create_groups_ftn(status, rfint, bid, famid, olf)
|
| rf_backend_interface::reconcile_pids() status = rfint->reconcile_pids(mi, noORFN)
| rf_reconcile_pids() status = rf_reconcile_pids(rfint, mi, noORFN)
| rf_reconcile_pids_ftn() call rf_reconcile_pids_ftn(status, rfint, mi, noORFN)
|
| rf_backend_interface::reconcile_extpids() status = rfint->reconcile_extpids(nfams, famids, famnames);
| rf_reconcile_extpids() status = rf_reconcile_extpids(rfint, nfams, famids, famnames);
| Not available. |
Users can query the result library for information stored in each family descriptor. There are four seperate family 'keys' that can be used by the application:
Family name is especially useful if the user is using the result library in connection with other applications that also impose identifiers on groups of elements. For instance, the ICEMCFD tetin file also defines families. If this is the case, the family name is the unambigious identifier that can be used to link together the family definitions.
The result library can reconcile family names and identification number from an external source with the result library family names and identification numbers. After reconciliation, the families with the same name have the same identification number. The 'reconciled' identification numbers will be imposed on each element in the mesh.
There are two methods:
status=rfint->reconcile_pids(mi, noORFN)
or
status = rfint->reconcile_extpids(nfam, famids, famnames)
where mi is the mesh representation, noORFN signals if any elements are allowed to be in the 'ORFN' family, nfam is the number of external families to reconcile, famids are the external family identification numbers and famnames are the external family names. The former method handles the case where the mesh representation itself holds a set of family names and id numbers. The later method handles the case where the application has a list of families and identification numbers it wants the result library to use.
Family identification number is a number that is stored with each element. By default, each new family is assigned a sequential identification number starting. The family id number of 0 is reserved for the special 'ORFN' family.
The family index is the order families are defined in the result library. The family index is the C array index and ranges from 0 to the number of families minus one. This is probably the easiest method for applications to iterate over the families in the mesh.
The last important identifier is the reader identification number also known as the backend id. Backend identification numbers are assigned sequentially starting at 0. However, families without surface elements are currently dropped by readers. Hence, when an application requests family information from a backend or the backend itself is adding families or elements to families, the backend identifier must often used or specified.
The method
status = boco_infobynum(num, ...)
is used to extract the boundary condition information for the family at index num. Callers are returned
Users should consult the detailed documentation for the specific calling sequence.
The method
status = rfint->boco_dimensionbynum(num, dim_mask)
returns the dimension of the entities in the family. A NODE element has dimension 0, a bar element dimension 1, a surface element dimension 2, and volume elements dimension 3. If the family has more than one class of entity, a -1 is returned and users can use the dim_mask to determine the set of entity classes in the family. The 2^d bit represents dimension d in the family.
The following table shows all of the possible queries in the C++, C, and FORTRAN interfaces.
| Key
| C++
| C
| FORTRAN
|
| Family name
| rf_backend_interface::boco_infobyname()
| rf_boco_infobyname()
| rf_boco_infobyname_ftn()
|
| Family identification number
| rf_backend_interface::boco_infobypid()
| rf_boco_infobypid()
| rf_boco_infobypid_ftn()
|
| Family index
| rf_backend_interface::boco_infobynum()
| rf_boco_info()
| rf_boco_info_ftn()
|
| Family name
| rf_backend_interface::boco_dimensionbyname()
| rf_boco_dimbyname()
| rf_boco_dimbyname_ftn()
|
| Family identification number
| rf_backend_interface::boco_dimensionbypid()
| rf_boco_dimbypid()
| rf_boco_dimbypid_ftn()
|
| Family index
| rf_backend_interface::boco_dimensionbynum()
| rf_boco_dimbynum()
| rf_boco_dimbynum_ftn() |
Getting Result Information and Values
The primary purpose of the result library is to handle the mesh and results on the mesh. Results are defined on a class of mesh entities. Results can be on nodes, cells, or faces. It is possible for a result to be defined on both volume cells and faces. The result library will handle results on bar elements or node elements in a future release.
Readers define results that exist in the file. There is no requirement that results exist in a file. The result library will still manage the mesh if this is the case.
There are three information query keys for results. They are result name, result index, and result backend identification number. The result index is the order in which the result is defined in the family starting at 0 (ie, the C index) and the backend identification number is an application specified number. The result index and the result backend identification number are one in the same for readers but this need not be true across all applications.
Applications request results via
rfint->results(num, type, values, skip, prec, tstep, meth, mr)
where num is the result index, type is the entity class the results are requested on, values is an array large enough to hold the results of the requested precision and skip value, skip is for loading vector results directly into the values array, prec is the desired precision of the results, meth is the method by which cell results will be converted to node results if applicable, and mr is to flag if the array is to be filled with the raw unmapped values from the backend or mapped to the application mesh.
The key num can also be the result name. You can also request the result values by backend identification number.
The calling function should only request entity types that the result is valid on. Users can use the function rf_backend_interface::getResultInfo to get the mask of valid types the result is valid on.
Users should beware of requesting face results from the result library. The indexing of face results is not straight forward due to legacy issues. These issues will be retired when all older readers have been replaced by newer readers. Instead, users should use cell to node results if at all possible. Additionally, an interface that will return node results only will be available shortly. This interface will internally properly convert the result specified by the key to node results. This process is equivalent to the current cell to node result mechanism but will not rely on creating a cell to node variable.
The array values must be sized large enough to hold the results. The typical coding sequence in an application is
int mask = RFNodeType; // RFCellType or, RFCellType
size_t nres = rfint->num_results(mask, RFMapResult);
float *values = new float[nres];
rfint->results(idx, RFNodeType, values, 0, RFPrecFloat, ...);
The call rfint->num_results(type, mr, ndoms, rind_layers) where ndoms is the number of structured domains in the mesh and rind_layers are the rind layer descriptor for the structured mesh returns the number of results for the specified entity type in the mesh. Both ndoms and rind_layers are optional arguments and are only specified if one is working with raw data from the backend.
The variable skip can be specified to directly fill a vector result. The result library currently only returns scalar results from readers even though the visual3 evaluator can handle vector expressions. However, if the skip value is n, n slots in the values array will be skipped for every result. For example, a skip of 2 fills a 3D vector. Users only need to offset the fill array values when filling each vector index.
The variable meth is the methodolgy the cell to node result code should use to convert cell result to nodal values. The acceptable values for this parameter are
| Method
| Method flag
|
| Use current method
| RFMethodNone
|
| Use the default method (usually cell average)
| RFUseDefaultMethod
|
| Average the cell values on node
| RFCellAvgWeight
|
| Weight the cell value by 1/distance(cellcenter, node)
| RFInvDistWeight
|
| Weight the cell value by (1/distance(cellcenter, node))**2
| RFInvDist2Weight
|
| Weight the cell value by the cell volume
| RFVolumeWeight |
Finally, mr can be used by the user to request specialized mappings of the result. Applications typically request mapping of result to current mesh (RFMapResult). Users of the C or FORTRAN interfaces do not have the option of using a different flag. Users of the C++ interface can request raw and/or to include rind layer in the results. Currently only the cell to node conversion code uses either flag.
The information query routine
rfint->getResult_info(num, ...)
returns
The following table summerizes the result query routines in the C++, C, and FORTRAN interfaces.
| Query
| C++
| C
| FORTRAN
|
| Number of results
| rf_backend_interface::n_results() nres = rfint->n_results();
| rf_n_results() nres = rf_n_results(rfint);
| rf_n_results_ftn() call rf_n_results_ftn(nres, rfint)
|
| Result info by index
| rf_backend_interface::getResult_info() status = rfint->getResult_info(num, name, ...);
| rf_result_info() status = rf_result_info(rfint, num, ...);
| rf_result_info_ftn() call rf_result_info_ftn(status, rfint, num, ...)
|
| Result info by name
| rf_backend_interface::getResult_info() status = rfint->getResult_info(name, num,...);
| rf_result_info_byname() status = rf_result_info_byname(rfint, name, ...);
| rf_result_info_byname_ftn() call rf_result_info_byname_ftn(status, rfint, name, ...)
|
| Result info by backend id
| rf_backend_interface::getResult_info() status = rfint->getResult_info(idnum, num, ...);
| rf_result_info_byidnum() status = rf_result_info_byidnum(rfint, idnum, ...);
| Not Available
|
| Number of result values for mesh entity type
| rf_backend_interface::num_results() nvals = rfint->num_results(type, mapflag, ndoms, rind_layers);
| rf_num_results() nvals = rf_n_results(rfint);
| rf_num_results_ftn() call rf_num_results_ftn(nvals, rfint)
|
| Results by index
| rf_backend_interface::results() nres = rfint->results(num,...);
| rf_results() nres = rf_results(rfint, num,...)
| rf_results_ftn() call rf_results_ftn(nres, rfint, num, ...)
|
| Results by name
| rf_backend_interface::results() nres = rfint->results(name,...);
| rf_results_byname() nres = rf_results_byname(rfint, name, ...);
| Not Available
|
| Results by backend id
| rf_backend_interface::results_by_idnum() nres = rfint->results_by_idnum(idnum,...);
| rf_results_byidnum() nres = rf_results_byidnum(rfint, idnum, ...);
| Not Available |
The user can configure the result object to create result names that represent cell to node converted results automatically. The result object can be configured with "make_node_var" parameter set to the value "1" passed in the rf_open() call, or the run-time parameter RFRunPars::make_node_vars can be set to 1 using the rf_set_runpars() call.
Similarly, users can ask the result object to create intrinsic variables. The intrinsic variables are X, Y, Z, Radius, Theta, Node number, Zero and One as node variables and "Cell number" as a cell variable. Additionally, if a material id number (material family) is available from the reader, the cell variable "Material id" will be created. Only the internal domain reader automatically supports this, though users can configure any result object to make the "Material id" result variable. Users should configure the result object with the parameter "make_intrinsics" set to the value "1" to create all intrinsic variables.
The example translator, translator.c takes any file read by the result library and creates a very simple format for input to a solver using the C callable interface. The output file is named "generic.txt" which is in ASCII format and is of the following format:
Nfamilies family-id family-name (1st family in list) family-id family-name (2nd family in list) ... family-id family-name (Nth family in list) Nnodes x y z (node 1) x y z (node 2) ... x y z (node Nnodes) Element_Name1 Nelements Nnodes_per_elementtype1 n1 n2 ... nnpe1 family-id (element 1 of type Element_Name1) n1 n2 ... nnpe1 family-id (element 2 of type Element_Name1) ... n1 n2 ... nnpe1 family-id (element Nelements of type Element_Name1) Element_Name2 Nelements Nnodes_per_elementtype2 ...
Node numbering in the example output begins at one.
The sample code is mostly straight forward. The code first opens the file of interest. This example opens a domain file, but any file format that has a reader can be translated in this manner. The code then emits a family block by looping over the families. It then emits the node block by enumerating the nodes. The callback routine just writes the node coordinates. Finally, the code writes out the element block by enumerating the elements.
Note the use of rf_v3_nelements_literal instead of rf_v3_nelements. The reason is that as long as TRI_3's are read as QUAD_4's, the number returned by rf_v3_nelements for TRI_3 is zero. The routine rf_v3_nelements_literal returns the literal number of elements in the specific type. It is always the case users should use the literal number of elements with the enumeration routines since enumeration doesn't use the 'read tri_3 as quad_4' flag.
However, the call rf_cells does use the 'read tri_3 as quad_4' flag. Hence, if the translator application uses rf_cells to get the connectivity instead of using enumeration, then the user should use rf_v3_nelement and seperate the TRI_3 from the QUAD_4 elements in the returned block.
When all older readers are converted to the new reader interface, 'read tri_3 as quad_4' will go away and this distinction will go with it. Until then, I personally recommend use of the enumeration routine as opposed to the cell connectivity call because there is no ambiguity about which element type is being queried.
The result library can be used to build meshes in an application. For example, a result format reader is a specialized result library application. The application writer can create structured domains and populate these domains with structured nodes, or create unstructured nodes and elements. The examples in example_struct.cxx, example_struct_ftn.f, example_uns.cxx, and example_uns_ftn.f show how an application can create both structured and unstructured meshes using the C, C++ and FORTRAN interfaces and create a domain file.
Domain files cannot represent mixed structured and unstructured meshes even though the result library can. The examples readexuns.cxx, readexuns_c.c and readexuns_ftn.f which are discussed further in the reader section shows how a mixed structured and unstructured mesh can be built using the result library.
Starting a Result Library Session
Instead of opening a result file, we create an empty interface in C or C++ using the call
where mi is an optional mesh interface specification. If mi is 0, then the mesh implementation will default to openMesh. Applications in FORTRAN should call
call rfmi_empty_impl_ftn(rfint, mi)
where rfint is an integer large enough to hold pointers on the computer architecture. The user will use the token created by intializing the domain in all subsequent calls as described in previous section.
The created interface is a minimal interface that cannot write domain files for instance. Application writes should call
instead if the application writer wants access to the domain library. The equivalent FORTRAN call is call rfmi_empty_domain_ftn(rfint, mi). The application will have to be linked with additional libraries in this case. Please consult the example Makefile for the additional required libraries.
Structured meshes are simply a set of nodes at specified ijk values in a set of structured domains. Users first create a structured mesh by first creating a set of structured domains. After the domains are defined, the user can then fill the domains with vertex node positions.
Users define the structured domain using the method
dom = rfint->add_domain(ijkmax)
where dom is the index of the created structued domain, rfint is the empty domain token and ijkmax is the maximum ijk value for the domain creates a structured domain. The ijk range for the domain will run from (1,1,1) to the specified maximum value. Calls that create structured vertices use the returned index value. The first structured domain created will have an index of 0 in C or C++. The FORTRAN structured domain index will begin at 1 as opposed to 0 to facilitate usage with FORTRAN arrays.
Users can now add structured vertices by a variety of methods. Users can structured vertices using arrays or one at a time. The C++ method
status = rfint->add_svertex(loc, dom)
where status is the status return of the procedure, rfint is the empty domain token, ,loc is the array of coordinate locations, and dom is the index of the created structued domain creates all structured vertices for the specified structured domain. The node coordinates are (x,y,z), ie the dimension is loc[][3]. This is in contrast to FORTRAN where the dimension of loc must be loc[3][nnodes] because FORTRAN array storage is column major.
Users can also define each structured vertex seperately. The C++ method
status = rfint->add_svertex_at(loc, dom, ijk, ca)
where loc now is the (x,y,z) coordinate of the node, ijk is the ijk coordinate of the node within the structured domain, and ca, if set, will cause a sanity check to be performed on the layout of structured vertex memory. The suggestion is to set ca to 0 in absence of any problems.
The following table shows the equivalent call sequences for C and FORTRAN users.
| C++
| C
| FORTRAN
|
| rf_backend_interface::add_domain() dom = rfint->add_domain(ijkmax)
| rf_add_domain() dom = rf_add_domain(rfint, ijkmax)
| rf_add_domain_ftn() call rf_add_domain_ftn(dom, rfint, ijkmax)
|
| rf_backend_interface::add_svertex() status = rfint->add_svertex(loc, dom)
| rf_add_svertex_dma_f() (or rf_add_svertex_dma_d()) status = rf_add_svertex_dma_f(rfint, loc, dom)
| rf_add_svertex_dma_f_ftn() (or rf_add_svertex_dma_d_ftn()) call rf_add_svertex_dma_f_ftn(status, rfint, loc, dom)
|
| rf_backend_interface::add_svertex_at() status = rfint->add_svertex_at(loc, dom, ijk, ca)
| rf_add_svertex_at_f() (or rf_add_svertex_at_d()) status = rf_add_svertex_at_f(rfint, loc, dom, ijk, ca)
| rf_add_svertex_at_f_ftn() (or rf_add_svertex_at_d_ftn()) call rf_add_svertex_at_f_ftn(status, rfint, loc, dom, ijk, ca) |
There are two stages in creating an unstructured mesh. First the user needs to create the unstructured vertices via a call to
status = rfint->add_unsvertex(locs, nunsnodes)
or equivalent routine where locs are the array of nunsnodes unstructured vertices. There are a number of methods that the user can use to define the grid. By default the unstructured vertices are in sequential order and are number starting at zero. However users can create a randomly ordered mesh by specifying an array of vertex numbers
status = rfint->add_unsvertex(locs, nunsnodes, map)
where map is an array of the vertex number for each node location specified.
If the vertex numbering is sequential but begins at an integer greater than zero (most commonly one), users simply need to define the vertex offset for the mesh. Users should invoke
status = rfint->setNodeOffset(v)
where v is the node numbering offset value before any unstructured elements are defined.
Unstructured elements in the mesh can be defined once the vertices are defined. The result library supports three different connection orders. So called NASTRAN order is documented at http://www.grc.nasa.gov/WWW/cgns/sids/conv.html .
The internal order called mesh order differs for QUAD and HEXA elements. Visual3 order differs from display order only for prisms. QUAD's have nodes 3 and 4 swapped in internal order from 'display order'. That is, if nodes 100, 101, 102 and 103 defined a QUAD in display order, the internal order of the same QUAD would be 100, 101, 103 and 102. Similarly, HEXA's have nodes 3 and 4 along with 7 and 8 swapped.
Prism elements in visual3 order are very different from 'display' order. Node's 2 and 4 are swapped, nodes 3, 5 and 6 are permuted. If a prism in display order is composed of nodes 100, 101, 102, 103, 104 and 105, in that order, then in visual3 order the same prism would be defined as 100, 103, 104, 101, 105, and 102. The differences for quads, hexas and prisms are shown in figure 3.
Figure 3. The element definitions for QUAD, HEXA and PENTA that differ from NASTRAN order.
The node swappings only apply to the linear portion of the element definition. All higher quadratic nodes for these elements are in the same order as in the 'display' or NASTRAN order.
Elements in the mesh, at user discretion, can be partiioned into families. Elements can be placed into families either when the element is created or this can be deferred until later.
Families must exist before the element is put into a family. Families are created by one of the following calls:
The difference between add_group and create_group is that create_group will not create a family if the family already exists. If the family already exists when using the method add_group, a number will be added to the name in order to make the name unique.
The unique family identification number is returned to the caller. Notice that although the user can request a specific identification number with the method create_group_by_pid, but if the number already exists, no group will be created.
Once the family is created, one can add elements to the family. The most performant method is to create the element and specify the family identification number of the family in the call to AddElementsId or an equivalent method. Generally, this is almost always possible to arrange.
If for some reason this cannot be done or is inconvienent, users can use either rf_backend_interface::add_element_to_group() or rf_backend_interface::AddFacesToGroup(). Users almost never have to use the add_element_to_group methods, but the AddFacesToGroup methods can be used to specify the QUAD_4 nodes of a face (TRI_3 faces are defined by using a -1 for the 4th vertex). The AddFacesToGroup method then will match the face to an existing face element. If such an element is found, the family identification number of the existing element is set to the value for the specified family. If no face exists, the correct face element is created with the proper family identification number.
If application code uses any of the add_element_to_group methods, the method status= rfint->groups_are_done()" should be invoked after all family assignments are completed. The add_element_to_group method defers assignment of the element to a family for performance reasons. When groups_are_done is invoked, all family assignments occur at once.
The following table summerizes the methods to create families and add families to groups using the three possible interfaces.
The user must signal when the mesh is complete. When both the structured and unstructured part of the mesh is complete, the application must call
status = rfint->mark_mesh_read(tstep)
where tstep is the time step that has been completed.
If the mesh is stored inside of an instantiated mesh representation, then the application can signal completion of the mesh by
status = rfint->mark_mesh_fromrep(tstep)
This advanced usage is meant for applications that can represent their internal data structures as a mesh representation. The mesh representation is an adaptor to the application\'s internal storage in this case therefore requiring the specialized mesh completion call.
| C++
| C
| FORTRAN
|
| rf_backend_interface::mark_mesh_read() status = rfint->mark_mesh_read(tstep);
| rf_mark_mesh_read() status = rf_mark_mesh_read(rfint, tstep);
| rf_mark_mesh_read_ftn() call rf_mark_mesh_read(status, rfint, tstep)
|
| rf_backend_interface::mark_mesh_fromrep() status = rfint->mark_mesh_fromrep(tstep);
| rf_mark_mesh_fromrep() status = rf_mark_mesh_fromrep(rfint, tstep);
| rf_mark_mesh_fromrep_ftn() call rf_mark_mesh_fromrep(status, rfint, tstep) |
A reader is a special result library application where the user implements methods to carry out specific tasks in order to build the mesh and retrieve the results. Generally readers are written to read a specific file format from the disk and pass the information on to an application.
The easiest way to build a reader is to use the C++ interface. The user overrides specific methods to carry out a file description task. One advantage of using a C++ reader is that all methods in rf_backend_interface can be reimplemented in the reader. If either the file format is non-standard or there is a clear optimization gain to be made, an application writer can tailor the reader in this manner. An additional advantage of using C++ interface is that it is quite easy to create an internal reader by instantiatation of the reader object.
Users can also create a reader using the ANSI C or the FORTRAN instead of using C++ classes. The user calls the rfmi_create_cback() to create a C backend or rf_create_ftnback to create a FORTRAN backend. Instead of specifying the methods to create the mesh as C++ methods, the user passes pointers to callback routines written in the appropriate language. That is, users should specify ANSI C routines to rfmi_create_cback() and FORTRAN routines in rfmi_create_ftnback().
Caution: if users are creating a C++ reader, the destructor should invoke rf_backend_interface::close_internal_reader(). Because of the symantics of destructors, this method must be invoked in the destructor of the reader and not any of its parent classes. The reason is that even though the virtual methods rf_backend_impl::user_external_close() and rf_backend_impl::user_free_local() are what close_internal_reader invokes, the complier quite properly invokes the method for the class in the current destructor, not the virtual method of the object being rundown.
Also, readers should not define the connection order of elements in the mesh implementation. This is because a reader can be external or internal. An external reader is a standalone program that can, without side effect, define the connectivity order. However, if an internal reader sets and depends on a specific mesh connectivity order, it can override an application connectivity order. Users instead must add elements to the mesh by using one of the predefined orders such as NASTRAN, ICEMCFD, or Visual3. A reader which can rely on a specific application connectivity order can of course add elements in using that order.
A reader main program in C++ consists of instantiating a reader object, optionally configuring the object with the command line passed to the reader and then executing the method write_to_pipe.
The coding pattern is:
exuns_impl exuns;
exuns.parse_parameters(argc-1, (const char **) argv+1);
exuns.write_to_pipe();
Readers written in C or FORTRAN are very similar except that the reader must be instantiated via a function call with callback functions specified as the arguments. Users cannot, as of yet, configure a FORTRAN reader via command line arguments. The examples readexuns_c.c and readexuns_ftn.f are equivalent to the C++ example readexuns.cxx.
| Function
| C++
| C
| FORTRAN
|
| Instantiation
| R r; where R is reader.
| rfmi_create_cback()
| rfmi_create_ftnback_ftn()
|
| Configuration
| rf_backend_interface::parse_parameters()
| rf_parse_parameters()
| Not Available.
|
| Handling Information Requests.
| rf_backend_interface::write_to_pipe()
| rf_write_to_pipe()
| rf_write_to_pipe_ftn() |
The following table shows the standard methods that the user can or must implement to create a a C++ reader.
| Name
| Function
| Required (R)/Optional (O)
|
| rf_backend::user_parse_inputinit()
| Parse line from inputinit. See exuns_impl::user_parse_inputinit()
| R
|
| rf_backend::user_fileset_load()
| Open files specified in inputinit line. Should describe file here; number and size of structured domains; number of time steps available, result title, parameters, result description (if possible at this point), etc. See exuns_impl::user_fileset_load()
| R
|
| rf_backend::user_external_close()
| Close files specified in inputinit line. See exuns_impl::user_external_close()
| O
|
| rf_backend::user_external_grid()
| Create nodes in mesh. See exuns_impl::user_external_grid()
| R
|
| rf_backend::user_external_elements()
| Create unstructured elements in mesh. See exuns_impl::user_external_elements()
| R/O If user is defining an unstructured mesh, this routine is required.
|
| rf_backend::user_external_groups()
| Create families of elements. Can also occur in rf_backend::user_external_elements(). See exuns_impl::user_external_groups()
| O
|
| rf_backend::user_external_postmesh()
| Carry out post mesh definition functions For instance, if mesh is required to define the class of data results are defined, user can define result in this function.
| O
|
| rf_backend::user_external_scalar()
| Provide results to caller. See exuns_impl::user_external_scalar()
| R
|
| rf_backend::user_external_iblank()
| Define structured subfaces to blank.
| O
|
| rf_backend::user_free_local()
| Free any locally allocated data. See exuns_impl::user_free_local()
| O
|
| rf_backend::user_file_descriptor()
| Provide number counts for mesh without defining mesh Default implementation defines mesh (and hence provides counts) User can override to simply provide counts.
| O |
There are five important methods the user generally needs to implement. Users call the mesh defining methods described in application programming to define the mesh.
The first method is rf_backend::user_parse_inputinit(). Its task is to parse the inputinit line. If the result reader requires multiple files to specify one output (for instance a grid and result file seperately), then this method needs to put together the two files and pass it off as one 'result file' specification. This method is called from external readers.
The method rf_backend::user_fileset_load() notes and/or opens all of the result files specified on the input line. It should also describe the structured domains, time steps, parameters, and results available in the set of files.
The method rf_backend::user_external_grid() should define the nodes of the mesh. Users will make calls to routines like rf_backend_interface::add_svertex() or rf_backend_interface::add_unsvertex() to define the grid.
The method rf_backend::user_external_elements() should define the unstructured elements in the grid. Users will make calls to routines such as rf_backend_interface::AddElementsId() to define the unstructured elements.
The final important method to implement is rf_backend::load_external_scalar that returns to the caller the results requested. When an application requests a result, this method is eventually invoked. If no results are available in the files, this routine will never be called. Even if the file format only defines a grid, this method still must be implemented as a NOP method.
Reader Methods as C/FORTRAN Callback functions
The user provides callback methods to carry out the same functions as the equivalent C++ reader object. The following two tables provide the coorespondence between the C or FORTRAN callback routine and the C++ method.
Readers add result descriptors by invoking the method
status = rfint->add_result(name, ...)
The calling arguments describe
The name and identification number of the result are user assigned. The name can be of any length and the idnumber must be a non-negative integer (ie, >= 0).
The possible values for the entity mask are an OR of the five possible field values: RFNodeType, RFNodeEleType, RFBarType, RFFaceType or RFCellType. Structured face results are not implemented per-se but rind layer results are.
The default precision should be set to the precision of the result in the result file or application data structure. This precision is just used by the result library as a hint. That is, users must be prepared to handle requests for precisions other than this when retrieving results.
The result source for results that are retrieved directly from a file or data structure should be set to RFBackendSource. If the user does not configure the result object to automatically create "cell to node" conversion variables, a user can manually create a conversion variable by specifying RFCellToNodeSource and setting the cell variable name as the source name parameter. Similarly, the user can manually create an intrinsic variable (X, Y, Z, etc) by specifying RFIntrinsicSource as the result source and the proper intrinsic variable name as the result variable name. There is no 'source variable' required in the intrinsic variable case.
The result library currently cannot fetch vector results from readers. However, applications can add vector variables and handle a vector variable at the application level. Visual3 in conjunction with the visual3 evaluator does this. Ultimately, the result readers only handle scalar requests.
The source variable is the cell variable source in the case of "cell to node" conversion. Users can specify 0 in all other cases.
If there are no rind layers, the number of domains and the rind layer descriptor should be specified as zero. If there are rind layers, the number of domains must equal the number of structured domains. The descriptor contains the number of rind layers in the result. There are six rind descriptors per structured domain in the mesh; two numbers per dimension. The first number is the number of rind layers before the minimum plane of that dimension. The second number is the number of rind layers after the maximum plane of that dimension, ie the rind descriptor is (n xmin, n xmax, n ymin, n ymax, n zmin, n zmax) where n refers to the number of layers.
The following table shows the calls in C++, C, and FORTRAN to add a result descriptor.
| C++
| C
| FORTRAN
|
| rf_backend_interface::add_result()
| rf_add_result()
| rf_add_result_ftn() |
The C++ interface can support two advanced features of the result library. Users can create internal readers by instantiating the implementation reader object inside of their application to create an internal reader. Users can directly map memory for the mesh coordinates and element connectivity with mesh representations that support sharing memory. The openMesh representation is an example of a mesh representation that can share memory with an application. Users who wish to create a mesh representation that shares mesh memory with the application should consult Mesh Representations for additional information.
Internal readers have two advantages over external readers. First, their is only one result library representation of the mesh. This reduces usage of system resources because external readers have a seperate mesh representation. Internally, the result library carefully seperates backend requests from application requests. This allows the result library to only require one mesh representation.
The second advantage of an internal reader is that no I/O transmission to the reader is required for information. An internal reader directly fills the memory with the requested values and there is no need to decode values from I/O pipes used with external readers.
Typically users create a factory object that only has two methods: a constructor and an implementation of rf_backend_impl::backend_for(). All the method rf_backend_impl::backend_for() method does is field requests for specific backends. If the backend requested matches one of the backends the application can internally create, it instantiates the backend and returns an interface to the backend. If the factory cannot handle the backend requested, it simply passes the request on to the parent class. The coding pattern is
rf_backend_interface *
rf_yourfactory_impl::backend_for(const char *be, int force, rfobj_impl rfobj, rf_mesh_interface *mi) const {
...
if (!force && !strcasecmp(abe, "yourbackendname") || !strcasecmp(abe, "readyourbackendname")) {
rf_backend_impl *temp = new yourbackend_impl(rfobj, mi);
return rfinterface_create(temp);
}
return rf_backend_impl::backend_for(be, f, rfobj, mi);
}
Users can specify a pointer to their factory in the call to rfmi_open_full() or equivalent. If a value of 0 is specified, the default method will be used. Only external readers will be created if users don't specify a factory with rfmi_open_full().
Users do not have direct access to result library implementation objects. That is they cannot directly instantiate them. However, there are routines named rfmi_<implementation object>_factory that users can use to create an interface to a specific implementation. Typically users do not need to specify a particular implementation. However, there may be certain instances where a specific implementation is desired. A short cut method, rfmi_empty_XXX(RFMeshInterface *mi) is provided that creates the interface to an implementation using the specified interface. This method invokes the rfmi_<implementation object>_factory followed by the rf_backend_interface::setMeshimpl() method. See rf_exuns_impl for an example of using factory routines to specify an implementation.
The C/C++ interface also supports directly mapping the result library onto application memory. The openMesh representation can pass to C/C++ applications the memory for node coordinates and seperately the memory for the connectivity of each different element type. The element connectivity can be configured to have an arbitrary node offset (typically either 1 or 0) and three possible connectivity orders (ICEMCFD mesh order, display (NASTRAN) order, or visual3 order). After the application edits the mesh (addition and deletion of nodes and elements), the mesh can be compressed so that the memory usage is minimized.
Memory sharing is an optional and not all mesh representations will support it. Users must query the mesh representation to see if memory sharing is possible.
The method void *coords = rfint->coordinate_data(&prec) returns the block of coordinate memory and the precision of that memory to the caller. If coords is 0 or the precision is not what the application desires, it cannot share the memory with the application. Users can hint to the mesh representation the precision via the method rf_backend_interface::allocate_vertex_memory(), but the mesh representation may not be able to satisfy the requested precision.
Shared connectivity data if available is retrieved on an element type by element type basis. Users call
int *conn = rfint->connectivity_data(type, &order, &vtxoff);
where type is the element type, order is the connectivity order and vtxoff is the vertex offset of the connectivity data. Both order and vtxoff are returned to the caller and the application should make sure that is the order and offset it can use and/or expects. If the connecivity is 0 or the connectivity order or vertex offset cannot be used by the application, the standard methods of retrieving the connectivity must be used instead.
One other caution. By default tris are read as quads. However, this does not work directly with the connectivity sharing routine. The connectivity routines return storage to their particular type of element -- tris not included with quads.
| C++
| C
|
| rf_backend_interface::coordinate_data() const void *coords = rfint->coordinate_data(&prec)
| rf_coordinate_data() void *coords = rf_coordinate_data(rfint, &prec);
|
| rf_backend_interface::connectivity_data() const int *connect = rfint->connectivity_data(eltype, &order, &vtxoff)
| rf_connectivity_data() int *connect = rf_connecivity_data(rfint, &order, &vtxoff) |
A C++ reader consists of an interface, an implementation, and an executable. The file exuns_impl.h is an example of a reader interface. The interface mearly declares the methods that will be implemented in the C++ reader.
The file exuns_impl.cxx is the implementation of the reader.
The user can parse the command line arguments passed to the reader and this is shown in readexuns.cxx. However, this is optional and reader object can be configured in code or even not configured at all instead of using command line arguments.
This particular example will build a mixed structured and unstructured mesh. The example highlights the basic features of what is expected of each method. While this example builds a mesh and creates a fake result on this mesh, a user created reader would substitute reading of the mesh coordinates and unstructured element connectivity along with reading the result data.
The example readexuns_c.c is the C equivalent to the C++ reader. Here, users call rfmi_create_cback() to create the appropriate C++ objects and the user is returned an RFInterface token that then is used in all subsequent calls. The user specifies the callbacks to carry out the methods shown in table 15 in the call to rfmi_create_cback(). The user then calls rf_write_to_pipe() and runs down the reader by calling rf_close().
The FORTRAN reader is similar. The program readexuns_ftn.f has a series of callback functions written in FORTRAN. Again note that FORTRAN indexing for structured domains, element numbers, results, times, paramters begins with 1 in the FORTRAN interface whereas it begins with 0 in the C interface.
The user can, using the mesh interface, iterate over the unstructured portion of the mesh. Users can use a for style loop to iterate over the vertices and elements of a mesh. The user can also use a procedural call and specify Wa callback function to be invoked in each iteration. The user can optionally specify a pointer to data that will be passed to the callback routine. There are 4 iteration methods:
The for style iteration requires the user to first intialize the loop with a call to the procedure first_XXX where XXX is either vertex or el for iterating over vertices or elements respectively. The return of this routine is a handle to the mesh element. The loop stops with the contents of the handle are 0. The loop is advanced to the next mesh element by the call to next_XXX. Lastly, the iteration loops require the user to declare a void *temporary; for iteration procedure use.
The usage pattern is
void *temp;
vertex_handle vh;
for (vh=mi->first_vertex(&temp); vh != NULL_int; vh=mi->next_vertex(&temp)) {
... Carry out computations on vertices ...
}
The element interation procedures take an optional element type. If the type is specified as 0, then all elements are looped over in the mesh. This is the default. The type specified in first_el and next_el should be the same. The usage pattern is
void *temp;
element_handle eh;
int ty = 0; // or whatever type you would like
for (eh=mi->first_el(&temp,ty); eh != NULL_int; eh=mi->next_el(&temp,ty)) {
... Carry out computations on elements ...
}
Applications can procedurally enumerate the vertices in the mesh by invoking
mi->enumerate_vertices(vtx_callback, data)
where mi is the interface to the mesh representation, vtx_callback is a vertex callback function and data is anonymous user data passed directly to the callback routine. The callback routine is declared
int vtx_callback(rf_mesh_interface *mi, vertex_handle& vtx, void *data)
where vtx is the vertex handle.
All other procedural enumeration routines are similar. The following table details all of the possible unstructured mesh procedural enumeration methods, their invocation, and the declaration of the callback function.
| Enumeration
| Invocation
| Callback Declaration
|
| Vertices
| rf_mesh_interface::enumerate_vertices() mi->enumerate_vertices(vtx_callback, data)
| enum_vertex_int_func int vtx_callback(rf_mesh_interface *mi, vertex_handle& vtx, void data)
|
| Elements;
| rf_mesh_interface::enumerate_elements() mi->enumerate_elements(eltype, ele_callback, data)
| enum_element_int_func int ele_callback(rf_mesh_interface *mi, element_handle& ele, void data)
|
| Faces of Element (vertex handles)
| rf_mesh_interface::enumerate_faces() mi->enumerate_faces(ele, eleface_callback, data)
| enum_face_int_func int eleface_callback(rf_mesh_interface *mi, int nv, vertex_handle *vv, void *data)"
|
| Faces of Element (vertex indices)
| rf_mesh_interface::enumerate_faces_vtx() mi->enumerate_faces_vtx(ele, elefacevtx_callback, data)
| enum_face_int_vtx_func int elefacevtx_callback(rf_mesh_interface *mi, int nv, const int *vtxs, void *data)"
|
| Edges of Element
| rf_mesh_interface::enumerate_edges() mi->enumerate_edges(ele, eleedge_callback, data)
| enum_face_int_func int eleedge_callback(rf_mesh_interface *mi, int nv, vertex_handle *vv, void *data) |
The differences between the enumeration methods reflects the difference in the entity being enumerated. When enumerating over elements, users must specify the element type (eltype) being enumerated. If the element type is MIXED, then all unstructured elements will be enumerate. The element enumeration callback is called with the element handle as opposed to the vertex handle.
The enumeration over faces and edges of an element also require a mesh interface. The callback routines recieve an array of vertices specified by the number of vertices in the face or edge (nv) and the vertices themselves (vv or vtxs). Finally, the difference between rf_mesh_interface::enumerate_faces() and rf_mesh_interface::enumerate_faces_vtx() is that in the former case, the callback is passed an array of vertex handles whereas in the later case the callback is passed an array of vertex indices.
Generally, users who wish to enumerate faces or edges of a mesh embed the enumeration of the face within an enumeration callback for the elements. The example routine iteration_routine and example callback myele_callback_faces_edges and myele_callback_faces_edges_c shows this usage.
There are equivalent enumeration routines in C (FORTRAN enumeration interface not yet available). The following table shows the equivalence between the C++ methods and the C and FORTRAN calls.
| C++
| C
| FORTRAN (when available)
|
| rf_mesh_interface::enumerate_vertices()
| rf_enumerate_vertices()
| rf_enumerate_vertices_ftn
|
| rf_mesh_interface::enumerate_elements()
| rf_enumerate_elements()
| rf_enumerate_elements_ftn
|
| rf_mesh_interface::enumerate_faces()
| rf_enumerate_faces()
| rf_enumerate_faces_ftn
|
| rf_mesh_interface::enumerate_faces_vtx()
| rf_enumerate_facesvtx()
| rf_enumerate_facesvtx_ftn
|
| rf_mesh_interface::enumerate_edges()
| rf_enumerate_edges()
| rf_enumerate_edges_ftn |
The difference between the C++ and other enumeration interface is a small modification of the calling sequence. Callers of rf_enumerate_vertices() and rf_enumerate_elements() call using the interface token. The callback method will extract the mesh interface from the interface so callers need not concern themselves with that.
The calls to rf_enumerate_faces(), rf_enumerate_facesvtx(), and rf_enumerate_edges() are called with a mesh interface pointer along with a pointer to element an handle (RFElementHandle *).
Callback functions recieve different information in the different languages. The following table shows the equivalence of the callback function declarations.
| C++
| C
| FORTRAN (when available)
|
| enum_vertex_int_func
| rf_enum_vertex
| rf_enum_vertex_ftn
|
| enum_element_int_func
| rf_enum_element
| rf_enum_element_ftn
|
| enum_face_int_func
| rf_enum_face
| rf_enum_face_ftn
|
| enum_face_int_vtx_func
| rf_enum_facevtx
| rf_enum_facevtx_ftn
|
| enum_edge_int_func
| rf_enum_edge
| rf_enum_edge_ftn |
The callback functions written in C or FORTRAN are passed tokens which can be used to extract information. The routines rfmi_vertex_info_f(), rfmi_vertex_info_d() and rfmi_element_info() can be used from C to extract all of the vertex or element information using the tokens available in the C callback routines.
See iteration.cxx for examples of all iteration methods in C++ and C.
The result library also allows users to create implementations of the rf_mesh_interface class to describe their own internal mesh representation. The result library uses the interface to query and add new mesh entities. Specifying your own implementation of this interface will cause the result library to directly use or create the data structure.
The mesh interface methods that must be implemented by the user can be broken into several distinct categories:
Most of the required methods merely return easily accessible information that any mesh data structure must have. For instance, the number of vertices or unstructured elements of a particular type must be easily available. The iteration methods over the mesh probably will be the most difficult for the user to implement. Iteration must continue to be valid in the presence of deletions of vertices and elements from an unstructured mesh.
There are a number of other methods in the rf_mesh_interface base class for which base implementations have been supplied. Some can be overridden by subclasses in cases where performance will be improved by using a more native implementation of the method. Most of the rest of the supplied implementations are for use by a general mesh editing library (under development) that will be able to carry out high-level mesh editing on all mesh representations.
There are three additional mesh entity description interfaces that must be implemented. They are a structured domain interface, a vertex interface and an element interface. Only the structured domain interface is at all extensive. The element interface must have access to basic information about the element and its type. The vertex interface is just a place holder for the information about a vertex in a mesh. All three interfaces will be discussed in subsequent subsections.
The most important concept of the interface is that structured domains, elements and vertices are never referenced directly but rather via a handle. The handle owns the interface and maintains the reference count. Only when the reference count for the interface goes to zero will the interface be disposed of. This allows the interfaces to be properly accounted for no matter the complexity of usage by the application. This allows, for instance, iterations over a mesh to be within other iterations over a mesh.
The infrastructure for handles is built into the base interface class. It is the standard smart pointer mechanism. The template class sharable is used to implement the smart pointer protocol.
The handle has a deference operator so a handle can be treated just like an interface pointer. Before usage, the user should make sure the interface pointer managed by the handle is not a 'NULL' pointer. The expression !handle is equivalent to handle == NULL_int and will evaluate to true if the interface pointer is the 'NULL' pointer. The check to ensure the interface is valid is (handle != NULL_int). The statement (handle) is invalid and cannot be used.
For example, the following code loops over all elements and extracts the number of vertices from each element before further processing:
void *temp;
'NULL' interface pointers signal a variety of conditions and in this case signals end of loop.
All interfaces in the mesh interface inherit from base_interface. The base class handles the reference counting of the interface. Users typically need only worry about the usage of handles when coding the mesh interface implementation. Users only need 'new' the particular interface.
A template function to efficiently create and delete blocks of interface pointers is available for use. Users can use the template function by following these four steps:
element_handle eh;
int ty = 0; // or whatever type you would like
for (eh=mi->first_el(&temp,ty); eh != NULL_int; eh=mi->next_el(&temp,ty)) {
int nverts = eh->n_vertices();
...
}