# Office Assignments by Binary Integer Programming: Solver-Based

This example shows how to solve an assignment problem by binary integer programming using the `intlinprog `

function. For the problem-based approach to this problem, see Office Assignments by Binary Integer Programming: Problem-Based.

### Office Assignment Problem

You want to assign six people, Marcelo, Rakesh, Peter, Tom, Marjorie, and Mary Ann, to seven offices. Each office can have no more than one person, and each person gets exactly one office. So there will be one empty office. People can give preferences for the offices, and their preferences are considered based on their seniority. The longer they have been at the MathWorks, the higher the seniority. Some offices have windows, some do not, and one window is smaller than others. Additionally, Peter and Tom often work together, so should be in adjacent offices. Marcelo and Rakesh often work together, and should be in adjacent offices.

### Office Layout

Offices 1, 2, 3, and 4 are inside offices (no windows). Offices 5, 6, and 7 have windows, but the window in office 5 is smaller than the other two. Here is how the offices are arranged.

name = {'Office 1','Office 2','Office 3','Office 4','Office 5','Office 6','Office 7'}; printofficeassign(name)

### Problem Formulation

You need to formulate the problem mathematically. First, choose what each element of your solution variable `x`

represents in the problem. Since this is a binary integer problem, a good choice is that each element represents a person assigned to an office. If the person is assigned to the office, the variable has value 1. If the person is not assigned to the office, the variable has value 0. Number people as follows:

Mary Ann

Marjorie

Tom

Peter

Marcelo

Rakesh

`x`

is a vector. The elements `x(1)`

to `x(7)`

correspond to Mary Ann being assigned to office 1, office 2, etc., to office 7. The next seven elements correspond to Marjorie being assigned to the seven offices, etc. In all, the `x`

vector has 42 elements, since six people are assigned to seven offices.

### Seniority

You want to weight the preferences based on seniority so that the longer you have been at MathWorks, the more your preferences count. The seniority is as follows: Mary Ann 9 years, Marjorie 10 years, Tom 5 years, Peter 3 years, Marcelo 1.5 years, and Rakesh 2 years. Create a normalized weight vector based on seniority.

seniority = [9 10 5 3 1.5 2]; weightvector = seniority/sum(seniority);

### People's Office Preferences

Set up a preference matrix where the rows correspond to offices and the columns correspond to people. Ask each person to give values for each office so that the sum of all their choices, i.e., their column, sums to 100. A higher number means the person prefers the office. Each person's preferences are listed in a column vector.

MaryAnn = [0; 0; 0; 0; 10; 40; 50]; Marjorie = [0; 0; 0; 0; 20; 40; 40]; Tom = [0; 0; 0; 0; 30; 40; 30]; Peter = [1; 3; 3; 3; 10; 40; 40]; Marcelo = [3; 4; 1; 2; 10; 40; 40]; Rakesh = [10; 10; 10; 10; 20; 20; 20];

The ith element of a person's preference vector is how highly they value the ith office. Thus, the combined preference matrix is as follows.

prefmatrix = [MaryAnn Marjorie Tom Peter Marcelo Rakesh];

Weight the preferences matrix by `weightvector`

to scale the columns by seniority. Also, it's more convenient to reshape this matrix as a vector in column order so that it corresponds to the `x`

vector.

PM = prefmatrix * diag(weightvector); c = PM(:);

### Objective Function

The objective is to maximize the satisfaction of the preferences weighted by seniority. This is a linear objective function

`max c'*x`

or equivalently

`min -c'*x`

.

### Constraints

The first set of constraints requires that each person gets exactly one office, that is for each person, the sum of the `x`

values corresponding to that person is exactly one. For example, since Marjorie is the second person, this means that `sum(x(8:14))=1`

. Represent these linear constraints in an equality matrix `Aeq`

and vector `beq`

, where `Aeq*x = beq`

, by building the appropriate matrices. The matrix `Aeq`

consists of ones and zeros. For example, the second row of `Aeq`

will correspond to Marjorie getting one office, so the row looks like this:

`0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0`

There are seven 1s in columns 8 through 14 and 0s elsewhere. Then `Aeq(2,:)*x = 1`

is equivalent to `sum(x(8:14)) = 1`

.

numOffices = 7; numPeople = 6; numDim = numOffices * numPeople; onesvector = ones(1,numOffices);

Each row of `Aeq`

corresponds to one person.

```
Aeq = blkdiag(onesvector,onesvector,onesvector,onesvector, ...
onesvector,onesvector);
beq = ones(numPeople,1);
```

The second set of constraints are inequalities. These constraints specify that each office has no more than one person in it, i.e., each office has one person in it, or is empty. Build the matrix `A`

and the vector `b`

such that `A*x <= b`

to capture these constraints. Each row of `A`

and `b`

corresponds to an office and so row 1 corresponds to people assigned to office 1. This time, the rows have this type of pattern, for row 1:

`1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 ... 1 0 0 0 0 0 0`

Each row after this is similar but shifted (circularly) to the right by one spot. For example, row 3 corresponds to office 3 and says that `A(3,:)*x <= 1`

, i.e., office 3 cannot have more than one person in it.

A = repmat(eye(numOffices),1,numPeople); b = ones(numOffices,1);

The next set of constraints are also inequalities, so add them to the matrix `A`

