Master thesis

Approximate optimization algorithms of
Quadratic Assignment Problem (QAP)

AGH, Faculty of Automatics and Robotics Engineering, Kraków 2000

 


    1.  Theoretical introduction
          1.1  General description of the problem
          1.2  Description of exact optimization methods
          1.3  Description of approximate QAP optimization methods
                     1.3.1  Construction methods
                     1.3.2  Iterative methods
          1.4  Description of the Tabu Search algorithm
          1.5  Application areas of QAP

    2.  Building a research tool
          2.1  Introduction
          2.2  Application operation
          2.3  Construction of Graphical User Interface
          2.4  Description of the event handling functions  
          2.5  Description of the structure of classes of objects  

    3.  Resolving QAP problems
    4.  Conclusions
    5.  Bibliography







1.
Theoretical introduction

 

1.1. General description of the problem

The problem of assigning tasks to resources with a quadratic quality index QAP (Quadratic Assignment Problem) belongs to the NP-difficult problems of combinatorial optimization. This problem occurs in numerous practical issues in such areas as: economics, ergonomics, electronics, architecture, information technology, etc. For the first time, the QAP problem was formulated by Koopmans and Beckman [5] for economic applications. The QAP problem is a generalization of the linear AP (Assignment Problem) that belongs to the class P - polynomial problems.

It is defined as follows. The data are n2 coefficients of costs of assigning tasks to resources Cij and n4 coefficients of connections Dijkl:

One should find the solutionsuch that:

Equation 1. Objective function - general notation.

 

with limitations:


Equation 2. Constraints for the objective function.

 

Decision variable xij = 1 if task or object j is assigned to resource or item i (i,j = 1,.......,n) and xij = 0 otherwise.

In most applications, the cost factors dijkl can be expressed as the product of the elements of two matrices A and B:

Equation 3. Cost coefficients

 

where

 

 

 

 

 

 

A - matrix of distances between i and k positions of objects

B - matrix of relations between objects j and l

 

where  i,k,j,l = 1,2,....,n

 

They are non-negative square nxn matrices with zero elements on the main diagonal. In this case, the problem Objective function can be written as

Equation 4. Objective function - matrix notation.

 

Often, in order to shorten the notation of the problem and simplify the presentation of solving methods, the solution of the allocation problem is formalized in the form of permutations

p = [p (1), p (2), .........., p (n)] with the set of n prime natural numbers.

The problem model, equations 1-4, can then be written as follows:

Equation 5. Objective function - notation by permutation.

 

where

p (i) is the number of the task or object assigned to the resource or item i,

P is the set of permutations on the set of natural numbers

N = {1,2,.............,n}.

 

QAP optimization methods, like any other NP-difficult problem can be divided into two main groups:

      exact methods (giving the optimal solution) useful for effective solution of the problem when its dimension n <20,

      approximate methods, more or less complex, giving a suboptimal solution. They are the only computationally efficient methods for n> 20.

In the next part, the QAP solution will be presented in the form of permutations

p = [p (1), p (2), ............., p (n)] and the linear component of the objective function(matrix C) was omitted.

 

 


1.2. Description of exact optimization methods

There are a large number of exact methods. They can be divided into two groups. The first one is based on the Branch and Bound Method, while the second one uses various types of linearization of the objective function.

In the first subgroup, simple assignment methods and double assignment methods can be distinguished. The methods of simple assignment boil down to the assignment of one element of the (unallocated) set of tasks, to one element of the resource set, at each stage of the calculations, until all assignments are exhausted.

The group of these methods includes methods that use QAP decomposition. In this case, the matrices A and B are reduced and the objective function is decomposed into a linear function and a reduced quadratic function. Typical representatives of this approach are the algorithms of Roucairol [9] and Christofides-Mingozzi and Toth [2].

Since the Roucairol algorithm solves relatively large problems in a user-acceptable time, it can be used to test approximation algorithms. The algorithm is described below.

C. Roucairol presents the objective function as a sum of a linear function and a reduced quadratic function. This decomposition makes it possible to easily find both the lower and upper limits of the optimal solution. Then, using the standard Branch and Bound method approach, a solution tree is constructed and an intermediate solution review is performed. The basic steps of this method are shown below for the following form of the objective function:

 

 

Equation 6. Objective function - notation without the linear component.

 

Phase 1 of the algorithm: reduction of matrices A and B.

Definition. Reduce the square matrix M = [mij] with non-negative elements of the order n means finding 2n non-negative numbers ai, bj (i, j = 1, ........, n) such that the matrix

M '= [m'ij ] = [mij - ai - bj] is a matrix with non-negative elements and contains at least one zero in each row and column.

Two methods of matrix reduction are known:

1)    Standard "row-column" method, known from applications in the Hungarian algorithm for the AP problem and in the Little (et al.) Algorithm for TSP - Traveling Salesman Problem.

2)    The method called "maximal element first" consists in finding the maximal element of the matrix M:

 

The maximum element is reduced first. For this, we are looking for the minimum element in the line i* :

 

 

minimum element in column j* :

 

if now

then we reduce the elements of the line i* by the value of mi*l and substitute

ai* = mi*l , b1 = 0 , and when

then we reduce the elements of column j * by the value mkj* and substitute

bj* = mkj* , ak = 0. This procedure successively leads to obtaining the reduced matrix M'.

 

 

Reduction of the QAP problem

Let A’ = [a’ik] be the reduced matrix of A., where

 a’ik = aik - ai - bk;  ai , bk , aik ³ 0

and the matrix B’ = [b’jl] is the reduced matrix of the matrix B, where

b’jl = bjl - aj - bl;  aj , bl , bjl ³ 0.

 

The reduced QAP problem is a problem with the following objective function:

By developing this formula, we prove the relationship between the original problem and the reduced problem:

where gamma is a constant defined as the relation:

K(p) is an objective function of an AP problem of the form

elements of the matrix K = [kij] are defined by the relation:

 

Then we calculate the lower and upper bounds of the optimal solution.

 

Lower bound

Since the reduced matrices A', B' are non-negative matrices, so

hence

in particular

So you can see that the lower bound of the function f(p) is

Where

 

Upper bound

Let p * be the desired solution to the original problem, i.e.

It is not difficult to compute f (p) if we know

Because

Ultimately we get

and f '(p) here represents the difference between the lower and upper bounds. So the implication is right:

 

 

Phase 2 of the algorithm: Solution Tree Intermediate Review Procedure.

 

The optimal solution (assignment) is searched for in the solution tree in a similar way to Little's Branch and Bound algorithm for the TSP problem.

The double assignment methods were developed in the works of Land [6], Garett and Plyter [4] and Pierce and Growston [8]. They come down to the simultaneous allocation of a pair of elements (tasks) (i, k) to a fixed pair of distributions (resources) (j, l). The QAP problem is transformed into the AP problem with a cost matrix mm where m = n (n-1) / 2. The standard technique of the Branch and Bound method is then applied.

The second subgroup of strict methods are linearization methods. These methods transform the quadratic problem into an equivalent integer linear problem with additional variables and constraints.

The Lawler approach [7] is typical for this group of methods. A QAP problem having n2 xij variables is linearized by additionally introducing n4 yijkl variables, where yijkl = xijxkl. The obtained linear model has n4 + n2 variables yijkl and xij , and 2n4 + 2n constraints and has the following form:

with restrictions

A significant disadvantage of linearization methods is a significant increase in the number of variables and constraints, which in the case of large sizes of problems significantly extends the calculation time. When the problem dimension is n> 10, these algorithms are practically useless.

 


 


1.3. Description of approximate QAP optimization methods

In practical terms, the problem n dimension usually meets the following condition: n> 20. Hence, the researchers' effort has been focused on developing approximate methods, which, in general, can be divided into:

      Construction methods

      Iterative methods of correcting the solution

 


1.3.1. Construction methods

In the first case, the solution is constructed in such a way that the execution of one iteration of the procedure consists in determining the value of one component of the solution. Most often, the acceptable solution is determined using the greedy rule, list scheduling and also by drawing lots. In the last case, the order of the elements from the set N in the permutation p is determined by a random number generator. The most popular of the inaugural methods is the BEST_MATCH procedure, which has been used by many QAP researchers to generate an initial solution. The basis of this procedure is the process of assigning the most related (large flow of materials, parts, patients, large number of connections, etc.) objects (industrial plants, machines, clinics, modules, etc.) to key items (e.g. central items).


1.3.1.1. BEST MATCH
procedure

Step 1. For j = 1, 2, .............,n calculate values

Ordering the indices j in the order of the non-increasing values of BBj, create the vector BB.

Step 2. For i = 1, 2, .............,n calculate values

Line up the indices and in order of non-decreasing AAi values, create the vector AA.

Step 3. Create a solution to the problem by sequentially assigning the elements of the vector BB to the elements of the vector AA.


 


1.3.2. Iterative methods.

Iterative correction methods consist in searching, in each iteration, for a solution better than the successor, where the best known solution is the successor. The most popular and effective are the methods of local optimization, in the scope of which the most frequently sought solution is better in the vicinity of the successor obtained by changing the position of two or three elements of the successor, whereby individual authors propose different rules for changing the position of elements in the permutation and different rules determining the order of quality checking solutions from the environment. A representative example of generating solutions from the successor's environment by changing the position of two elements of the permutation is the Heider algorithm. If the successor is a permutation p, then the permutations p' belonging to the environment of the successor N(p) are defined as follows:

 

 

The algorithm systematically checks up to n (n-1)/2 (because |N2(p)| = n(n-1)/2) solutions from the successor's environment in the order determined arbitrarily or determined by the selected rule. Checking the solutions from the successor's environment is carried out until the first solution better than the successor is received, which becomes the new successor, and the search process is continued in the vicinity of the new successor. If the algorithm does not find a better solution in the entire environment of the successor, the successor is the locally optimal solution and the search process ends. The cost of calculating the value of the objective function of solutions p' belonging to the environment N2(p) is reduced if only the difference of the value of the objective function of the successor p and p' is computed:

If df>0, then the permutation of p' is a better solution than the successor of p. The calculation of the increment of df requires the performance of (2n-2) multiplication and (5n-4) addition operations. Hence, the Heider algorithm is considered by many authors to be the most effective double change algorithm.

This work focuses on the Tabu Search procedure, which is also based on the double change method and is described in detail in point 1.4.


 


