Namespace Space represents a collection of all the domains and their relations to each-other. This namespace includes various functions to read the configuration from data files, create/destroy domains and connections and write new configuration files.
Namespace Domain contain declarations and definitions of the basic data structures Connection, Domain, NeibList and related functions. Both Domain and Connection have variables name and type. Domain also has an integer process identifier variable iproc, which is simply domain's sequence number, and is used to associate the domain with a process number.
Namespace Run includes declarations of the variables related to the execution of the code, such as runtime system parameters, I/O data streams and files.
The domain decomposition information in the kernel is stored in dynamically linked lists of objects. Kernel uses a set of functions to manipulate these objects and to control the execution of the program. The main kernel objects are: Domains, Connections and Neighbor Lists. All the functions related to program execution are encapsulated into the name-space "Run" and the objects related to user interface, domain decomposition and problem-specific modeling are encapsulated within the name-space "Space".
Class "Domain" refers to the data and processes belonging to a single process. It consists of a generic and a problem-specific parts. While the generic part is always the same, the problem-specific part includes data sets and solution algorithms that are usually defined by the user inside the model namespace.
Domain data are stored in a grid, which is a pixel-array of components. Each element of the array is a single byte that represents a sequential number for one of 255 components, with 0 (zero) being an empty space.
Class Connection defines a type of data exchange between the domains. In particular they contain specifications of the type of connection (boundary) between the domains and the list of variables that needs to be exchanged.
Class NeibList defines for each domain the list of pointers to the neighbor domains and the corresponding connections.
All objects of each class are stored as binary linked lists, where each member of the list is connected to other members by the pointers to the previous and the next members in the list. The first and the last objects of the list are also connected to each other, so that the whole set of objects forms a ring.
The figure above shows the relations between the rings that store the information on connections, domains and neighbors. Domains A and B shown in the figure are neighboring to each other. They have each other represented in their respective neighbor list rings. Each entry in the neighbor list should also point to the specific connection in the connection ring.
The advantage of the ring-list versus an ordinary linked list is in the fact that no member of the ring needs a special treatment, like the first or the last members of the linked list. The next and the previous pointers of the ring members never point to NULL, whereas the previous pointer of the first member and the next pointer last member of the ordinary linked list point to NULL. Because of this the operations of creation and destruction of the ring members are simpler and more efficient than those for the ordinary linked list. The operation of looping through all the members except the current one is also simpler, since it does not require the extra if-statement on equality to NULL. This is demonstrated in the following examples.
The code below creates a new ring element and inserts it into the ring before the current element.
element=current->prev; current->prev=new Element; current->prev->next=current; current->prev->prev=element; element->next=current->prev; element=element->next;Now the element points to the newly created element.
In this example the element is deleted from the ring.
element->prev->next=element->next; element->next->prev=element->prev; delete element;
This example shows a simple loop printing names of all the elements of the ring, except the current element.
for ( Element *next=current->next; next!=current; next=next->next ) { cout << next->name; }
main.h, main.cc - Global data declarations and the body of the main procedure of the program.
run.h, run.cc - Declaration and implementation of the name-space "Run".
space.h, space.cc - Declaration and implementation of the name-space "Space".
domain.h, domain.cc - Declaration and implementation of the name-space "Domain".
templates.cc - Template definitions of some common functions.
The body of the main procedure in main.cc may look like this
int main ( int argc, char *argv[] ) { using namespace Run; init(argc,argv); Model::init(); Space::init(); Space::load(spacecfg); Model::run(); }
After initializations of the namespaces Run, Model and Space the configuration file for the Space namespace is loaded and the model run function is called. The Model::run() function is not part of the kernel, and it resides in model/main.cc file.
The basic operations of the kernel can be demonstrated on the following example. Consider a fuel cell stack simulated on a workstation cluster. Each fuel cell is computed by a specialized solver that should run on a separate processor, so that the number of processors will be equal to the number of cells.
Suppose that the cells have a rectangular shape and are placed in the corners of a rectangle, so that there are totally 8 cells each touching the other three at the faces. Considering that the corners of a rectangle can be numbered by their positions with respect to the Cartesian coordinate system, and assuming that the positions along each axis can be either 0 or 1, we can number all nodes as "000", "001", "010", "011", ..., "111". We'll use these sequences of strings to name the 8 fuel cells. Each cell is represented by a corresponding domain.
The setup of the whole stack is given in a space configuration file stack.cfg which contains the following sections.
DomainTypes { 1 Planar 2 Tubular 3 Gasreformer 4 GasChannel }
Stack { 000 1 001 1 010 2 011 1 100 1 101 2 110 1 111 1 }
connection_name1 { variable_list1 } connection_name2 { variable_list2 } ...where the variable_namei is a list of variables corresponding to connection i. Variables are the objects of a class Variable which is defined inside the Model namespace.
Connections { 1 { 1 2 3 } 2 { 1 } 3 { 2 3 } }Note, that in this case the names of the connections are given by integer numbers, but they can be given by any strings of characters.
domain_name1 { neighbor_list1 } domain_name2 { neighbor_list2 } ...where each neighbor_list has the format
domain_name connection_name ...So, in our example the neighbor list is given as
Neighbors { 000 { 001 1 010 2 100 3 } 001 { 000 2 011 1 101 3 } 010 { 011 1 000 1 110 1 } 011 { 010 2 001 2 111 2 } 100 { 000 3 110 3 101 1 } 101 { 001 2 111 1 100 3 } 110 { 111 1 100 2 010 2 } 111 { 110 2 101 3 011 2 } }
void run() { using namespace Run; using namespace Space; show(); deldom("001"); show(); deldom("010"); show(); deldom("100"); show(); deldom("101"); show(); deldom("101"); show(); deldom("110"); show(); deldom("111"); show(); deldom("011"); show(); deldom("010"); show(); deldom("101"); show(); deldom("100"); show(); deldom("000"); show(); deldom("000"); show(); insdom("000",2); show(); insdom("001",1); show(); connect("001","000","1"); output: show(); save(spacenew); #ifdef MULTIPROC {//This domain data: Domain *dom=locate(iproc); cout << "Process number=" << iproc << endl; if(dom==NULL)ERROR1("NO DOMAIN ASSOCIATED WITH PROCESS NO. ",iproc); cout << "Domain name=" << dom->name << endl << "\tNeighbor list:" << endl; NeibListThe part of the code conditioned on the MULTIPROC constant is used on the server side and displays the information of the domain local to the current process.*neibroot=dom->neib,*neib=neibroot; do { cout << '\t' << neib->element->name << ",iproc="<< neib->element->iproc << ",con=" << neib->con->name << endl; neib=neib->next; } while(neib!=neibroot); } #endif }