Update 0.2 docs

docs
Alinson S. Xavier 4 years ago
parent 465005b493
commit 3faa6e2a1c

@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c5a596fb", "id": "546635ee",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Getting started with MIPLearn\n", "# Getting started with MIPLearn\n",
@ -19,8 +19,7 @@
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
"Note\n", "Note\n",
" \n", " \n",
"We use SCIP in this tutorial because it is a fast and widely available noncommercial MIP solver. All the steps shown here also work for Gurobi, CPLEX and XPRESS, although the performance impact might be different.\n", "In this tutorial, we use SCIP because it is more widely available than commercial MIP solvers. However, all the steps below should work for Gurobi, CPLEX or XPRESS, as long as you have a license for these solvers. The performance impact of MIPLearn may also change for different solvers.\n",
" \n",
"</div>\n", "</div>\n",
"\n", "\n",
"<div class=\"alert alert-warning\">\n", "<div class=\"alert alert-warning\">\n",
@ -33,10 +32,10 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "1f59417f", "id": "8b97258c",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Installing MIPLearn\n", "## Installation\n",
"\n", "\n",
"MIPLearn is available in two versions:\n", "MIPLearn is available in two versions:\n",
"\n", "\n",
@ -49,14 +48,16 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"id": "1ddeeb8e", "id": "2dbeacbc",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stderr", "name": "stderr",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct package. Using existing path.\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/ANL-CEEESA/MIPLearn.jl.git`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n",
"\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n" "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n"
@ -65,40 +66,35 @@
], ],
"source": [ "source": [
"using Pkg\n", "using Pkg\n",
"Pkg.develop(PackageSpec(path=\"/home/axavier/Packages/MIPLearn.jl/dev\"))" "Pkg.add(PackageSpec(url=\"https://github.com/ANL-CEEESA/MIPLearn.jl.git\"))"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "de7ab489", "id": "b2f449e7",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:\n", "In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:\n",
"\n", "\n",
"- `SCIP`, a non-commercial mixed-integer programming solver\n", "- [**SCIP**](https://www.scipopt.org/), one of the fastest non-commercial MIP solvers currently available\n",
"- `JuMP`, an open-source modeling language for Julia\n", "- [**JuMP**](https://jump.dev/), an open source modeling language for Julia\n",
"- `Distributions`, a statistics package that we will use to generate random inputs\n", "- [**Distributions.jl**](https://github.com/JuliaStats/Distributions.jl), a statistics package that we will use to generate random inputs\n",
"- `Glob`, a package that retrieves all files in a directory matching a certain pattern" "- [**Glob.jl**](https://github.com/vtjnash/Glob.jl), a package that retrieves all files in a directory matching a certain pattern"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"id": "29d29925", "id": "68f99568",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stderr", "name": "stderr",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n",
"\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n"
"\u001b[32m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n",
"\u001b[32m ✓ \u001b[39mMIPLearn\n",
"1 dependency successfully precompiled in 10 seconds (96 already precompiled)\n"
] ]
} }
], ],
@ -109,13 +105,12 @@
" PackageSpec(name=\"JuMP\", version=\"0.21\"),\n", " PackageSpec(name=\"JuMP\", version=\"0.21\"),\n",
" PackageSpec(name=\"Distributions\", version=\"0.25\"),\n", " PackageSpec(name=\"Distributions\", version=\"0.25\"),\n",
" PackageSpec(name=\"Glob\", version=\"1\"),\n", " PackageSpec(name=\"Glob\", version=\"1\"),\n",
"])\n", "])"
"using Revise"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "88074d87", "id": "51e09fc9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
@ -129,16 +124,16 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "78482747", "id": "18c300c4",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Modeling a simple optimization problem\n", "## Modeling a simple optimization problem\n",
"\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", "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", "\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, and 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{fixed}_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", "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", "\n",
"This simple problem 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 we need to solve is given by:\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",
"$$\n", "$$\n",
"\\begin{align}\n", "\\begin{align}\n",
@ -155,17 +150,17 @@
" \n", " \n",
"Note\n", "Note\n",
" \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 the benchmark sections for more details.\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", " \n",
"</div>\n", "</div>\n",
"\n", "\n",
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using the Julia and the JuMP modeling language. We start by defining a data structure that holds all input data:" "Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Julia and JuMP. We start by defining a data structure that holds all the input data."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"id": "ec7dbab4", "id": "b12d6483",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -180,16 +175,16 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c8f6a5b8", "id": "55cdb64b",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Next, we create a function that converts this data into a concrete JuMP model:" "Next, we create a function that converts this data structure into a concrete JuMP model. For more details on the JuMP syntax, see [the official JuMP documentation](https://jump.dev/JuMP.jl/stable/)."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "14e84c92", "id": "1e38a266",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -218,7 +213,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "f647734f", "id": "d28c4d5a",
"metadata": {}, "metadata": {},
"source": [ "source": [
"At this point, we can already use JuMP 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, using SCIP:" "At this point, we can already use JuMP 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, using SCIP:"
@ -227,7 +222,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"id": "b2abe5e2", "id": "9ff9f05c",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -242,7 +237,6 @@
], ],
"source": [ "source": [
"using SCIP\n", "using SCIP\n",
"using Printf\n",
"\n", "\n",
"model = build_uc_model(\n", "model = build_uc_model(\n",
" UnitCommitmentData(\n", " UnitCommitmentData(\n",
@ -266,7 +260,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "5be976f5", "id": "345de591",
"metadata": {}, "metadata": {},
"source": [ "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." "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."
@ -274,7 +268,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "96a1f952", "id": "eb8904ef",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Generating training data\n", "## Generating training data\n",
@ -289,7 +283,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"id": "353e6199", "id": "7298bb0d",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -317,7 +311,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "2140968d", "id": "c1feed43",
"metadata": {}, "metadata": {},
"source": [ "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", "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",
@ -328,7 +322,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 7,
"id": "1bb24909", "id": "61d43994",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -339,34 +333,33 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "96bc0e42", "id": "3fdeb8cd",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Next, we will write these data structures 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 JuMP 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", "Next, we write these data structures 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 JuMP 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", "\n",
"The code below generates the files `uc/train/000001.jld2`, `uc/train/000002.jld2`, etc." "The code below generates the files `uc/train/000001.jld2`, `uc/train/000002.jld2`, etc., which contain the input data in [JLD2 format](https://github.com/JuliaIO/JLD2.jl)."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 8,
"id": "8ec476b1", "id": "31b48701",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"using MIPLearn\n", "using MIPLearn\n",
"using Glob\n",
"\n",
"MIPLearn.save(data[1:90], \"uc/train/\")\n", "MIPLearn.save(data[1:90], \"uc/train/\")\n",
"MIPLearn.save(data[91:100], \"uc/test/\")\n", "MIPLearn.save(data[91:100], \"uc/test/\")\n",
"\n", "\n",
"using Glob\n",
"train_files = glob(\"uc/train/*.jld2\")\n", "train_files = glob(\"uc/train/*.jld2\")\n",
"test_files = glob(\"uc/test/*.jld2\");" "test_files = glob(\"uc/test/*.jld2\");"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "5d53a783", "id": "5cecea59",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Finally, we use `MIPLearn.LearningSolver` and `MIPLearn.solve!` to solve all the training instances. `LearningSolver` is the main component provided by MIPLearn, which integrates MIP solvers and ML. The `solve!` function can be used to solve either one or multiple instances, and requires: (i) the list of files containing the training data; and (ii) the function that converts the data structure into a concrete JuMP model:" "Finally, we use `MIPLearn.LearningSolver` and `MIPLearn.solve!` to solve all the training instances. `LearningSolver` is the main component provided by MIPLearn, which integrates MIP solvers and ML. The `solve!` function can be used to solve either one or multiple instances, and requires: (i) the list of files containing the training data; and (ii) the function that converts the data structure into a concrete JuMP model:"
@ -375,14 +368,14 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 9,
"id": "514a3b3a", "id": "60732af0",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"101.279699 seconds (93.52 M allocations: 3.599 GiB, 1.23% gc time, 0.52% compilation time)\n" "103.808547 seconds (93.52 M allocations: 3.604 GiB, 1.19% gc time, 0.52% compilation time)\n"
] ]
}, },
{ {
@ -401,33 +394,33 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "72eb09f4", "id": "bbc7ad82",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The macro `@time` shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, is stored by MIPLearn in `.h5` files, stored side-by-side with the original `.jld2` files." "The macro `@time` shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, are stored by MIPLearn in `.h5` files, stored side-by-side with the original `.jld2` files."
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "90406b90", "id": "73379180",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Solving new instances\n", "## Solving new instances\n",
"\n", "\n",
"Now that we have training data, we can fit the ML models using `MIPLearn.fit!`, then solve the test instances with `MIPLearn.solve!`, as shown below:" "With training data in hand, we can now fit the ML models using `MIPLearn.fit!`, then solve the test instances with `MIPLearn.solve!`, as shown below:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 10,
"id": "e4de94db", "id": "e045d644",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
" 5.693951 seconds (9.33 M allocations: 334.689 MiB, 1.62% gc time)\n" " 5.951264 seconds (9.33 M allocations: 334.657 MiB, 1.51% gc time)\n"
] ]
} }
], ],
@ -439,23 +432,23 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "247c1087", "id": "d8de7b26",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The trained MIP solver was able to solve all test instances in about 5 seconds. To see that ML is being helpful here, let us repeat the code above, but remove the `fit!` line:" "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", "cell_type": "code",
"execution_count": 11, "execution_count": 11,
"id": "62061b12", "id": "cf2a989e",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
" 9.829350 seconds (8.17 M allocations: 278.008 MiB, 0.47% gc time)\n" " 10.390325 seconds (8.17 M allocations: 278.042 MiB, 0.89% gc time)\n"
] ]
} }
], ],
@ -466,10 +459,10 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "8ea5c423", "id": "e100b25d",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances, or about twice as long.\n", "Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances.\n",
"\n", "\n",
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
"Note\n", "Note\n",
@ -480,18 +473,18 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "569f7c7a", "id": "af451e87",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Understanding the acceleration\n", "## Understanding the acceleration\n",
"\n", "\n",
"Let us know go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIP's performance. First, we are going to solve one of the training instances again, using the trained solver, but this time using the `tee=true` parameter, so that we can see SCIP's log:" "Let us go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIP'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 SCIP's log:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 12,
"id": "46739739", "id": "0c675452",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -538,7 +531,7 @@
"presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints\n", "presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints\n",
" 2000 constraints of type <varbound>\n", " 2000 constraints of type <varbound>\n",
" 1 constraints of type <linear>\n", " 1 constraints of type <linear>\n",
"Presolving Time: 0.10\n", "Presolving Time: 0.11\n",
"transformed 1/1 original solutions to the transformed problem space\n", "transformed 1/1 original solutions to the transformed problem space\n",
"\n", "\n",
" time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl. \n", " time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl. \n",
@ -560,18 +553,18 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "9cdc02d0", "id": "ff0b6858",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The log above is quite complicated if you have never seen it before, but the important line in 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 proceeded with the branch-and-cut process to either prove its optimality or find an even better solution. Very quickly, however, SCIP proved that the solution produced by MIPLearn was indeed optimal and terminated. 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", "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", "\n",
"Let us now do the same thing again, but using the untrained solver this time:" "Let us now repeat the process, but using the untrained solver this time:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 13,
"id": "555af477", "id": "1aa9230e",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -642,7 +635,7 @@
"L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown\n", "L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown\n",
"\n", "\n",
"SCIP Status : solving was interrupted [gap limit reached]\n", "SCIP Status : solving was interrupted [gap limit reached]\n",
"Solving Time (sec) : 0.67\n", "Solving Time (sec) : 0.68\n",
"Solving Nodes : 1\n", "Solving Nodes : 1\n",
"Primal Bound : +1.70517823853380e+07 (13 solutions)\n", "Primal Bound : +1.70517823853380e+07 (13 solutions)\n",
"Dual Bound : +1.70503798271962e+07\n", "Dual Bound : +1.70503798271962e+07\n",
@ -657,15 +650,17 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "72a52d26", "id": "9417bb85",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In this log file, notice how the line we saw before is now missing; SCIP needs to find an initial solution using its own internal heuristics. The solution SCIP initially found has value `2.335200e+07`, which is significantly worse than the one MIPLearn constructed before. SCIP then proceeds to improve this solution by generating a number of cutting planes and repeatedly running primal heuristics. In the end, it is able to find the optimal solution, as expected, but it takes longer." "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", "cell_type": "markdown",
"id": "36fb5f02", "id": "ab9c1ff4",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Accessing the solution\n", "## Accessing the solution\n",
@ -678,7 +673,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 14,
"id": "f62f28b4", "id": "79759e87",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -710,7 +705,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d5722dcf", "id": "b096dcf9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We can then solve this model as before, with `MIPLearn.solve!`:" "We can then solve this model as before, with `MIPLearn.solve!`:"
@ -719,7 +714,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 15,
"id": "e49f9e60", "id": "1b668c12",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {

@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c5a596fb", "id": "546635ee",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Getting started with MIPLearn\n", "# Getting started with MIPLearn\n",
@ -19,8 +19,7 @@
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
"Note\n", "Note\n",
" \n", " \n",
"We use SCIP in this tutorial because it is a fast and widely available noncommercial MIP solver. All the steps shown here also work for Gurobi, CPLEX and XPRESS, although the performance impact might be different.\n", "In this tutorial, we use SCIP because it is more widely available than commercial MIP solvers. However, all the steps below should work for Gurobi, CPLEX or XPRESS, as long as you have a license for these solvers. The performance impact of MIPLearn may also change for different solvers.\n",
" \n",
"</div>\n", "</div>\n",
"\n", "\n",
"<div class=\"alert alert-warning\">\n", "<div class=\"alert alert-warning\">\n",
@ -33,10 +32,10 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "1f59417f", "id": "8b97258c",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Installing MIPLearn\n", "## Installation\n",
"\n", "\n",
"MIPLearn is available in two versions:\n", "MIPLearn is available in two versions:\n",
"\n", "\n",
@ -49,14 +48,16 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"id": "1ddeeb8e", "id": "2dbeacbc",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stderr", "name": "stderr",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct package. Using existing path.\n", "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/ANL-CEEESA/MIPLearn.jl.git`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n",
"\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n" "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n"
@ -65,40 +66,35 @@
], ],
"source": [ "source": [
"using Pkg\n", "using Pkg\n",
"Pkg.develop(PackageSpec(path=\"/home/axavier/Packages/MIPLearn.jl/dev\"))" "Pkg.add(PackageSpec(url=\"https://github.com/ANL-CEEESA/MIPLearn.jl.git\"))"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "de7ab489", "id": "b2f449e7",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:\n", "In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:\n",
"\n", "\n",
"- `SCIP`, a non-commercial mixed-integer programming solver\n", "- [**SCIP**](https://www.scipopt.org/), one of the fastest non-commercial MIP solvers currently available\n",
"- `JuMP`, an open-source modeling language for Julia\n", "- [**JuMP**](https://jump.dev/), an open source modeling language for Julia\n",
"- `Distributions`, a statistics package that we will use to generate random inputs\n", "- [**Distributions.jl**](https://github.com/JuliaStats/Distributions.jl), a statistics package that we will use to generate random inputs\n",
"- `Glob`, a package that retrieves all files in a directory matching a certain pattern" "- [**Glob.jl**](https://github.com/vtjnash/Glob.jl), a package that retrieves all files in a directory matching a certain pattern"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"id": "29d29925", "id": "68f99568",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stderr", "name": "stderr",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General`\n",
"\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m git-repo `https://github.com/JuliaRegistries/General.git`\n",
"\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`\n",
"\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`\n"
"\u001b[32m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n",
"\u001b[32m ✓ \u001b[39mMIPLearn\n",
"1 dependency successfully precompiled in 10 seconds (96 already precompiled)\n"
] ]
} }
], ],
@ -109,13 +105,12 @@
" PackageSpec(name=\"JuMP\", version=\"0.21\"),\n", " PackageSpec(name=\"JuMP\", version=\"0.21\"),\n",
" PackageSpec(name=\"Distributions\", version=\"0.25\"),\n", " PackageSpec(name=\"Distributions\", version=\"0.25\"),\n",
" PackageSpec(name=\"Glob\", version=\"1\"),\n", " PackageSpec(name=\"Glob\", version=\"1\"),\n",
"])\n", "])"
"using Revise"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "88074d87", "id": "51e09fc9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
@ -129,16 +124,16 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "78482747", "id": "18c300c4",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Modeling a simple optimization problem\n", "## Modeling a simple optimization problem\n",
"\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", "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", "\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, and 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{fixed}_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", "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", "\n",
"This simple problem 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 we need to solve is given by:\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",
"$$\n", "$$\n",
"\\begin{align}\n", "\\begin{align}\n",
@ -155,17 +150,17 @@
" \n", " \n",
"Note\n", "Note\n",
" \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 the benchmark sections for more details.\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", " \n",
"</div>\n", "</div>\n",
"\n", "\n",
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using the Julia and the JuMP modeling language. We start by defining a data structure that holds all input data:" "Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Julia and JuMP. We start by defining a data structure that holds all the input data."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"id": "ec7dbab4", "id": "b12d6483",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -180,16 +175,16 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c8f6a5b8", "id": "55cdb64b",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Next, we create a function that converts this data into a concrete JuMP model:" "Next, we create a function that converts this data structure into a concrete JuMP model. For more details on the JuMP syntax, see [the official JuMP documentation](https://jump.dev/JuMP.jl/stable/)."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "14e84c92", "id": "1e38a266",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -218,7 +213,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "f647734f", "id": "d28c4d5a",
"metadata": {}, "metadata": {},
"source": [ "source": [
"At this point, we can already use JuMP 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, using SCIP:" "At this point, we can already use JuMP 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, using SCIP:"
@ -227,7 +222,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"id": "b2abe5e2", "id": "9ff9f05c",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -242,7 +237,6 @@
], ],
"source": [ "source": [
"using SCIP\n", "using SCIP\n",
"using Printf\n",
"\n", "\n",
"model = build_uc_model(\n", "model = build_uc_model(\n",
" UnitCommitmentData(\n", " UnitCommitmentData(\n",
@ -266,7 +260,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "5be976f5", "id": "345de591",
"metadata": {}, "metadata": {},
"source": [ "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." "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."
@ -274,7 +268,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "96a1f952", "id": "eb8904ef",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Generating training data\n", "## Generating training data\n",
@ -289,7 +283,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"id": "353e6199", "id": "7298bb0d",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -317,7 +311,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "2140968d", "id": "c1feed43",
"metadata": {}, "metadata": {},
"source": [ "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", "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",
@ -328,7 +322,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 7,
"id": "1bb24909", "id": "61d43994",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -339,34 +333,33 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "96bc0e42", "id": "3fdeb8cd",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Next, we will write these data structures 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 JuMP 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", "Next, we write these data structures 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 JuMP 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", "\n",
"The code below generates the files `uc/train/000001.jld2`, `uc/train/000002.jld2`, etc." "The code below generates the files `uc/train/000001.jld2`, `uc/train/000002.jld2`, etc., which contain the input data in [JLD2 format](https://github.com/JuliaIO/JLD2.jl)."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 8,
"id": "8ec476b1", "id": "31b48701",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"using MIPLearn\n", "using MIPLearn\n",
"using Glob\n",
"\n",
"MIPLearn.save(data[1:90], \"uc/train/\")\n", "MIPLearn.save(data[1:90], \"uc/train/\")\n",
"MIPLearn.save(data[91:100], \"uc/test/\")\n", "MIPLearn.save(data[91:100], \"uc/test/\")\n",
"\n", "\n",
"using Glob\n",
"train_files = glob(\"uc/train/*.jld2\")\n", "train_files = glob(\"uc/train/*.jld2\")\n",
"test_files = glob(\"uc/test/*.jld2\");" "test_files = glob(\"uc/test/*.jld2\");"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "5d53a783", "id": "5cecea59",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Finally, we use `MIPLearn.LearningSolver` and `MIPLearn.solve!` to solve all the training instances. `LearningSolver` is the main component provided by MIPLearn, which integrates MIP solvers and ML. The `solve!` function can be used to solve either one or multiple instances, and requires: (i) the list of files containing the training data; and (ii) the function that converts the data structure into a concrete JuMP model:" "Finally, we use `MIPLearn.LearningSolver` and `MIPLearn.solve!` to solve all the training instances. `LearningSolver` is the main component provided by MIPLearn, which integrates MIP solvers and ML. The `solve!` function can be used to solve either one or multiple instances, and requires: (i) the list of files containing the training data; and (ii) the function that converts the data structure into a concrete JuMP model:"
@ -375,14 +368,14 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 9,
"id": "514a3b3a", "id": "60732af0",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"101.279699 seconds (93.52 M allocations: 3.599 GiB, 1.23% gc time, 0.52% compilation time)\n" "103.808547 seconds (93.52 M allocations: 3.604 GiB, 1.19% gc time, 0.52% compilation time)\n"
] ]
}, },
{ {
@ -401,33 +394,33 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "72eb09f4", "id": "bbc7ad82",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The macro `@time` shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, is stored by MIPLearn in `.h5` files, stored side-by-side with the original `.jld2` files." "The macro `@time` shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, are stored by MIPLearn in `.h5` files, stored side-by-side with the original `.jld2` files."
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "90406b90", "id": "73379180",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Solving new instances\n", "## Solving new instances\n",
"\n", "\n",
"Now that we have training data, we can fit the ML models using `MIPLearn.fit!`, then solve the test instances with `MIPLearn.solve!`, as shown below:" "With training data in hand, we can now fit the ML models using `MIPLearn.fit!`, then solve the test instances with `MIPLearn.solve!`, as shown below:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 10,
"id": "e4de94db", "id": "e045d644",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
" 5.693951 seconds (9.33 M allocations: 334.689 MiB, 1.62% gc time)\n" " 5.951264 seconds (9.33 M allocations: 334.657 MiB, 1.51% gc time)\n"
] ]
} }
], ],
@ -439,23 +432,23 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "247c1087", "id": "d8de7b26",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The trained MIP solver was able to solve all test instances in about 5 seconds. To see that ML is being helpful here, let us repeat the code above, but remove the `fit!` line:" "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", "cell_type": "code",
"execution_count": 11, "execution_count": 11,
"id": "62061b12", "id": "cf2a989e",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
" 9.829350 seconds (8.17 M allocations: 278.008 MiB, 0.47% gc time)\n" " 10.390325 seconds (8.17 M allocations: 278.042 MiB, 0.89% gc time)\n"
] ]
} }
], ],
@ -466,10 +459,10 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "8ea5c423", "id": "e100b25d",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances, or about twice as long.\n", "Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances.\n",
"\n", "\n",
"<div class=\"alert alert-info\">\n", "<div class=\"alert alert-info\">\n",
"Note\n", "Note\n",
@ -480,18 +473,18 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "569f7c7a", "id": "af451e87",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Understanding the acceleration\n", "## Understanding the acceleration\n",
"\n", "\n",
"Let us know go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIP's performance. First, we are going to solve one of the training instances again, using the trained solver, but this time using the `tee=true` parameter, so that we can see SCIP's log:" "Let us go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIP'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 SCIP's log:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 12,
"id": "46739739", "id": "0c675452",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -538,7 +531,7 @@
"presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints\n", "presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints\n",
" 2000 constraints of type <varbound>\n", " 2000 constraints of type <varbound>\n",
" 1 constraints of type <linear>\n", " 1 constraints of type <linear>\n",
"Presolving Time: 0.10\n", "Presolving Time: 0.11\n",
"transformed 1/1 original solutions to the transformed problem space\n", "transformed 1/1 original solutions to the transformed problem space\n",
"\n", "\n",
" time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl. \n", " time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl. \n",
@ -560,18 +553,18 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "9cdc02d0", "id": "ff0b6858",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The log above is quite complicated if you have never seen it before, but the important line in 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 proceeded with the branch-and-cut process to either prove its optimality or find an even better solution. Very quickly, however, SCIP proved that the solution produced by MIPLearn was indeed optimal and terminated. 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", "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", "\n",
"Let us now do the same thing again, but using the untrained solver this time:" "Let us now repeat the process, but using the untrained solver this time:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 13,
"id": "555af477", "id": "1aa9230e",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -642,7 +635,7 @@
"L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown\n", "L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown\n",
"\n", "\n",
"SCIP Status : solving was interrupted [gap limit reached]\n", "SCIP Status : solving was interrupted [gap limit reached]\n",
"Solving Time (sec) : 0.67\n", "Solving Time (sec) : 0.68\n",
"Solving Nodes : 1\n", "Solving Nodes : 1\n",
"Primal Bound : +1.70517823853380e+07 (13 solutions)\n", "Primal Bound : +1.70517823853380e+07 (13 solutions)\n",
"Dual Bound : +1.70503798271962e+07\n", "Dual Bound : +1.70503798271962e+07\n",
@ -657,15 +650,17 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "72a52d26", "id": "9417bb85",
"metadata": {}, "metadata": {},
"source": [ "source": [
"In this log file, notice how the line we saw before is now missing; SCIP needs to find an initial solution using its own internal heuristics. The solution SCIP initially found has value `2.335200e+07`, which is significantly worse than the one MIPLearn constructed before. SCIP then proceeds to improve this solution by generating a number of cutting planes and repeatedly running primal heuristics. In the end, it is able to find the optimal solution, as expected, but it takes longer." "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", "cell_type": "markdown",
"id": "36fb5f02", "id": "ab9c1ff4",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Accessing the solution\n", "## Accessing the solution\n",
@ -678,7 +673,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 14,
"id": "f62f28b4", "id": "79759e87",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -710,7 +705,7 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d5722dcf", "id": "b096dcf9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We can then solve this model as before, with `MIPLearn.solve!`:" "We can then solve this model as before, with `MIPLearn.solve!`:"
@ -719,7 +714,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 15,
"id": "e49f9e60", "id": "1b668c12",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {

@ -178,8 +178,8 @@
</a> </a>
</li> </li>
<li class="toc-h2 nav-item toc-entry"> <li class="toc-h2 nav-item toc-entry">
<a class="reference internal nav-link" href="#Installing-MIPLearn"> <a class="reference internal nav-link" href="#Installation">
1.2. Installing MIPLearn 1.2. Installation
</a> </a>
</li> </li>
<li class="toc-h2 nav-item toc-entry"> <li class="toc-h2 nav-item toc-entry">
@ -479,15 +479,15 @@ div.rendered_html tbody tr:hover {
</ol> </ol>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>We use SCIP in this tutorial because it is a fast and widely available noncommercial MIP solver. All the steps shown here also work for Gurobi, CPLEX and XPRESS, although the performance impact might be different.</p> <p>In this tutorial, we use SCIP because it is more widely available than commercial MIP solvers. However, all the steps below should work for Gurobi, CPLEX or XPRESS, as long as you have a license for these solvers. The performance impact of MIPLearn may also change for different solvers.</p>
</div> </div>
<div class="admonition warning"> <div class="admonition warning">
<p class="admonition-title">Warning</p> <p class="admonition-title">Warning</p>
<p>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!</p> <p>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!</p>
</div> </div>
</div> </div>
<div class="section" id="Installing-MIPLearn"> <div class="section" id="Installation">
<h2><span class="section-number">1.2. </span>Installing MIPLearn<a class="headerlink" href="#Installing-MIPLearn" title="Permalink to this headline"></a></h2> <h2><span class="section-number">1.2. </span>Installation<a class="headerlink" href="#Installation" title="Permalink to this headline"></a></h2>
<p>MIPLearn is available in two versions:</p> <p>MIPLearn is available in two versions:</p>
<ul class="simple"> <ul class="simple">
<li><p>Python version, compatible with the Pyomo modeling language,</p></li> <li><p>Python version, compatible with the Pyomo modeling language,</p></li>
@ -500,7 +500,7 @@ div.rendered_html tbody tr:hover {
</div> </div>
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre> <div class="input_area highlight-julia notranslate"><div class="highlight"><pre>
<span></span><span class="k">using</span> <span class="n">Pkg</span> <span></span><span class="k">using</span> <span class="n">Pkg</span>
<span class="n">Pkg</span><span class="o">.</span><span class="n">develop</span><span class="p">(</span><span class="n">PackageSpec</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="s">&quot;/home/axavier/Packages/MIPLearn.jl/dev&quot;</span><span class="p">))</span> <span class="n">Pkg</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PackageSpec</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="s">&quot;https://github.com/ANL-CEEESA/MIPLearn.jl.git&quot;</span><span class="p">))</span>
</pre></div> </pre></div>
</div> </div>
</div> </div>
@ -509,7 +509,9 @@ div.rendered_html tbody tr:hover {
</div> </div>
<div class="output_area stderr docutils container"> <div class="output_area stderr docutils container">
<div class="highlight"><pre> <div class="highlight"><pre>
Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct package. Using existing path. <span class="ansi-green-intense-fg ansi-bold"> Updating</span> git-repo `https://github.com/ANL-CEEESA/MIPLearn.jl.git`
<span class="ansi-green-intense-fg ansi-bold"> Updating</span> registry at `~/.julia/registries/General`
<span class="ansi-green-intense-fg ansi-bold"> Updating</span> git-repo `https://github.com/JuliaRegistries/General.git`
<span class="ansi-green-intense-fg ansi-bold"> Resolving</span> package versions... <span class="ansi-green-intense-fg ansi-bold"> Resolving</span> package versions...
<span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml` <span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`
<span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml` <span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`
@ -517,10 +519,10 @@ Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct
</div> </div>
<p>In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:</p> <p>In addition to MIPLearn itself, we will also install a few other packages that are required for this tutorial:</p>
<ul class="simple"> <ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">SCIP</span></code>, a non-commercial mixed-integer programming solver</p></li> <li><p><a class="reference external" href="https://www.scipopt.org/">SCIP</a>, one of the fastest non-commercial MIP solvers currently available</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">JuMP</span></code>, an open-source modeling language for Julia</p></li> <li><p><a class="reference external" href="https://jump.dev/">JuMP</a>, an open source modeling language for Julia</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">Distributions</span></code>, a statistics package that we will use to generate random inputs</p></li> <li><p><a class="reference external" href="https://github.com/JuliaStats/Distributions.jl">Distributions.jl</a>, a statistics package that we will use to generate random inputs</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">Glob</span></code>, a package that retrieves all files in a directory matching a certain pattern</p></li> <li><p><a class="reference external" href="https://github.com/vtjnash/Glob.jl">Glob.jl</a>, a package that retrieves all files in a directory matching a certain pattern</p></li>
</ul> </ul>
<div class="nbinput docutils container"> <div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
@ -534,7 +536,6 @@ Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct
<span class="n">PackageSpec</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&quot;Distributions&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;0.25&quot;</span><span class="p">),</span> <span class="n">PackageSpec</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&quot;Distributions&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;0.25&quot;</span><span class="p">),</span>
<span class="n">PackageSpec</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&quot;Glob&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;1&quot;</span><span class="p">),</span> <span class="n">PackageSpec</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">&quot;Glob&quot;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s">&quot;1&quot;</span><span class="p">),</span>
<span class="p">])</span> <span class="p">])</span>
<span class="k">using</span> <span class="n">Revise</span>
</pre></div> </pre></div>
</div> </div>
</div> </div>
@ -543,14 +544,9 @@ Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct
</div> </div>
<div class="output_area stderr docutils container"> <div class="output_area stderr docutils container">
<div class="highlight"><pre> <div class="highlight"><pre>
<span class="ansi-green-intense-fg ansi-bold"> Updating</span> registry at `~/.julia/registries/General`
<span class="ansi-green-intense-fg ansi-bold"> Updating</span> git-repo `https://github.com/JuliaRegistries/General.git`
<span class="ansi-green-intense-fg ansi-bold"> Resolving</span> package versions... <span class="ansi-green-intense-fg ansi-bold"> Resolving</span> package versions...
<span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml` <span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Project.toml`
<span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml` <span class="ansi-green-intense-fg ansi-bold"> No Changes</span> to `~/Packages/MIPLearn/dev/docs/jump-tutorials/Manifest.toml`
<span class="ansi-green-intense-fg ansi-bold">Precompiling</span> project...
<span class="ansi-green-fg"></span>MIPLearn
1 dependency successfully precompiled in 10 seconds (96 already precompiled)
</pre></div></div> </pre></div></div>
</div> </div>
<div class="admonition note"> <div class="admonition note">
@ -561,9 +557,9 @@ Path `/home/axavier/Packages/MIPLearn.jl/dev` exists and looks like the correct
<div class="section" id="Modeling-a-simple-optimization-problem"> <div class="section" id="Modeling-a-simple-optimization-problem">
<h2><span class="section-number">1.3. </span>Modeling a simple optimization problem<a class="headerlink" href="#Modeling-a-simple-optimization-problem" title="Permalink to this headline"></a></h2> <h2><span class="section-number">1.3. </span>Modeling a simple optimization problem<a class="headerlink" href="#Modeling-a-simple-optimization-problem" title="Permalink to this headline"></a></h2>
<p>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 <strong>unit commitment problem,</strong> a practical optimization problem solved daily by electric grid operators around the world.</p> <p>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 <strong>unit commitment problem,</strong> a practical optimization problem solved daily by electric grid operators around the world.</p>
<p>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, and how much power should each generator produce. More specifically, assume that your company owns <span class="math notranslate nohighlight">\(n\)</span> generators, denoted by <span class="math notranslate nohighlight">\(g_1, \ldots, g_n\)</span>. Each generator can either be online or offline. An online generator <span class="math notranslate nohighlight">\(g_i\)</span> can produce between <span class="math notranslate nohighlight">\(p^\text{min}_i\)</span> to <span class="math notranslate nohighlight">\(p^\text{max}_i\)</span> megawatts of power, and it costs your <p>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 <span class="math notranslate nohighlight">\(n\)</span> generators, denoted by <span class="math notranslate nohighlight">\(g_1, \ldots, g_n\)</span>. Each generator can either be online or offline. An online generator <span class="math notranslate nohighlight">\(g_i\)</span> can produce between <span class="math notranslate nohighlight">\(p^\text{min}_i\)</span> to <span class="math notranslate nohighlight">\(p^\text{max}_i\)</span> megawatts of power, and it costs
company <span class="math notranslate nohighlight">\(c^\text{fixed}_i + c^\text{var}_i y_i\)</span>, where <span class="math notranslate nohighlight">\(y_i\)</span> 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 <span class="math notranslate nohighlight">\(d\)</span> (in megawatts). To minimize the costs to your company, which generators should be online, and how much power should they produce?</p> your company <span class="math notranslate nohighlight">\(c^\text{fix}_i + c^\text{var}_i y_i\)</span>, where <span class="math notranslate nohighlight">\(y_i\)</span> 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 <span class="math notranslate nohighlight">\(d\)</span> (in megawatts). To minimize the costs to your company, which generators should be online, and how much power should they produce?</p>
<p>This simple problem be modeled as a <em>mixed-integer linear optimization</em> problem as follows. For each generator <span class="math notranslate nohighlight">\(g_i\)</span>, let <span class="math notranslate nohighlight">\(x_i \in \{0,1\}\)</span> be a decision variable indicating whether <span class="math notranslate nohighlight">\(g_i\)</span> is online, and let <span class="math notranslate nohighlight">\(y_i \geq 0\)</span> be a decision variable indicating how much power does <span class="math notranslate nohighlight">\(g_i\)</span> produce. The problem we need to solve is given by:</p> <p>This simple problem can be modeled as a <em>mixed-integer linear optimization</em> problem as follows. For each generator <span class="math notranslate nohighlight">\(g_i\)</span>, let <span class="math notranslate nohighlight">\(x_i \in \{0,1\}\)</span> be a decision variable indicating whether <span class="math notranslate nohighlight">\(g_i\)</span> is online, and let <span class="math notranslate nohighlight">\(y_i \geq 0\)</span> be a decision variable indicating how much power does <span class="math notranslate nohighlight">\(g_i\)</span> produce. The problem is then given by:</p>
<div class="math notranslate nohighlight"> <div class="math notranslate nohighlight">
\[\begin{split}\begin{align} \[\begin{split}\begin{align}
\text{minimize } \quad &amp; \sum_{i=1}^n \left( c^\text{fix}_i x_i + c^\text{var}_i y_i \right) \\ \text{minimize } \quad &amp; \sum_{i=1}^n \left( c^\text{fix}_i x_i + c^\text{var}_i y_i \right) \\
@ -575,9 +571,9 @@ company <span class="math notranslate nohighlight">\(c^\text{fixed}_i + c^\text{
\end{align}\end{split}\]</div> \end{align}\end{split}\]</div>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>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 the benchmark sections for more details.</p> <p>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.</p>
</div> </div>
<p>Next, let us convert this abstract mathematical formulation into a concrete optimization model, using the Julia and the JuMP modeling language. We start by defining a data structure that holds all input data:</p> <p>Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Julia and JuMP. We start by defining a data structure that holds all the input data.</p>
<div class="nbinput nblast docutils container"> <div class="nbinput nblast docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]:
</pre></div> </pre></div>
@ -593,7 +589,7 @@ company <span class="math notranslate nohighlight">\(c^\text{fixed}_i + c^\text{
</pre></div> </pre></div>
</div> </div>
</div> </div>
<p>Next, we create a function that converts this data into a concrete JuMP model:</p> <p>Next, we create a function that converts this data structure into a concrete JuMP model. For more details on the JuMP syntax, see <a class="reference external" href="https://jump.dev/JuMP.jl/stable/">the official JuMP documentation</a>.</p>
<div class="nbinput nblast docutils container"> <div class="nbinput nblast docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]:
</pre></div> </pre></div>
@ -630,7 +626,6 @@ company <span class="math notranslate nohighlight">\(c^\text{fixed}_i + c^\text{
</div> </div>
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre> <div class="input_area highlight-julia notranslate"><div class="highlight"><pre>
<span></span><span class="k">using</span> <span class="n">SCIP</span> <span></span><span class="k">using</span> <span class="n">SCIP</span>
<span class="k">using</span> <span class="n">Printf</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">build_uc_model</span><span class="p">(</span> <span class="n">model</span> <span class="o">=</span> <span class="n">build_uc_model</span><span class="p">(</span>
<span class="n">UnitCommitmentData</span><span class="p">(</span> <span class="n">UnitCommitmentData</span><span class="p">(</span>
@ -711,19 +706,18 @@ obj = 1320.0
</pre></div> </pre></div>
</div> </div>
</div> </div>
<p>Next, we will write these data structures 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 JuMP 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.</p> <p>Next, we write these data structures 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 JuMP 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.</p>
<p>The code below generates the files <code class="docutils literal notranslate"><span class="pre">uc/train/000001.jld2</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/000002.jld2</span></code>, etc.</p> <p>The code below generates the files <code class="docutils literal notranslate"><span class="pre">uc/train/000001.jld2</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/000002.jld2</span></code>, etc., which contain the input data in <a class="reference external" href="https://github.com/JuliaIO/JLD2.jl">JLD2 format</a>.</p>
<div class="nbinput nblast docutils container"> <div class="nbinput nblast docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
</pre></div> </pre></div>
</div> </div>
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre> <div class="input_area highlight-julia notranslate"><div class="highlight"><pre>
<span></span><span class="k">using</span> <span class="n">MIPLearn</span> <span></span><span class="k">using</span> <span class="n">MIPLearn</span>
<span class="k">using</span> <span class="n">Glob</span>
<span class="n">MIPLearn</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">90</span><span class="p">],</span> <span class="s">&quot;uc/train/&quot;</span><span class="p">)</span> <span class="n">MIPLearn</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">90</span><span class="p">],</span> <span class="s">&quot;uc/train/&quot;</span><span class="p">)</span>
<span class="n">MIPLearn</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">91</span><span class="o">:</span><span class="mi">100</span><span class="p">],</span> <span class="s">&quot;uc/test/&quot;</span><span class="p">)</span> <span class="n">MIPLearn</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">91</span><span class="o">:</span><span class="mi">100</span><span class="p">],</span> <span class="s">&quot;uc/test/&quot;</span><span class="p">)</span>
<span class="k">using</span> <span class="n">Glob</span>
<span class="n">train_files</span> <span class="o">=</span> <span class="n">glob</span><span class="p">(</span><span class="s">&quot;uc/train/*.jld2&quot;</span><span class="p">)</span> <span class="n">train_files</span> <span class="o">=</span> <span class="n">glob</span><span class="p">(</span><span class="s">&quot;uc/train/*.jld2&quot;</span><span class="p">)</span>
<span class="n">test_files</span> <span class="o">=</span> <span class="n">glob</span><span class="p">(</span><span class="s">&quot;uc/test/*.jld2&quot;</span><span class="p">);</span> <span class="n">test_files</span> <span class="o">=</span> <span class="n">glob</span><span class="p">(</span><span class="s">&quot;uc/test/*.jld2&quot;</span><span class="p">);</span>
</pre></div> </pre></div>
@ -746,7 +740,7 @@ obj = 1320.0
</div> </div>
<div class="output_area docutils container"> <div class="output_area docutils container">
<div class="highlight"><pre> <div class="highlight"><pre>
101.279699 seconds (93.52 M allocations: 3.599 GiB, 1.23% gc time, 0.52% compilation time) 103.808547 seconds (93.52 M allocations: 3.604 GiB, 1.19% gc time, 0.52% compilation time)
</pre></div></div> </pre></div></div>
</div> </div>
<div class="nboutput nblast docutils container"> <div class="nboutput nblast docutils container">
@ -757,11 +751,11 @@ obj = 1320.0
WARNING: Dual bound 1.98665e+07 is larger than the objective of the primal solution 1.98665e+07. The solution might not be optimal. WARNING: Dual bound 1.98665e+07 is larger than the objective of the primal solution 1.98665e+07. The solution might not be optimal.
</pre></div></div> </pre></div></div>
</div> </div>
<p>The macro <code class="docutils literal notranslate"><span class="pre">&#64;time</span></code> shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, is stored by MIPLearn in <code class="docutils literal notranslate"><span class="pre">.h5</span></code> files, stored side-by-side with the original <code class="docutils literal notranslate"><span class="pre">.jld2</span></code> files.</p> <p>The macro <code class="docutils literal notranslate"><span class="pre">&#64;time</span></code> shows us how long did the code take to run. We can see that SCIP was able to solve all training instances in about 2 minutes. The solutions, and other useful training data, are stored by MIPLearn in <code class="docutils literal notranslate"><span class="pre">.h5</span></code> files, stored side-by-side with the original <code class="docutils literal notranslate"><span class="pre">.jld2</span></code> files.</p>
</div> </div>
<div class="section" id="Solving-new-instances"> <div class="section" id="Solving-new-instances">
<h2><span class="section-number">1.5. </span>Solving new instances<a class="headerlink" href="#Solving-new-instances" title="Permalink to this headline"></a></h2> <h2><span class="section-number">1.5. </span>Solving new instances<a class="headerlink" href="#Solving-new-instances" title="Permalink to this headline"></a></h2>
<p>Now that we have training data, we can fit the ML models using <code class="docutils literal notranslate"><span class="pre">MIPLearn.fit!</span></code>, then solve the test instances with <code class="docutils literal notranslate"><span class="pre">MIPLearn.solve!</span></code>, as shown below:</p> <p>With training data in hand, we can now fit the ML models using <code class="docutils literal notranslate"><span class="pre">MIPLearn.fit!</span></code>, then solve the test instances with <code class="docutils literal notranslate"><span class="pre">MIPLearn.solve!</span></code>, as shown below:</p>
<div class="nbinput docutils container"> <div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[10]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[10]:
</pre></div> </pre></div>
@ -778,10 +772,10 @@ WARNING: Dual bound 1.98665e+07 is larger than the objective of the primal solut
</div> </div>
<div class="output_area docutils container"> <div class="output_area docutils container">
<div class="highlight"><pre> <div class="highlight"><pre>
5.693951 seconds (9.33 M allocations: 334.689 MiB, 1.62% gc time) 5.951264 seconds (9.33 M allocations: 334.657 MiB, 1.51% gc time)
</pre></div></div> </pre></div></div>
</div> </div>
<p>The trained MIP solver was able to solve all test instances in about 5 seconds. To see that ML is being helpful here, let us repeat the code above, but remove the <code class="docutils literal notranslate"><span class="pre">fit!</span></code> line:</p> <p>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 <code class="docutils literal notranslate"><span class="pre">fit!</span></code> line:</p>
<div class="nbinput docutils container"> <div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[11]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[11]:
</pre></div> </pre></div>
@ -797,10 +791,10 @@ WARNING: Dual bound 1.98665e+07 is larger than the objective of the primal solut
</div> </div>
<div class="output_area docutils container"> <div class="output_area docutils container">
<div class="highlight"><pre> <div class="highlight"><pre>
9.829350 seconds (8.17 M allocations: 278.008 MiB, 0.47% gc time) 10.390325 seconds (8.17 M allocations: 278.042 MiB, 0.89% gc time)
</pre></div></div> </pre></div></div>
</div> </div>
<p>Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances, or about twice as long.</p> <p>Without the help of the ML models, SCIP took around 10 seconds to solve the same test instances.</p>
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>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 <p>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
@ -809,7 +803,7 @@ we will see in a later tutorial.</p>
</div> </div>
<div class="section" id="Understanding-the-acceleration"> <div class="section" id="Understanding-the-acceleration">
<h2><span class="section-number">1.6. </span>Understanding the acceleration<a class="headerlink" href="#Understanding-the-acceleration" title="Permalink to this headline"></a></h2> <h2><span class="section-number">1.6. </span>Understanding the acceleration<a class="headerlink" href="#Understanding-the-acceleration" title="Permalink to this headline"></a></h2>
<p>Let us know go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIPs performance. First, we are going to solve one of the training instances again, using the trained solver, but this time using the <code class="docutils literal notranslate"><span class="pre">tee=true</span></code> parameter, so that we can see SCIPs log:</p> <p>Let us go a bit deeper and try to understand how exactly did MIPLearn accelerate SCIPs performance. First, we are going to solve one of the test instances again, using the trained solver, but this time using the <code class="docutils literal notranslate"><span class="pre">tee=true</span></code> parameter, so that we can see SCIPs log:</p>
<div class="nbinput docutils container"> <div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[12]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[12]:
</pre></div> </pre></div>
@ -864,7 +858,7 @@ presolving (4 rounds: 4 fast, 3 medium, 3 exhaustive):
presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints presolved problem has 2000 variables (1000 bin, 0 int, 0 impl, 1000 cont) and 2001 constraints
2000 constraints of type &lt;varbound&gt; 2000 constraints of type &lt;varbound&gt;
1 constraints of type &lt;linear&gt; 1 constraints of type &lt;linear&gt;
Presolving Time: 0.10 Presolving Time: 0.11
transformed 1/1 original solutions to the transformed problem space transformed 1/1 original solutions to the transformed problem space
time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl. time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl.
@ -879,9 +873,9 @@ Gap : 0.01 %
</pre></div></div> </pre></div></div>
</div> </div>
<p>The log above is quite complicated if you have never seen it before, but the important line in the one starting with <code class="docutils literal notranslate"><span class="pre">feasible</span> <span class="pre">solution</span> <span class="pre">found</span> <span class="pre">[...]</span> <span class="pre">objective</span> <span class="pre">value</span> <span class="pre">1.705169e+07</span></code>. This line indicates that MIPLearn was able to construct a warm start with value <code class="docutils literal notranslate"><span class="pre">1.705169e+07</span></code>. Using this warm start, SCIP then proceeded with the branch-and-cut process to either prove its optimality or find an even better solution. Very quickly, however, SCIP proved that the solution produced by MIPLearn was <p>The log above is quite complicated if you have never seen it before, but the important line is the one starting with <code class="docutils literal notranslate"><span class="pre">feasible</span> <span class="pre">solution</span> <span class="pre">found</span> <span class="pre">[...]</span> <span class="pre">objective</span> <span class="pre">value</span> <span class="pre">1.705169e+07</span></code>. This line indicates that MIPLearn was able to construct a warm start with value <code class="docutils literal notranslate"><span class="pre">1.705169e+07</span></code>. 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
indeed optimal and terminated. 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.</p> 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.</p>
<p>Let us now do the same thing again, but using the untrained solver this time:</p> <p>Let us now repeat the process, but using the untrained solver this time:</p>
<div class="nbinput docutils container"> <div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[13]: <div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[13]:
</pre></div> </pre></div>
@ -960,7 +954,7 @@ L 0.6s| 1 | 0 | 1707 | - | rens| 0 |2000 |2001 |2015 | 14 | 1
L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown L 0.7s| 1 | 0 | 1707 | - | alns| 0 |2000 |2001 |2015 | 14 | 11 | 0 | 0 | 1.705038e+07 | 1.705178e+07 | 0.01%| unknown
SCIP Status : solving was interrupted [gap limit reached] SCIP Status : solving was interrupted [gap limit reached]
Solving Time (sec) : 0.67 Solving Time (sec) : 0.68
Solving Nodes : 1 Solving Nodes : 1
Primal Bound : +1.70517823853380e+07 (13 solutions) Primal Bound : +1.70517823853380e+07 (13 solutions)
Dual Bound : +1.70503798271962e+07 Dual Bound : +1.70503798271962e+07
@ -968,7 +962,9 @@ Gap : 0.01 %
</pre></div></div> </pre></div></div>
</div> </div>
<p>In this log file, notice how the line we saw before is now missing; SCIP needs to find an initial solution using its own internal heuristics. The solution SCIP initially found has value <code class="docutils literal notranslate"><span class="pre">2.335200e+07</span></code>, which is significantly worse than the one MIPLearn constructed before. SCIP then proceeds to improve this solution by generating a number of cutting planes and repeatedly running primal heuristics. In the end, it is able to find the optimal solution, as expected, but it takes longer.</p> <p>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 SCIPs heuristics has value <code class="docutils literal notranslate"><span class="pre">2.335200e+07</span></code>, 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.</p>
<p>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.</p>
</div> </div>
<div class="section" id="Accessing-the-solution"> <div class="section" id="Accessing-the-solution">
<h2><span class="section-number">1.7. </span>Accessing the solution<a class="headerlink" href="#Accessing-the-solution" title="Permalink to this headline"></a></h2> <h2><span class="section-number">1.7. </span>Accessing the solution<a class="headerlink" href="#Accessing-the-solution" title="Permalink to this headline"></a></h2>

Binary file not shown.

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save