1.4. Description of the Tabu Search algorithm


The Tabu procedure is based on the principle of the double shift algorithm, which is described in point 1.3. Additionally, it contains short-term memory called Tabu List (LT), which remembers the last shifts of pairs of successor elements, in order to prohibit shifting these successor elements for a given period T, called the grace period for a given (switched) pair. This means that the Tabu List contains the current solution and forbidden solutions that have occurred in the past. All this to avoid cycles and speed up the process of finding the best solution. The Tabu list also includes a long-term memory that remembers all the pairings' adjustments in order to diversify the process of finding a solution. Both memories serve to favor misalignments that have not been made and to punish those that have been frequently made. The Tabu Search algorithm is shown below.



Step 1.

Create a boot solution using the construction procedure. Substitute:




Determine the positive parameters of the algorithm:

K - number of iterations,

T - grace period,

Alpha - penalty for frequent adjustments of a pair of elements.


Step 2.

For k = 1 to K do:

 

a) determine the best solution in the environment:



b) correct the solution:



c) correct the memory state:



where:

DLT (i, j) - Lower Tabu List (long-term memory)

GLT (i, j) - Upper Tabu List (short term memory), contains the remaining "tabu time" of a pair of items (i, j). If GLT (i, j) = 0 then the pair can be rearranged in the process of looking for a better solution, the pair has no "tabu" condition. If, for example, GLT (i, j) = 2, then a pair of elements (i, j) has the "tabu" condition for two more iterations.


Description of step 1.

First, the starting solution vector is selected using the construction procedure. The written program contains 3 construction algorithms:

a) random algorithm - determines the solution by drawing successive elements of the vector of the starting solution,

b) averaging algorithm No. 1 - sums the elements in individual rows of matrices A and B,

c) averaging algorithm No. 2 - sums the elements in individual columns of matrices A and B,

 

Both averaging algorithms are based on the BEST-MATCH procedure. The created start vector is substituted for the variables, the value of the objective function is calculated and also substituted for the appropriate variables. The parameters of the algorithm are set.

Description of step 2.

This step consists of 3 parts, performed one after another:

a)    determining the best solution in the environment,

b)    improvement of solutions,

c)     correction of the memory state.


Each of the above parts of step 2 is performed K times, where K is the number of iterations.

The first part is the determination of the best solution in the N environment. First, a solution is sought according to the formula (*) in such a way that the pair of elements (i, j) in the solution vector is rearranged. The expression in braces is computed, the objective function for the current solution vector is computed, a value equal to the product of the penalty for frequent switching of the pair (i, j) and the frequency of switching the pair (i, j) is added to this value. These operations are performed to shift the pair (i, j) from the entire environment. The environment is all possible adjustments, ie those for which the grace period T = 0. The pair whose adjustment gave the smallest value of the expression in brackets (*) is remembered. Then a solution is sought according to the expression (**). This time, the environment are all changes for which the grace period is greater than zero. The value of the objective function is computed for each implementation of the shift (i, j) from the environment N. If the solution found by the expression is better, it is taken as the best solution found in step k. Then a memory correction is performed. The grace period for pairs for which switching is prohibited is reduced by 1. For the pair whose switching gave the best solution in step k, the grace period T on the Upper Tabu List is substituted and the counter of the occurrence of switching this pair on the Lower Tabu List is increased by 1 in the process of finding the best solution.


Tabu List.

The Tabou list can be realized with a square matrix with a dimension equal to the size of the problem under study, but references to elements on the main diagonal of the matrix are not allowed for a simple reason - the matrix remembers the permutation (rearrangement) of two different elements. It is difficult to talk about a permutation of the same element. The figure below shows the implementation of short-term (GLT) and long-term (DLT) memory using matrices. This is an example of a problem with the dimension n = 9, grace period T = 5, and the situation on the Tabu List is presented after iteration, in which the elements (5,8) were switched.

 

 

1

2

3

4

5

6

7

8

9

1

x

 

 

2

 

 

3

 

 

2

 

x

 

 

 

 

 

4

 

3

 

5

x

 

 

1

 

 

 

4

1

 

 

x

 

 

 

 

 

5

 

3

 

2

x

 

 

5

 

6

 

 

2

 

 

x

 

 

 

7

1

 

 

 

3

 

x

 

 

8

 

1

 

 

6

 

 

x

0

9

1

 

 

2

 

 

1

 

x



The top of the GLT matrix contains short-term memory. It remembers that elements (5,8) cannot be rearranged in the process of searching for a better solution for the period of 5 iterations. Likewise, elements (1,4) through 2 iterations, elements (1,7) through 3, elements (2,8) through 4, and elements (3,6) through 1 iteration. After each subsequent iteration, the time for prohibiting the switching of a given pair is decreased by 1.

The lower part of the DLT matrix contains the long term memory. It remembers all the changes of pairs of elements, after each switching of a pair of elements, it increases the memory cell for this pair by 1. The pair (3,2) has already been switched 3 times, pairs (4,1), (7,1), (9,1 ), (8,2), (9,7) only once, etc.

The main diagonal of the matrix is not available.


 


1.5. Application areas of QAP


A frequently considered example of the application of the QAP model is the location of production plants (machines in the plant hall), where the set N defines a set of localities (set of positions in the hall) where plants (machines) should be built (set up). In this case, the matrix A is a matrix of distances between locations (positions), while the matrix of relations B=[bij] describes the mutual dependence of plants or the flow of materials (parts) between the plant (machine) j and l. The linear component, described by the matrix C=[cij] can be interpreted as the cost of building the plant j in the locality i.


In electronics, the issue of the optimal arrangement of electronic objects (modules, integrated circuits) on a plane into a specific connection diagram is considered. An example may be the problem considered in 1961 by Steinberg [10], of the optimal arrangement of the UNIVAC-1 digital machine section modules. The positions on which the modules can be placed are treated as points in the 2-dimensional 9x4 space, where two positions are reserved. Matrix A determines the distance between individual positions, and you can enter various metrics, eg Euclidean, Square of the Euclidean distance, Lobachevsky. The matrix B = [bjl] specifies the number of connections between module j and l. The purpose of optimizing the module placement is to minimize the overall length of the connections between modules.


For architectural purposes, the issues of the location of offices in a production plant, the location of shopping, educational and cultural centers in the newly built district of the city and the location of clinics in a hospital are considered. A representative example according to Elshafei [3] is the issue of the distribution of 19 clinics in the Ahmed Maher hospital in Cairo. The element aik of the matrix A defines the distance between the distribution sites (i, k), and the element bjl of the matrix B defines the average number of patients moving between clinics (j, l) during the examinations and treatments. The practical goal of optimization is to minimize the distances (patient/meter) traveled by patients during the year.

Burkard and Offerman [1] applied the QAP problem model to design a new typewriter keyboard in French, English and German. The problem was to arrange the 26 letter symbols on the keys in order to minimize the average time needed to write the text. In this case, the element aik determines the average frequency of occurrence of the letter pair (i, k) and the element bjl is the time required to press the key j after pressing the key l first.


The quadratic assignment problem model is also used in computer science. In this case, the optimal allocation of computing tasks to such basic resources of multiprocessor computer systems as computing or communication processors is sought. The element aik of the distance matrix A determines the distance of the processor i from the processor k (measured, for example, in a distributed computer system, the number of processors intermediating in the transmission of messages between processor i and k), while the element bjl determines the unit cost (per unit distance) of sending the message from task j to the task l or the unit cost multiplied by the probability of sending the message. With this definition of cost factors, the objective function determines the total messaging costs between tasks performed on different processors.


It should be emphasized that in the works quoted here we are dealing with some generalizations of the standard QAP problem.


 


2.
Building a research tool

 


2.1. Introduction

The QAP problem research program based on the Tabu Search algorithm was written in a Windows environment using Borland International's Bilder C ++ version 4.0 utility, as the name suggests in the C ++ object-oriented programming language. The project is called Krzyś and consists of 7 files: executable file krzys.exe, project source file krzys.cpp, files containing individual classes vector.h, table.h, TS.h, QAP.h, files with windows forms Mform.h, Aform.h and the corresponding files of the same name with the extension .cpp, containing implementations of class member functions. A detailed description of the classes is provided below.


2.2. Application operation

After starting the program, a rectangular window appears on the screen with only the toolbar. There are buttons on it that enable efficient operation of the program.

 

Figure 1. Appearance of the toolbar with buttons.

 

The first button from the left "Otwórz" allows you to load the problem from the file. Pressing the button opens a dialog box that allows you to browse directories and files on disks and select a data file to open.

 

 

Figure 2. Dialog box for opening data file.

 

Data files should have the following structure:

The first number to read from the file should be the problem size, then the next numbers are the elements of matrix A, then the elements of matrix B, and possibly the elements of matrix C. Pressing the OK button closes the file open dialog box and if the file format is inconsistent with the one presented above, the program will report the error: "Incorrect file format". On the other hand, if the file structure is correct, the data will be loaded into appropriate table variables, the main program window will enlarge and will look like it is shown in the figure below.

 

 

Figure 3. Program window appearance after opening a data file.

 

At the top there is a description field that contains information about the file from which the problem was loaded and its size.

"Tabu Search (TS) algorithm in the Quadratic Assignment Problem (QAP). Problem loaded from file ............ dat. The size of the problem: ...."


Below is a graph in which the program plots the course of the objective function while performing calculations.

On the top left there is a field for setting algorithm parameters. There are three edit fields here that allow you to enter new values. The first field allows you to change the number of K iterations at which the algorithm stops. The second field allows you to change the value of the grace period for a given pair T, i.e. the number entered specifies the number of iterations through which the replaced pair cannot be changed in search of a better solution. The third edit field gives the possibility to change the parameter a, which is a penalty incurred by the pair for having already been changed in the process of searching for a better solution.

Below is a drop-down list that allows you to select a construction algorithm to derive the Pst start solution.

There are three algorithms on the list: random, algorithm 1, algorithm 2. The first determines the vector of the starting solution using a random number generator. The second and third work as described in section 1.2.2 of the Best_Match procedure. Algorithm 1 sums successive elements in individual rows of matrix B, orders the sums in a non-ascending order and creates a vector BB from them, then sums the successive elements of columns in matrix A, orders the sums non-decreasingly and creates vector AA from them, and then constructs the starting solution vector assigning successive elements vector BB to vector AA elements. Algorithm 2 works on the same principle, but differs in that it sums the elements in individual columns of matrix B, arranges the sums in non-ascending order, sums the elements in successive rows of matrix A, orders the sums in non-decreasing order, the vector of the starting solution is constructed in the same way as the Algorithm 1.
In the program window, below the parameter setting field, there is a field in which the values of the objective function are displayed. The value of the starting target function calculated on the basis of the starting vector Pst, and the minimum value of the target function underneath, which changes during the algorithm's calculations and its value is updated in this field with each change.

