{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Solving Linear Programming Problems with PuLP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This [IPython notebook](http://ipython.org/notebook.html) demonstrates the use of the [Python package PuLP](https://pypi.python.org/pypi/PuLP/1.5.4) for the modeling and solution of linear programming problems.\n",
"\n",
"J.C. Kantor (Kantor.1@nd.edu)\n",
" \n",
"The latest version of this IPython notebook is available at [http://github.com/jckantor/CBE20255](http://github.com/jckantor/CBE20255) for noncommercial use under terms of the [Creative Commons Attribution Noncommericial ShareAlike License](http://creativecommons.org/licenses/by-nc-sa/4.0/)."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Installing PuLP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PuLP is an external package that may need to be installed into your python environment. The following cell issues a system command to install PuLP if needed. PuLP includes the [COIN-OR CBC](https://projects.coin-or.org/Cbc) solver."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"!pip install PuLP"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Requirement already satisfied (use --upgrade to upgrade): PuLP in /Applications/anaconda/lib/python2.7/site-packages\r\n",
"Requirement already satisfied (use --upgrade to upgrade): pyparsing<=1.9.9 in /Applications/anaconda/lib/python2.7/site-packages (from PuLP)\r\n",
"Cleaning up...\r\n"
]
}
],
"prompt_number": 1
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Example: Giapetto's Workshop"
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Model Formulation"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import pulp\n",
"\n",
"# Create problem instance\n",
"giapetto = pulp.LpProblem(\"Giapetto's Workshop\", pulp.LpMaximize)\n",
"\n",
"# Define Variables with lower bounds of zero\n",
"x1 = pulp.LpVariable(\"Soldiers\",0)\n",
"x2 = pulp.LpVariable(\"Trains\",0)\n",
"\n",
"# Add Objective\n",
"giapetto += 3*x1 + 2*x2, \"Profit\"\n",
"\n",
"# Add Constraints\n",
"giapetto += 2*x1 + x2 <= 100,\"Finishing Labor\"\n",
"giapetto += x1 + x2 <= 80, \"Carpentry Labor\"\n",
"giapetto += x1 <= 40, \"Soldier Demand\"\n",
"giapetto += x1 + x2 == 20, \"Minimum Production\"\n",
"\n",
"# Display Problem\n",
"print giapetto"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Giapetto's Workshop:\n",
"MAXIMIZE\n",
"3*Soldiers + 2*Trains + 0\n",
"SUBJECT TO\n",
"Finishing_Labor: 2 Soldiers + Trains <= 100\n",
"\n",
"Carpentry_Labor: Soldiers + Trains <= 80\n",
"\n",
"Soldier_Demand: Soldiers <= 40\n",
"\n",
"Minimum_Production: Soldiers + Trains = 20\n",
"\n",
"VARIABLES\n",
"Soldiers Continuous\n",
"Trains Continuous\n",
"\n"
]
}
],
"prompt_number": 2
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Model Solution"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"giapetto.solve()\n",
"\n",
"print pulp.LpStatus[giapetto.status]\n",
"print pulp.LpSenses[giapetto.sense], giapetto.objective.name, \"=\", pulp.value(giapetto.objective)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Optimal\n",
"Maximize OBJ = 60.0\n"
]
}
],
"prompt_number": 3
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Displaying the Decision Variables"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Values for the decision variables can be accessed with the `pulp.value()` method."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print x1.name, \"=\" , pulp.value(x1)\n",
"print x2.name, \"=\" , pulp.value(x2)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Soldiers = 20.0\n",
"Trains = 0.0\n"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively, the decision variables can be accessed by interating through the list created by the `variables()` method."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for x in giapetto.variables():\n",
" print \"Name: \", x.name\n",
" print \"Value: \", x.varValue\n",
" print \"Category: \", x.cat\n",
" print \"Lower Bound:\", x.lowBound\n",
" print \"Upper Bound:\", x.upBound\n",
" print "
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Name: Soldiers\n",
"Value: 20.0\n",
"Category: Continuous\n",
"Lower Bound: 0\n",
"Upper Bound: None\n",
"\n",
"Name: Trains\n",
"Value: 0.0\n",
"Category: Continuous\n",
"Lower Bound: 0\n",
"Upper Bound: None\n",
"\n"
]
}
],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Pandas](http://pandas.pydata.org/) is a python package for managing and analysing data. The following cell shows how to use a list comprehension and panda DataFrame to create and display a table from the solution data."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Displaying the Constraints"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The constraints are stored as an ordered dictionary in `constraints` attribute of the problem. "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for (name,constraint) in giapetto.constraints.items():\n",
" print \"Name: \", name\n",
" print \"Constraint: \", constraint\n",
" print \"Lower Bound: \", constraint.getLb()\n",
" print \"Upper Bound: \", constraint.getUb()\n",
" print \"Value: \", pulp.value(constraint)\n",
" print \"Sense: \", pulp.LpConstraintSenses[constraint.sense]\n",
" print \"Constant: \", constraint.constant\n",
" print \"Slack: \", constraint.slack\n",
" print \"Slack (Feas):\", constraint.slack if constraint.sense < 0 else -constraint.slack\n",
" print"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Name: Finishing_Labor\n",
"Constraint: 2*Soldiers + Trains <= 100\n",
"Lower Bound: None\n",
"Upper Bound: 100\n",
"Value: -60.0\n",
"Sense: <=\n",
"Constant: -100\n",
"Slack: 60.0\n",
"Slack (Feas): 60.0\n",
"\n",
"Name: Carpentry_Labor\n",
"Constraint: Soldiers + Trains <= 80\n",
"Lower Bound: None\n",
"Upper Bound: 80\n",
"Value: -60.0\n",
"Sense: <=\n",
"Constant: -80\n",
"Slack: 60.0\n",
"Slack (Feas): 60.0\n",
"\n",
"Name: Soldier_Demand\n",
"Constraint: Soldiers <= 40\n",
"Lower Bound: None\n",
"Upper Bound: 40\n",
"Value: -20.0\n",
"Sense: <=\n",
"Constant: -40\n",
"Slack: 20.0\n",
"Slack (Feas): 20.0\n",
"\n",
"Name: Minimum_Production\n",
"Constraint: Soldiers + Trains\n",
"Lower Bound: 20\n",
"Upper Bound: 20\n",
"Value: 0.0\n",
"Sense: =\n",
"Constant: -20\n",
"Slack: -0.0\n",
"Slack (Feas): 0.0\n",
"\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"A simple function to solve and display the solution to a linear program"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import pandas\n",
"\n",
"def solvelp(lp):\n",
" lp.solve();\n",
"\n",
" data = [[lp.objective.name, pulp.LpSenses[lp.sense], pulp.LpStatus[lp.status], pulp.value(lp.objective)]]\n",
" objDF = pandas.DataFrame(data,columns=[\"Objective\", \"Sense\", \"Status\", \"Value\"])\n",
"\n",
" data = [[x.name, x.cat, x.lowBound, x.upBound, x.varValue] \n",
" for x in lp.variables()]\n",
" varDF = pandas.DataFrame(data,columns=[\"Name\",\"Category\",\"Lower Bound\",\"Upper Bound\",\"Value\"]) \n",
"\n",
" data = [[c.name, c.getLb(), c.getUb(), c.slack, pulp.value(c) - c.constant] \n",
" for (k,c) in lp.constraints.items()]\n",
" conDF = pandas.DataFrame(data,columns=[\"Name\", \"Lower Bound\", \"Upper Bound\", \"Slack\", \"Value\"])\n",
"\n",
" return (objDF,varDF,conDF)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 7
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"(odf,vdf,cdf) = solvelp(giapetto);\n",
"\n",
"from IPython.display import display,HTML\n",
"\n",
"display(HTML('
Model
'))\n",
"display(giapetto)\n",
"display(HTML('Objective
'))\n",
"display(odf)\n",
"display(HTML('Variables
'))\n",
"display(vdf)\n",
"display(HTML('Constraints
'))\n",
"display(cdf)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"Model
"
],
"metadata": {},
"output_type": "display_data",
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"Giapetto's Workshop:\n",
"MAXIMIZE\n",
"3*Soldiers + 2*Trains + 0\n",
"SUBJECT TO\n",
"Finishing_Labor: 2 Soldiers + Trains <= 100\n",
"\n",
"Carpentry_Labor: Soldiers + Trains <= 80\n",
"\n",
"Soldier_Demand: Soldiers <= 40\n",
"\n",
"Minimum_Production: Soldiers + Trains = 20\n",
"\n",
"VARIABLES\n",
"Soldiers Continuous\n",
"Trains Continuous\n"
]
},
{
"html": [
"Objective
"
],
"metadata": {},
"output_type": "display_data",
"text": [
""
]
},
{
"html": [
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Objective | \n",
" Sense | \n",
" Status | \n",
" Value | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" OBJ | \n",
" Maximize | \n",
" Optimal | \n",
" 60 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"metadata": {},
"output_type": "display_data",
"text": [
" Objective Sense Status Value\n",
"0 OBJ Maximize Optimal 60"
]
},
{
"html": [
"Variables
"
],
"metadata": {},
"output_type": "display_data",
"text": [
""
]
},
{
"html": [
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Name | \n",
" Category | \n",
" Lower Bound | \n",
" Upper Bound | \n",
" Value | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" Soldiers | \n",
" Continuous | \n",
" 0 | \n",
" None | \n",
" 20 | \n",
"
\n",
" \n",
" 1 | \n",
" Trains | \n",
" Continuous | \n",
" 0 | \n",
" None | \n",
" 0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"metadata": {},
"output_type": "display_data",
"text": [
" Name Category Lower Bound Upper Bound Value\n",
"0 Soldiers Continuous 0 None 20\n",
"1 Trains Continuous 0 None 0"
]
},
{
"html": [
"Constraints
"
],
"metadata": {},
"output_type": "display_data",
"text": [
""
]
},
{
"html": [
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Name | \n",
" Lower Bound | \n",
" Upper Bound | \n",
" Slack | \n",
" Value | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" Finishing_Labor | \n",
" NaN | \n",
" 100 | \n",
" 60 | \n",
" 40 | \n",
"
\n",
" \n",
" 1 | \n",
" Carpentry_Labor | \n",
" NaN | \n",
" 80 | \n",
" 60 | \n",
" 20 | \n",
"
\n",
" \n",
" 2 | \n",
" Soldier_Demand | \n",
" NaN | \n",
" 40 | \n",
" 20 | \n",
" 20 | \n",
"
\n",
" \n",
" 3 | \n",
" Minimum_Production | \n",
" 20 | \n",
" 20 | \n",
" -0 | \n",
" 20 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"metadata": {},
"output_type": "display_data",
"text": [
" Name Lower Bound Upper Bound Slack Value\n",
"0 Finishing_Labor NaN 100 60 40\n",
"1 Carpentry_Labor NaN 80 60 20\n",
"2 Soldier_Demand NaN 40 20 20\n",
"3 Minimum_Production 20 20 -0 20"
]
}
],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"cdf.plot(title=\"Value\",kind='bar')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 9,
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAEFCAYAAAD+A2xwAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XtYFPe9x/H3rmKDXGTxAgIiHg0iiYoJR2u8gI9FOUaN\njxrUiC4mjU9qk0Y5fRpNE0PSJNLmctRE27Qar8cLpt611EuDVestTdQ0QolppAhKomJU0AjC+cMw\nJ6goLLuws35ez8MjM7Mz85uf8GH2O7Pzs1RWVlYiIiKmZW3sBoiISP0oyEVETE5BLiJicgpyERGT\nU5CLiJicglxExOQU5CLfsVqt/Otf/2rsZojUmYJcPEpiYiIvvfTSTfM3bNhA27ZtqaioaIRWibiW\nglw8SkpKCsuXL79p/rJly0hOTsZq1Y+8eB79VItHeeSRRzh79iy7d+825hUXF7NlyxaGDRtG7969\nsdlshISE8Mwzz1BWVnbL7cTHx7Nw4UJjevHixfTr18+YzsnJISEhgZYtWxIVFcWaNWtcd1Aid6Ag\nF4/i7e1NUlISS5cuNeZlZGTQpUsXfH19mTNnDmfPnmXfvn3s3LmT+fPn33I7FosFi8Vyy2UlJSUk\nJCSQnJzM119/zapVq5gyZQrZ2dkuOSaRO1GQi8ex2+188MEHXL16FYClS5dit9t54IEH6NmzJ1ar\nlfbt2zN58mR27dpV5+1v3ryZDh06YLfbsVqtxMTEMHLkSJ2VS6Np2tgNEHG2Pn360KpVK9atW0ds\nbCyHDh1i/fr15Obmkpqayt///ndKS0spLy8nNja2ztvPy8vjwIED2Gw2Y155eTkTJ0505mGI1JqC\nXDzSxIkTWbp0KTk5OSQmJtK6dWvGjh3Lgw8+yOrVq/Hx8WH27Nn88Y9/vOX6Pj4+lJSUGNOnT582\nvg8PDycuLo5t27a5/DhEakOlFfFIEydOZPv27SxYsAC73Q7ApUuX8PPzo3nz5uTk5PDb3/62xvVj\nYmJYu3Ytly9f5vjx49UufD788MPk5uayfPlyysrKKCsr49ChQ+Tk5Lj8uERuRUEuHql9+/b06dOH\n0tJShg8fDsCbb77JihUr8Pf3Z/LkyYwdO7baBc3vfz9t2jSaNWtGUFAQkyZNIjk52Vju5+fHtm3b\nWLVqFaGhobRt25YZM2YYNXmRhma53cASjz/+OFu2bKFNmzZ8+umnAJw7d44xY8aQl5dHREQEGRkZ\nBAQEADBr1izef/99mjRpwty5cxk0aFDDHIWIyF3stmfkkyZNIjMzs9q89PR0EhISyM3NZeDAgaSn\npwNw7NgxVq9ezbFjx8jMzGTKlCn6FJ2ISAO4bZD369ev2pV5gI0bNxo1R7vdzvr164HrH4EeN24c\nXl5eRERE0KlTJw4ePOiiZouISJU618iLiooICgoCICgoiKKiIgAKCwsJCwszXhcWFkZBQYGTmiki\nIjWp18XO2336rWq5iIi4Vp3vIw8KCuL06dMEBwdz6tQp2rRpA0BoaCj5+fnG606ePEloaOhN63fq\n1IkvvviiHk0WEbn7dO/encOHD99yWZ2DfPjw4SxZsoTnnnuOJUuWMGLECGP+Y489RmpqKgUFBXz+\n+ef07NnzpvW/+OILbnOjTIOyWCyQ5uDKabjNcZhJWloaaWlpjd0MU1LfOc4T+u52FY7bBvm4cePY\ntWsXZ86coV27drzyyitMnz6dpKQkFi5caNx+CBAdHU1SUhLR0dE0bdqU+fPnq7QiNzlx4kRjN8G0\n1HeO8/S+u22Qr1y58pbzd+zYccv5zz//PM8//3z9WyUiIrWmT3ZKg0pJSWnsJpiW+s5xnt53t/1k\np0t2aLG4TW1ZNXIRMYvbZafOyKVBZWVlNXYTTKuq7wIDA41bf/XleV+BgYF1/tnQY2xFTKa4uFjv\nBj2YIzeJqLSS5uDKaSqtSONwp98hcb6a/n9VWhER8WAKcmlQqpE7Tn0nNVGQi4g0sBMnTmC1Wp32\nqG8FuTSo+Pj4xm6CadXUd/7+rr2Lxd+/9ndRREREsHPnTicdsXNVhaefnx9+fn4EBwfz05/+lPLy\n8sZuWr0pyEVM7uLFYqDSZV/Xt187VeHf2G4Xzt988w0XL17k008/Zd++fcybN68BW+YaCnJpUKrz\nOs7Mffftt98ydepUQkNDCQ0NZdq0acYYp3FxcaxduxaAvXv3YrVa2bp1KwA7d+6kR48exnbef/99\noqOjCQwMJDExkX//+9/GMqvVyvz587n33nvp3LnzHdvUunVrEhISOHbsmDEvOzub+Ph4bDYb999/\nP5s2bTKWxcfHVxuEe/HixfTr16/a/t977z0iIyOx2Ww8/fTTxrKKigp+/vOf07p1azp27MiWLVtq\n3Xe1oSAXEZd77bXXOHjwIEeOHOHIkSMcPHiQV199FbgekFV/pHbt2sV//Md/8Ne//tWYriopbdiw\ngVmzZrFu3TrOnDlDv379GDduXLX9bNiwgUOHDlUL5xtV3cJXWFjIn//8Z3r37g1AWVkZw4YNIzEx\nka+//pp33nmH8ePH8/nnnwO1e7exZcsWPvroI44ePUpGRgZ//vOfAfj973/Pli1bOHz4MB999BEf\nfPCBU9+5KMilQalG7jgz992KFSuYOXMmrVq1olWrVrz00kssW7YMgP79+7Nr1y4Adu/ezYwZM4zp\nXbt2ERcXB8Dvfvc7ZsyYQefOnbFarcyYMYPDhw9XGwdhxowZBAQE8IMf/KDGtrRq1QqbzUZYWBi+\nvr6MGjUKgP3791NSUsL06dNp2rQpAwYMYOjQoaxYsaLWxzl9+nT8/f1p164dAwYM4MiRIwBkZGQw\nbdo0QkNDsdlsPP/88079LICCXERcrrCwkPbt2xvT4eHhFBYWAtC7d29yc3P56quvOHz4MBMnTiQ/\nP5+zZ89y6NAh+vfvD0BeXh7PPvssNpsNm81Gy5YtAaoNKdmuXbs7tuXs2bMUFxdTWlrKQw89xODB\ng4023rh++/btjXbWRnBwsPF98+bNuXTpEgCnTp2qtu3w8PBab7M2FOTSoMxc521sZu67kJCQas8E\n//e//01ISAhwPfAefPBBZs+eTdeuXfHy8uKhhx7irbfeolOnTsazR8LDw/n9739PcXGx8VVSUsIP\nf/hDY7t1KVfcc8892O129u/fz7lz5wgJCSE/P7/amXJeXp4x0pmPjw8lJSXGstOnT9d6X23btq1W\nz//+986gIBcRp7p69SpXrlwxvsrLyxk3bhyvvvoqZ86c4cyZM7zyyitMmDDBWCcuLo558+YZZZT4\n+HjeffddYxrgqaee4vXXXzfq39988w1r1qypc/uqgvrbb79l2bJltG3blsDAQHr16kXz5s35zW9+\nQ1lZGVlZWWzevJmxY8cCEBMTw9q1a7l8+TLHjx+vduGzpv1U7SspKYm5c+dSUFBAcXEx6enpdW73\n7SjIpUGZuc7b2GrqOz8/G2Bx2df17dfekCFDaN68ufH1yiuv8MILLxAbG0u3bt3o1q0bsbGxvPDC\nC8Y6cXFxXLp0ySij9O/fn5KSEmMaYMSIETz33HOMHTuWFi1a0LVrV+NiItT+bDwgIMC4j/zAgQNs\n3LgRgGbNmrFp0yb+9Kc/0bp1a55++mmWLVtGZGQkANOmTaNZs2YEBQUxadIkkpOTq+3zxv1//+Lo\nk08+yeDBg+nevTuxsbGMGjXKqRc79dCsNAdXTtNDs6RxuNPvkDifHpolbs/Mdd7Gpr6TmijIRURM\nTqWVNAdXTlNpRRqHO/0OifOptCIichdSkEuDUp3Xceo7qYmCXETE5FQjT3Nw5TTVyKVxuNPvkDif\nauQiInchBbk0KNV5Hae+8yw3Pt+8PhTkIibnH+Dv2qHeAvxr3Rar1cq//vWvavPS0tKqPVelMcXH\nx+Pt7Y2fnx8BAQHExcXxj3/8o1Ha4szRlJo6ZSsitaRnrTiupr67+M1Fx6/11MLFtIv1Wr+xhn6r\nqKjAaq1+rmqxWJg3bx6PP/44FRUVvPzyy0yYMIFPPvmkUdroLDojFxGX+v4FuqysLMLCwpg1axat\nW7emQ4cO1QZuSElJ4amnnmLQoEH4+/sTHx9f7ZGvOTk5JCQk0LJlS6Kioqo9/TAlJYWf/OQnDBky\nBF9f3zuWoqxWK2PGjKk2mtDthqS7cWi3qm1UvQNJSUnhpz/9KUOHDsXf358f/vCH1d6dbN++naio\nKAICAnjmmWeqPR2xvhTk0qBU53Wcp/RdUVERZ8+epbCwkCVLljB58mRyc3ON5VWjCZ05c4aYmBjG\njx8PQElJCQkJCSQnJ/P111+zatUqpkyZQnZ2trHuypUrefHFF7l06RJ9+vS55f6rwvPq1av87//+\nrzHUG9x+SLraWL16NWlpaRQXF9OpUyd++ctfAnDmzBlGjRrF66+/ztmzZ+nYsSN79+512rsVBbmI\nNLhf/epXeHl50b9/fx5++GEyMjKMZUOHDqVv3740a9aM1157jX379nHy5Ek2b95Mhw4dsNvtWK1W\nYmJiGDlyZLWz8hEjRhjBfKvh3iorK/nZz36GzWbD39+f+fPnM3PmTGP57YakuxOLxcLIkSOJjY2l\nSZMmjB8/nsOHDwOwdetW7r//fkaOHEmTJk2YOnVqtdGE6ktBLg1KNXLHmaHvmjRpQllZWbV5ZWVl\neHl5GdM2mw1vb29jun379pw6dQq4HoZhYWHGMh8fHwIDAyksLCQvL48DBw4YQ73ZbDZWrFhBUVGR\nse6dhnqzWCy88847FBcXc+XKFTZt2sTo0aONC563G5KuNoKCgozvvb29jaHeCgsLqx0X1G5YutpS\nkIuI04SHh/Pll19Wm/fll18SERFhTFeNl1klLy/PGPatsrKy2mDKly5d4ty5c4SGhhIeHk5cXFy1\nod4uXrzIvHnzHG5v37596dSpE9u2bQNuPySdj49PtXbXZai3qmHkqtx4nPWlIJcG5Sl13sZghr4b\nM2YMr776KgUFBVRUVLBjxw42b97M6NGjq73upZdeoqysjN27d7NlyxYeffRRY9nWrVvZu3cvV69e\n5cUXX6R3796Ehoby8MMPk5uby/LlyykrK6OsrIxDhw6Rk5MD1P6T1t9/3b59+zh27Bj33XcfwG2H\npOvevTufffYZR44c4cqVK6SlpdW43RsNGTKEzz77jHXr1lFeXs7cuXPr9IfgTnT7oYjJ+bXwq/ct\ngnfafm3NnDmTmTNn0rdvX+OC34oVK4iOjjZeExwcjM1mIyQkBB8fH9577z1jODWLxcJjjz3Gyy+/\nzL59+3jwwQdZvnz59Xb4+bFt2zZSU1NJTU2loqKCmJgY3n77bWPd2lw8fPrpp5k6darRltdee43B\ngwcD8MILL3DhwgW6desGXB9rs2pIusjISGbOnMmPfvQjmjdvzuuvv84f/vAHY7u32n/VdKtWrViz\nZg0/+9nPmDRpEhMmTKBv37617tc7cfhZK7NmzWL58uVYrVa6du3KokWLKCkpYcyYMeTl5REREUFG\nRgYBAQHVd+hGz4nQs1bEjNzpd6iusrKymDBhQo1lhUmTJhEWFsavfvWrBm6Z+2iwZ62cOHGCP/zh\nD3z88cd8+umnXLt2jVWrVpGenk5CQgK5ubkMHDjQ6SNFi4hnM+sfqMbmUJD7+/vj5eVFaWkp5eXl\nlJaWEhISwsaNG7Hb7QDY7XbWr1/v1MaK+ZmhzuuuPKXvblf+cObH1u8mDtXIAwMD+e///m/Cw8Px\n9vZm8ODBJCQkUFRUZNx+ExQUZNwWJCIC3PRJzRstWrSoAVvjORwK8i+++ILZs2dz4sQJWrRowaOP\nPmpckKhyu7+sKSkpxu1IAQEBxMTEGPfIVp11NNQ0VXdKdaBu099p6Pa6y/TwEcOvP+Ojjrx9vNm6\neWujt9+M0/Hx8R5zVi53lpWVxeLFiwGq3b55Kw5d7Fy9ejXbt29nwYIFACxbtoz9+/fzl7/8hQ8/\n/JDg4GBOnTrFgAEDjFuDjB260YUaXex0nMN9l3Z395szuNPvkDhfg13sjIqKYv/+/Vy+fJnKykp2\n7NhBdHQ0w4YNY8mSJQAsWbKEESNGOLJ5EbkFnY1LTRwqrXTv3p2JEycSGxuL1WrlgQceYPLkyVy8\neJGkpCQWLlxo3H4oIiKupTE70xxcOe3uLhGotNJ43Ol3SJxPY3aKiFu61bO8HXGrEYhEQS5iGjXV\nyAP9XTvUW6B/7Yd627NnDw899BABAQG0bNmSvn378tFHHzmpB6QmetaKiMkVX7yIKwstlou1u830\nwoULDB06lPfee4+kpCS+/fZbdu/efcvngotz6YxcxCTc/Xnkubm5WCwWxowZg8Vi4Z577iEhIYGu\nXbve9Npnn32W8PBwWrRoQWxsLHv27DGWVVRU8Prrr9OpUyf8/f2JjY2loKDgpm3s2bOH8PBw/vrX\nv7r0uMxAQS4iTtG5c2eaNGlCSkoKmZmZFBcX1/janj17cuTIEYqLi3nsscd49NFHjbEx33rrLVat\nWsWf/vQnLly4wPvvv19tIAqAzMxMHnvsMdauXUv//v1delxmoCAXMQl3v4/cz8+PPXv2YLFYePLJ\nJ2nTpg2PPPIIX3311U2vHT9+PDabDavVSmpqKt9++y3//Oc/AViwYAGvvfYa9957LwDdunUjMDDQ\nWHf16tU89dRTZGZmEhsb2zAH5+YU5CLiNFFRUSxatIj8/Hz+8Y9/UFhYyNSpU296XMebb75JdHQ0\nAQEB2Gw2vvnmG86cOQPAyZMn6dixY437mDt3LmPGjKn2jPO7nYJcxCTcvUZ+o86dO2O3243xMKvs\n3r2bN954gzVr1nD+/HmKi4tp0aKFcY90u3btOH78eI3bXbNmDevWrWPu3Lkubb+ZKMhFxCn++c9/\n8vbbbxsXJvPz81m5cqUxqn2Vixcv0rRpU1q1asXVq1d55ZVXuHDhgrH8xz/+MS+++CLHjx+nsrKS\no0ePcu7cOWN5SEgIO3fuZM6cOfzud79rmINzcwpyEZOoqUZu8/PDAi77svnVbqg3Pz8/Dhw4QK9e\nvfD19aV3795069aNt956C/j/55AnJiaSmJhIZGQkEREReHt7Ex4ebmwnNTWVpKQkBg0aRIsWLXjy\nySe5cuVKtW20a9eOnTt3kp6ezvvvv1+r9nkyfUQ/zcGV0+7uj5rrI/oNLysri/j4eLf6HRLn00f0\nRTyY2Wrk0nAU5CIiJqcgFzEJd7+PXBqPglxExOQU5CImoRq51ERBLiJicgpyEZNQjVxqoiAXETE5\nBbmISXhyjVxDuNWPglzE5PxtNpcO9eZvs9WqHYmJibz00ks3zd+wYQNt27aloqLC2Ycu39FQbyIm\nUfUR/RtdPH8ePvzQZfu9OGBArV6XkpLCL3/5S15++eVq85ctW0ZycjJWq84bXUU9KyJO8cgjj3D2\n7Fl2795tzCsuLmbLli0MGzaM3r17Y7PZCAkJ4ZlnnqGsrOyW24mPj2fhwoXG9OLFi+nXr58xnZOT\nQ0JCAi1btiQqKoo1a9a47qBMQkEuYhLuXiP39vYmKSmJpUuXGvMyMjLo0qULvr6+zJkzh7Nnz7Jv\n3z527tzJ/Pnzb7mdqpLOrZSUlJCQkEBycjJff/01q1atYsqUKWRnZ7vkmMxCQS4iTmO32/nggw+M\n8TeXLl2K3W7ngQceoGfPnlitVtq3b8/kyZPZtWtXnbe/efNmOnTogN1ux2q1EhMTw8iRI+/6s3LV\nyEVMoqYauTvp06cPrVq1Yt26dcTGxnLo0CHWr19Pbm4uqamp/P3vf6e0tJTy8nKHxtvMy8vjwIED\n2L53Aba8vJyJEyc68zBMR0EuIk41ceJEli5dSk5ODomJibRu3ZqxY8fy4IMPsnr1anx8fJg9ezZ/\n/OMfb7m+j48PJSUlxvTp06eN78PDw4mLi2Pbtm0uPw4zUWlFxCTc/Wy8ysSJE9m+fTsLFizAbrcD\ncOnSJfz8/GjevDk5OTn89re/rXH9mJgY1q5dy+XLlzl+/Hi1C58PP/wwubm5LF++nLKyMsrKyjh0\n6BA5OTkuPy53pjNyEZPzCwio9S2Cjm6/Ltq3b0+fPn04evQow4cPB+DNN99k8uTJ/OY3v6FHjx6M\nHTuWD793y+T3L25OmzaNQ4cOERQURPfu3UlOTmbnzp3X2+Lnx7Zt20hNTSU1NZWKigpiYmJ4++23\nnXCk5qWh3tIcXDnt7h6yTEO9NTwN9XZ30FBvIiJ3IQW5iEmYpUYuDU9BLiJicgpyEZPQ88ilJgpy\nERGTczjIz58/z+jRo+nSpQvR0dEcOHCAc+fOkZCQQGRkJIMGDeL8+fPObKvIXU01cqmJw0H+7LPP\nMmTIELKzszl69ChRUVGkp6eTkJBAbm4uAwcOJD093ZltFRHA5uLnj+urcb9stXz++/c5dB/5N998\nQ48ePW4a0SMqKopdu3YRFBTE6dOniY+Pv+kTV+50D6zuI3ec7iNveGZ41oq78oS+c/p95F9++SWt\nW7dm0qRJPPDAAzz55JOUlJRQVFREUFAQAEFBQRQVFTneahERqRWHgry8vJyPP/6YKVOm8PHHH+Pj\n43NTGaXqbYKIOIfZzygbk6f3nUPPWgkLCyMsLIz//M//BGD06NHMmjWL4OBgTp8+TXBwMKdOnaJN\nmza3XD8lJYWIiAgAAgICiImJMTq66harhprmy+8a1YE6TTcFh/5Q+Xp7s2nr1kY7XmdNG+rYf1Yc\n6zcAm58fazdudEr7Na1pd5/Oyspi8eLFAEZe1sThZ63079+fBQsWEBkZSVpaGqWlpQC0bNmS5557\njvT0dM6fP3/LM3V3qZHWu0buyD7xjBpxvWrkju4Tz+g7R2V5QJ23sXhC390uOx1++uE777zD+PHj\nuXr1Kh07dmTRokVcu3aNpKQkFi5cSEREBBkZGQ43WkREakdPP0xzcOU0nZHrjFyk4Tj9rhUREXEf\nCnIRk7jpQrPUmqf3nYJcRMTkFOQiJmH2uy4ak6f3nYJcRMTkFOQiJuHpdV5X8vS+U5CLiJicglzE\nJDy9zutKnt53CnIREZNTkIuYhKfXeV3J0/tOQS4iYnIKchGT8PQ6ryt5et8pyEVETE5BLmISnl7n\ndSVP7zsFuYiIySnIRUzC0+u8ruTpfacgFxExOQW5iEl4ep3XlTy97xTkIiImpyAXMQlPr/O6kqf3\nnYJcRMTkFOQiJuHpdV5X8vS+U5CLiJicglzEJDy9zutKnt53CnIREZNTkIuYhKfXeV3J0/tOQS4i\nYnIKchGT8PQ6ryt5et8pyEVETE5BLmISnl7ndSVP7zsFuYiIySnIRUzC0+u8ruTpfacgFxExOQW5\niEl4ep3XlTy97xTkIiImpyAXMQlPr/O6kqf3Xb2C/Nq1a/To0YNhw4YBcO7cORISEoiMjGTQoEGc\nP3/eKY0UEZGa1SvI58yZQ3R0NBaLBYD09HQSEhLIzc1l4MCBpKenO6WRIuL5dV5X8vS+czjIT548\nydatW/nxj39MZWUlABs3bsRutwNgt9tZv369c1opIiI1cjjIp02bxhtvvIHV+v+bKCoqIigoCICg\noCCKiorq30IRATy/zutKnt53DgX55s2badOmDT169DDOxm9ksViMkouIiLhOU0dW+tvf/sbGjRvZ\nunUrV65c4cKFC0yYMIGgoCBOnz5NcHAwp06dok2bNrdcPyUlhYiICAACAgKIiYkx/mJW1bIaapov\nv2tUB+o2/Z2s7/6Nr+U0VqvDf+D8AgLYuG7d9e01Un/dVGusa/9xvU/iv/c9dZlupON11nRz3+Zc\nLrmMI7x9vNm6eatbHY9ZpqvmuUt7ajOdlZXF4sWLAYy8rImlsqZT6lratWsXb775Jps2beIXv/gF\nLVu25LnnniM9PZ3z58/fdMHTYrHUeBbf0CwWC6Q5uHIaOHIUFoAPP3RsnwMGmL/v0hzrN7jed+5y\n/I6q98+cyY+/sWRlZZm+vHK77HTKfeRVZ5jTp09n+/btREZG8pe//IXp06c7Y/MiIvVi9hC/E4dK\nK98XFxdHXFwcAIGBgezYsaPejRIRkdrTJztFxOPpPnIREXFrCnIR8XieXiNXkIuImJyCXEQ8nmrk\nIiLi1hTkIuLxVCMXERG3piAXEY+nGrmIiLg1BbmIeDzVyEVExK0pyEXE46lGLiIibk1BLiIeTzVy\nERFxawpyEfF4qpGLiIhbU5CLiMdTjVxERNyaglxEPJ5q5CIi4tYU5CLi8VQjFxERt6YgFxGPpxq5\niIi4NQW5iHg81chFRMStKchFxOOpRi4iIm5NQS4iHk81chERcWsKchHxeKqRi4iIW1OQi4jHU41c\nRETcmkNBnp+fz4ABA7jvvvu4//77mTt3LgDnzp0jISGByMhIBg0axPnz553aWBERR6hGfgteXl78\nz//8D5999hn79+9n3rx5ZGdnk56eTkJCArm5uQwcOJD09HRnt1dERG7gUJAHBwcTExMDgK+vL126\ndKGgoICNGzdit9sBsNvtrF+/3nktFRFxkGrkd3DixAk++eQTevXqRVFREUFBQQAEBQVRVFRU7waK\niMjtNa3PypcuXWLUqFHMmTMHPz+/asssFgsWi+WW66WkpBAREQFAQEAAMTExxl/MqlpWQ03z5XeN\n6kDdpr+T9d2/8bWcBuDwYfjuHQ2HD1//t5bTDd0/NU0b6tp/XO+T+O99T22nmzSp8WfqTvwCArhQ\nXNzo/Qdc75O6/rx1uH7W5ejx06QJXLvm0KrePj6UXroE1P/4mzf34/LlSw61AytQ4diq3j7elF4q\ndaj9Q4YO5XJJiYP79WHr5s0O9VdWVhaLFy8GMPKyJpbKyspKRxpYVlbG0KFD+a//+i+mTp0KQFRU\nFFlZWQQHB3Pq1CkGDBhATk5O9R1aLDi4S6ezWCyQ5uDKaeDIUVgAPvzQsX0OGGD+vktzrN/AM/qu\nMX7mwH367vofonocRZqDq6bh8DFYLBa36buatuVQaaWyspInnniC6OhoI8QBhg8fzpIlSwBYsmQJ\nI0aMcGTzIiJSBw6VVvbu3cvy5cvp1q0bPXr0AGDWrFlMnz6dpKQkFi5cSEREBBkZGU5trIiI3Myh\nIO/bty8F8cmxAAAEVElEQVQVFbcuVu3YsaNeDRIRkbrRJztFRExOQS4iYnIKchERk1OQi4iYnIJc\nRMTkFOQiIianIBcRMTkFuYiIySnIRURMTkEuImJyCnIREZNTkIuImJyCXETE5BTkIiImpyAXETE5\nBbmIiMkpyEVETE5BLiJicgpyERGTU5CLiJicglxExOQU5CIiJqcgFxExOQW5iIjJKchFRExOQS4i\nYnIKchERk1OQi4iYnIJcRMTkFOQiIianIBcRMTkFuYiIySnIRURMTkEuImJyCnIREZNzepBnZmYS\nFRXFvffey69//Wtnb15ERG7g1CC/du0aTz/9NJmZmRw7doyVK1eSnZ3tzF2IiMgNnBrkBw8epFOn\nTkRERODl5cXYsWPZsGGDM3chIiI3cGqQFxQU0K5dO2M6LCyMgoICZ+5CRERu4NQgt1gsztyciIjU\nQlNnbiw0NJT8/HxjOj8/n7CwsGqv6d69u3sFfprjqzp8FAMGOL5PD+i7eh2BJ/RdmuOrekbf1WNb\nafXYa32OwQ36rnv37jXvo7KystIpewHKy8vp3LkzO3fuJCQkhJ49e7Jy5Uq6dOnirF2IiMgNnHpG\n3rRpU959910GDx7MtWvXeOKJJxTiIiIu5tQzchERaXhOPSP3VNnZ2WzYsMG4AycsLIzhw4fr3Ya4\nTHZ2NoWFhfTq1QtfX19jfmZmJomJiY3YMve3Z88eAgMDiY6OJisri48++ogePXowcODAxm6ay+gj\n+nfw61//mnHjxgHQq1cvevXqRUVFBePGjWPWrFmN3DpzWrRoUWM3wa3NnTuXESNG8M4773Dfffex\nfv16Y9mMGTMasWXub8aMGfz85z/Hbrfzi1/8gunTp3P58mVefvll3njjjcZunsuotHIH9957L8eO\nHcPLy6va/KtXrxIdHc3x48cbqWXm1a5du2p3N0l1999/P/v378fX15cTJ04wevRokpOTmTp1Kj16\n9OCTTz5p7Ca6rejoaI4ePcrVq1cJCgri5MmTtGjRgsuXL9OrVy+OHj3a2E10CZVW7qBJkyYUFBQQ\nERFRbX5hYSFNmjRpnEaZQNeuXWtc9tVXXzVgS8ynsrLSKKdERESQlZXFqFGjyMvLQ+ddt9esWTOa\nNm1K06ZN6dixIy1atADA29sbq9VzCxAK8juYPXs2P/rRj+jUqZPxqdX8/Hw+//xz3n333UZunfv6\n6quvyMzMxGaz3bTsoYceaoQWmUebNm04fPgwMTExAPj6+rJ582aeeOIJjz2jdJYf/OAHlJaW0rx5\ncz7++GNj/vnz5z06yFVaqYVr165x8OBBCgoKsFgshIaGEhsbS9Om+jtYk8cff5xJkybRr1+/m5aN\nGzeOlStXNkKrzCE/Px8vLy+Cg4Orza+srGTv3r307du3kVrm/q5cucI999xz0/wzZ85w6tSp275T\nNDMFuYiIyXnuew0RkbuEglxExOQU5CIiJqcgFxExOQW5iIjJ/R/RxOAmD2N3iAAAAABJRU5ErkJg\ngg==\n",
"text": [
""
]
}
],
"prompt_number": 9
}
],
"metadata": {}
}
]
}