Quadratic Assignment Problem (QAP)
Problem
In the Quadratic Assignment Problem (QAP), a set of n facilities has to be assigned to n locations. The problem data comprises the distance between each pair of locations and the flow (or weight) between each pair of facilities (the amount of supplies transported between them). The problem is to assign each facility to one location with the goal of minimizing the sum of distances multiplied by the corresponding flows. This problem is similar to that of the assignment problem, except that the cost function is expressed in terms of quadratic inequalities, hence the name. For more details, we invite the reader to have a look at the QAPLIB webpage.
Principles learned
- Add a list decision variable to model a permutation of the facilities
- Constrain the number of elements in the list using the ‘count’ operator
- Access the elements in the list using the ‘at’ operator to define the objective function
Data
We provide instances from the QAPLIB. The format of the data files is as follows:
- Number of points
- Matrix A: distance between each pair of locations
- Matrix B: flow between each pair of facilities
Program
The Hexaly model for the Quadratic Assignment Problem (QAP) uses only one list decision variable. This list represents a permutation of the facilities: the element in position i in the list represents the index of the facility assigned to location i.
Using the ‘count’ operator, we constrain the size of the list to be equal to the total number of facilities. We thus ensure that all facilities are assigned to a location.
Finally, we compute the objective function. Using the ‘at’ operator, we can access the facility assigned to location i (p[i], where p is the list variable). We can then easily access the flow between the facilities assigned to locations i and j (B[p[i]][p[j]]). By multiplying this quantity by the distance between locations i and j (A[i][j]), we get the cost associated with these two facilities.
- Execution
- hexaly qap.hxm inFileName=instances/esc32a.dat [hxTimeLimit=] [solFileName=]
use io;
/* Read instance data */
function input() {
local usage = "Usage: hexaly qap.hxm "
+ "inFileName=inName [solFileName=solName] [hxTimeLimit=limit]";
if (inFileName == nil) throw usage;
local inFile = io.openRead(inFileName);
n = inFile.readInt();
// Distance between locations
A[i in 0...n][j in 0...n] = inFile.readInt();
// Flow between facilites (indices of the map must start at 0
// to access it with an "at" operator")
B[i in 0...n][j in 0...n] = inFile.readInt();
}
/* Declare the optimization model */
function model() {
// Permutation such that p[i] is the facility on the location i
p <- list(n);
// The list must be complete
constraint count(p) == n;
// Minimize the sum of product distance*flow
obj <- sum[i in 0...n][j in 0...n](A[i][j] * B[p[i]][p[j]]);
minimize obj;
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 30;
}
/* Write the solution in a file with the following format:
* - n objValue
* - permutation p */
function output() {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
solFile.println(n + " " + obj.value);
for [i in 0...n] {
solFile.print(p.value[i] + " ");
}
solFile.println();
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython qap.py instances\esc32a.dat
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython qap.py instances/esc32a.dat
import hexaly.optimizer
import sys
if len(sys.argv) < 2:
print("Usage: python qap.py inputFile [outputFile] [timeLimit]")
sys.exit(1)
def read_integers(filename):
with open(filename) as f:
return [int(elem) for elem in f.read().split()]
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Read instance data
#
file_it = iter(read_integers(sys.argv[1]))
# Number of points
n = next(file_it)
# Distance between locations
A = [[next(file_it) for j in range(n)] for i in range(n)]
# Flow between factories
B = [[next(file_it) for j in range(n)] for i in range(n)]
#
# Declare the optimization model
#
model = optimizer.model
# Permutation such that p[i] is the facility on the location i
p = model.list(n)
# The list must be complete
model.constraint(model.eq(model.count(p), n))
# Create B as an array to be accessed by an at operator
array_B = model.array(B)
# Minimize the sum of product distance*flow
obj = model.sum(A[i][j] * model.at(array_B, p[i], p[j])
for j in range(n) for i in range(n))
model.minimize(obj)
model.close()
# Parameterize the optimizer
if len(sys.argv) >= 4:
optimizer.param.time_limit = int(sys.argv[3])
else:
optimizer.param.time_limit = 30
optimizer.solve()
#
# Write the solution in a file with the following format:
# - n objValue
# - permutation p
#
if len(sys.argv) >= 3:
with open(sys.argv[2], 'w') as outfile:
outfile.write("%d %d\n" % (n, obj.value))
for i in range(n):
outfile.write("%d " % p.value[i])
outfile.write("\n")
- Compilation / Execution (Windows)
-
cl /EHsc qap.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.libqap instances\esc32a.dat
- Compilation / Execution (Linux)
-
g++ qap.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o qap./qap instances/esc32a.dat
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
class Qap {
public:
// Number of points
int n;
// Distance between locations
vector<vector<int>> A;
// Flow between facilites
vector<vector<int>> B;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Hexaly Program variables
HxExpression p;
// Objective
HxExpression obj;
// Read instance data
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
infile >> n;
A.resize(n);
for (int i = 0; i < n; ++i) {
A[i].resize(n);
for (int j = 0; j < n; ++j) {
infile >> A[i][j];
}
}
B.resize(n);
for (int i = 0; i < n; ++i) {
B[i].resize(n);
for (int j = 0; j < n; ++j) {
infile >> B[i][j];
}
}
}
void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Permutation such that p[i] is the facility on the location i
p = model.listVar(n);
// The list must be complete
model.constraint(model.count(p) == n);
// Create B as an array to be accessed by an at operator
HxExpression arrayB = model.array();
for (int i = 0; i < n; ++i) {
arrayB.addOperand(model.array(B[i].begin(), B[i].end()));
}
// Minimize the sum of product distance * flow
obj = model.sum();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
obj.addOperand(A[i][j] * model.at(arrayB, p[i], p[j]));
}
}
model.minimize(obj);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - n objValue
* - permutation p */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << n << " " << obj.getValue() << "\n";
HxCollection pCollection = p.getCollectionValue();
for (int i = 0; i < n; ++i) {
outfile << pCollection.get(i) << " ";
}
outfile << endl;
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: qap inputFile [outputFile] [timeLimit]" << endl;
return 1;
}
const char* instanceFile = argv[1];
const char* solFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "30";
try {
Qap model;
model.readInstance(instanceFile);
model.solve(atoi(strTimeLimit));
if (solFile != NULL)
model.writeSolution(solFile);
return 0;
} catch (const exception& e) {
cerr << "An error occurred:" << e.what() << endl;
return 1;
}
}
- Compilation / Execution (Windows)
-
copy %HX_HOME%\bin\Hexaly.NET.dll .csc Qap.cs /reference:Hexaly.NET.dllQap instances\esc32a.dat
using System;
using System.IO;
using Hexaly.Optimizer;
public class Qap : IDisposable
{
// Number of points
int n;
// Distance between locations
int[][] A;
// Flow between facilites
long[][] B;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Hexaly Program variables
HxExpression p;
// Objective
HxExpression obj;
public Qap()
{
optimizer = new HexalyOptimizer();
}
private int readInt(string[] splittedLine, ref int lastPosRead)
{
lastPosRead++;
return int.Parse(splittedLine[lastPosRead]);
}
// Read instance data
void ReadInstance(string fileName)
{
string text = File.ReadAllText(fileName);
string[] splitted = text.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
int lastPosRead = -1;
n = readInt(splitted, ref lastPosRead);
A = new int[n][];
for (int i = 0; i < n; ++i)
{
A[i] = new int[n];
for (int j = 0; j < n; ++j)
A[i][j] = readInt(splitted, ref lastPosRead);
}
B = new long[n][];
for (int i = 0; i < n; ++i)
{
B[i] = new long[n];
for (int j = 0; j < n; ++j)
B[i][j] = readInt(splitted, ref lastPosRead);
}
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Permutation such that p[i] is the facility on the location i
p = model.List(n);
// The list must be complete
model.Constraint(model.Count(p) == n);
// Create B as an array to be accessed by an at operator
HxExpression arrayB = model.Array(B);
// Minimize the sum of product distance*flow
obj = model.Sum();
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
// arrayB[a, b] is a shortcut for accessing the multi-dimensional array
// arrayB with an at operator. Same as model.At(arrayB, a, b)
obj.AddOperand(A[i][j] * arrayB[p[i], p[j]]);
}
}
model.Minimize(obj);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
// Write the solution in a file with the following format:
// - n objValue
// - permutation p
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(n + " " + obj.GetValue());
HxCollection pCollection = p.GetCollectionValue();
for (int i = 0; i < n; ++i)
output.Write(pCollection.Get(i) + " ");
output.WriteLine();
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Qap inputFile [solFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "30";
using (Qap model = new Qap())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac Qap.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. Qap instances\esc32a.dat
- Compilation / Execution (Linux)
-
javac Qap.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/bin/hexaly.jar:. Qap instances/esc32a.dat
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class Qap {
// Number of points
private int n;
// Distance between locations
private int[][] A;
// Flow between facilites
private long[][] B;
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Hexaly Program variables
private HxExpression p;
// Objective
private HxExpression obj;
private Qap(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
// Read instance data
private void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
n = input.nextInt();
A = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
A[i][j] = input.nextInt();
}
}
B = new long[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
B[i][j] = input.nextInt();
}
}
}
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Permutation such that p[i] is the facility on the location i
p = model.listVar(n);
// [] operator is not overloaded, so we create a HxExpression array for easier access
// of the elements of the permitation (instead of creating an at operator by hand
// everytime we want to access an element in the list)
HxExpression[] pElements = new HxExpression[n];
for (int i = 0; i < n; ++i) {
pElements[i] = model.at(p, i);
}
// The list must be complete
model.constraint(model.eq(model.count(p), n));
// Create B as an array to be accessed by an at operator
HxExpression arrayB = model.array(B);
// Minimize the sum of product distance * flow
obj = model.sum();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
HxExpression prod = model.prod();
prod.addOperand(A[i][j]);
prod.addOperand(model.at(arrayB, pElements[i], pElements[j]));
obj.addOperand(prod);
}
}
model.minimize(obj);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
/*
* Write the solution in a file with the following format:
* - n objValue
* - permutation p
*/
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
output.println(n + " " + obj.getValue());
HxCollection pCollection = p.getCollectionValue();
for (int i = 0; i < n; ++i)
output.print(pCollection.get(i) + " ");
output.println();
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java Qap inputFile [outputFile] [timeLimit]");
System.exit(1);
}
String instanceFile = args[0];
String outputFile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "30";
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
Qap model = new Qap(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (outputFile != null) {
model.writeSolution(outputFile);
}
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
System.exit(1);
}
}
}
Results
Hexaly Optimizer reaches an average optimality gap of 1.1% for the Quadratic Assignment Problem (QAP) in 1 minute of running time, on the instances from the QAPLIB research benchmark, with up to 256 facilities. Our Quadratic Assignment Problem (QAP) benchmark page illustrates how Hexaly Optimizer outperforms traditional general-purpose optimization solvers like Gurobi on this challenging problem.