Underneath is an algorithm progress indicator that shows how many iterations have been done.

At the bottom there is a field in which the starting and minimum solution vectors are displayed. These are all the necessary elements in the window for efficient operation of the algorithm.

The next button on the toolbar is the Print button. Pressing this button will open the printing dialog box as shown in the figure below.

 

 

Figure 4. Print dialog box.

The printing dialog box displays the current status of the printer, its name, and allows you to open the printer properties window, where, depending on the type and software of the printer, you can perform, among others, changes in paper quality, print quality, and many other options; has a field to enter the number of copies for printing. Pressing the OK button causes the current program window to be printed. The next three buttons allow you to preview the matrix with data, i.e. pressing the Matrix A button displays the data in matrix A in the program window, similarly the buttons Matrix B and Matrix C.

 

Figure 5. The appearance of the program window after pressing the Matrix A button.

The next Algorithm button displays the algorithm window described above in the description of the Open button. Another Start button runs the Tabu Search algorithm and is active only when the algorithm window is visible. After pressing the button, the course of the objective function is drawn on the graph, the current value of the objective function is displayed on the left, and the algorithm progress indicator changes below the number of iterations until the number of iterations reaches the value of the K parameter.

 

Figure 6. Program window appearance after the algorithm completes calculations.

 

The History button displays a window with all the algorithm calls, i.e. the window contains a list where a new record is added each time the algorithm is called. The record contains the following information about a given algorithm invocation: file where the data comes from, problem size, whether the problem contains matrix A or matrix B or matrix C, values of parameters K, T and alpha, value of the start and minimum target function, file to which successive values of the objective function were saved while searching for a solution.

 

 

Figure 7. Program window appearance after pressing the History button.

The next Help button displays the program help. The program help contains basic information about the QAP problem and the Tabu Search procedure as well as the program's manual. The last button available is the End button, which ends the program.

The toolbar also contains a drop-down menu that can be called by right-clicking within the toolbar.

 

 

Figure 8. The appearance of the drop-down menu for the toolbar.

Using the drop-down menu, you can decide what is to be visible in the program window. Ie. you can display the program's main menu, which is normally invisible, you can disable the button bar or only the button labels, you can enable or disable the main window display, you can enable or disable the display of the status bar. The status bar contains hints or information about the object currently under the mouse pointer. When the status bar is invisible, the same information is displayed in the algorithm window above the graph. The main menu contains the same options as the buttons on the toolbar. Additionally, you can display the program card with basic information about the program and the author. These are all basic information about using the program. The next section presents in detail how the graphic interface was implemented and describes in detail the functions of handling individual events, e.g. pressing a button.


 


2.3. Construction of Graphical User Interface (GUI)

The BilderC ++ system includes a very convenient library of ready-made Visual Component Library objects in short VCL. Building an application with this library is as simple as creating a new form on which individual components are placed with the mouse. Each component has its own properties, such as size, color, font, and more. These properties are changed at the design stage using the Object Inspector by entering appropriate values into the fields of individual properties. The ToolBar component from the VCL library was used as the toolbar in the program. It contains 10 buttons, i.e. ToolButton components. To manage the view in the main program window, the PageControl component was used, in which five pages (TabSheet components) were defined: TabSheetMacierzA, TabSheetMacierzB, TabSheetMacierzC, TabSheetTS, TabSheetHistoria. The first three pages display the A, B, and C matrices with StringGrid components on them that make it easy to display the data in the tables. The fourth page is devoted to the Tabu Search algorithm. There are three components of the Edit type, which are used to set parameters K, T, and alpha; one ComboBox component with which you can select a construction algorithm; one component of the PerformanceGraph type with which the objective function is plotted; one component of the ProgressBar type, i.e. the progress indicator; 5 Bevel components, i.e. frames that affect the aesthetic appearance of the window; one component of the StringGrid type displaying the starting and minimal solution vectors; 22 components of the Label type, i.e. labels containing text, where the text in 19 labels is constant and does not change during the program operation, and the text in three labels changes, they are a label called LabelOpisAlgorytmu, which displays the name of the file from which the data comes, and the size of the problem, a label named LabelQst that displays the value of the starting target function, a label LabelQmin that displays the current value of the target function each time a better solution is determined. On the fifth page there is a component of the StringGrid type, which displays information about all algorithm calls with individual parameters, the new algorithm call is a new record on the list. The main form also has a component of the StatusBar type, which acts as a status bar, and the MainMenu component, which is the main menu of the program. The CoolBar component, which is a flexible toolbar, was used to manage the view of the entire program. The flexible toolbar is divided into bands. MainMenu component is located on tape with index 0, ToolBar component on tape with index 1, PageControl component on tape with index 2, StatusBar component on tape with index 3. The flexible toolbar allows for changing the size of individual tapes and their order during program operation, however, this program has blocked this action because it was determined that there was no need for it. The status bar always appears at the very bottom of the window, above it the main window, which displays individual pages of the PageControl component, above it the toolbar and at the top the main menu.


 


2.4. Description of the event handling functions

The first function that will be discussed is the main form's OnCreate event handler and is called when the program starts. Below is a listing of this feature.

void __fastcall TMainForm::OnCreate(TObject *Sender)
{
  Application->OnHint = OnHint;
  OpisHistorii();
  ileTS = 0;
}

Listing 1. The main form's OnCreate event handler function.

In the first line of the function, the OnHint event handler of the main form is assigned the function of the same name OnHint. The OnHint event is generated when the mouse cursor is moved over a component and displays a tooltip if one has been previously defined for that component. By default, the hints are displayed in the status bar and you do not need to define your own function for handling the OnHint event. The program decided to display the tooltips using the LabelOpisTS label belonging to the TabSheetTS page when the status bar is invisible. The following is the contents of the OnHint function.

void __fastcall TMainForm::OnHint(TObject *Sender)
{
  if(!CoolBar->Bands->Items[3]->Visible)
   LabelOpisTS->Caption = Application->Hint;
  else
   StatusBar->Panels->Items[0]->Text = Application->Hint;
}

Listing 2. The main form's OnHint event handler function.

If the status bar is not visible, display hints with the LabelOpisTS label. Otherwise display them in the status bar.

In the second line of the OnCreate function, theOpis Historii function is called to display the column labels in the StringGridHistoria component on the TabSheetHistoria page. Below is the content of this feature.

void TMainForm::OpisHistorii(void)
{
  StringGridHistoria->Cells[1][0] = "Otwarto z:";
  StringGridHistoria->Cells[2][0] = " n";
  StringGridHistoria->Cells[3][0] = " A";
  StringGridHistoria->Cells[4][0] = " B";
  StringGridHistoria->Cells[5][0] = " C";
  StringGridHistoria->Cells[6][0] = " K";
  StringGridHistoria->Cells[7][0] = " T";
  StringGridHistoria->Cells[8][0] = "alfa";
  StringGridHistoria->Cells[9][0] = " Qst";
  StringGridHistoria->Cells[10][0] = " Qmin";
  StringGridHistoria->Cells[11][0] = "Alg. konstr.";
  StringGridHistoria->Cells[12][0] = "Zapisano do:";
  StringGridHistoria->Cells[0][1] = "1";
}

Listing 3. OpisHistorii() function.

In the third line of the OnCreate function, the global variable ileTS is reset, which counts the successive calls of the Tabu Search algorithm, i.e. the next press of the Start button. It was introduced to automatically number files to which successive values of the objective function are written during the algorithm's calculations.

The main menu commands have the same event handling functions as the buttons on the toolbar. The Open button has the operation function shown below.

void __fastcall TMainForm::MenuOpenClick(TObject *Sender)
{
  int rozmiar = 0;
  if(OpenDialog->Execute()){
   ifstream file(OpenDialog->FileName.c_str());
   file >> rozmiar;
   if(pProblem != NULL){
    delete pProblem;
    pProblem = NULL;
   }
   pProblem = new QAP(rozmiar);
   int Krzys = pProblem->WczytajMacierze(file);
   if(Krzys > 0){
    if(Krzys == 1){
     pProblem->MacierzA = true;
     ToolButtonMacierzB->Enabled = false;
     ToolButtonMacierzC->Enabled = false;
    }
    if(Krzys == 2){
     pProblem->MacierzA = true;
     pProblem->MacierzB = true;
     ToolButtonMacierzC->Enabled = false;
    }
    if(Krzys == 3){
     pProblem->MacierzA = true;
     pProblem->MacierzB = true;
     pProblem->MacierzC = true;
    }
    if(!CoolBar->Bands->Items[2]->Visible)
     CoolBar->Bands->Items[2]->Visible = true;
    if(pTabu !=
NULL){
     delete pTabu;
     pTabu = NULL;
    }
    pTabu = new TS(pProblem);
    LabelOpisProblemu->Caption = "Problem wczytany z pliku: " +
            ExtractFileName(OpenDialog->FileName) +
            ". Rozmiar problemu: " + IntToStr(pProblem->ZwrocRozmiar()) + ".";
    LabelOpisProblemu->Repaint();
    StringGridPI->RowCount = 0;
    StringGridPI->Repaint();
    for(int i=0; i< rozmiar; i++){
     StringGridPI->Cells[i][1] = 0;
     StringGridPI->Repaint();
    }
    Wykres->Visible = false;
    for(int i=0; i
<100; i++){ >     Wykres->DataPoint(clRed,0);
     Wykres->Update();
    }
    Wykres->Visible = true;
    ButtonActivate(true);
    ToolButtonStart->Enabled = false;
   }
   else{
    delete pProblem;
    Application->MessageBox("Niewlasciwy format pliku !", "Blad",
                               MB_ICONHAND | MB_OK);
   }
   file.close();
  }
}

Listing 4. MenuOpenClick() function.

 

