You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
672 lines
44 KiB
672 lines
44 KiB
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6b8983b1",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Getting started with MIPLearn\n",
|
|
"\n",
|
|
"## Introduction\n",
|
|
"\n",
|
|
"**MIPLearn** is an open source framework that uses machine learning (ML) to accelerate the performance of both commercial and open source mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS, Cbc or SCIP). In this tutorial, we will:\n",
|
|
"\n",
|
|
"1. Install the Python/Pyomo version of MIPLearn\n",
|
|
"2. Model a simple optimization problem using JuMP\n",
|
|
"3. Generate training data and train the ML models\n",
|
|
"4. Use the ML models together Gurobi to solve new instances\n",
|
|
"\n",
|
|
"<div class=\"alert alert-info\">\n",
|
|
"Note\n",
|
|
" \n",
|
|
"The Python/Pyomo version of MIPLearn is currently only compatible with with Gurobi, CPLEX and XPRESS. For broader solver compatibility, see the Julia/JuMP version of the package.\n",
|
|
"</div>\n",
|
|
"\n",
|
|
"<div class=\"alert alert-warning\">\n",
|
|
"Warning\n",
|
|
" \n",
|
|
"MIPLearn is still in early development stage. If run into any bugs or issues, please submit a bug report in our GitHub repository. Comments, suggestions and pull requests are also very welcome!\n",
|
|
" \n",
|
|
"</div>\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "02f0a927",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Installation\n",
|
|
"\n",
|
|
"MIPLearn is available in two versions:\n",
|
|
"\n",
|
|
"- Python version, compatible with the Pyomo modeling language,\n",
|
|
"- Julia version, compatible with the JuMP modeling language.\n",
|
|
"\n",
|
|
"In this tutorial, we will demonstrate how to use and install the Python/Pyomo version of the package. The first step is to install Python 3.8+ in your computer. See the [official Python website for more instructions](https://www.python.org/downloads/). After Python is installed, we proceed to install MIPLearn using `pip`:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "cd8a69c1",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: MIPLearn==0.2.0.dev13 in /home/axavier/.local/lib/python3.7/site-packages (0.2.0.dev13)\n",
|
|
"Requirement already satisfied: p-tqdm<2,>=1 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (1.3.3)\n",
|
|
"Requirement already satisfied: overrides<4,>=3 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (3.1.0)\n",
|
|
"Requirement already satisfied: scikit-learn<0.25,>=0.24 in /opt/anaconda3/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (0.24.1)\n",
|
|
"Requirement already satisfied: h5py<4,>=3 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (3.5.0)\n",
|
|
"Requirement already satisfied: matplotlib<4,>=3 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (3.4.1)\n",
|
|
"Requirement already satisfied: seaborn<0.12,>=0.11 in /opt/anaconda3/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (0.11.1)\n",
|
|
"Requirement already satisfied: python-markdown-math<0.9,>=0.8 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (0.8)\n",
|
|
"Requirement already satisfied: decorator<5,>=4 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (4.4.2)\n",
|
|
"Requirement already satisfied: mypy==0.790 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (0.790)\n",
|
|
"Requirement already satisfied: pandas<2,>=1 in /opt/anaconda3/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (1.2.3)\n",
|
|
"Requirement already satisfied: pytest<7,>=6 in /opt/anaconda3/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (6.2.3)\n",
|
|
"Requirement already satisfied: numpy<1.21,>=1 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (1.19.5)\n",
|
|
"Requirement already satisfied: tqdm<5,>=4 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (4.60.0)\n",
|
|
"Requirement already satisfied: pyomo<6,>=5 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (5.7.3)\n",
|
|
"Requirement already satisfied: networkx<3,>=2 in /home/axavier/.local/lib/python3.7/site-packages (from MIPLearn==0.2.0.dev13) (2.5.1)\n",
|
|
"Requirement already satisfied: typed-ast<1.5.0,>=1.4.0 in /opt/anaconda3/lib/python3.7/site-packages (from mypy==0.790->MIPLearn==0.2.0.dev13) (1.4.2)\n",
|
|
"Requirement already satisfied: typing-extensions>=3.7.4 in /opt/anaconda3/lib/python3.7/site-packages (from mypy==0.790->MIPLearn==0.2.0.dev13) (3.7.4.3)\n",
|
|
"Requirement already satisfied: mypy-extensions<0.5.0,>=0.4.3 in /home/axavier/.local/lib/python3.7/site-packages (from mypy==0.790->MIPLearn==0.2.0.dev13) (0.4.3)\n",
|
|
"Requirement already satisfied: cached-property in /home/axavier/.local/lib/python3.7/site-packages (from h5py<4,>=3->MIPLearn==0.2.0.dev13) (1.5.2)\n",
|
|
"Requirement already satisfied: pyparsing>=2.2.1 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (2.4.7)\n",
|
|
"Requirement already satisfied: pillow>=6.2.0 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (8.2.0)\n",
|
|
"Requirement already satisfied: python-dateutil>=2.7 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (2.8.1)\n",
|
|
"Requirement already satisfied: cycler>=0.10 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (0.10.0)\n",
|
|
"Requirement already satisfied: kiwisolver>=1.0.1 in /opt/anaconda3/lib/python3.7/site-packages (from matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (1.3.1)\n",
|
|
"Requirement already satisfied: six in /opt/anaconda3/lib/python3.7/site-packages (from cycler>=0.10->matplotlib<4,>=3->MIPLearn==0.2.0.dev13) (1.15.0)\n",
|
|
"Requirement already satisfied: pathos in /home/axavier/.local/lib/python3.7/site-packages (from p-tqdm<2,>=1->MIPLearn==0.2.0.dev13) (0.2.7)\n",
|
|
"Requirement already satisfied: pytz>=2017.3 in /opt/anaconda3/lib/python3.7/site-packages (from pandas<2,>=1->MIPLearn==0.2.0.dev13) (2021.1)\n",
|
|
"Requirement already satisfied: PyUtilib>=6.0.0 in /home/axavier/.local/lib/python3.7/site-packages (from pyomo<6,>=5->MIPLearn==0.2.0.dev13) (6.0.0)\n",
|
|
"Requirement already satisfied: ply in /opt/anaconda3/lib/python3.7/site-packages (from pyomo<6,>=5->MIPLearn==0.2.0.dev13) (3.11)\n",
|
|
"Requirement already satisfied: attrs>=19.2.0 in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (20.3.0)\n",
|
|
"Requirement already satisfied: iniconfig in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (1.1.1)\n",
|
|
"Requirement already satisfied: packaging in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (20.9)\n",
|
|
"Requirement already satisfied: pluggy<1.0.0a1,>=0.12 in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (0.13.1)\n",
|
|
"Requirement already satisfied: py>=1.8.2 in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (1.10.0)\n",
|
|
"Requirement already satisfied: toml in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (0.10.2)\n",
|
|
"Requirement already satisfied: importlib-metadata>=0.12 in /opt/anaconda3/lib/python3.7/site-packages (from pytest<7,>=6->MIPLearn==0.2.0.dev13) (3.10.0)\n",
|
|
"Requirement already satisfied: zipp>=0.5 in /opt/anaconda3/lib/python3.7/site-packages (from importlib-metadata>=0.12->pytest<7,>=6->MIPLearn==0.2.0.dev13) (3.4.1)\n",
|
|
"Requirement already satisfied: Markdown>=3.0 in /opt/anaconda3/lib/python3.7/site-packages (from python-markdown-math<0.9,>=0.8->MIPLearn==0.2.0.dev13) (3.3.4)\n",
|
|
"Requirement already satisfied: nose in /opt/anaconda3/lib/python3.7/site-packages (from PyUtilib>=6.0.0->pyomo<6,>=5->MIPLearn==0.2.0.dev13) (1.3.7)\n",
|
|
"Requirement already satisfied: scipy>=0.19.1 in /opt/anaconda3/lib/python3.7/site-packages (from scikit-learn<0.25,>=0.24->MIPLearn==0.2.0.dev13) (1.6.2)\n",
|
|
"Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/anaconda3/lib/python3.7/site-packages (from scikit-learn<0.25,>=0.24->MIPLearn==0.2.0.dev13) (2.1.0)\n",
|
|
"Requirement already satisfied: joblib>=0.11 in /opt/anaconda3/lib/python3.7/site-packages (from scikit-learn<0.25,>=0.24->MIPLearn==0.2.0.dev13) (1.0.1)\n",
|
|
"Requirement already satisfied: multiprocess>=0.70.11 in /home/axavier/.local/lib/python3.7/site-packages (from pathos->p-tqdm<2,>=1->MIPLearn==0.2.0.dev13) (0.70.11.1)\n",
|
|
"Requirement already satisfied: ppft>=1.6.6.3 in /home/axavier/.local/lib/python3.7/site-packages (from pathos->p-tqdm<2,>=1->MIPLearn==0.2.0.dev13) (1.6.6.3)\n",
|
|
"Requirement already satisfied: dill>=0.3.3 in /home/axavier/.local/lib/python3.7/site-packages (from pathos->p-tqdm<2,>=1->MIPLearn==0.2.0.dev13) (0.3.3)\n",
|
|
"Requirement already satisfied: pox>=0.2.9 in /home/axavier/.local/lib/python3.7/site-packages (from pathos->p-tqdm<2,>=1->MIPLearn==0.2.0.dev13) (0.2.9)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"!pip install MIPLearn==0.2.0.dev13"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e8274543",
|
|
"metadata": {},
|
|
"source": [
|
|
"In addition to MIPLearn itself, we will also install Gurobi 9.1, a state-of-the-art commercial MILP solver. To succesfully complete this step, you will need a valid Gurobi license."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "dcc8756c",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Looking in indexes: https://pypi.gurobi.com\n",
|
|
"Requirement already satisfied: gurobipy==9.1.2 in /home/axavier/.local/lib/python3.7/site-packages (9.1.2)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"!pip install --upgrade -i https://pypi.gurobi.com gurobipy==9.1.2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a14e4550",
|
|
"metadata": {},
|
|
"source": [
|
|
"<div class=\"alert alert-info\">\n",
|
|
" \n",
|
|
"Note\n",
|
|
" \n",
|
|
"In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Python projects.\n",
|
|
" \n",
|
|
"</div>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "16b86823",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Modeling a simple optimization problem\n",
|
|
"\n",
|
|
"To illustrate how can MIPLearn be used, we will model and solve a small optimization problem related to power systems optimization. The problem we discuss below is a simplification of the **unit commitment problem,** a practical optimization problem solved daily by electric grid operators around the world. \n",
|
|
"\n",
|
|
"Suppose that you work at a utility company, and that it is your job to decide which electrical generators should be online at a certain hour of the day, as well as how much power should each generator produce. More specifically, assume that your company owns $n$ generators, denoted by $g_1, \\ldots, g_n$. Each generator can either be online or offline. An online generator $g_i$ can produce between $p^\\text{min}_i$ to $p^\\text{max}_i$ megawatts of power, and it costs your company $c^\\text{fix}_i + c^\\text{var}_i y_i$, where $y_i$ is the amount of power produced. An offline generator produces nothing and costs nothing. You also know that the total amount of power to be produced needs to be exactly equal to the total demand $d$ (in megawatts). To minimize the costs to your company, which generators should be online, and how much power should they produce?\n",
|
|
"\n",
|
|
"This simple problem can be modeled as a *mixed-integer linear optimization* problem as follows. For each generator $g_i$, let $x_i \\in \\{0,1\\}$ be a decision variable indicating whether $g_i$ is online, and let $y_i \\geq 0$ be a decision variable indicating how much power does $g_i$ produce. The problem is then given by:\n",
|
|
"\n",
|
|
"$$\n",
|
|
"\\begin{align}\n",
|
|
"\\text{minimize } \\quad & \\sum_{i=1}^n \\left( c^\\text{fix}_i x_i + c^\\text{var}_i y_i \\right) \\\\\n",
|
|
"\\text{subject to } \\quad & y_i \\leq p^\\text{max}_i x_i & i=1,\\ldots,n \\\\\n",
|
|
"& y_i \\geq p^\\text{min}_i x_i & i=1,\\ldots,n \\\\\n",
|
|
"& \\sum_{i=1}^n y_i = d \\\\\n",
|
|
"& x_i \\in \\{0,1\\} & i=1,\\ldots,n \\\\\n",
|
|
"& y_i \\geq 0 & i=1,\\ldots,n\n",
|
|
"\\end{align}\n",
|
|
"$$\n",
|
|
"\n",
|
|
"<div class=\"alert alert-info\">\n",
|
|
" \n",
|
|
"Note\n",
|
|
" \n",
|
|
"We use a simplified version of the unit commitment problem in this tutorial just to make it easier to follow. MIPLearn can also handle realistic, large-scale versions of this problem. See benchmarks for more details.\n",
|
|
" \n",
|
|
"</div>\n",
|
|
"\n",
|
|
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Python and Pyomo. We start by defining a class `UnitCommitmentInstance`, which holds all the input data. The class also contains a method `to_model` which constructs a concrete Pyomo model object:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "b5bd2c58",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import pyomo.environ as pe\n",
|
|
"import pyomo.opt as pyo\n",
|
|
"from miplearn import Instance\n",
|
|
"\n",
|
|
"class UnitCommitmentInstance(Instance):\n",
|
|
" def __init__(\n",
|
|
" self,\n",
|
|
" demand,\n",
|
|
" pmin,\n",
|
|
" pmax,\n",
|
|
" cfix,\n",
|
|
" cvar,\n",
|
|
" ):\n",
|
|
" super().__init__()\n",
|
|
" self.demand = demand\n",
|
|
" self.pmin = pmin\n",
|
|
" self.pmax = pmax\n",
|
|
" self.cfix = cfix\n",
|
|
" self.cvar = cvar\n",
|
|
" \n",
|
|
" def to_model(self):\n",
|
|
" n = len(self.pmin)\n",
|
|
" model = pe.ConcreteModel()\n",
|
|
" model.x = pe.Var(range(n), domain=pe.Binary)\n",
|
|
" model.y = pe.Var(range(n), bounds=(0, float(\"inf\")))\n",
|
|
" model.obj = pe.Objective(\n",
|
|
" expr=sum(\n",
|
|
" self.cfix[i] * model.x[i] +\n",
|
|
" self.cvar[i] * model.y[i]\n",
|
|
" for i in range(n)\n",
|
|
" )\n",
|
|
" )\n",
|
|
" model.eq_max_power = pe.ConstraintList()\n",
|
|
" model.eq_min_power = pe.ConstraintList()\n",
|
|
" for i in range(n):\n",
|
|
" model.eq_max_power.add(model.y[i] <= self.pmax[i] * model.x[i])\n",
|
|
" model.eq_min_power.add(model.y[i] >= self.pmin[i] * model.x[i])\n",
|
|
" model.eq_demand = pe.Constraint(\n",
|
|
" expr=sum(model.y[i] for i in range(n)) == self.demand,\n",
|
|
" )\n",
|
|
" return model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c22714a3",
|
|
"metadata": {},
|
|
"source": [
|
|
"At this point, we can already use Pyomo and any mixed-integer linear programming solver to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "2a896f47",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"obj = 1320.0\n",
|
|
" x = [0.0, 1.0, 1.0]\n",
|
|
" y = [0.0, 60.0, 40.0]\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"instance = UnitCommitmentInstance(\n",
|
|
" demand = 100.0,\n",
|
|
" pmin = [10, 20, 30],\n",
|
|
" pmax = [50, 60, 70],\n",
|
|
" cfix = [700, 600, 500],\n",
|
|
" cvar = [1.5, 2.0, 2.5],\n",
|
|
")\n",
|
|
"model = instance.to_model()\n",
|
|
"opt = pyo.SolverFactory(\"gurobi\")\n",
|
|
"opt.solve(model)\n",
|
|
"\n",
|
|
"print(\"obj = \", pe.value(model.obj))\n",
|
|
"print(\" x = \", [pe.value(model.x[i]) for i in range(3)])\n",
|
|
"print(\" y = \", [pe.value(model.y[i]) for i in range(3)])\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "41b03bbc",
|
|
"metadata": {},
|
|
"source": [
|
|
"Running the code above, we found that the optimal solution for our small problem instance costs \\$1320. It is achieve by keeping generators 2 and 3 online and producing, respectively, 60 MW and 40 MW of power."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cf60c1dd",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Generating training data\n",
|
|
"\n",
|
|
"Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a **trained** version of Gurobi, which can solve new instances (similar to the ones it was trained on) faster.\n",
|
|
"\n",
|
|
"In the following, we will use MIPLearn to train machine learning models that can be used to accelerate Gurobi's performance on a particular set of instances. More specifically, MIPLearn will train a model that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start.\n",
|
|
"\n",
|
|
"Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a random instance generator:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 42,
|
|
"id": "5eb09fab",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from scipy.stats import uniform\n",
|
|
"import random\n",
|
|
"\n",
|
|
"def random_uc_instances(samples, n, seed=42):\n",
|
|
" random.seed(seed)\n",
|
|
" pmin = uniform(loc=100_000.0, scale=400_000.0).rvs(n)\n",
|
|
" pmax = pmin * uniform(loc=2.0, scale=2.5).rvs(n)\n",
|
|
" cfix = pmin * uniform(loc=100.0, scale=25.0).rvs(n)\n",
|
|
" cvar = uniform(loc=1.25, scale=0.25).rvs(n)\n",
|
|
" return [\n",
|
|
" UnitCommitmentInstance(\n",
|
|
" demand = pmax.sum() * uniform(loc=0.5, scale=0.25).rvs(),\n",
|
|
" pmin = pmin,\n",
|
|
" pmax = pmax,\n",
|
|
" cfix = cfix,\n",
|
|
" cvar = cvar,\n",
|
|
" )\n",
|
|
" for i in range(samples)\n",
|
|
" ]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3a03a7ac",
|
|
"metadata": {},
|
|
"source": [
|
|
"In this example, for simplicity, only the demands change from one instance to the next. We could also have made the prices and the production limits random. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.\n",
|
|
"\n",
|
|
"Now we generate 100 instances of this problem, each one with 1,000 generators. We will use the first 90 instances for training, and the remaining 10 instances to evaluate performance."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 43,
|
|
"id": "6156752c",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Using license file /home/axavier/gurobi.lic\n",
|
|
"Using gurobi.env file\n",
|
|
"Set parameter Threads to value 1\n",
|
|
"Read LP format model from file /tmp/tmpl37dgdq_.pyomo.lp\n",
|
|
"Reading time = 0.01 seconds\n",
|
|
"x2001: 2002 rows, 2001 columns, 5001 nonzeros\n",
|
|
"Gurobi Optimizer version 9.1.1 build v9.1.1rc0 (linux64)\n",
|
|
"Thread count: 16 physical cores, 32 logical processors, using up to 1 threads\n",
|
|
"Optimize a model with 2002 rows, 2001 columns and 5001 nonzeros\n",
|
|
"Model fingerprint: 0x637c283e\n",
|
|
"Variable types: 1001 continuous, 1000 integer (1000 binary)\n",
|
|
"Coefficient statistics:\n",
|
|
" Matrix range [1e+00, 2e+06]\n",
|
|
" Objective range [1e+00, 6e+07]\n",
|
|
" Bounds range [1e+00, 1e+00]\n",
|
|
" RHS range [1e+00, 7e+08]\n",
|
|
"Presolve removed 1 rows and 1 columns\n",
|
|
"Presolve time: 0.00s\n",
|
|
"Presolved: 2001 rows, 2000 columns, 5000 nonzeros\n",
|
|
"Variable types: 1000 continuous, 1000 integer (1000 binary)\n",
|
|
"\n",
|
|
"Root relaxation: objective 2.143251e+10, 1010 iterations, 0.00 seconds\n",
|
|
"\n",
|
|
" Nodes | Current Node | Objective Bounds | Work\n",
|
|
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
|
"\n",
|
|
" 0 0 2.1433e+10 0 1 - 2.1433e+10 - - 0s\n",
|
|
"H 0 0 2.143648e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 0 2.1433e+10 0 3 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 0 2.1433e+10 0 1 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 0 2.1433e+10 0 3 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
"H 0 0 2.143631e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 0 2.1433e+10 0 2 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 0 2.1433e+10 0 2 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
" 0 2 2.1433e+10 0 2 2.1436e+10 2.1433e+10 0.02% - 0s\n",
|
|
"H 224 189 2.143590e+10 2.1433e+10 0.02% 1.0 0s\n",
|
|
"H 259 218 2.143566e+10 2.1433e+10 0.01% 1.0 0s\n",
|
|
"H 516 430 2.143497e+10 2.1433e+10 0.01% 1.1 0s\n",
|
|
"H 631 467 2.143400e+10 2.1433e+10 0.01% 1.7 0s\n",
|
|
"\n",
|
|
"Cutting planes:\n",
|
|
" Gomory: 1\n",
|
|
"\n",
|
|
"Explored 632 nodes (2451 simplex iterations) in 0.34 seconds\n",
|
|
"Thread count was 1 (of 32 available processors)\n",
|
|
"\n",
|
|
"Solution count 6: 2.1434e+10 2.1435e+10 2.14357e+10 ... 2.14365e+10\n",
|
|
"\n",
|
|
"Optimal solution found (tolerance 1.00e-04)\n",
|
|
"Best objective 2.143399941747e+10, best bound 2.143258805394e+10, gap 0.0066%\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"instances = random_uc_instances(samples=100, n=1000);\n",
|
|
"train_instances = instances[1:90]\n",
|
|
"test_instances = instances[91:100];\n",
|
|
"\n",
|
|
"model = instances[0].to_model()\n",
|
|
"opt = pyo.SolverFactory(\"gurobi\")\n",
|
|
"opt.solve(model, tee=True);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b795a0b1",
|
|
"metadata": {},
|
|
"source": [
|
|
"Next, we write these objects to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold the entire training data, as well as the concrete Pyomo models, in memory. Files also make it much easier to solve multiple instances simultaneously, potentially even on multiple machines. We will cover parallel and distributed computing in a future tutorial.\n",
|
|
"\n",
|
|
"The code below generates the files `uc/train/00001.h5`, `uc/train/00002.h5`, etc., which contain the input data in HDF5 format."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "e21d9aa0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from miplearn.instance.file import FileInstance\n",
|
|
"from glob import glob\n",
|
|
"import os\n",
|
|
"\n",
|
|
"os.makedirs(\"uc/train\", exist_ok=True)\n",
|
|
"os.makedirs(\"uc/test\", exist_ok=True)\n",
|
|
"\n",
|
|
"for (i, instance) in enumerate(train_instances):\n",
|
|
" FileInstance.save(instance, f\"uc/train/{i:05d}.h5\")\n",
|
|
" \n",
|
|
"for (i, instance) in enumerate(test_instances):\n",
|
|
" FileInstance.save(instance, f\"uc/test/{i:05d}.h5\")\n",
|
|
"\n",
|
|
"train_instances = [FileInstance(f) for f in glob(\"uc/train/*.h5\")]\n",
|
|
"test_instances = [FileInstance(f) for f in glob(\"uc/test/*.h5\")]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b17af877",
|
|
"metadata": {},
|
|
"source": [
|
|
"Finally, we use `LearningSolver` to solve all the training instances. `LearningSolver` is the main component provided by MIPLearn, which integrates MIP solvers and ML. The solutions, and other useful training data, are stored in the same HDF5 files."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "7623f002",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
" 12%|█▏ | 11/89 [00:50<05:56, 4.57s/it]\n"
|
|
]
|
|
},
|
|
{
|
|
"ename": "KeyboardInterrupt",
|
|
"evalue": "",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
|
|
"\u001b[0;32m<ipython-input-8-b1e67b5febb2>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0msolver\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mLearningSolver\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0minstance\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtqdm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrain_instances\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0msolver\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msolve\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
|
"\u001b[0;32m~/Packages/MIPLearn/dev/miplearn/solvers/learning.py\u001b[0m in \u001b[0;36msolve\u001b[0;34m(self, instance, model, discard_output, tee)\u001b[0m\n\u001b[1;32m 354\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0minstance\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 355\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minstance\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 356\u001b[0;31m return self._solve(\n\u001b[0m\u001b[1;32m 357\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minstance\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 358\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/Packages/MIPLearn/dev/miplearn/solvers/learning.py\u001b[0m in \u001b[0;36m_solve\u001b[0;34m(self, instance, model, discard_output, tee)\u001b[0m\n\u001b[1;32m 169\u001b[0m \u001b[0mlogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Extracting features (after-load)...\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 170\u001b[0m \u001b[0minitial_time\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 171\u001b[0;31m self.extractor.extract_after_load_features(\n\u001b[0m\u001b[1;32m 172\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minternal_solver\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msample\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 173\u001b[0m )\n",
|
|
"\u001b[0;32m~/Packages/MIPLearn/dev/miplearn/features/extractor.py\u001b[0m in \u001b[0;36mextract_after_load_features\u001b[0;34m(self, instance, solver, sample)\u001b[0m\n\u001b[1;32m 35\u001b[0m ) -> None:\n\u001b[1;32m 36\u001b[0m \u001b[0mvariables\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msolver\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_variables\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwith_static\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 37\u001b[0;31m \u001b[0mconstraints\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msolver\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_constraints\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwith_static\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwith_lhs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_lhs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 38\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mconstraints\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnames\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0msample\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mput_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"static_var_lower_bounds\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvariables\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower_bounds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/Packages/MIPLearn/dev/miplearn/solvers/pyomo/base.py\u001b[0m in \u001b[0;36mget_constraints\u001b[0;34m(self, with_static, with_sa, with_lhs)\u001b[0m\n\u001b[1;32m 237\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0midx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mconstr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 238\u001b[0m \u001b[0mnames\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconstr\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0midx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 239\u001b[0;31m \u001b[0m_parse_constraint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconstr\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0midx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcurr_row\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 240\u001b[0m \u001b[0mcurr_row\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 241\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/Packages/MIPLearn/dev/miplearn/solvers/pyomo/base.py\u001b[0m in \u001b[0;36m_parse_constraint\u001b[0;34m(c, row)\u001b[0m\n\u001b[1;32m 204\u001b[0m \u001b[0mlhs_row\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrow\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 205\u001b[0m lhs_col.append(\n\u001b[0;32m--> 206\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_varname_to_idx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mterm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_args_\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 207\u001b[0m )\n\u001b[1;32m 208\u001b[0m \u001b[0mlhs_data\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mterm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_args_\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/.conda/envs/miplearn/lib/python3.8/site-packages/Pyomo-5.7.3-py3.8-linux-x86_64.egg/pyomo/core/base/component.py\u001b[0m in \u001b[0;36mname\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 285\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 286\u001b[0m \u001b[0;34m\"\"\"Get the fully qualifed component name.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 287\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetname\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfully_qualified\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 288\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 289\u001b[0m \u001b[0;31m# Adding a setter here to help users adapt to the new\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/.conda/envs/miplearn/lib/python3.8/site-packages/Pyomo-5.7.3-py3.8-linux-x86_64.egg/pyomo/core/base/component.py\u001b[0m in \u001b[0;36mgetname\u001b[0;34m(self, fully_qualified, name_buffer, relative_to)\u001b[0m\n\u001b[1;32m 910\u001b[0m \u001b[0;31m# more expensive than if a buffer is provided.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 911\u001b[0m \u001b[0;31m#\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 912\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0midx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0miteritems\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 913\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 914\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mbase\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0m_name_index_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0midx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/.conda/envs/miplearn/lib/python3.8/site-packages/Pyomo-5.7.3-py3.8-linux-x86_64.egg/pyomo/core/base/indexed_component.py\u001b[0m in \u001b[0;36miteritems\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 370\u001b[0m \u001b[0;34m\"\"\"Return an iterator of (index,data) tuples from the dictionary\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 371\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 372\u001b[0;31m \u001b[0;32myield\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 373\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 374\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m~/.conda/envs/miplearn/lib/python3.8/site-packages/Pyomo-5.7.3-py3.8-linux-x86_64.egg/pyomo/core/base/indexed_component.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, index)\u001b[0m\n\u001b[1;32m 380\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 381\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 382\u001b[0;31m \u001b[0mobj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_data\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m_NotFound\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 383\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 384\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from miplearn import LearningSolver\n",
|
|
"from tqdm import tqdm\n",
|
|
"\n",
|
|
"solver = LearningSolver()\n",
|
|
"for instance in tqdm(train_instances):\n",
|
|
" solver.solve(instance)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2f24ee83",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Solving new instances\n",
|
|
"\n",
|
|
"With training data in hand, we can now fit the ML models, using the `LearningSolver.fit` method, then solve the test instances with `MIPLearn.solve!`, as shown below:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c8385030",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"solver_ml = LearningSolver()\n",
|
|
"solver_ml.fit(train_instances)\n",
|
|
"for instance in tqdm(test_instances):\n",
|
|
" solver_ml.solve(instance)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8ae11854",
|
|
"metadata": {},
|
|
"source": [
|
|
"The trained MIP solver was able to solve all test instances in about 6 seconds. To see that ML is being helpful here, let us repeat the code above, but remove the `fit!` line:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "71c58e4a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"solver_baseline = LearningSolver()\n",
|
|
"for instance in tqdm(test_instances):\n",
|
|
" solver_baseline.solve(instance)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "117adfb7",
|
|
"metadata": {},
|
|
"source": [
|
|
"Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances.\n",
|
|
"\n",
|
|
"<div class=\"alert alert-info\">\n",
|
|
"Note\n",
|
|
" \n",
|
|
"Note that is is not necessary to specify what ML models to use. MIPLearn, by default, will try a number of classical ML models and will choose the one that performs the best, based on k-fold cross validation. MIPLearn is also able to automatically collect features based on the MIP formulation of the problem and the solution to the LP relaxation, among other things, so it does not require handcrafted features. If you do want to customize the models and features, however, that is also possible, as we will see in a later tutorial.\n",
|
|
"</div>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dec6acd7",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Understanding the acceleration\n",
|
|
"\n",
|
|
"Let us go a bit deeper and try to understand how exactly did MIPLearn accelerate Gurobi's performance. First, we are going to solve one of the test instances again, using the trained solver, but this time using the `tee=True` parameter, so that we can see Gurobi's log:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "0d4df336",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"solver_ml.solve(test_instances[0], tee=True);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "df5108b9",
|
|
"metadata": {},
|
|
"source": [
|
|
"The log above is quite complicated if you have never seen it before, but the important line is the one starting with `feasible solution found [...] objective value 1.705169e+07`. This line indicates that MIPLearn was able to construct a warm start with value `1.705169e+07`. Using this warm start, SCIP then used the branch-and-cut method to either prove its optimality or to find an even better solution. Very quickly, however, SCIP proved that the solution produced by MIPLearn was indeed optimal. It was able to do this without generating a single cutting plane or running any other heuristics; it could tell the optimality by the root LP relaxation alone, which was very fast. \n",
|
|
"\n",
|
|
"Let us now repeat the process, but using the untrained solver this time:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "51864ed9",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"solver_baseline.solve(test_instances[0], tee=True);"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5a78550e",
|
|
"metadata": {},
|
|
"source": [
|
|
"In this log file, notice how the previous line about warm starts is missing. Since no warm starts were provided, SCIP had to find an initial solution using its own internal heuristics, which are not specifically tailored for this problem. The initial solution found by SCIP's heuristics has value `2.335200e+07`, which is significantly worse than the one constructed by MIPLearn. SCIP then proceeded to improve this solution, by generating cutting planes and repeatedly running additional primal heuristics. In the end, it was able to find the optimal solution, as expected, but it took longer.\n",
|
|
"\n",
|
|
"In summary, MIPLearn accelerated the solution process by constructing a high-quality initial solution. In the following tutorials, we will see other strategies that MIPLearn can use to accelerate MIP performance, besides warm starts."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "eec97f06",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Accessing the solution\n",
|
|
"\n",
|
|
"In the example above, we used `MIPLearn.solve!` together with data files to solve both the training and the test instances. The solutions were saved to a `.h5` files in the train/test folders, and could be retrieved by reading theses files, but that is not very convenient. In this section we will use an easier method.\n",
|
|
"\n",
|
|
"We can use the function `MIPLearn.load!` to obtain a regular JuMP model:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "67a6cd18",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"model = MIPLearn.load(\"uc/test/000001.jld2\", build_uc_model)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b7a5629c",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can then solve this model as before, with `MIPLearn.solve!`:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "6d5fa7e9",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"solve!(solver_ml, model)\n",
|
|
"println(\"obj = \", objective_value(model))\n",
|
|
"println(\" x = \", round.(value.(model[:x][1:10])))\n",
|
|
"println(\" y = \", round.(value.(model[:y][1:10]), digits=2))"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "miplearn",
|
|
"language": "python",
|
|
"name": "miplearn"
|
|
},
|
|
"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.8.10"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|