Path: blob/main/notebooks/04/01-dinner-seat-allocation.ipynb
663 views
4.1 Dinner seating arrangement
Preamble: Install Pyomo and a solver
The following cell sets and verifies a global SOLVER for the notebook. If run on Google Colab, the cell installs Pyomo and the HiGHS solver, while, if run elsewhere, it assumes Pyomo and HiGHS have been previously installed. It then sets to use HiGHS as solver via the appsi module and a test is performed to verify that it is available. The solver interface is stored in a global object SOLVER for later use.
Problem description
Assume that we are organizing a wedding dinner and our goal is to have guests from different families mingle with each other as much as possible. One way to do this is to seat people at tables so that no more people than a given threshold from the same family sit at the same table. How could we solve a problem like this?
First, we need the problem data. For each family we need to know the number of its members , and for each table we need to know its capacity . Using these data and the tools we have learned so far, we can formulate this problem as a LO problem.
If we are not concerned about specific individuals, but only about the number of people in a given family, then we can use variable to determine the number of people in family who will sit at table . In the problem formulation, we were not given any objective function, since our goal is to find a feasible seating arrangement. For this reason, we can set the objective function to a constant value, say , and, in this way, do not differentiate between the various feasible solutions.
The mathematical formulation of this seating allocation problem is:
The constraints ensure that the seating capacity for each table is not exceeded, that each family is fully seated, and that the number of elements of each family at each table does not exceed the threshold .
Implementation
The problem statement will be satisfied by finding a feasible solution, if one exists. Because no specific objective has been specified, the mathematical formulation uses a constant 0 as essentially a placeholder for the objective function. Some optimization solvers, however, issue warning messages if they detect a constant objective, and others will fail to execute at all. A simple work around for these cases is to replace the constant objective with a 'dummy' variable that doesn't appear elsewhere in the optimization problem.
Let us now consider and solve a specific instance of this problem with six families with sizes , five tables with capacities , and a threshold for the number of members of each family that can be seated at the same table.
A peculiar fact is that although we did not explicitly require that all variables be integer, the optimal solution turned out to be integer anyway. This is no coincidence as it follows from a certain property of the problem we solve. This also means we can solve larger versions of this problem with LO instead of MILO solvers to find integer solutions, gaining a large computational advantage.
Minimize the maximum group size
Our objective was that we make members of different families mingle as much as possible. Is the lowest possible number for which a feasible table allocation exists or can we make the tables even more diverse by bringing this number down?
In order to find out, we can make a decision variable and change the objective function as to minimize , obtaining the following problem:
We now solve the same instance as before.
Unfortunately, this solution is no longer integer. Mathematically, this is because the "structure" that previously ensured integer solutions at no extra cost has been lost as a result of making a decision variable. To find the solution to this problem we need to impose that the variables are integers.
Using an MILO solver such as HiGHS and specifying the desired domain for the variables using domain=pyo.NonNegativeIntegers, we can recover the original optimal value .
Minimize number of tables
Let us fix again to the maximum number of family members that can be seated at the same table and focus on minimizing the number of tables used. Let us introduce a binary variable that is equal to 1 if table is used and 0 otherwise. We can thus consider a new optimization problem in which we minimize the sum of over . The mathematical formulation of this new problem is:
We can implement it in Pyomo as follows.
Reformulation as max flow problem
However, using an MILO solver is not necessarily the best approach for problems like this. Many real-life situations (e.g., assigning people to groups/teams) require solving really large problems. There are existing algorithms that can leverage the special network structure of the problem at hand and scale better than LO solvers.
To illustrate this, we first visualize the seating problem using a graph where:
the nodes on the left-hand side stand for the families and the numbers next to them provide the family size;
the nodes on the left-hand side stand for the tables and the numbers next to them provide the table size;
each left-to-right arrow stand comes with a number denoting the capacity of arc -- how many people of family can be assigned to table .

If we think of each family as a "supply of individuals" and each table as a "demand of individuals", then we can rephrase our original task as the problem of sending people from families to tables so that everyone is assigned to some table, the tables' capacities are respected, and no table gets more than members of the same family.
A Pyomo version of this model is given in the next cell. After that we will show how to reformulate the calculation using network algorithms.
By adding two more nodes to the graph above, we can formulate the problem as a slightly different flow problem where all the data is formulated as the arc capacity, see figure below. In a network like this, we can imagine a problem of sending resources from the source node "door" to the target node "seat", subject to the restriction that for any node that is neither the source nor the target, the sum of incoming and outgoing flows are equal (balance constraint). If there exists a flow in this new graph that respects the arc capacities and the sum of outgoing flows at the source is equal to the total number of individuals, that is , it means that there exists a family-to-table assignment that meets our requirements.

Thus, if we maximize the total flow going out of door and reaching seat and it matches the total number of individuals, the problem is solved. As unimpressive as this sounds, this means that it can be treated as a special case of a famous maximum flow problem, for which there exist algorithms that are way more efficient than a generic LO solver. One such algorithm is the Bellman-Ford algorithm, implicitly invoked in the following code using the Python package networkx.
Even for this very small example, we see that network algorithms generate a solution significantly faster than using the MILO formulation above. Realizing that the optimization problem we are tackling is a particular problem class for which a tailored algorithm is available can result in solving times orders of magnitude faster, particularly for large instances.
A more systematic comparison between LO solvers and network algorithms
We now create increasingly larger instances of the dinner seating allocation problem and compare the performance of the MILO solvers highs, cbc, gurobi, and the algorithm maximum_flow from the networkx library. We will use the following code to generate the instances.
We stored all the runtimes in a dataframe, which we can now plot. We see that the network algorithm maximum_flow is the fastest, followed by highs, cbc, and gurobi. The network algorithm is the fastest because it is able to exploit the special structure of the problem, which the MILO solvers do not.