The first line declares an integer variable named size, which is a local variable of this function and its task is to remember the size of the loaded problem. On the second line, the open file dialog is called. The ability to open the file has been limited to files with the .dat extension by entering an appropriate filter in the filter property of the dialog box component. If you click Cancel, no statements are executed and the MenuOpenClick() function ends. If the data file is selected and the OK button is clicked the rest of the statement is executed. A file object of the ifstream class is created and the file with the name stored in the FileName property of the file open dialog box is automatically opened. Since the constructor of the file object of the ifstream class requires the char* pointer type parameter, and the FileName property is an object of the AnsiString class, the standard c_str() method was used, which ensures the appropriate conversion. Then the first number from the file is read, which informs about the size of the problem defined in the file. Next it is checked whether the global variable pProblem, which is a QAP pointer, contains the address of the object. If so, the object is destroyed and the pointer is reset to zero. Then, memory is dynamically allocated for the QAP type object with the address entered under the pProblem pointer, using the new operator. The constructor of the QAP class is invoked with one parameter in which the problem size is passed. The next line calls the WczytajMacierze(file) function of the QAP object to read the data from the file. A detailed description of the functions can be found later in the work, when individual classes are discussed in detail. The WczytajMacierze function returns the number of matrices read from the file. It is used in the following lines to set bool variables MatrixA, MatrixB, MatrixC, QAP object to true or false, respectively, and depending on which matrices have been read, the appropriate buttons on the toolbar are blocked.

Previously, ButtonActivate(true) is called, which makes all buttons active. When the function parameter is false, all buttons are blocked.

void TMainForm::ButtonActivate(bool _aktywne)
{
  ToolButtonTS->Enabled = _aktywne;
  ToolButtonHistoria->Enabled = _aktywne;
  ToolButtonDrukuj->Enabled = _aktywne;
  ToolButtonMacierzA->Enabled = _aktywne;
  ToolButtonMacierzB->Enabled = _aktywne;
  ToolButtonMacierzC->Enabled = _aktywne;
  MenuAMatrix->Enabled = _aktywne;
  MenuBMatrix->Enabled = _aktywne;
  MenuCMatrix->Enabled = _aktywne;
  MenuTabuSearch->Enabled = _aktywne;
  MenuStart->Enabled = _aktywne;
  MenuHistoria->Enabled = _aktywne;
  MenuPrint->Enabled = _aktywne;
  MenuPrintSetup->Enabled = _aktywne;
}

Listing 5. ButtonActivate(bool _aktywne) function.

Then, if the program view was limited to the toolbar, the program window is enlarged and the TabSheetTS page of the PageControl component is displayed. The next line checks if a TS object exists by checking whether the pTabu pointer contains the address of the object or the value of NULL. If the TS object existed, it is destroyed and the pointer is reset to zero. Then memory is dynamically allocated for the TS object, using the new operator, the constructor of the TS class is called, and the address of the object in memory is assigned under the pTabu variable. The next line updates the problem information with the LabelOpisProblemu on the TabSheetTS page. The name of the data file and the size of the problem are changed. The Repaint() method ensures that the new data is displayed correctly. The following lines reset the data in the StringGridPI component that displays the starting and minimum solution vectors. Also the content of the graph is cleared. This is ensured by the following lines. Then the "Start" button is blocked until the construction algorithm is selected and the starting solution is determined. Then the "Start" button is made available. These are all actions performed in the MenuOpenClick() function if the execution of the WczytajMacierze(file) function was successful. Otherwise, the object with the address stored under the pProblem indicator is deleted and a window with the following information is displayed: Wrong file format! Finally, the file is closed and the file object is destroyed. After executing the MenuOpenClick() function, memory was allocated for two objects. The first object was created on the basis of the QAP class containing data about the problem and the second object was created on the basis of the TS class, prepared to handle the TabuSearch algorithm.

The Print button contains the event handling function shown below.

void __fastcall TMainForm::MenuPrintClick(TObject *Sender)
{
  if(PrintDialog->Execute())
   Print();
}

Listing 6. MenuPrintClick() function.

This function only has one line where the print dialog is called. If the OK button is pressed, the Print () method is called and causes the current window to be printed. Depending on which page of the PageControl component is active, this will be printed.

The buttons Matrix A, Matrix B, Matrix C have similar operating functions.

void __fastcall TMainForm::MenuAMatrixClick(TObject *Sender)
{
  if(PageControl->Visible == false)
   PageControl->Visible = true;
  PageControl->ActivePage = TabSheetMacierzA;
  ToolButtonStart->Enabled = false;
}

Listing 7. MenuAMatrixClick() function.

void __fastcall TMainForm::MenuBMatrixClick(TObject *Sender)
{
  if(PageControl->Visible == false)
   PageControl->Visible = true;
  PageControl->ActivePage = TabSheetMacierzB;
  ToolButtonStart->Enabled = false;
}

Listing 8. MenuBMatrixClick() function.

void __fastcall TMainForm::MenuCMatrixClick(TObject *Sender)
{
  if(PageControl->Visible == false)
   PageControl->Visible = true;
  PageControl->ActivePage = TabSheetMacierzC;
  ToolButtonStart->Enabled = false;
}

Listing 9. MenuCMatrixClick() function.

In the first and second line of the function, the main program window is activated if it was inactive. All the above functions differ in the third row, in which the current page of the PageControl component is determined, i.e. which matrix is to be displayed. The instruction on the last line disables the Start button, which is activated only when a new construction algorithm is selected.

The Algorithm button has the operation function shown below.

void __fastcall TMainForm::ToolButtonTSClick(TObject *Sender)
{
  if(PageControl->Visible == false) PageControl->Visible = true;
   PageControl->ActivePage = TabSheetTS;
}

Listing 10. ToolButtonTSClick() function.

The function has only two lines, the instruction in the first turns on the main program window if it was invisible, the instruction in the second sets the active page of the PageControl component to TabSheetTS.

The Start button has the operation function shown below.

void __fastcall TMainForm::ToolButtonStartClick(TObject *Sender)
{
  int i;
  int rozmiar = pProblem->ZwrocRozmiar();
  String sk = EditK->Text;
  String st = EditT->Text;
  String sa = EditA->Text;
  Wektor Pii;
  int k, t;
  float alfa;

  try{
   k = StrToInt(sk);
   t = StrToInt(st);
   alfa = StrToFloat(sa);
   pTabu->NoweK(k);
   pTabu->NoweT(t);
   pTabu->NoweAlfa(alfa);
   pTabu->AlgorytmTS();
   DodajDoHistorii();
   Pii = pTabu->ZwrocPImin();
   StringGridPI->RowCount = 2;
   StringGridPI->Repaint();
   for(i=0; i< rozmiar; i++){
    StringGridPI->Cells[i][1] = Pii(i)+1;
    StringGridPI->Repaint();
   }
   TabSheetTS->Repaint();
   LabelQmin->Caption = FloatToStr((float)pTabu->ZwrocQmin());
   LabelQmin->Repaint();
   ToolButtonStart->Enabled = false;
  }
  catch(...){
  
Application->MessageBox("Niewlasciwy parametr !",
                                   "Blad", MB_ICONHAND | MB_OK);
  }
}

Listing 11. ToolButtonStartClick() function.

In the first part of the function, variables are declared: two integer variables, the first named i used for indexing in the for loop, the second named size, which is initialized with the value returned by the function ZwrocRozmiar() of the QAP object; three objects of the String type, initialized with the values ​​of K, T, a parameters, and taken from the edit fields on the TabSheetTS page; one Wektor object named Pii; two integer variables, k storing the number of iterations, t storing the grace period value for a given pair; one float variable named alpha that holds the value of the alpha parameter. The statements following the try statement are then executed. If execution of any of these statements fails, an exception will be thrown, caught by the catch statement, and the exception handling statements will be executed, ie a dialog will be displayed with the information: Invalid parameter! This means catching an exception that the conversion cannot be performed using StrToInt() or StrToFloat(). These conversions are performed on the first lines following the try statement. Then the new k, t, alpha parameters obtained after converting the text from the edit fields are entered into the TS object using the functions defined for this object: NoweK(), NoweT(), NoweAlfa(). The next line calls the TS object's AlgorithmTS() function, which contains the body of the TabuSearch algorithm. This feature will be discussed later in this work. After the TabuSearch algorithm computes, the DodajDoHistorii() function is called. The program remembers each invocation of the algorithm along with the parameters of its invocation, and saves them as another record in the table on the TabSheetHistoria page. This is what the DodajDoHistorii () function does.

void TMainForm::DodajDoHistorii()
{
  String a,b,c;
  int n = StringGridHistoria->RowCount;

  if(pProblem->MacierzA == true)
   a = "Tak";
  else {a = "Nie";}
  if(pProblem->MacierzB == true)
   b = "Tak";
  else
   b = "Nie";
  if(pProblem->MacierzC == true)
   c = "Tak";
  else
   c = "Nie";

  StringGridHistoria->Cells[1][n-1] = ExtractFileName(OpenDialog->FileName);
  StringGridHistoria->Cells[2][n-1] = IntToStr(pProblem->ZwrocRozmiar());
  StringGridHistoria->Cells[3][n-1] = a;
  StringGridHistoria->Cells[4][n-1] = b;
  StringGridHistoria->Cells[5][n-1] = c;
  StringGridHistoria->Cells[6][n-1] = IntToStr(pTabu->ZwrocK());
  StringGridHistoria->Cells[7][n-1] = IntToStr(pTabu->ZwrocT());
  StringGridHistoria->Cells[8][n-1] = FloatToStr(pTabu->ZwrocAlfa());
  StringGridHistoria->Cells[9][n-1] = FloatToStr((float)pTabu->ZwrocQst());
  StringGridHistoria->Cells[10][n-1] = FloatToStr((float)pTabu->ZwrocQmin());
  StringGridHistoria->Cells[11][n-1] = pProblem->ZwrocAlgorytmSt();
  StringGridHistoria->Cells[12][n-1] = pTabu->ZwrocNazwePliku();
  StringGridHistoria->RowCount = n + 1;
  StringGridHistoria->Cells[0][n] = n;
}

Listing 12. DodajDoHistorii() function.

 

