# Scheduling class presentations using linear programming with Python

My first year programming class culminates in a final week of group presentations. This is always a highlight of the teaching period as I get to see the awesome things my students have come up with. However, scheduling 30-40 group presentations every year could be a real nightmare. This is where mathematics comes to the rescue. I’ll describe in this post how I use linear programming implemented in the Python library Pulp to get the schedule easily.

**TLDR** We built a python library for scheduling conferences, you can find the
source code
here:github.com/PyconUK/ConferenceScheduler,
the documentation is here:
conference-scheduler.readthedocs.io/en/latest/
and you can `pip install conference_scheduler`

.

I have previously done this using Sagemath but this year I decided to use Pulp (“A python Linear Programming API”). This probably follows from @DRMacIver’s talk at PyCon UK 2016: “Easy solutions to hard problems” in which I saw Pulp for the first time.

The first step to solving this problem is getting the availability of all the student groups. I use doodle for this just to get a quick poll with dates and times. Doodle has the ability to export a poll to excel so I open that up in spreadsheet and tidy things up a bit (deleting irrelevant rows/columns).

Once I’ve done that I can read in the file which I do using Pandas:

```
>>> import pandas as pd
>>> df = pd.read_excel("availability.xls")
>>> df = df.replace("OK", 1)
>>> df = df.fillna(0)
>>> df.head()
Company Time slot
0 Alternative Maths Wed 13:30 – 14:00
1 Europa Thu 13:00 – 13:30
2 L.A.S.T Resort Mon 12:30 – 13:00
3 DividedByZeroStudios Mon 17:30 – 18:00
4 Effervescence Fri 17:30 – 18:00
```

I then transform that in to a numeric array:

```
>>> import numpy as np
>>> A = np.array(df)
```

**Now I start using Pulp**, first let’s create a problem:

```
>>> import pulp
>>> M, N = A.shape # Dimensions
>>> prob = pulp.LpProblem("Scheduling")
>>> x = pulp.LpVariable.dicts("x", itertools.product(range(M), range(N)),
cat=pulp.LpBinary) # Variables
```

Then we add constraints:

Ie \(x_{ij}\) can be 1 iff team \(i\) is available in slot \(j\).

Here is how we **add** the constraint to our pulp `prob`

:

```
>>> for index in x:
... try:
... x[index].upBound = float(A[index])
... except ValueError: # Seemed to be an artifact in the matrix
... x[index].upBound = 0
```

Next we need to make sure that any slot can only be used by one team at a time:

Ie slot \(j\) can only be used by one team.

```
>>> for slot in range(N):
... prob += sum(x[(team, slot)] for team in range(M)) <= 1
```

I quite like this syntax: we’re “literally” **adding** the constraints to our
problem.

We now need to make sure that each team appears in exactly one slot:

```
>>> for team in range(M):
... prob += sum(x[(team, slot)] for slot in range(N)) == 1
```

Once we have done this **we could** add an objective function. I could for
example write a mathematical function that chooses to minimise the number of
days used but in practice I don’t need to do that so I’m just looking for a
feasible solution to the problem:

```
>>> prob.solve(pulp.GLPK())
1
```

The `1`

is the status which confirms in this case that a solution has been found.

So let us take a look at the solution:

```
>>> solution = []
>>> for company in range(M):
... for slot in range(N):
... if x[(company, slot)].value() == 1:
... solution.append([df.index[company], df.columns[slot]])
>>> df = pd.DataFrame(solution, columns=["Company", "Time slot"])
>>> df.sort_values(by="Company")
Company Time slot
14 Apollo Tue 16:30 – 17:00
17 Coding Cymraeg Thu 13:30 – 14:00
23 Complexus Tue 10:00 – 10:30
3 DividedByZeroStudios Mon 17:30 – 18:00
4 Effervescence Fri 17:30 – 18:00
1 Europa Thu 13:00 – 13:30
11 F.E.J.L. Tue 15:00 – 15:30
12 Ferdie Amor Wed 12:30 – 13:00
16 Framtak Tue 17:30 – 18:00
15 FridgeVentory Tue 17:00 – 17:30
30 Generic Group Tue 16:00 – 16:30
26 GeoCampus Tue 11:30 – 12:00
5 Green and Russian Standard Fri 16:30 – 17:00
19 JALE Mon 16:00 – 16:30
27 JEM'D Fri 16:00 – 16:30
7 J^2AG Wed 16:30 – 17:00
2 L.A.S.T Resort Mon 12:30 – 13:00
29 MACT enterprises Tue 12:30 – 13:00
10 MBAS Thu 17:30 – 18:00
21 MRJL Mon 15:30 – 16:00
8 Merge Wed 09:00 – 09:30
13 MyTime Tue 15:30 – 16:00
22 Noteable Mon 17:00 – 17:30
20 Oakheart Mon 15:00 – 15:30
24 PoshFit Tue 10:30 – 11:00
25 ReCollect Tue 11:00 – 11:30
0 Alternative Maths Wed 13:30 – 14:00
6 Room 1 Tue 14:00 – 14:30
9 StockSensors Fri 11:30 – 12:00
28 Swigg Tue 12:00 – 12:30
18 The Cyps Mon 16:30 – 17:00
31 Ticket Tiger Mon 13:00 – 13:30
```

This is a great example of using implemented applied mathematics to quickly solve a real world problem. The other benefit is that when/if I need to do this again in the feature I simply need to read in a different availability file.