{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The Lemke Howson algorithm\n",
"\n",
"The vertex and support enumeration algorithms are all algorithms that use an exhaustive search. For large games, this can take a long time and/or have a high computational cost. The following algorithm gives an approach to create a path through vertices in both best response polytopes to find a pair that is fully labelled.\n",
"\n",
"---\n",
"\n",
"## The Lemke Howson algorithm\n",
"\n",
"[Video](https://youtu.be/HekHAuWR_30?list=PLnC5h3PY-znxMsG0TRYGOyrnEO-QhVwLb)\n",
"\n",
"\n",
"For a nondegenerate 2 player game $(A, B)\\in{\\mathbb{R}^{m\\times n}_{>0}}^2$ the following algorithm returns a nash equilibrium:\n",
"\n",
"1. Start at the artificial equilibrium: $(0, 0)$\n",
"2. Choose a label to drop.\n",
"3. Remove this label from the corresponding vertex by traversing an edge of the corresponding polytope to another vertex. \n",
"4. The new vertex will now have a duplicate label in the other polytope. Remove this label from the vertex of the other polytope and traverse an edge of that polytope to another vertex.\n",
"5. Repeat step 4 until the pair of vertices is fully labelled.\n",
"\n",
"---\n",
"\n",
"As an example let us consider the matching pennies game:\n",
"\n",
"$$\n",
"A = \n",
"\\begin{pmatrix}\n",
" 1 & -1\\\\\n",
" -1& 1\n",
"\\end{pmatrix}\\qquad\n",
"B = \n",
"\\begin{pmatrix}\n",
" -1 & 1\\\\\n",
" 1& -1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"First let us add 2 to all utilities:\n",
"\n",
"$$\n",
"A = \n",
"\\begin{pmatrix}\n",
" 3 & 1\\\\\n",
" 1 & 3\n",
"\\end{pmatrix}\\qquad\n",
"B = \n",
"\\begin{pmatrix}\n",
" 1 & 3\\\\\n",
" 3 & 1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"The vertices for $\\mathcal{P}$ are:\n",
"\n",
"1. $a=(0, 0)$ has labels $\\{0, 1\\}$ \n",
"2. $b=(1/3, 0)$ has labels $\\{1, 3\\}$\n",
"3. $c=(1/4, 1/4)$ has labels $\\{2, 3\\}$\n",
"4. $d=(0, 1/3)$ has labels $\\{0, 2\\}$\n",
"\n",
"The vertics and labels for $\\mathcal{Q}$ are:\n",
"\n",
"1. $w=(0, 0)$ has labels $\\{2, 3\\}$\n",
"2. $x=(1/3, 0)$ has labels $\\{0, 3\\}$\n",
"3. $y=(1/4, 1/4)$ has labels $\\{0, 1\\}$\n",
"4. $z=(0, 1/3)$ has labels $\\{1, 2\\}$\n",
"\n",
"Let us apply the algorithm:\n",
"\n",
"- $(a, w)$ have labels: $\\{0, 1\\}, \\{2, 3\\}$. Drop 0 (arbitrary decision) in $\\mathcal{P}$.\n",
"- $\\to (b, w)$ have labels: $\\{1, 3\\}, \\{2, 3\\}$. In $\\mathcal{Q}$ drop 3.\n",
"- $\\to (b, z)$ have labels: $\\{1, 3\\}, \\{1, 2\\}$. In $\\mathcal{P}$ drop 1.\n",
"- $\\to (c, z)$ have labels: $\\{2, 3\\}, \\{1, 2\\}$. In $\\mathcal{Q}$ drop 2.\n",
"- $\\to (c, y)$ have labels: $\\{2, 3\\}, \\{0, 1\\}$. Fully labeled vertex pair.\n",
"\n",
"We now return the strategy pair by normalising these vertices:\n",
"\n",
"$$((1/2, 1/2), (1/2, 1/2))$$\n",
"\n",
"This is also implemented in `Nashpy`:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(array([ 0.5, 0.5]), array([ 0.5, 0.5]))"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import numpy as np\n",
"import nashpy as nash\n",
"A = np.array([[1, -1], [-1, 1]])\n",
"matching_pennies = nash.Game(A)\n",
"matching_pennies.lemke_howson(initial_dropped_label=0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also iterate over all possible starting labels:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(array([ 0.5, 0.5]), array([ 0.5, 0.5]))\n",
"(array([ 0.5, 0.5]), array([ 0.5, 0.5]))\n",
"(array([ 0.5, 0.5]), array([ 0.5, 0.5]))\n",
"(array([ 0.5, 0.5]), array([ 0.5, 0.5]))\n"
]
}
],
"source": [
"for eq in matching_pennies.lemke_howson_enumeration():\n",
" print(eq)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this case they will always give the same result (this game indeed has a unique equilibria), however that's not always the case and also note that not all equilibria can be obtained from a given starting vertex pair.\n",
"\n",
"---\n",
"\n",
"## Tableaux\n",
"\n",
"[Video](https://youtu.be/krXbHbVhVTM?list=PLnC5h3PY-znxMsG0TRYGOyrnEO-QhVwLb)\n",
"\n",
"Carrying out the Lemke Howson algorithm in this way is straightforward when the polytope can be visualised and/or the vertices themselves can all be enumerated to begin with. This is not always the case: using tools from linear programming we can traverse our polytopes using a technique called \"pivoting\" or a tabular representation of the polytopes called \"tableaux\" (plural).\n",
"\n",
"A tableau corresponds to the inequalities relating to the payoffs considered but as equalities.The inequalities for the row polytope for the matching pennies game can be rewritten as:\n",
"\n",
"1. $x_1+3x_2 + s_1 = 1$\n",
"2. $3x_1+x_2 + s_2 = 1$\n",
"\n",
"where $s_1, s_2$ are \"slack\" variables used to convert the inequality to an equality.\n",
"\n",
"This can be represented in tableau form as:\n",
"\n",
"$$\n",
"T_r =\n",
"\\begin{pmatrix}\n",
"1 & 3 & 1 & 0 & 1\\\\\n",
"3 & 1 & 0 & 1 & 1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"Note that this corresponds to:\n",
"\n",
"$$\n",
"T_r = \n",
"\\begin{pmatrix}\n",
"B^T&I &1\\\\\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"---\n",
"\n",
"## Basic variables and labelled vertices\n",
"\n",
"[Video](https://youtu.be/6ujffslY318?list=PLnC5h3PY-znxMsG0TRYGOyrnEO-QhVwLb)\n",
"\n",
"A basic variable of a tableau is a column (ignoring the last column) that is linearly independent from the others. Any given vertex of a polytope corresponds to a tableau where the non basic variables are set to 0 and the basic variables are solved. Non basic variables corresponds to labels of a vertex.\n",
"\n",
"---\n",
"\n",
"For example setting the non basic variables in $T_r$ to 0 we get:\n",
"\n",
"$$x_1=x_2=0$$ which corresponds to the origin of the polytope (which is the starting point for the Lemke Howson algorithm). Thus the labels of vertex corresponds to the non basic variables.\n",
"\n",
"\n",
"---\n",
"\n",
"## Pivoting and the Lemke Howson Algorithm\n",
"\n",
"[Video](https://youtu.be/aJq73gJwd24?list=PLnC5h3PY-znxMsG0TRYGOyrnEO-QhVwLb)\n",
"\n",
"In the Lemke Howson algorithm we need to drop a label and move along an edge of the Polytope to arrive at another vertex where a new label will replace the dropped label. This is equivalent to making a non basic variable basic (and vice versa). We do this by carrying out row operations on the tableau.\n",
"\n",
"\n",
"Let us drop the 0 label (corresponding to the first column) on $T_r$. We need to \"pivot\" the first column, so one of those non zero variables in that column will become 0. We identify this by choosing the row with the minimum positive ratio of $(T_r)_{i5} / (T_r)_{i1}$. This ensures that the resulting vertex is valid (and will have no negative variables). In our case the ratios in the first column are:\n",
"\n",
"1. First row: $1/1$\n",
"2. Second row: $1/3$\n",
"\n",
"Thus we pivot on the second row. We here take multiples of rows and carry out subtractions/additions of row so as to obtain a basic variable in the first column. In our case let us multiple the first row by 3 and subtract the second row from it:\n",
"\n",
"$$\n",
"T_r =\n",
"\\begin{pmatrix}\n",
" 0 & 8 & 3 & -1 & 2\\\\\n",
" 3 & 1 & 0 & 1 & 1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"We now (as in the example we carried out before) have non basic variables $x_2$ and $s_2$ in other words we have labels: $\\{1, 3\\}$. We have thus picked up the label $3$ which we need to drop from the other polytope.\n",
"\n",
"In tableau form the column best response polytope is given by:\n",
"\n",
"$$\n",
"T_c =\n",
"\\begin{pmatrix}\n",
" 1 & 0 & 3 & 1 & 1\\\\\n",
" 0 & 1 & 1 & 3 & 1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"Note that this corresponds to:\n",
"\n",
"$$\n",
"T_c = \n",
"\\begin{pmatrix}\n",
"I & A & 1\\\\\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"we need to pivot the fourth column corresponding to label $3$ of $T_c$. First the minimum ratios: $1/1>1/3$, so we pivot on the second row. Let us multiply the first row by 3 and subtract the second row from it:\n",
"\n",
"$$\n",
"T_c =\n",
"\\begin{pmatrix}\n",
" 3 & -1 & 8 & 0 & 2\\\\\n",
" 0 & 1 & 1 & 3 & 1\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"This has labels: $\\{1, 2\\}$ with $1$ being the newly picked up label that we now drop (by pivoting the second column) from $T_r$:\n",
"\n",
"$$\n",
"T_r =\n",
"\\begin{pmatrix}\n",
" 0 & 8 & 3 & -1 & 2\\\\\n",
" 24 & 0 & -3 & 9 & 6\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"which has labels: $\\{2, 3\\}$ with $2$ being the newly picked up label so we now pivot the third column of $T_c$:\n",
"\n",
"$$\n",
"T_c =\n",
"\\begin{pmatrix}\n",
" 3 & -1 & 8 & 0 & 2\\\\\n",
" -3 & 9 & 0 & 24 & 6\n",
"\\end{pmatrix}\n",
"$$\n",
"\n",
"which has labels: $\\{0, 1\\}$ so we have a fully labelled vertex pair. Setting the non basic variables to 0 in each tableau we obtain:\n",
"\n",
"$$\n",
"\\left((2/8, 8/24), (2/8, 6/24)\\right) = \\left((1/4, 1/4), (1/4, 1/4)\\right)\n",
"$$\n",
"\n",
"which, when normalised to have sum 1 gives:\n",
"\n",
"$$\n",
"\\left((1/2, 1/2), (1/2, 1/2)\\right)\n",
"$$\n",
"\n",
"---\n",
"\n",
"A summary of integer pivoting:\n",
"\n",
"1. Identify the row to pivot on using the minumum ratio test.\n",
"2. Carry out row operations to ensure that the pivot row is the only row with non zero variables in that column."
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python [conda env:gt]",
"language": "python",
"name": "conda-env-gt-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}