The first line declares three objects a, b, c of the String class. Then, depending on the contents of the variables MacierzA, MacierzB, MacierzC, QAP object, objects a, b, c are assigned strings Yes or No. Then, for the current record, individual values ​​are entered into the subsequent columns of the table. In the first field of the record, the name of the file from which the data comes is entered, in the second field the problem size n previously obtained using the ZwrocRozmiar() function of the QAP object. In the next three fields, the contents of the objects a, b, c are entered, the next three fields contain the values ​​of the parameters K, T, a retrieved from the TS object using the functions ZwrocK(), ZwrocT(), ZwrocAlfa(),the next two fields are the values of the start and minimum objective function, also retrieved from the TS object using the functions ZwrocQst() and ZwrocQmin(). The next field is filled with a string returned by the function ZwrocAlgorytmSt() of the QAP object, the last field of the record contains the name of the file where the next values ​​of the objective function have been stored, this name is entered using the ZwrocNazwePliku() function of the TS object. In the penultimate row of the DodajDoHistorii() function, a new record is created in the StringGridHistory table and numbered with the next value in the last row.

In the next line of the ToolButtonStartClick () function, the local Pii object of the Wektor class is assigned the solution vector obtained after the execution of the AlgorytmTS() function. The solution vector is obtained from the TS object using the function ZwrocPImin(). In the following lines the vector Pii is entered into the StringGridPI table. Then the page content is refreshed, and the value of the objective function calculated for the found solution is displayed using the LabelQmin label, namely its Caption property.

The History button has the operation function shown below.

void __fastcall TMainForm::ToolButtonHistoriaClick(TObject *Sender)
{
  if(PageControl->Visible == false)
   PageControl->Visible = true;
  PageControl->ActivePage = TabSheetHistoria;
  ToolButtonStart->Enabled = false;
}

Listing 13. ToolButtonHistoriaClick() function.

The function displays the main program window if it was invisible, switches the active page in the PageControl component to TabSheetHistoria and blocks the Start button.

The End button has the operation function shown below.

void __fastcall TMainForm::MenuExitClick(TObject *Sender)
{
  if(pProblem !=
NULL)
   delete pProblem;
  if(pTabu != NULL)
   delete pTabu;
  Application->Terminate();
}

Listing 14. MenuExitClick() function.

The function checks for the existence of QAP and TS objects. If so, they are destroyed. The Terminate() method is then called to terminate the program.

Above are presented all operating functions defined for the toolbar buttons and the same commands of the main menu. There are other events in the program that have their own handling functions. These functions are outlined below.

Clicking the Matrices command from the main menu executes the operation function shown below.

void __fastcall TMainForm::MenuMatricesClick(TObject *Sender)
{
  if(PageControl->ActivePage == TabSheetMacierzA)
   MenuAMatrix->Checked = true;
  else
   MenuAMatrix->Checked = false;
  if(PageControl->ActivePage == TabSheetMacierzB)
   MenuBMatrix->Checked = true;
  else
   MenuBMatrix->Checked = false;
  if(PageControl->ActivePage == TabSheetMacierzC)
   MenuCMatrix->Checked = true;
  else
   MenuCMatrix->Checked = false;
}

Listing 15. MenuMatricesClick() function.

The function is to check, when you click the Matrices of the main menu command, which page of the PageControl component is active, and to set the marker in the drop-down menu for the Matrices of the main menu to the appropriate position.

 

Figure 9. Matrices command in the main menu.

The MenuWidokClick() function handles an event related to selecting the View command in the main menu. The View command manages the program view, ie it enables or disables individual program window elements: toolbar, main menu, main program window (PageControl component), status bar. The MenuWidokClick() function checks which items are visible when the View command is selected, and on this basis update the markers next to items visible or invisible in the expanded menu when the View command is selected.

 

Figure 10. Main menu View command.

 

void __fastcall TMainForm::MenuWidokClick(TObject *Sender)
{
  if(ToolBar->Visible){
   MenuPasekprzyciskow->Checked = true;
   MenuTekstnaprzyciskach->Enabled = true;
  }
  else{
   MenuPasekprzyciskow->Checked = false;
   MenuTekstnaprzyciskach->Enabled = false;
  }
  if(PageControl->Visible)
   MenuOknodanych->Checked = true;
  else
   MenuOknodanych->Checked = false;
  if(StatusBar->Visible)
   MenuPasekstatusowy->Checked = true;
  else
   MenuPasekstatusowy->Checked = false;
  if(ToolBar1->Visible)
   MenuMenuglowne->Checked = true;
  else
   MenuMenuglowne->Checked = false;
  if(ToolBar->ShowCaptions)
   MenuTekstnaprzyciskach->Checked=true;
  else
   MenuTekstnaprzyciskach->Checked = false;
}

Listing 16. MenuWidokClick() function.

By selecting the View command from the main menu, the MenuWidokClick () function is performed and the submenu shown in Figure 10 is expanded. Selecting the Button Bar command performs the operation function shown below.

void __fastcall TMainForm::MenuPasekprzyciskowClick(TObject *Sender)
{
  if(CoolBar->Bands->Items[1]->Visible){
   if(!CoolBar->Bands->Items[0]->Visible)
    CoolBar->Bands->Items[0]->Visible = true;
   CoolBar->Bands->Items[1]->Visible = false;
  }
  else
   CoolBar->Bands->Items[1]->Visible = true;
}

Listing 17. MenuPasekprzyciskowClick() function.

This function enables or disables the visibility of the second band of the CoolBar component on which the bar with buttons was placed. In addition, the function has a protection against switching off the button bar when the main menu is invisible. In this case, the function automatically enables the visibility of the main menu, and more precisely the first band (indexing from 0) of the CoolBar component.

The function MenuMenuglowneClick() below works similarly.

void __fastcall TMainForm::MenuMenuglowneClick(TObject *Sender)
{
  if(CoolBar->Bands->Items[0]->Visible){
   if(!CoolBar->Bands->Items[1]->Visible)
    CoolBar->Bands->Items[1]->Visible = true;
   CoolBar->Bands->Items[0]->Visible = false;
  }
  else CoolBar->Bands->Items[0]->Visible = true;
}

Listing 18. MenuMenuglowneClick() function.

This function is activated after selecting the Main menu command from the submenu shown in Fig. 10. It enables or disables the visibility of the main menu, has the same protection as the MenuPasekprzyciskowClick() button menu function.

The MenuOknodanychClick() function is performed when the Main window command is selected from the submenu shown in Figure 10. Enables or disables the visibility of the PageControl component.

void __fastcall TMainForm::MenuOknodanychClick(TObject *Sender)
{
  if(CoolBar->Bands->Items[2]->Visible)
   CoolBar->Bands->Items[2]->Visible = false;
  else
   CoolBar->Bands->Items[2]->Visible = true;
}

Listing 19. MenuOknodanychClick() function.

The MenuPasekstatusowyClick() function is performed when the StatusBar command is selected from the submenu shown in Figure 10. Enables or disables visibility of the StatusBar component.

void __fastcall TMainForm::MenuPasekstatusowyClick(TObject *Sender)
{
  if(CoolBar->Bands->Items[3]->Visible)
   CoolBar->Bands->Items[3]->Visible = false;
  else
   CoolBar->Bands->Items[3]->Visible = true;
}

Listing 20. MenuPasekstatusowyClick() function.

The MenuTekstnaprzyciskachClick() is designed to enable or disable text labels on toolbar buttons.

void __fastcall TMainForm::MenuTekstnaprzyciskachClick(TObject *Sender)
{
  if(ToolBar->ShowCaptions){
   ToolBar->ShowCaptions = false;
   ToolBar->ButtonHeight = 25;
   ToolBar->ButtonWidth = 27;
  }
  else{
   ToolBar->ShowCaptions = true;
  }
}

Listing 21. MenuTekstnaprzyciskachClick() function.

The above function, additionally, after disabling the display of text labels on the buttons, changes the size of the buttons. The size of the toolbar is automatically adjusted to the size of the buttons by setting its AutoSize property to true.

The StatusBarDblClick () function has a handler function as shown below.

void __fastcall TMainForm::StatusBarDblClick(TObject *Sender)
{
  StatusBar->Hide();
}

Listing 22. StatusBarDblClick() function.

This function is to disable the visibility of the status bar after double-clicking the right mouse button in the area of the bar.

The ComboBoxClick () function is called when an item is selected from the drop-down list on the TabSheetTS page. There are three items on the list, the names of the available construction algorithms. Selecting an item from the list by clicking it with the left mouse button causes the event handling function which is presented below.

void __fastcall TMainForm::ComboBoxClick(TObject *Sender)
{
  int i, rozmiar = pProblem->ZwrocRozmiar();
  String alg = ComboBox->Text;
  Wektor Pii;

  if(pProblem->AlgorytmStartowy(alg)){
   pTabu->UstalProblem();
   Pii = pTabu->ZwrocPIst();
   StringGridPI->ColCount = rozmiar;
   StringGridPI->RowCount = 1;
   StringGridPI->Repaint();
   for(i=0; i< rozmiar; i++){
    StringGridPI->Cells[i][0] = Pii(i)+1;
    StringGridPI->Repaint();
   }
   TabSheetTS->Repaint();
   LabelQst->Caption = FloatToStr((float)pTabu->ZwrocQmin());
   LabelQst->Repaint();
   LabelQmin->Caption = LabelQst->Caption;
   LabelQmin->Repaint();
   ToolButtonStart->Enabled = true;
  }
  else
   Application->MessageBox("Nie wygenerowano rozwiazania startowego !",
                                  "Blad", MB_ICONHAND | MB_OK);
}

Listing 23. ComboBoxClick() function.

The function above in the first line declares two local variables of integer type, the first one named i used as an index in a for loop, the second the size initialized on the same line with the value returned by the ZwrocRozmiar() function of the QAP object. The second line declares a local object called alg of the String class, initialized with the text selected from the ComboBox drop-down list, the third line declares a local object of the Wektor class named Pii. Next, the QAP Object AlgorytmStartowy function is called with the alg parameter. This function contains three algorithms that generate a startup solution, the appropriate algorithm is called depending on the alg parameter. A detailed description of this function is presented later in the work, during the description of functions and components of objects.
If the computation of the construction algorithm is successful and the Pst startup solution is generated, the function returns true, and all subsequent statements enclosed in the if statement braces are executed.  TS object UstalProblem() function is called to initialize three TS object fields: PIst, Qst, Qmin. A detailed description of the functions is presented later in the work. In the next line, the PIst field of the TS object is assigned to the local object Pii using the function ZwrocPIst() of that object. The local Pii object is used to display the Pst boot solution that is stored in the PIst field of the TS object. The following lines display the boot solution vector stored in the local Pii object, using the StringGridPI component located on the TabSheetTS page. The start value of the objective function is then displayed using the LabelQst and LabelQmin text labels on the TabSheetTS page. In the last line of the function there is a Start button on the toolbar. If the execution of the AlgorytmStartowy function was unsuccessful, the following error message is displayed: "No startup solution was generated".


 


