Suppose n teams play each other, and let Team Team Team denote some fixed ranking (where is some permutation of ). An *upset* occurs when a lower ranked team beats an upper ranked team. For each ranking, , let denote the total number of upsets. The *minimum upset problem* is to find an “efficient” construction of a ranking for which is as small as possible.

In general, let denote the number of times Team i beat team $j$ minus the number of times Team j beat Team i. We regard this matrix as the signed adjacency matrix of a digraph . Our goal is to find a Hamiltonian (undirected) path through the vertices of which goes the “wrong way” on as few edges as possible.

- Construct the list of spanning trees of (regarded as an undirected graph).
- Construct the sublist of Hamiltonian paths (from the spanning trees of maximum degree 2).
- For each Hamiltonian path, compute the associated
*upset number*: the total number of edges transversal in going the “right way” minus the total number going the “wrong way.”
- Locate a Hamiltonian for which this upset number is as large as possible.

Use this sagemath/python code to compute such a Hamiltonian path.

def hamiltonian_paths(Gamma, signed_adjacency_matrix = []):
"""
Returns a list of hamiltonian paths (spanning trees of
max degree <=2).
EXAMPLES:
sage: Gamma = graphs.GridGraph([3,3])
sage: HP = hamiltonian_paths(Gamma)
sage: len(HP)
20
sage: A = matrix(QQ,[
[0 , -1 , 1 , -1 , -1 , -1 ],
[1, 0 , -1, 1, 1, -1 ],
[-1 , 1 , 0 , 1 , 1 , -1 ],
[1 , -1 , -1, 0 , -1 , -1 ],
[1 , - 1 , - 1 , 1 , 0 , - 1 ],
[1 , 1 , 1 , 1 , 1 , 0 ]
])
sage: Gamma = Graph(A, format='weighted_adjacency_matrix')
sage: HP = hamiltonian_paths(Gamma, signed_adjacency_matrix = A)
sage: L = [sum(x[2]) for x in HP]; max(L)
5
sage: L.index(5)
21
sage: HP[21]
[Graph on 6 vertices,
[0, 5, 2, 1, 3, 4],
[-1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1]]
sage: L.count(5)
1
"""
ST = Gamma.spanning_trees()
if signed_adjacency_matrix == []:
HP = []
for X in ST:
L = X.degree_sequence()
if max(L)<=2:
#print L,ST.index(X), max(L)
HP.append(X)
return HP
if signed_adjacency_matrix != []:
A = signed_adjacency_matrix
HP = []
for X in ST:
L = X.degree_sequence()
if max(L)<=2:
#VX = X.vertices()
EX = X.edges()
if EX[0][1] != EX[-1][1]:
ranking = X.shortest_path(EX[0][0],EX[-1][1])
else:
ranking = X.shortest_path(EX[0][0],EX[-1][0])
signature = [A[ranking[i]][ranking[j]] for i in range(len(ranking)-1) for j in range(i+1,len(ranking))]
HP.append([X,ranking,signature])
return HP

Wessell describes this method in a different way.

- Construct a matrix, , with rows and columns indexed by the teams in some fixed order. The entry in the i-th row and the j-th column is defined by
- Reorder the rows (and corresponding columns) to in a basic win-loss order: the teams that won the most games go at the

top of , and those that lost the most at the bottom.
- Randomly swap rows and their associated columns, each time checking if the

number of upsets has gone down or not from the previous time. If it has gone down, we keep

the swap that just happened, if not we switch the two rows and columns back and try again.

An implementaiton of this in Sagemath/python code is:

def minimum_upset_random(M,N=10):
"""
EXAMPLES:
sage: M = matrix(QQ,[
[0 , 0 , 1 , 0 , 0 , 0 ],
[1, 0 , 0, 1, 1, 0 ],
[0 , 1 , 0 , 1 , 1 , 0 ],
[1 , 0 , 0, 0 , 0 , 0 ],
[1 , 0 , 0 , 1 , 0 , 0 ],
[1 , 1 , 1 , 1 , 1 , 0 ]
])
sage: minimum_upset_random(M)
(
[0 0 1 1 0 1]
[1 0 0 1 0 1]
[0 1 0 0 0 0]
[0 0 1 0 0 0]
[1 1 1 1 0 1]
[0 0 1 1 0 0], [1, 2, 0, 3, 5, 4]
)
"""
n = len(M.rows())
Sn = SymmetricGroup(n)
M1 = M
wins = sum([sum([M1[j][i] for i in range(j,6)]) for j in range(6)])
g0 = Sn(1)
for k in range(N):
g = Sn.random_element()
P = g.matrix()
M0 = P*M1*P^(-1)
if sum([sum([M0[j][i] for i in range(j,6)]) for j in range(6)])>wins:
M1 = M0
g0 = g*g0
return M1,g0(range(n))