and vector `b`

, which already contain the inequalities from above. You want Tom and Peter no more than one office away from each other, and the same with Marcelo and Rakesh. First, build the distance matrix of the offices based on their physical locations and using approximate Manhattan distances. This is a symmetric matrix.

D = zeros(numOffices);

Set up the top right half of the matrix.

D(1,2:end) = [1 2 3 2 3 4]; D(2,3:end) = [1 2 1 2 3]; D(3,4:end) = [1 2 1 2]; D(4,5:end) = [3 2 1]; D(5,6:end) = [1 2]; D(6,end) = 1;

The lower left half is the same as the upper right.

D = triu(D)' + D;

Find the offices that are more than one distance unit away.

[officeA,officeB] = find(D > 1); numPairs = length(officeA)

numPairs = 26

This finds `numPairs`

pairs of offices that are not adjacent. For these pairs, if Tom occupies one office in the pair, then Peter cannot occupy the other office in the pair. If he did, they would not be adjacent. The same is true for Marcelo and Rakesh. This gives `2*numPairs`

more inequality constraints that you add to `A`

and `b`

.

Add enough rows to `A`

to accommodate these constraints.

numrows = 2*numPairs + numOffices; A((numOffices+1):numrows, 1:numDim) = zeros(2*numPairs,numDim);

For each pair of offices in `numPairs`

, for the `x(i)`

that corresponds to Tom in `officeA`

and for the `x(j)`

that corresponds to Peter in `OfficeB`

,

`x(i) + x(j) <= 1`

.

This means that either Tom or Peter can occupy one of these offices, but they both cannot.

Tom is person 3 and Peter is person 4.

tom = 3; peter = 4;

Marcelo is person 5 and Rakesh is person 6.

marcelo = 5; rakesh = 6;

The following anonymous functions return the index in x corresponding to Tom, Peter, Marcelo and Rakesh respectively in office i.

tom_index=@(officenum) (tom-1)*numOffices+officenum; peter_index=@(officenum) (peter-1)*numOffices+officenum; marcelo_index=@(officenum) (marcelo-1)*numOffices+officenum; rakesh_index=@(officenum) (rakesh-1)*numOffices+officenum; for i = 1:numPairs tomInOfficeA = tom_index(officeA(i)); peterInOfficeB = peter_index(officeB(i)); A(i+numOffices, [tomInOfficeA, peterInOfficeB]) = 1; % Repeat for Marcelo and Rakesh, adding more rows to A and b. marceloInOfficeA = marcelo_index(officeA(i)); rakeshInOfficeB = rakesh_index(officeB(i)); A(i+numPairs+numOffices, [marceloInOfficeA, rakeshInOfficeB]) = 1; end b(numOffices+1:numOffices+2*numPairs) = ones(2*numPairs,1);

### Solve Using `intlinprog`

The problem formulation consists of a linear objective function

`min -c'*x`

subject to the linear constraints

`Aeq*x = beq`

`A*x <= b`

all variables binary

You already made the `A`

, `b`

, `Aeq`

, and `beq`

entries. Now set the objective function.

f = -c;

To ensure that the variables are binary, put lower bounds of 0, upper bounds of 1, and set `intvars`

to represent all variables.

lb = zeros(size(f)); ub = lb + 1; intvars = 1:length(f);

Call `intlinprog`

to solve the problem.

[x,fval,exitflag,output] = intlinprog(f,intvars,A,b,Aeq,beq,lb,ub);

LP: Optimal objective value is -33.868852. Cut Generation: Applied 1 Gomory cut. Lower bound is -33.836066. Relative gap is 0.00%. Optimal solution found. Intlinprog stopped at the root node because the objective value is within a gap tolerance of the optimal value, options.AbsoluteGapTolerance = 0 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value).

### View the Solution -- Who Got Each Office?

numPeople = 7; office = cell(numPeople,1); for i=1:numPeople office{i} = find(x(i:numPeople:end)); % people index in office end people = {'Mary Ann', 'Marjorie',' Tom ',' Peter ','Marcelo ',' Rakesh '}; for i=1:numPeople if isempty(office{i}) name{i} = ' empty '; else name{i} = people(office{i}); end end printofficeassign(name); title('Solution of the Office Assignment Problem');

### Solution Quality

For this problem, the satisfaction of the preferences by seniority is maximized to the value of `-fval`

. `exitflag`

= 1 tells you that `intlinprog`

converged to an optimal solution. The output structure gives information about the solution process, such as how many nodes were explored, and the gap between the lower and upper bounds in the branching calculation. In this case, no branch-and-bound nodes were generated, meaning the problem was solved without a branch-and-bound step. The gap is 0, meaning the solution is optimal, with no difference between the internally calculated lower and upper bounds on the objective function.

fval,exitflag,output

fval = -33.8361

exitflag = 1

`output = `*struct with fields:*
relativegap: 0
absolutegap: 0
numfeaspoints: 1
numnodes: 0
constrviolation: 0
message: 'Optimal solution found.↵↵Intlinprog stopped at the root node because the objective value is within a gap tolerance of the optimal value, options.AbsoluteGapTolerance = 0 (the default value). The intcon variables are integer within tolerance, options.IntegerTolerance = 1e-05 (the default value).'