2.5. Description of the structure of classes of objects


2.5.1. Wektor
class


The Wektor class was written in order to create vector type objects, ie a one-dimensional array of integer elements of a given size. The class has two private fields visible only to the member functions of this class, three functions to create objects of this class called constructors: the first default constructor, the second constructor with a parameter containing the size of the Vector class object being created, and the third copy constructor, which is used at the time of object initialization with using an object of the same type; the class also has the overloaded comparison operator "=", and the overloaded function call operator: () as the indexing operator, ie the operator of access to individual vector elements; the class also has a destructor for removing objects.

class Wektor
{
  int*  pwektor;
  int  n;

  public:
   Wektor(void);
   Wektor(int);
   Wektor(const Wektor&);
   ~Wektor(void);
   Wektor& operator=(Wektor&);
   int& operator()(int);
};

Listing 24. Wektor class.

Private fields of a class are an integer pwektor pointer that stores the address of an area in memory reserved for the created object of this class, and an integer variable n that stores the size of the vector. The Wektor.h header file shown in Listing 26 contains only the Wektor class structure. The exact structure of the function, i.e. its implementation, can be found in the Wektor.cpp file. Only the construction of a function constructing objects of the Wektor type is presented below.

Wektor::Wektor(int _n)
{
  pwektor = new int[_n];
  n = _n;
  for(int i=0; i< n; i++)
   pwektor[i] = 0;
}

Listing 25. The Wektor class constructor.

In the first line of the function, memory is dynamically allocated for an array of integer elements with the dimensions passed to the function by the _n parameter. The address of the memory area is written to the pwektor variable. In the second line of the function, a private variable of class n is initialized with the value passed by the _n parameter. In the next two lines, a Wektor object is initialized to 0 using a for loop.


 


2.5.2. Tablica class

The Tablica class was created in order to construct square matrix objects with integer elements. This class has two private fields, an integer ptab pointer to the memory area where memory is allocated for a two-dimensional array, and an integer variable n that stores the size of the matrix. The class has three constructors, a destructor, and overloaded assignment operators and function calls. Like the Wektor class described above. The structure of the Tablica class is shown below.

class Tablica
{
  public:
   int **ptab;
   int n;

   Tablica(void);
   Tablica(int);
   Tablica(const Tablica&);
   ~Tablica(void);
   Tablica& operator=(Tablica&);
   int& operator()(int,int);
};

Listing 26. Tablica class.

The above structure is a class header file named Tablica.h. On the other hand, the function definitions, ie the structure, are in the file Tablica.cpp. Only the construction of the Tablica class constructor is shown below.

Tablica::Tablica(int _n)
{
  ptab = new int*[_n];
  for(int i=0;i
<_n;i++) >   ptab[i] = new int[_n];
  for(int i=0;i
<_n;i++) >   for(int j=0;j
<_n;j++) >    ptab[i][j] = 0;
  n = _n;
};

Listing 27. The Tablica class constructor.

In the first line, the new operator dynamically allocates memory for an integer array of pointers of the size given in the parameter to the _n function. The memory address where the pointer table is stored is entered under the variable ptab. Then, with the for loop, memory is allocated for the array of integer elements, and in each iteration, the address of the allocated memory area is written to the next element from the previously dynamically created pointer array. The next two for loops initialize the array elements with the values of 0. In the last line of the function, the size stored in the local variable _n is copied to the private variable of the object named n.


 


2.5.3. QAP class.


The QAP class was created in order to construct objects that will store data about the currently loaded problem. The class structure is shown below.

class QAP
{
  int rozmiar;
  Tablica A, B, C;
  Wektor P;

  public:
   QAP(int _rozmiar);
   bool AlgorytmStartowy(String);
   String ZwrocAlgorytmSt(void);
   int ZwrocRozmiar(void);
   void NowyPI(Wektor&);
   int WczytajMacierze(ifstream _file);
   void WypiszMacierze(void);
   void Permutacja(Para&);
   double Q();
   Wektor& ZwrocPI();
   bool MacierzA;
   bool MacierzB;
   bool MacierzC;
   String AlgorytmSt;
   void WypiszPI();
};

Listing 28. QAP class.

The class has five private fields, the first is an integer variable size that stores the size of the problem this object concerns. The next three fields are Tablica class objects used to store the matrices read from the file that define the current problem. The next field is an object of the Wektor class that stores the solution vector. This vector changes during the operation of the Tabu Search algorithm due to the replacement of individual pairs of vector P elements. The last field is the AlgStartowy object of the String class that stores the name of the startup algorithm selected from the list. The first public member function is a constructor with one parameter to pass the size of the problem. This function calls the constructors of the Tablica and Wektor classes directly and assigns the created objects to private objects of the QAP class.

The function WczytajMacierze() is to read data from a file and write them under private objects A, B, C.

{
  while(!_file.eof()){
   _file >> liczba;
   ile++;
  }
  _file.seekg(0,ios_base::beg);
  _file >> temp;
  if(ile == (rozmiar*rozmiar) || ile == (rozmiar*rozmiar)+1){
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> A(i,j);
   WypiszMacierze();
   return 1;
  }
  else if(ile == (rozmiar*rozmiar*2) || ile == (rozmiar*rozmiar*2)+1){
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> A(i,j);
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> B(i,j);
   WypiszMacierze();
   return 2;
  }
  else if(ile == (rozmiar*rozmiar*3) || ile == (rozmiar*rozmiar*3)+1){
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> A(i,j);
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> B(i,j);
   for(i=0;i<   rozmiar;i++)
    for(j=0;j<   rozmiar;j++)
     _file >> C(i,j);
   WypiszMacierze();
   return 3;
  }
  else
   return 0;
}

Listing 29. WczytajMacierze() function.

In the first line of the function, all characters in the file are counted by the while statement. This action is to find out how many matrices with data are in the file. A properly defined problem has two or three matrices. The program exits the loop when it encounters an end-of-file character. In the second line of the function, the file pointer is placed at the beginning of the file. Then the first number from the file, which is the problem size, is read. This information is not needed for this function and is not used. Data download starts with the second number. Depending on the number of counted numbers in the file, i.e. on the value of the variable number ile, numbers are read successively and entered as successive elements of private objects A, B, C. Depending on the number of defined matrices in the file, the function returns the number of read matrices.

The AlgorytmStartowy(String) function is to create a startup solution, i.e. the vector of the startup solution. Depending on the parameter passed to the function, the startup solution is generated using one of the three available algorithms.

