Program Kernel

Program kernel is based on the concept of voxel-graphics. General description is given below, and more details can be found in our paper.

Kernel Structure

Kernel includes three main namespaces: Space, Domain, and Run.

Space

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.

Domain

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.

Run

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.


Kernel Functions

The main task of the kernel is to keep and update the domain decomposition information, and to process client requests. The kernel is included into both the client and the server part of the code. Although the server implementation operates with the same data structures as that of the client, it has some minor differences. In particular the server should identify the local process number and keep some extra data related to the domain, which belongs to the local process. The client part on the other hand includes an extra module responsible for the transfer and building of the server executables on the remote cluster.

Data Structures

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.


Ring data structures.

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.

Inserting a new member into the ring

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.

Deleting a member from the ring

In this example the element is deleted from the ring.

	element->prev->next=element->next;
	element->next->prev=element->prev;
	delete element;

Looping through the ring

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;
	}

Source Files

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.

Main

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.


Prototype Example

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.

After the configuration is read by the Space::load function called from the global
main procedure the Model::run procedure is executed, which in our example performs a set of delete, insert and write operations on the stack, displaying the contents of the stack after each operation.
	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;
			NeibList *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
	}

The 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.