bool QAP::AlgorytmStartowy(String _alg)
{
  AlgorytmSt = _alg;

  if(_alg == "losowy"){
   int i,k,m;
   int niestety;
   bool nieroznisie;
   bool nowelosowanie;
   int los, nowylos;

   for(i = 0; i < rozmiar; i++){
    los = losuj(rozmiar);
    if(i == 0)
     P(0) = los;
    else{
     for(k = 0; k < i; k++){
      if(P(k) == los){
       nowelosowanie = true;
       nieroznisie = true;
       while(nieroznisie){
        niestety = 0;
        nowylos = losuj(rozmiar);
        for(m = 0; m < i; m++){
         if(P(m) == nowylos)
          niestety++;
        }
        if(niestety == 0)
         nieroznisie = false;
       }
       P(i) = nowylos;
      }
     }
     if(!nowelosowanie) P(i) = los;
      P(i) = los;
     else
      nowelosowanie = false;
    }
   }
  }

Listing 30. AlgorytmStartowy() - losowy function.

When the parameter of the function is a sequence of characters 'losowy', the starting solution is generated based on the randomization of successive elements of the vector. The algorithm works in such a way that it randomizes a number, if it is the first drawn number, it enters it as the first element of the starting solution vector. If it is another number drawn, the algorithm checks whether such a number has already been drawn. If the vector already contains such an element, a new draw is performed until a number different from all the vector elements drawn so far is obtained.

If the algorithm parameter is a sequence of characters 'algorytm 1', the starting solution is generated on the basis of the list serialization method described in section 1.2.2. The following is a continuation of the AlgorytmStartowy(String) function.

else if(_alg == "algorytm 1"){
  Para *rA = new Para[rozmiar];
  Para *rB = new Para[rozmiar];
  int k,l;

  for(k = 0; k <   rozmiar; k++){
   rA[k].i = 0;
   rA[k].j = k;
   rB[k].i = 0;
   rB[k].j = k;
   for(l = 0; l <   rozmiar ; l++){
    rA[k].i += A(k,l);
    rB[k].i += B(k,l);
   }
  }
  qsort((void*)rA,(size_t)rozmiar,(size_t)sizeof(Para),PorownajPary);
  qsort((void*)rB,(size_t)rozmiar,(size_t)sizeof(Para),PorownajPary);
  for(k = 0; k < rozmiar; k++)
   P(rA[k].j) = rB[rozmiar-k-1].j;
  delete[] rA;
  delete[] rB;
}

Listing 31. AlgorytmStartowy() - algorytm1 function.

In the first two lines, memory is dynamically allocated for an array of Para-type structures and the address of the allocated memory area is written under the pointer variables rA and rB. Each pair contains two fields. In this case, the first field i of the pair rA were used to store the sum of the elements in individual lines, the second field is the line number. The first two for loops do the summation of items across lines. The sums are then sorted in ascending order using the qsort function from the BilderC ++ standard library Stdlib. The last for loop generates the start solution vector in such a way that the minimum value of the field i of the pair rA is assigned to the maximum value of the field i of the pair rB. This averages the value of the objective function, and thus the optimal solution is found faster. In the same loop, the memory for the structure tables rA and rB is released.

If the parameter of the function is the sequence of characters 'algorytm 2' then the vector of the starting solution is generated based on the algorithm shown below.

else if(_alg == "algorytm 2"){
  Para *cA = new Para[rozmiar];
  Para *cB = new Para[rozmiar];
  int k,l;
  for(k = 0; k < rozmiar; k++){
   cA[k].i = 0;
   cA[k].j = k;
   cB[k].i = 0;
   cB[k].j = k;
   for(l = 0; l < rozmiar ; l++){
    cA[k].i += A(l,k);
    cB[k].i += B(l,k);
   }
  }
  qsort((void*)cA,(size_t)rozmiar,(size_t)sizeof(Para),PorownajPary);
  qsort((void*)cB,(size_t)rozmiar,(size_t)sizeof(Para),PorownajPary);
  for(k = 0; k < rozmiar; k++)
   P(cA[k].j) = cB[rozmiar-k-1].j;
  delete[] cA;
  delete[] cB;
}
return true;

Listing 32.AlgorytmStartowy() - algorytm2 function.

The above algorithm differs from the one in Listing 31 only in that it counts the sums of the elements of the individual columns of matrices A and B, not the rows. If the matrices are symmetric, the vector of the starting solution generated by Algorithm 1 is the same as the vector generated by Algorithm 2.

The function WypiszMacierze() is called inside the WczytajMacierze() function to display matrices A, B, and C on the appropriate pages of the PageControl component. At the beginning, the function determines the page properties - StringGrid components: ColCount sets the number of columns (the value is greater than the size of the problem by one column because the first column contains individual row numbers), RowCount determines the number of rows (the value is greater than the size of the problem because the first row contains the numbers of each columns), Cells[0][0] is the upper-left corner element of the StringGrid component and contains the name of the matrix. Then the rows and columns of the matrix are numbered, and the values ​​of matrices A, B and C are entered under individual Cells[i][j] elements of the StringGrid components.

void QAP::WypiszMacierze(void)
{
  MainForm->StringGridMacierzA->ColCount = rozmiar + 1;
  MainForm->StringGridMacierzB->ColCount = rozmiar + 1;
  MainForm->StringGridMacierzC->ColCount = rozmiar + 1;

  MainForm->StringGridMacierzA->RowCount = rozmiar + 1;
  MainForm->StringGridMacierzB->RowCount = rozmiar + 1;
  MainForm->StringGridMacierzC->RowCount = rozmiar + 1;

  MainForm->StringGridMacierzA->Cells[0][0] = "A";
  MainForm->StringGridMacierzB->Cells[0][0] = "B";
  MainForm->StringGridMacierzC->Cells[0][0] = "C";

  for(int i=1; i< rozmiar+2; i++){
   MainForm->StringGridMacierzA->Cells[0][i] = i;
   MainForm->StringGridMacierzA->Cells[i][0] = i;
  }
  for(int i=1; i< rozmiar+2; i++){
   MainForm->StringGridMacierzB->Cells[0][i] = i;
   MainForm->StringGridMacierzB->Cells[i][0] = i;
  }
  for(int i=1; i< rozmiar+2; i++){
   MainForm->StringGridMacierzC->Cells[0][i] = i;
   MainForm->StringGridMacierzC->Cells[i][0] = i;
  }

  for(int i=0;i< rozmiar;i++)
   for(int j=0;j< rozmiar;j++)
    MainForm->StringGridMacierzA->Cells[j+1][i+1] = A(i,j);

  for(int i=0;i< rozmiar;i++)
   for(int j=0;j< rozmiar;j++)
    MainForm->StringGridMacierzB->Cells[j+1][i+1] = B(i,j);

  for(int i=0;i< rozmiar;i++)
   for(int j=0;j< rozmiar;j++)
    MainForm->StringGridMacierzC->Cells[j+1][i+1] = C(i,j);

}

Listing 33. WypiszMacierze() function.

The Permutation (Para &p) function is designed to switch two elements (passed as a parameter) in the solution vector P. The function has one local variable used to store one element of the pair passed as a parameter. This is necessary to properly swap two items.

void QAP::Permutacja(Para &p)
{
  int temp;
 
  temp = P(p.i);
  P(p.i) = P(p.j);
  P(p.j) = temp;
 

};

Listing 34. Permutacja() function.

The function Q() calculates the value of the objective function. Its structure is presented below.

double QAP::Q(void)
{
  double Suma1=0, Suma2=0;
  int i,k;
 
  for(i=0;i< rozmiar;i++){
   Suma1 += C(i,P(i));
  }
  for(i=0;i< rozmiar;i++){
   for(k=0;k< rozmiar;k++){
    Suma2 += A(i,k) * B(P(i),P(k));
   }
  }
  return (Suma1 + Suma2);
};

Listing 35. Q() function.

The above function declares two local variables of the double type, which hold the sums of the auxiliary objective functions. The first one contains the sum calculated by adding the appropriate elements of matrix C, the second one contains the sum calculated by adding individual products of the elements of matrices A and B. The function contains two local integer variables serving as indices in the for loop.

The QAP class contains still various functions for accessing its private fields. They enable saving new values from the outside or reading the current values of individual fields. An example would be the ZwrocRozmiar() function, which returns the value of a private variable size.

int QAP::ZwrocRozmiar(void)
{
  return rozmiar;
};

Listing 36. ZwrocRozmiar() function.


 


2.5.4. TS class.

The TS class was created in order to construct objects that are designed to perform and store data about the Tabu Search algorithm. The class has 11 private fields, two of the integer type: K holds the number of iterations, T holds the grace period (blocking) value for a given pair; one alpha floating point field holds the value of the parameter which is a penalty for frequent pairing; LT object of the Tablica class, which is a tabu list for the Tabu Search algorithm; pointer QAP Problem that stores the address of the object where the QAP problem is defined; two objects PImin, PIst of the Wektor class holding the start and minimum vectors; two variables Qst, Qmin of the double type holding the values of the start and minimum objective function; an AnsiString NazwaPliku object that stores the filename for storing the value of the objective function; object Plik of ofstream class to provide a write operation to the file.

class TS
{
  int K;
  int T;
  float alfa;
  Tablica LT;
  QAP* Problem;
  Wektor PIst;
  Wektor PImin;
  double Qst, Qmin;
  AnsiString NazwaPliku;
  ofstream Plik;

  public:
   TS(QAP*);
   void UstalProblem();
   void NoweK(int);
   void NoweT(int);
   void NoweAlfa(float);
   double ZwrocQmin(void);
   double ZwrocQst(void);
   int ZwrocK(void);
   int ZwrocT(void);
   float ZwrocAlfa(void);
   Wektor& ZwrocPImin(void);
   Wektor& ZwrocPIst(void);
   String ZwrocNazwePliku(void);
   bool AlgorytmTS(void);
}

Listing 37. TS class.

The class has one constructor with a parameter where the address of the object where the QAP problem is defined is passed. The function UstalProblem() is to initialize three private fields of TS class. Its structure is presented below.

void TS::UstalProblem(void)
{
  PIst = Problem->ZwrocPI();
  Qst = Problem->Q();
  Qmin = Qst;
}

Listing 38. UstalProblem() function.

The private object PIst is initialized with the boot solution vector obtained from the QAP object through the public function ZwrocPI() of the QAP class. Private variable Qst is initialized with the value of the objective function computed by the public function Q() available in the class QAP. The private variable Qmin is initialized with the value of the variable Qst, because the minimum value of the objective function is initially the starting value.

The AlgorytmTS() function is to determine the best solution to the problem defined in the QAP object, using the Tabu Search algorithm.

bool TS::AlgorytmTS(void)
{
  int k;
  bool NoweQprim;
  bool NoweQprimodQmin;
  Para gwiazdka;
  Para prim;
  Para loop;
  double Qprim;
  double Qakt;
  double Qgwiazdka;
  int rozmiar = Problem->ZwrocRozmiar();
  MainForm->ProgressBar->Max = K;
  NazwaPliku = "Q" + IntToStr(ileTS) + ".dat";
  Plik.open(NazwaPliku.c_str());
  Plik << Qst << " \n";
  ileTS++;

Listing 39. AlgorytmTS() function - local variable declarations.

The first part of the function is shown above. One integer variable k containing the number of the current iteration is declared, two variables of the bool type are declared: NoweQprim with the value true if the found current value of the objective function is determined on the basis of the Qprim formula; NoweQminodQprim() true when a new minimum value of the Qmin objective function based on the formula Qprim is found; three gwiazdka, prim, loop structures of the Para type to remember the pair of rearranged elements, i.e. the gwiazdka remembers a pair of elements after the change of which there was an improvement in the value of the objective function calculated by means of the formula Qgwiazdka,  prim remembers a pair of elements after the change of which there was an improvement in the value of the objective function calculated by means of the formula Qprim, loop serves as a helper pair used in for loops. In the further part, three local variables of the double type are declared to store the values ​​of the objective functions Qakt, Qprim, Qgwiazdka. Next, an integer variable rozmiar is declared, initialized with the problem size value from the QAP object. Then the maximum value of the progress indicator is set to the value of the K variable. In the next line, the file name is created with the next number obtained from the ileTS global variable, and the file with this name is opened for writing, where the value of the start objective function is saved, and the count of algorithm calls is increased.

  for(k=1; k<(K+1); k++){              >    <-- początek pętli głównej
   MainForm->ProgressBar->Position = k;
   MainForm->Wykres->DataPoint(clRed,(Longint)Qmin);
   MainForm->Wykres->Update();
   MainForm->LabelQmin->Caption = FloatToStr((float)Qmin);
   MainForm->LabelQmin->Repaint();

Listing 40. AlgorytmTS() function - visualization.

In the next part, the algorithm runs in the for loop. The number of iterations is equal to the value of the variable K. The first five lines of the loop are responsible for visualizing the solution search process on the TabSheetTS page of the PageControl component. The value of the progress indicator is changed to a value stored in the variable k, then the value of the objective function is plotted in the graph window, and displayed using the LabelQmin label.

  Qprim = Problem->Q();
  Qgwiazdka=((float)Qprim)+alfa*((float)(LT(loop.j,loop.i)))/((float)k);
  for(loop.i=0, plum=0; loop.i
<(rozmiar-1);loop.i++) >   for(loop.j=loop.i+1; loop.j< rozmiar; loop.j++){
    Problem->Permutacja(loop);
    Qakt = Problem->Q();
    if(LT(loop.i,loop.j) > 0 && Qakt < Qprim){
     Qprim = Qakt;
     prim = loop;
     NoweQprim = true;
    }
    else if(LT(loop.i,loop.j) == 0 &&
     (((float)Qakt)+alfa*((float)LT(loop.j,loop.i))/((float)k)) < Qgwiazdka){
     Qgwiazdka = ((float)Qakt)+alfa*((float)LT(loop.j,loop.i))/((float)k);
     gwiazdka = loop;
    }
    else
     ;
    Problem->Permutacja(loop);
   }
  }

Listing 41. AlgorytmTS() function - determining the best solution in the environment.

The first part of the algorithm, according to its structure described in point 1.3 of the paper, concerns the determination of the best solution in the environment. This is done by the part of the algorithm presented in Listing 43. The variable Qprim is initialized with the value of the objective function. The variable Qgwiazdka has the same value, because in the first iteration each element of the tabu list has the value zero, so the value of the second term of the formula is equal to zero. The next operations take place in two for loops, which are designed to switch in pairs all elements of the solution vector using the Permutacja() function and after each conversion, calculate the current value of the objective function using the Q() function of the QAP class object. Then, two conditions specified in step 2 of the algorithm described in point 1.3 are checked, and implemented by the if/else if loop. If there has been an improvement in the value of the objective function, then a pair of elements is remembered, after changing which the value of the objective function has improved. If there has been an improvement in the value of the objective function Qgwiazdka, then the asterisk pair is remembered, if there has been an improvement in Qprim value, then the prim pair is remembered. At the end of each iteration, the pivoted items return to their starting position. The rest of the algorithm is presented below (step 3), in which the solutions are improved.

  Problem->Permutacja(gwiazdka);
  Qakt = Problem->Q();
  if(Qmin > Qakt){
   Qmin = Qakt;
   PImin = Problem->ZwrocPI();
  }
  Problem->Permutacja(gwiazdka);
  NoweQminodQprim = false;
  if(NoweQprim){
   Problem->Permutacja(prim);
   Qakt = Problem->Q();
   if(Qmin > Qakt){
    PImin = Problem->ZwrocPI();
    Qmin = Qakt;
    NoweQminodQprim = true;
   }
   Problem->Permutacja(prim);
  }

Listing 42. AlgorytmTS() function - improvement of solutions.

By means of the Permutacja() function, two elements stored in the variable gwiazdka are displayed. Then the value of the objective function is calculated and if it is better than the current minimum value, the solution vector is stored in the PImin variable. Then the moved elements return to their position. If there is a change in the value of the objective function Qprim, the current value of the objective function is calculated after rearranging the elements stored in the variable prime, and if it is better than the current value of Qmin, the new solution vector is stored in the variable PImin. Then the moved elements return to their position. The last part of the algorithm is presented below, in which the memory state is corrected, i.e. the tabu list content is updated.

   for(loop.i=0;loop.i
<(rozmiar-1);loop.i++) >    for(loop.j=loop.i+1;loop.j< rozmiar;loop.j++)
     LT(loop.i,loop.j) = max(0,LT(loop.i,loop.j) - 1);
   if(NoweQminodQprim){
    LT(prim.i,prim.j) = T;
    Problem->Permutacja(prim);
    ++(LT(prim.j,prim.i));
    loop = prim;
   }
   else{
    LT(gwiazdka.i,gwiazdka.j) = T;
    Problem->Permutacja(gwiazdka);
    ++(LT(gwiazdka.j,gwiazdka.i));
    loop = gwiazdka;
   }
   Plik << Qmin << " \n";
  }                                         <-- koniec pętli głównej
  Plik.close();
  MainForm->ProgressBar->Position = 0;
  return true;
}

Listing 43. AlgorytmTS() function - memory state correction.

Two for loops are searched at the top of the tabu list and the value of each item in the top tabu list greater than zero is decreased by one. Ie. the blocking period for a given pair in each iteration is decreased by one. The tabu list is then further revised using the if else statement. If the improvement of solutions resulted from shifting a pair of prim elements (if statement), then the position on the upper taboo list for these elements is initialized with the value of the T parameter, this is the grace period for a given pair, which is entered using the edit field on the TabSheetTS page; two elements in the solution vector are shifted with the Permutacja() function of the QAP class object; it is increased by one value of the lower tabu list for prim pair elements, i.e. the counter of the occurrence of a given pair is increased by one. If the solution was improved as a result of rearranging the elements of the gwiazdka pair, the operations described above are performed, but for the gwiazdka pair (the else statement). The last instruction in the main loop of the algorithm writes the current value of the objective function to a file. When the counter in the main for loop reaches the value of K, the execution of the statements in the loop is terminated, and thus the Tabu Search algorithm is over. The AlgorytmTS() function in the last three lines closes the file for writing, sets the algorithm progress indicator to zero, and returns true.


 


2.5.5. Para structure.

The Para structure was created in order to remember two integer elements. It has two public fields i and j, a function that constructs a new object of the Para structure and initializes its fields with zero values, an overloaded assignment operator that ensures the correct assignment of one structure of type Para to another, the structure also has a friend function of PorownajPary() to sort an array of Para structures in the AlgorytmStartowy() function of the QAP class.

struct Para
{
  int i,j;
  Para(void):i(0),j(0){}
  Para& operator=(Para &P)
  {
  this->i = P.i;
  this->j = P.j;
  return *this;
  }
  protected:
   friend int PorownajPary(const void *, const void *);
}

Listing 44. Para structure.

The PorownajPary() function compares two pair structures according to the field and the structures. Returns positive if the structure passed in the first parameter to the function has a greater value in the field i than the structure passed in the second parameter, or the function returns negative if the field i of structure passed in the first parameter have a value smaller than the field i of structure passed in the second parameter to the function.

int PorownajPary(const void *arg1, const void *arg2)
{
  return (((Para*)arg1)->i)-(((Para*)arg2)->i);
}

Listing 45. PorownajPary() function.


 


3. Resolving QAP
problems

The process of examining QAP problems using the Tabu Search algorithm is presented below. Standard problems defined in files available on the Internet were successively loaded into the written program. For each problem, many series of tests were performed, changing the values of the parameters K, T and Alpha and the Starting Algorithms. The created tool was designed in such a way that after pressing one button on the toolbar, the results can be obtained in a readable form in a table containing both the minimum value of the objective function found, as well as the algorithm invocation parameters and the defined QAP problem under investigation. Each entry in the table shows the next performed test - a new Tabu Search procedure call.

 

 

Table 1. Test results of studying the Bur26h problem of 26 size.

 

Table 2. The results of studying the scr20 problem of size 20.

 

Table 3. Test results of studying the Esc32 problem of size 32.

 

Table 4. The results of the study of the size 32 Esc32h problem.

 

Table 5. The results of studying the Kra30a problem of size 30.

 

Table 6. The results of studying the Sko42 problem of size 42.

 

Table 7. The results of studying the Sko49 problem of size 49.

 

Table 8. The results of studying the Esc64 problem of size 64.





4. Conclusions

Many standard data problems with files ending in ".dat" have been investigated. In the process of searching for the best solution, the values of the T parameter and the Alpha parameter were changed. The starting solution was generated on the basis of three different algorithms, including one random and two averaging the initial value of the objective function built on the basis of the BEST-MATCH procedure. The number of iterations was also changed.

The course of the objective function values in the process of searching for the best solution to the Sko42 problem of size 42 is presented below. K = 250 and Alpha = 3000 remained unchanged.

 

Chart 1. Examination of the Sko42 problem for T = 25, 15, 10 and 5 as well as K = 250 and Alpha = 3000.

 

As can be seen from the above diagram, the change of the T parameter has an impact on the search for a solution in the Quadratic Assignment Problem using the Tabu Search algorithm. For T = 25 the minimum value of the objective function is 16036, for T = 15 the found value of the objective function for the best solution is 15576, for T = 10 Qmin = 16048, for T = 5 Qmin = 16008. As you can see, this parameter is used to differentiate the search process the best solution in the Tabu Search algorithm. The second parameter that was changed while testing the algorithm was the Alpha parameter. The graph below shows the curves of the objective function values for different values of the Alpha parameter.

 

 

Chart 2. Examination of the Sko42 problem for T = 5 and Alpha = 500, 1500, 2500 and 3000.

 

The Alpha parameter is a penalty for frequently rearranging a pair of elements in the process of finding the best solution. It was noticed from the above chart that when the penalty is small, the algorithm quickly goes to a certain minimum, but it is not a satisfactory solution. When the penalty was increased to 1500, it was found that the algorithm is slower towards the minimum, and in this case the solution found is better than when the penalty was 500. Further increasing the penalty, a similar effect of slowly going to the minimum was observed. In the last case, when the penalty is 3000, far too few iterations were used. The best solution was found for a penalty of 1500. For all cases, the grace period for a given couple was 5.

Many series of tests were made for many problems of the Quadratic Assignment Problem defined in matrix form. The program written in the Bilder C ++ environment works correctly, and the implemented Tabu Search algorithm minimizes the value of the objective function and finds an approximate solution depending on the entered parameters K, T and Alpha.

In order to find the optimal solution to a given problem, it is necessary to perform many series of tests, changing the values of the algorithm parameters experimentally, because each problem requires different parameter values to find the best solution.





5. Bibliogra
phy

[1].  Burkard R.E. Offerman J. Entwurf von Schreibmaschinen tastatwen mittels quadratischer Zuordnungsprobleme. Band 21, 1977.

[2].  Christofides, Mingozzi, Toth. Contribution to the Qadratic Assignment Problem. European Journal of Operational Research 4, 1980.

[3].  Elshafei A.N. Hospital Layout as a Quadratic Assignment Problem. Discrete Appl. Maths 5, 1983.

[4].  Garett J. Plyter N.V The Optimal Assignment of Facilities to Location by Branch and Bound. Operations Resarch, vol.28, 1977.

[5].  Koopmans T.C. Beckman M. Assignment Problem and the Location of Economic Activities. Econometica, vol.25, 1957.

[6].  Land A.M. A Problem of Assignment with Interpelated Costs. Operational Resarch Quarterly, vol.14, 1963.

[7].  Lawler E.L The Quadratic Assignment Problem. Management Science, vol.9

[8].  Pierce J.F. Crowston W.B. Tree Search Algorithm of Quadratic Assignment Problems. Naval Research Logistics Quarterly, vol.18

[9].  Roucairol C. Un nouvel algorithme pour le probleme d'affectation quadratique. RAIRO, vol.13, 1979.

[10].  Steinberg L. The Backaboard Wiring Problem: A Placement Algorithm.
Siam Review, vol.3, 1961.

[11].  Wala K. Operations Research - lectures, 1997-1999.