mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-07 01:48:51 -06:00
Add v0.4 docs
This commit is contained in:
541
0.4/tutorials/cuts-gurobipy.ipynb
Normal file
541
0.4/tutorials/cuts-gurobipy.ipynb
Normal file
@@ -0,0 +1,541 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b4bd8bd6-3ce9-4932-852f-f98a44120a3e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# User cuts and lazy constraints\n",
|
||||
"\n",
|
||||
"User cuts and lazy constraints are two advanced mixed-integer programming techniques that can accelerate solver performance. User cuts are additional constraints, derived from the constraints already in the model, that can tighten the feasible region and eliminate fractional solutions, thus reducing the size of the branch-and-bound tree. Lazy constraints, on the other hand, are constraints that are potentially part of the problem formulation but are omitted from the initial model to reduce its size; these constraints are added to the formulation only once the solver finds a solution that violates them. While both techniques have been successful, significant computational effort may still be required to generate strong user cuts and to identify violated lazy constraints, which can reduce their effectiveness.\n",
|
||||
"\n",
|
||||
"MIPLearn is able to predict which user cuts and which lazy constraints to enforce at the beginning of the optimization process, using machine learning. In this tutorial, we will use the framework to predict subtour elimination constraints for the **traveling salesman problem** using Gurobipy. We assume that MIPLearn has already been correctly installed.\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"\n",
|
||||
"Solver Compatibility\n",
|
||||
"\n",
|
||||
"User cuts and lazy constraints are also supported in the Python/Pyomo and Julia/JuMP versions of the package. See the source code of <code>build_tsp_model_pyomo</code> and <code>build_tsp_model_jump</code> for more details. Note, however, the following limitations:\n",
|
||||
"\n",
|
||||
"- Python/Pyomo: Only `gurobi_persistent` is currently supported. PRs implementing callbacks for other persistent solvers are welcome.\n",
|
||||
"- Julia/JuMP: Only solvers supporting solver-independent callbacks are supported. As of JuMP 1.19, this includes Gurobi, CPLEX, XPRESS, SCIP and GLPK. Note that HiGHS and Cbc are not supported. As newer versions of JuMP implement further callback support, MIPLearn should become automatically compatible with these solvers.\n",
|
||||
"\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "72229e1f-cbd8-43f0-82ee-17d6ec9c3b7d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modeling the traveling salesman problem\n",
|
||||
"\n",
|
||||
"Given a list of cities and the distances between them, the **traveling salesman problem (TSP)** asks for the shortest route starting at the first city, visiting each other city exactly once, then returning to the first city. This problem is a generalization of the Hamiltonian path problem, one of Karp's 21 NP-complete problems, and has many practical applications, including routing delivery trucks and scheduling airline routes.\n",
|
||||
"\n",
|
||||
"To describe an instance of TSP, we need to specify the number of cities $n$, and an $n \\times n$ matrix of distances. The class `TravelingSalesmanData`, in the `miplearn.problems.tsp` package, can hold this data:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4598a1bc-55b6-48cc-a050-2262786c203a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```python\n",
|
||||
"@dataclass\r\n",
|
||||
"class TravelingSalesmanData:\r\n",
|
||||
" n_cities: int\r\n",
|
||||
" distances: np.ndarray\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a43cc12-1207-4247-bdb2-69a6a2910738",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"MIPLearn also provides `TravelingSalesmandGenerator`, a random generator for TSP instances, and `build_tsp_model_gurobipy`, a function which converts `TravelingSalesmanData` into an actual gurobipy optimization model, and which uses lazy constraints to enforce subtour elimination.\n",
|
||||
"\n",
|
||||
"The example below is a simplified and annotated version of `build_tsp_model_gurobipy`, illustrating the usage of callbacks with MIPLearn. Compared the the previous tutorial examples, note that, in addition to defining the variables, objective function and constraints of our problem, we also define two callback functions `lazy_separate` and `lazy_enforce`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "e4712a85-0327-439c-8889-933e1ff714e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gurobipy as gp\n",
|
||||
"from gurobipy import quicksum, GRB, tuplelist\n",
|
||||
"from miplearn.solvers.gurobi import GurobiModel\n",
|
||||
"import networkx as nx\n",
|
||||
"import numpy as np\n",
|
||||
"from miplearn.problems.tsp import (\n",
|
||||
" TravelingSalesmanData,\n",
|
||||
" TravelingSalesmanGenerator,\n",
|
||||
")\n",
|
||||
"from scipy.stats import uniform, randint\n",
|
||||
"from miplearn.io import write_pkl_gz, read_pkl_gz\n",
|
||||
"from miplearn.collectors.basic import BasicCollector\n",
|
||||
"from miplearn.solvers.learning import LearningSolver\n",
|
||||
"from miplearn.components.lazy.mem import MemorizingLazyComponent\n",
|
||||
"from miplearn.extractors.fields import H5FieldsExtractor\n",
|
||||
"from sklearn.neighbors import KNeighborsClassifier\n",
|
||||
"\n",
|
||||
"# Set up random seed to make example more reproducible\n",
|
||||
"np.random.seed(42)\n",
|
||||
"\n",
|
||||
"# Set up Python logging\n",
|
||||
"import logging\n",
|
||||
"\n",
|
||||
"logging.basicConfig(level=logging.WARNING)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def build_tsp_model_gurobipy_simplified(data):\n",
|
||||
" # Read data from file if a filename is provided\n",
|
||||
" if isinstance(data, str):\n",
|
||||
" data = read_pkl_gz(data)\n",
|
||||
"\n",
|
||||
" # Create empty gurobipy model\n",
|
||||
" model = gp.Model()\n",
|
||||
"\n",
|
||||
" # Create set of edges between every pair of cities, for convenience\n",
|
||||
" edges = tuplelist(\n",
|
||||
" (i, j) for i in range(data.n_cities) for j in range(i + 1, data.n_cities)\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Add binary variable x[e] for each edge e\n",
|
||||
" x = model.addVars(edges, vtype=GRB.BINARY, name=\"x\")\n",
|
||||
"\n",
|
||||
" # Add objective function\n",
|
||||
" model.setObjective(quicksum(x[(i, j)] * data.distances[i, j] for (i, j) in edges))\n",
|
||||
"\n",
|
||||
" # Add constraint: must choose two edges adjacent to each city\n",
|
||||
" model.addConstrs(\n",
|
||||
" (\n",
|
||||
" quicksum(x[min(i, j), max(i, j)] for j in range(data.n_cities) if i != j)\n",
|
||||
" == 2\n",
|
||||
" for i in range(data.n_cities)\n",
|
||||
" ),\n",
|
||||
" name=\"eq_degree\",\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def lazy_separate(m: GurobiModel):\n",
|
||||
" \"\"\"\n",
|
||||
" Callback function that finds subtours in the current solution.\n",
|
||||
" \"\"\"\n",
|
||||
" # Query current value of the x variables\n",
|
||||
" x_val = m.inner.cbGetSolution(x)\n",
|
||||
"\n",
|
||||
" # Initialize empty set of violations\n",
|
||||
" violations = []\n",
|
||||
"\n",
|
||||
" # Build set of edges we have currently selected\n",
|
||||
" selected_edges = [e for e in edges if x_val[e] > 0.5]\n",
|
||||
"\n",
|
||||
" # Build a graph containing the selected edges, using networkx\n",
|
||||
" graph = nx.Graph()\n",
|
||||
" graph.add_edges_from(selected_edges)\n",
|
||||
"\n",
|
||||
" # For each component of the graph\n",
|
||||
" for component in list(nx.connected_components(graph)):\n",
|
||||
"\n",
|
||||
" # If the component is not the entire graph, we found a\n",
|
||||
" # subtour. Add the edge cut to the list of violations.\n",
|
||||
" if len(component) < data.n_cities:\n",
|
||||
" cut_edges = [\n",
|
||||
" [e[0], e[1]]\n",
|
||||
" for e in edges\n",
|
||||
" if (e[0] in component and e[1] not in component)\n",
|
||||
" or (e[0] not in component and e[1] in component)\n",
|
||||
" ]\n",
|
||||
" violations.append(cut_edges)\n",
|
||||
"\n",
|
||||
" # Return the list of violations\n",
|
||||
" return violations\n",
|
||||
"\n",
|
||||
" def lazy_enforce(m: GurobiModel, violations) -> None:\n",
|
||||
" \"\"\"\n",
|
||||
" Callback function that, given a list of subtours, adds lazy\n",
|
||||
" constraints to remove them from the feasible region.\n",
|
||||
" \"\"\"\n",
|
||||
" print(f\"Enforcing {len(violations)} subtour elimination constraints\")\n",
|
||||
" for violation in violations:\n",
|
||||
" m.add_constr(quicksum(x[e[0], e[1]] for e in violation) >= 2)\n",
|
||||
"\n",
|
||||
" return GurobiModel(\n",
|
||||
" model,\n",
|
||||
" lazy_separate=lazy_separate,\n",
|
||||
" lazy_enforce=lazy_enforce,\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58875042-d6ac-4f93-b3cc-9a5822b11dad",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The `lazy_separate` function starts by querying the current fractional solution value through `m.inner.cbGetSolution` (recall that `m.inner` is a regular gurobipy model), then finds the set of violated lazy constraints. Unlike a regular lazy constraint solver callback, note that `lazy_separate` does not add the violated constraints to the model; it simply returns a list of objects that uniquely identifies the set of lazy constraints that should be generated. Enforcing the constraints is the responsbility of the second callback function, `lazy_enforce`. This function takes as input the model and the list of violations found by `lazy_separate`, converts them into actual constraints, and adds them to the model through `m.add_constr`.\n",
|
||||
"\n",
|
||||
"During training data generation, MIPLearn calls `lazy_separate` and `lazy_enforce` in sequence, inside a regular solver callback. However, once the machine learning models are trained, MIPLearn calls `lazy_enforce` directly, before the optimization process starts, with a list of **predicted** violations, as we will see in the example below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5839728e-406c-4be2-ba81-83f2b873d4b2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"\n",
|
||||
"Constraint Representation\n",
|
||||
"\n",
|
||||
"How should user cuts and lazy constraints be represented is a decision that the user can make; MIPLearn is representation agnostic. The objects returned by `lazy_separate`, however, are serialized as JSON and stored in the HDF5 training data files. Therefore, it is recommended to use only simple objects, such as lists, tuples and dictionaries.\n",
|
||||
"\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "847ae32e-fad7-406a-8797-0d79065a07fd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generating training data\n",
|
||||
"\n",
|
||||
"To test the callback defined above, we generate a small set of TSP instances, using the provided random instance generator. As in the previous tutorial, we generate some test instances and some training instances, then solve them using `BasicCollector`. Input problem data is stored in `tsp/train/00000.pkl.gz, ...`, whereas solver training data (including list of required lazy constraints) is stored in `tsp/train/00000.h5, ...`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "eb63154a-1fa6-4eac-aa46-6838b9c201f6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Configure generator to produce instances with 50 cities located\n",
|
||||
"# in the 1000 x 1000 square, and with slightly perturbed distances.\n",
|
||||
"gen = TravelingSalesmanGenerator(\n",
|
||||
" x=uniform(loc=0.0, scale=1000.0),\n",
|
||||
" y=uniform(loc=0.0, scale=1000.0),\n",
|
||||
" n=randint(low=50, high=51),\n",
|
||||
" gamma=uniform(loc=1.0, scale=0.25),\n",
|
||||
" fix_cities=True,\n",
|
||||
" round=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Generate 500 instances and store input data file to .pkl.gz files\n",
|
||||
"data = gen.generate(500)\n",
|
||||
"train_data = write_pkl_gz(data[0:450], \"tsp/train\")\n",
|
||||
"test_data = write_pkl_gz(data[450:500], \"tsp/test\")\n",
|
||||
"\n",
|
||||
"# Solve the training instances in parallel, collecting the required lazy\n",
|
||||
"# constraints, in addition to other information, such as optimal solution.\n",
|
||||
"bc = BasicCollector()\n",
|
||||
"bc.collect(train_data, build_tsp_model_gurobipy_simplified, n_jobs=10)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6903c26c-dbe0-4a2e-bced-fdbf93513dde",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Training and solving new instances"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "57cd724a-2d27-4698-a1e6-9ab8345ef31f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"After producing the training dataset, we can train the machine learning models to predict which lazy constraints are necessary. In this tutorial, we use the following ML strategy: given a new instance, find the 50 most similar ones in the training dataset and verify how often each lazy constraint was required. If a lazy constraint was required for the majority of the 50 most-similar instances, enforce it ahead-of-time for the current instance. To measure instance similarity, use the objective function only. This ML strategy can be implemented using `MemorizingLazyComponent` with `H5FieldsExtractor` and `KNeighborsClassifier`, as shown below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "43779e3d-4174-4189-bc75-9f564910e212",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"solver = LearningSolver(\n",
|
||||
" components=[\n",
|
||||
" MemorizingLazyComponent(\n",
|
||||
" extractor=H5FieldsExtractor(instance_fields=[\"static_var_obj_coeffs\"]),\n",
|
||||
" clf=KNeighborsClassifier(n_neighbors=100),\n",
|
||||
" ),\n",
|
||||
" ],\n",
|
||||
")\n",
|
||||
"solver.fit(train_data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "12480712-9d3d-4cbc-a6d7-d6c1e2f950f4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we solve one of the test instances using the trained solver. In the run below, we can see that MIPLearn adds many lazy constraints ahead-of-time, before the optimization starts. During the optimization process itself, some additional lazy constraints are required, but very few."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "23f904ad-f1a8-4b5a-81ae-c0b9e813a4b2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set parameter Threads to value 1\n",
|
||||
"Restricted license - for non-production use only - expires 2024-10-28\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 1 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 50 rows, 1225 columns and 2450 nonzeros\n",
|
||||
"Model fingerprint: 0x04d7bec1\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+00]\n",
|
||||
" Objective range [1e+01, 1e+03]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [2e+00, 2e+00]\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 50 rows, 1225 columns, 2450 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 4.0600000e+02 9.700000e+01 0.000000e+00 0s\n",
|
||||
" 66 5.5880000e+03 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 66 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 5.588000000e+03\n",
|
||||
"\n",
|
||||
"User-callback calls 107, time in user-callback 0.00 sec\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"INFO:miplearn.components.cuts.mem:Predicting violated lazy constraints...\n",
|
||||
"INFO:miplearn.components.lazy.mem:Enforcing 19 constraints ahead-of-time...\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Enforcing 19 subtour elimination constraints\n",
|
||||
"Set parameter PreCrush to value 1\n",
|
||||
"Set parameter LazyConstraints to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 1 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 69 rows, 1225 columns and 6091 nonzeros\n",
|
||||
"Model fingerprint: 0x09bd34d6\n",
|
||||
"Variable types: 0 continuous, 1225 integer (1225 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+00]\n",
|
||||
" Objective range [1e+01, 1e+03]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [2e+00, 2e+00]\n",
|
||||
"Found heuristic solution: objective 29853.000000\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 69 rows, 1225 columns, 6091 nonzeros\n",
|
||||
"Variable types: 0 continuous, 1225 integer (1225 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 6.139000e+03, 93 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 6139.00000 0 6 29853.0000 6139.00000 79.4% - 0s\n",
|
||||
"H 0 0 6390.0000000 6139.00000 3.93% - 0s\n",
|
||||
" 0 0 6165.50000 0 10 6390.00000 6165.50000 3.51% - 0s\n",
|
||||
"Enforcing 3 subtour elimination constraints\n",
|
||||
" 0 0 6165.50000 0 6 6390.00000 6165.50000 3.51% - 0s\n",
|
||||
" 0 0 6198.50000 0 16 6390.00000 6198.50000 3.00% - 0s\n",
|
||||
"* 0 0 0 6219.0000000 6219.00000 0.00% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 11\n",
|
||||
" MIR: 1\n",
|
||||
" Zero half: 4\n",
|
||||
" Lazy constraints: 3\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (222 simplex iterations) in 0.03 seconds (0.02 work units)\n",
|
||||
"Thread count was 1 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 3: 6219 6390 29853 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 6.219000000000e+03, best bound 6.219000000000e+03, gap 0.0000%\n",
|
||||
"\n",
|
||||
"User-callback calls 141, time in user-callback 0.00 sec\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Increase log verbosity, so that we can see what is MIPLearn doing\n",
|
||||
"logging.getLogger(\"miplearn\").setLevel(logging.INFO)\n",
|
||||
"\n",
|
||||
"# Solve a new test instance\n",
|
||||
"solver.optimize(test_data[0], build_tsp_model_gurobipy_simplified);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79cc3e61-ee2b-4f18-82cb-373d55d67de6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we solve the same instance, but using a regular solver, without ML prediction. We can see that a much larger number of lazy constraints are added during the optimization process itself. Additionally, the solver requires a larger number of iterations to find the optimal solution. There is not a significant difference in running time because of the small size of these instances."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "a015c51c-091a-43b6-b761-9f3577fc083e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 1 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 50 rows, 1225 columns and 2450 nonzeros\n",
|
||||
"Model fingerprint: 0x04d7bec1\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+00]\n",
|
||||
" Objective range [1e+01, 1e+03]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [2e+00, 2e+00]\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 50 rows, 1225 columns, 2450 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 4.0600000e+02 9.700000e+01 0.000000e+00 0s\n",
|
||||
" 66 5.5880000e+03 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 66 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 5.588000000e+03\n",
|
||||
"\n",
|
||||
"User-callback calls 107, time in user-callback 0.00 sec\n",
|
||||
"Set parameter PreCrush to value 1\n",
|
||||
"Set parameter LazyConstraints to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 1 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 50 rows, 1225 columns and 2450 nonzeros\n",
|
||||
"Model fingerprint: 0x77a94572\n",
|
||||
"Variable types: 0 continuous, 1225 integer (1225 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+00]\n",
|
||||
" Objective range [1e+01, 1e+03]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [2e+00, 2e+00]\n",
|
||||
"Found heuristic solution: objective 29695.000000\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 50 rows, 1225 columns, 2450 nonzeros\n",
|
||||
"Variable types: 0 continuous, 1225 integer (1225 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 5.588000e+03, 68 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 5588.00000 0 12 29695.0000 5588.00000 81.2% - 0s\n",
|
||||
"Enforcing 9 subtour elimination constraints\n",
|
||||
"Enforcing 11 subtour elimination constraints\n",
|
||||
"H 0 0 27241.000000 5588.00000 79.5% - 0s\n",
|
||||
" 0 0 5898.00000 0 8 27241.0000 5898.00000 78.3% - 0s\n",
|
||||
"Enforcing 4 subtour elimination constraints\n",
|
||||
"Enforcing 3 subtour elimination constraints\n",
|
||||
" 0 0 6066.00000 0 - 27241.0000 6066.00000 77.7% - 0s\n",
|
||||
"Enforcing 2 subtour elimination constraints\n",
|
||||
" 0 0 6128.00000 0 - 27241.0000 6128.00000 77.5% - 0s\n",
|
||||
" 0 0 6139.00000 0 6 27241.0000 6139.00000 77.5% - 0s\n",
|
||||
"H 0 0 6368.0000000 6139.00000 3.60% - 0s\n",
|
||||
" 0 0 6154.75000 0 15 6368.00000 6154.75000 3.35% - 0s\n",
|
||||
"Enforcing 2 subtour elimination constraints\n",
|
||||
" 0 0 6154.75000 0 6 6368.00000 6154.75000 3.35% - 0s\n",
|
||||
" 0 0 6165.75000 0 11 6368.00000 6165.75000 3.18% - 0s\n",
|
||||
"Enforcing 3 subtour elimination constraints\n",
|
||||
" 0 0 6204.00000 0 6 6368.00000 6204.00000 2.58% - 0s\n",
|
||||
"* 0 0 0 6219.0000000 6219.00000 0.00% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 5\n",
|
||||
" MIR: 1\n",
|
||||
" Zero half: 4\n",
|
||||
" Lazy constraints: 4\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (224 simplex iterations) in 0.10 seconds (0.03 work units)\n",
|
||||
"Thread count was 1 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 6219 6368 27241 29695 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 6.219000000000e+03, best bound 6.219000000000e+03, gap 0.0000%\n",
|
||||
"\n",
|
||||
"User-callback calls 170, time in user-callback 0.01 sec\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solver = LearningSolver(components=[]) # empty set of ML components\n",
|
||||
"solver.optimize(test_data[0], build_tsp_model_gurobipy_simplified);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "432c99b2-67fe-409b-8224-ccef91de96d1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Learning user cuts\n",
|
||||
"\n",
|
||||
"The example above focused on lazy constraints. To enforce user cuts instead, the procedure is very similar, with the following changes:\n",
|
||||
"\n",
|
||||
"- Instead of `lazy_separate` and `lazy_enforce`, use `cuts_separate` and `cuts_enforce`\n",
|
||||
"- Instead of `m.inner.cbGetSolution`, use `m.inner.cbGetNodeRel`\n",
|
||||
"\n",
|
||||
"For a complete example, see `build_stab_model_gurobipy`, `build_stab_model_pyomo` and `build_stab_model_jump`, which solves the maximum-weight stable set problem using user cut callbacks."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e6cb694d-8c43-410f-9a13-01bf9e0763b7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
714
0.4/tutorials/cuts-gurobipy/index.html
Normal file
714
0.4/tutorials/cuts-gurobipy/index.html
Normal file
@@ -0,0 +1,714 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>4. User cuts and lazy constraints — MIPLearn 0.4</title>
|
||||
|
||||
<link href="../../_static/css/theme.css" rel="stylesheet" />
|
||||
<link href="../../_static/css/index.c5995385ac14fb8791e8eb36b4908be2.css" rel="stylesheet" />
|
||||
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="../../_static/vendor/fontawesome/5.13.0/css/all.min.css">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-solid-900.woff2">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-brands-400.woff2">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=362ab14a" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css?v=b0dfe17c" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css?v=2aa19091" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/custom.css?v=f8244a84" />
|
||||
|
||||
<link rel="preload" as="script" href="../../_static/js/index.1c5a1a01449ed65a7b51.js">
|
||||
|
||||
<script src="../../_static/documentation_options.js?v=751a5dd3"></script>
|
||||
<script src="../../_static/doctools.js?v=888ff710"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script crossorigin="anonymous" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"></script>
|
||||
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js?v=7c4c3336"></script>
|
||||
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"inlineMath": [["\\(", "\\)"]], "displayMath": [["\\[", "\\]"]], "processRefs": false, "processEnvironments": false}})</script>
|
||||
<script>window.MathJax = {"tex": {"inlineMath": [["$", "$"], ["\\(", "\\)"]], "processEscapes": true}, "options": {"ignoreHtmlClass": "tex2jax_ignore|mathjax_ignore|document", "processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}</script>
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<link rel="index" title="Index" href="../../genindex/" />
|
||||
<link rel="search" title="Search" href="../../search/" />
|
||||
<link rel="next" title="5. Benchmark Problems" href="../../guide/problems/" />
|
||||
<link rel="prev" title="3. Getting started (JuMP)" href="../getting-started-jump/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="docsearch:language" content="en" />
|
||||
|
||||
</head>
|
||||
<body data-spy="scroll" data-target="#bd-toc-nav" data-offset="80">
|
||||
|
||||
<div class="container-fluid" id="banner"></div>
|
||||
|
||||
|
||||
|
||||
<div class="container-xl">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 col-md-3 bd-sidebar site-navigation show" id="site-navigation">
|
||||
|
||||
<div class="navbar-brand-box">
|
||||
<a class="navbar-brand text-wrap" href="../../">
|
||||
|
||||
|
||||
<h1 class="site-logo" id="site-title">MIPLearn 0.4</h1>
|
||||
|
||||
</a>
|
||||
</div><form class="bd-search d-flex align-items-center" action="../../search/" method="get">
|
||||
<i class="icon fas fa-search"></i>
|
||||
<input type="search" class="form-control" name="q" id="search-input" placeholder="Search the docs ..." aria-label="Search the docs ..." autocomplete="off" >
|
||||
</form><nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
|
||||
<div class="bd-toc-item active">
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Tutorials
|
||||
</span>
|
||||
</p>
|
||||
<ul class="current nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-pyomo/">
|
||||
1. Getting started (Pyomo)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-gurobipy/">
|
||||
2. Getting started (Gurobipy)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-jump/">
|
||||
3. Getting started (JuMP)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current active">
|
||||
<a class="current reference internal" href="#">
|
||||
4. User cuts and lazy constraints
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
User Guide
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/problems/">
|
||||
5. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/collectors/">
|
||||
6. Training Data Collectors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/features/">
|
||||
7. Feature Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/primal/">
|
||||
8. Primal Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/solvers/">
|
||||
9. Learning Solver
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Python API Reference
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/problems/">
|
||||
10. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/collectors/">
|
||||
11. Collectors & Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/components/">
|
||||
12. Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/solvers/">
|
||||
13. Solvers
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/helpers/">
|
||||
14. Helpers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav> <!-- To handle the deprecated key -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="col py-md-3 pl-md-4 bd-content overflow-auto" role="main">
|
||||
|
||||
<div class="topbar container-xl fixed-top">
|
||||
<div class="topbar-contents row">
|
||||
<div class="col-12 col-md-3 bd-topbar-whitespace site-navigation show"></div>
|
||||
<div class="col pl-md-4 topbar-main">
|
||||
|
||||
<button id="navbar-toggler" class="navbar-toggler ml-0" type="button" data-toggle="collapse"
|
||||
data-toggle="tooltip" data-placement="bottom" data-target=".site-navigation" aria-controls="navbar-menu"
|
||||
aria-expanded="true" aria-label="Toggle navigation" aria-controls="site-navigation"
|
||||
title="Toggle navigation" data-toggle="tooltip" data-placement="left">
|
||||
<i class="fas fa-bars"></i>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="dropdown-buttons-trigger">
|
||||
<button id="dropdown-buttons-trigger" class="btn btn-secondary topbarbtn" aria-label="Download this page"><i
|
||||
class="fas fa-download"></i></button>
|
||||
|
||||
<div class="dropdown-buttons">
|
||||
<!-- ipynb file if we had a myst markdown file -->
|
||||
|
||||
<!-- Download raw file -->
|
||||
<a class="dropdown-buttons" href="../../_sources/tutorials/cuts-gurobipy.ipynb.txt"><button type="button"
|
||||
class="btn btn-secondary topbarbtn" title="Download source file" data-toggle="tooltip"
|
||||
data-placement="left">.ipynb</button></a>
|
||||
<!-- Download PDF via print -->
|
||||
<button type="button" id="download-print" class="btn btn-secondary topbarbtn" title="Print to PDF"
|
||||
onClick="window.print()" data-toggle="tooltip" data-placement="left">.pdf</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source interaction buttons -->
|
||||
|
||||
<!-- Full screen (wrap in <a> to have style consistency -->
|
||||
|
||||
<a class="full-screen-button"><button type="button" class="btn btn-secondary topbarbtn" data-toggle="tooltip"
|
||||
data-placement="bottom" onclick="toggleFullScreen()" aria-label="Fullscreen mode"
|
||||
title="Fullscreen mode"><i
|
||||
class="fas fa-expand"></i></button></a>
|
||||
|
||||
<!-- Launch buttons -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table of contents -->
|
||||
<div class="d-none d-md-block col-md-2 bd-toc show">
|
||||
|
||||
<div class="tocsection onthispage pt-5 pb-3">
|
||||
<i class="fas fa-list"></i> Contents
|
||||
</div>
|
||||
<nav id="bd-toc-nav">
|
||||
<ul class="visible nav section-nav flex-column">
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Modeling-the-traveling-salesman-problem">
|
||||
4.1. Modeling the traveling salesman problem
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Generating-training-data">
|
||||
4.2. Generating training data
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Training-and-solving-new-instances">
|
||||
4.3. Training and solving new instances
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Learning-user-cuts">
|
||||
4.4. Learning user cuts
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-content" class="row">
|
||||
<div class="col-12 col-md-9 pl-md-3 pr-md-0">
|
||||
|
||||
<div>
|
||||
|
||||
<section id="User-cuts-and-lazy-constraints">
|
||||
<h1><span class="section-number">4. </span>User cuts and lazy constraints<a class="headerlink" href="#User-cuts-and-lazy-constraints" title="Link to this heading">¶</a></h1>
|
||||
<p>User cuts and lazy constraints are two advanced mixed-integer programming techniques that can accelerate solver performance. User cuts are additional constraints, derived from the constraints already in the model, that can tighten the feasible region and eliminate fractional solutions, thus reducing the size of the branch-and-bound tree. Lazy constraints, on the other hand, are constraints that are potentially part of the problem formulation but are omitted from the initial model to reduce its
|
||||
size; these constraints are added to the formulation only once the solver finds a solution that violates them. While both techniques have been successful, significant computational effort may still be required to generate strong user cuts and to identify violated lazy constraints, which can reduce their effectiveness.</p>
|
||||
<p>MIPLearn is able to predict which user cuts and which lazy constraints to enforce at the beginning of the optimization process, using machine learning. In this tutorial, we will use the framework to predict subtour elimination constraints for the <strong>traveling salesman problem</strong> using Gurobipy. We assume that MIPLearn has already been correctly installed.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Solver Compatibility</p>
|
||||
<p>User cuts and lazy constraints are also supported in the Python/Pyomo and Julia/JuMP versions of the package. See the source code of build_tsp_model_pyomo and build_tsp_model_jump for more details. Note, however, the following limitations:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Python/Pyomo: Only <code class="docutils literal notranslate"><span class="pre">gurobi_persistent</span></code> is currently supported. PRs implementing callbacks for other persistent solvers are welcome.</p></li>
|
||||
<li><p>Julia/JuMP: Only solvers supporting solver-independent callbacks are supported. As of JuMP 1.19, this includes Gurobi, CPLEX, XPRESS, SCIP and GLPK. Note that HiGHS and Cbc are not supported. As newer versions of JuMP implement further callback support, MIPLearn should become automatically compatible with these solvers.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section id="Modeling-the-traveling-salesman-problem">
|
||||
<h2><span class="section-number">4.1. </span>Modeling the traveling salesman problem<a class="headerlink" href="#Modeling-the-traveling-salesman-problem" title="Link to this heading">¶</a></h2>
|
||||
<p>Given a list of cities and the distances between them, the <strong>traveling salesman problem (TSP)</strong> asks for the shortest route starting at the first city, visiting each other city exactly once, then returning to the first city. This problem is a generalization of the Hamiltonian path problem, one of Karp’s 21 NP-complete problems, and has many practical applications, including routing delivery trucks and scheduling airline routes.</p>
|
||||
<p>To describe an instance of TSP, we need to specify the number of cities <span class="math notranslate nohighlight">\(n\)</span>, and an <span class="math notranslate nohighlight">\(n \times n\)</span> matrix of distances. The class <code class="docutils literal notranslate"><span class="pre">TravelingSalesmanData</span></code>, in the <code class="docutils literal notranslate"><span class="pre">miplearn.problems.tsp</span></code> package, can hold this data:</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@dataclass</span>
|
||||
<span class="k">class</span> <span class="nc">TravelingSalesmanData</span><span class="p">:</span>
|
||||
<span class="n">n_cities</span><span class="p">:</span> <span class="nb">int</span>
|
||||
<span class="n">distances</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>MIPLearn also provides <code class="docutils literal notranslate"><span class="pre">TravelingSalesmandGenerator</span></code>, a random generator for TSP instances, and <code class="docutils literal notranslate"><span class="pre">build_tsp_model_gurobipy</span></code>, a function which converts <code class="docutils literal notranslate"><span class="pre">TravelingSalesmanData</span></code> into an actual gurobipy optimization model, and which uses lazy constraints to enforce subtour elimination.</p>
|
||||
<p>The example below is a simplified and annotated version of <code class="docutils literal notranslate"><span class="pre">build_tsp_model_gurobipy</span></code>, illustrating the usage of callbacks with MIPLearn. Compared the the previous tutorial examples, note that, in addition to defining the variables, objective function and constraints of our problem, we also define two callback functions <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code> and <code class="docutils literal notranslate"><span class="pre">lazy_enforce</span></code>.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[1]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">gurobipy</span> <span class="k">as</span> <span class="nn">gp</span>
|
||||
<span class="kn">from</span> <span class="nn">gurobipy</span> <span class="kn">import</span> <span class="n">quicksum</span><span class="p">,</span> <span class="n">GRB</span><span class="p">,</span> <span class="n">tuplelist</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.solvers.gurobi</span> <span class="kn">import</span> <span class="n">GurobiModel</span>
|
||||
<span class="kn">import</span> <span class="nn">networkx</span> <span class="k">as</span> <span class="nn">nx</span>
|
||||
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.problems.tsp</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">TravelingSalesmanData</span><span class="p">,</span>
|
||||
<span class="n">TravelingSalesmanGenerator</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">uniform</span><span class="p">,</span> <span class="n">randint</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">write_pkl_gz</span><span class="p">,</span> <span class="n">read_pkl_gz</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.collectors.basic</span> <span class="kn">import</span> <span class="n">BasicCollector</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.solvers.learning</span> <span class="kn">import</span> <span class="n">LearningSolver</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.components.lazy.mem</span> <span class="kn">import</span> <span class="n">MemorizingLazyComponent</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.extractors.fields</span> <span class="kn">import</span> <span class="n">H5FieldsExtractor</span>
|
||||
<span class="kn">from</span> <span class="nn">sklearn.neighbors</span> <span class="kn">import</span> <span class="n">KNeighborsClassifier</span>
|
||||
|
||||
<span class="c1"># Set up random seed to make example more reproducible</span>
|
||||
<span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Set up Python logging</span>
|
||||
<span class="kn">import</span> <span class="nn">logging</span>
|
||||
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">build_tsp_model_gurobipy_simplified</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
|
||||
<span class="c1"># Read data from file if a filename is provided</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">read_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Create empty gurobipy model</span>
|
||||
<span class="n">model</span> <span class="o">=</span> <span class="n">gp</span><span class="o">.</span><span class="n">Model</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Create set of edges between every pair of cities, for convenience</span>
|
||||
<span class="n">edges</span> <span class="o">=</span> <span class="n">tuplelist</span><span class="p">(</span>
|
||||
<span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">n_cities</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">data</span><span class="o">.</span><span class="n">n_cities</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># Add binary variable x[e] for each edge e</span>
|
||||
<span class="n">x</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">addVars</span><span class="p">(</span><span class="n">edges</span><span class="p">,</span> <span class="n">vtype</span><span class="o">=</span><span class="n">GRB</span><span class="o">.</span><span class="n">BINARY</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">"x"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Add objective function</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">setObjective</span><span class="p">(</span><span class="n">quicksum</span><span class="p">(</span><span class="n">x</span><span class="p">[(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)]</span> <span class="o">*</span> <span class="n">data</span><span class="o">.</span><span class="n">distances</span><span class="p">[</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">]</span> <span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span> <span class="ow">in</span> <span class="n">edges</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># Add constraint: must choose two edges adjacent to each city</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">addConstrs</span><span class="p">(</span>
|
||||
<span class="p">(</span>
|
||||
<span class="n">quicksum</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="nb">min</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">),</span> <span class="nb">max</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)]</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">n_cities</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">!=</span> <span class="n">j</span><span class="p">)</span>
|
||||
<span class="o">==</span> <span class="mi">2</span>
|
||||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">n_cities</span><span class="p">)</span>
|
||||
<span class="p">),</span>
|
||||
<span class="n">name</span><span class="o">=</span><span class="s2">"eq_degree"</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">lazy_separate</span><span class="p">(</span><span class="n">m</span><span class="p">:</span> <span class="n">GurobiModel</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Callback function that finds subtours in the current solution.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="c1"># Query current value of the x variables</span>
|
||||
<span class="n">x_val</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">cbGetSolution</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Initialize empty set of violations</span>
|
||||
<span class="n">violations</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="c1"># Build set of edges we have currently selected</span>
|
||||
<span class="n">selected_edges</span> <span class="o">=</span> <span class="p">[</span><span class="n">e</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">edges</span> <span class="k">if</span> <span class="n">x_val</span><span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">></span> <span class="mf">0.5</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># Build a graph containing the selected edges, using networkx</span>
|
||||
<span class="n">graph</span> <span class="o">=</span> <span class="n">nx</span><span class="o">.</span><span class="n">Graph</span><span class="p">()</span>
|
||||
<span class="n">graph</span><span class="o">.</span><span class="n">add_edges_from</span><span class="p">(</span><span class="n">selected_edges</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># For each component of the graph</span>
|
||||
<span class="k">for</span> <span class="n">component</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">nx</span><span class="o">.</span><span class="n">connected_components</span><span class="p">(</span><span class="n">graph</span><span class="p">)):</span>
|
||||
|
||||
<span class="c1"># If the component is not the entire graph, we found a</span>
|
||||
<span class="c1"># subtour. Add the edge cut to the list of violations.</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">component</span><span class="p">)</span> <span class="o"><</span> <span class="n">data</span><span class="o">.</span><span class="n">n_cities</span><span class="p">:</span>
|
||||
<span class="n">cut_edges</span> <span class="o">=</span> <span class="p">[</span>
|
||||
<span class="p">[</span><span class="n">e</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">e</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span>
|
||||
<span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">edges</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">component</span> <span class="ow">and</span> <span class="n">e</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">component</span><span class="p">)</span>
|
||||
<span class="ow">or</span> <span class="p">(</span><span class="n">e</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">component</span> <span class="ow">and</span> <span class="n">e</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="ow">in</span> <span class="n">component</span><span class="p">)</span>
|
||||
<span class="p">]</span>
|
||||
<span class="n">violations</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cut_edges</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Return the list of violations</span>
|
||||
<span class="k">return</span> <span class="n">violations</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">lazy_enforce</span><span class="p">(</span><span class="n">m</span><span class="p">:</span> <span class="n">GurobiModel</span><span class="p">,</span> <span class="n">violations</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Callback function that, given a list of subtours, adds lazy</span>
|
||||
<span class="sd"> constraints to remove them from the feasible region.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Enforcing </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">violations</span><span class="p">)</span><span class="si">}</span><span class="s2"> subtour elimination constraints"</span><span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">violation</span> <span class="ow">in</span> <span class="n">violations</span><span class="p">:</span>
|
||||
<span class="n">m</span><span class="o">.</span><span class="n">add_constr</span><span class="p">(</span><span class="n">quicksum</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="n">e</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">e</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">violation</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">2</span><span class="p">)</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">GurobiModel</span><span class="p">(</span>
|
||||
<span class="n">model</span><span class="p">,</span>
|
||||
<span class="n">lazy_separate</span><span class="o">=</span><span class="n">lazy_separate</span><span class="p">,</span>
|
||||
<span class="n">lazy_enforce</span><span class="o">=</span><span class="n">lazy_enforce</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code> function starts by querying the current fractional solution value through <code class="docutils literal notranslate"><span class="pre">m.inner.cbGetSolution</span></code> (recall that <code class="docutils literal notranslate"><span class="pre">m.inner</span></code> is a regular gurobipy model), then finds the set of violated lazy constraints. Unlike a regular lazy constraint solver callback, note that <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code> does not add the violated constraints to the model; it simply returns a list of objects that uniquely identifies the set of lazy constraints that should be generated. Enforcing the constraints is
|
||||
the responsbility of the second callback function, <code class="docutils literal notranslate"><span class="pre">lazy_enforce</span></code>. This function takes as input the model and the list of violations found by <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code>, converts them into actual constraints, and adds them to the model through <code class="docutils literal notranslate"><span class="pre">m.add_constr</span></code>.</p>
|
||||
<p>During training data generation, MIPLearn calls <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code> and <code class="docutils literal notranslate"><span class="pre">lazy_enforce</span></code> in sequence, inside a regular solver callback. However, once the machine learning models are trained, MIPLearn calls <code class="docutils literal notranslate"><span class="pre">lazy_enforce</span></code> directly, before the optimization process starts, with a list of <strong>predicted</strong> violations, as we will see in the example below.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Constraint Representation</p>
|
||||
<p>How should user cuts and lazy constraints be represented is a decision that the user can make; MIPLearn is representation agnostic. The objects returned by <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code>, however, are serialized as JSON and stored in the HDF5 training data files. Therefore, it is recommended to use only simple objects, such as lists, tuples and dictionaries.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Generating-training-data">
|
||||
<h2><span class="section-number">4.2. </span>Generating training data<a class="headerlink" href="#Generating-training-data" title="Link to this heading">¶</a></h2>
|
||||
<p>To test the callback defined above, we generate a small set of TSP instances, using the provided random instance generator. As in the previous tutorial, we generate some test instances and some training instances, then solve them using <code class="docutils literal notranslate"><span class="pre">BasicCollector</span></code>. Input problem data is stored in <code class="docutils literal notranslate"><span class="pre">tsp/train/00000.pkl.gz,</span> <span class="pre">...</span></code>, whereas solver training data (including list of required lazy constraints) is stored in <code class="docutils literal notranslate"><span class="pre">tsp/train/00000.h5,</span> <span class="pre">...</span></code>.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># Configure generator to produce instances with 50 cities located</span>
|
||||
<span class="c1"># in the 1000 x 1000 square, and with slightly perturbed distances.</span>
|
||||
<span class="n">gen</span> <span class="o">=</span> <span class="n">TravelingSalesmanGenerator</span><span class="p">(</span>
|
||||
<span class="n">x</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">1000.0</span><span class="p">),</span>
|
||||
<span class="n">y</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">1000.0</span><span class="p">),</span>
|
||||
<span class="n">n</span><span class="o">=</span><span class="n">randint</span><span class="p">(</span><span class="n">low</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">high</span><span class="o">=</span><span class="mi">51</span><span class="p">),</span>
|
||||
<span class="n">gamma</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">1.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.25</span><span class="p">),</span>
|
||||
<span class="n">fix_cities</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="nb">round</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># Generate 500 instances and store input data file to .pkl.gz files</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">gen</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
|
||||
<span class="n">train_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">450</span><span class="p">],</span> <span class="s2">"tsp/train"</span><span class="p">)</span>
|
||||
<span class="n">test_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">450</span><span class="p">:</span><span class="mi">500</span><span class="p">],</span> <span class="s2">"tsp/test"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Solve the training instances in parallel, collecting the required lazy</span>
|
||||
<span class="c1"># constraints, in addition to other information, such as optimal solution.</span>
|
||||
<span class="n">bc</span> <span class="o">=</span> <span class="n">BasicCollector</span><span class="p">()</span>
|
||||
<span class="n">bc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">train_data</span><span class="p">,</span> <span class="n">build_tsp_model_gurobipy_simplified</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Training-and-solving-new-instances">
|
||||
<h2><span class="section-number">4.3. </span>Training and solving new instances<a class="headerlink" href="#Training-and-solving-new-instances" title="Link to this heading">¶</a></h2>
|
||||
<p>After producing the training dataset, we can train the machine learning models to predict which lazy constraints are necessary. In this tutorial, we use the following ML strategy: given a new instance, find the 50 most similar ones in the training dataset and verify how often each lazy constraint was required. If a lazy constraint was required for the majority of the 50 most-similar instances, enforce it ahead-of-time for the current instance. To measure instance similarity, use the objective
|
||||
function only. This ML strategy can be implemented using <code class="docutils literal notranslate"><span class="pre">MemorizingLazyComponent</span></code> with <code class="docutils literal notranslate"><span class="pre">H5FieldsExtractor</span></code> and <code class="docutils literal notranslate"><span class="pre">KNeighborsClassifier</span></code>, as shown below.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">solver</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span>
|
||||
<span class="n">components</span><span class="o">=</span><span class="p">[</span>
|
||||
<span class="n">MemorizingLazyComponent</span><span class="p">(</span>
|
||||
<span class="n">extractor</span><span class="o">=</span><span class="n">H5FieldsExtractor</span><span class="p">(</span><span class="n">instance_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">"static_var_obj_coeffs"</span><span class="p">]),</span>
|
||||
<span class="n">clf</span><span class="o">=</span><span class="n">KNeighborsClassifier</span><span class="p">(</span><span class="n">n_neighbors</span><span class="o">=</span><span class="mi">100</span><span class="p">),</span>
|
||||
<span class="p">),</span>
|
||||
<span class="p">],</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">solver</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Next, we solve one of the test instances using the trained solver. In the run below, we can see that MIPLearn adds many lazy constraints ahead-of-time, before the optimization starts. During the optimization process itself, some additional lazy constraints are required, but very few.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="c1"># Increase log verbosity, so that we can see what is MIPLearn doing</span>
|
||||
<span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s2">"miplearn"</span><span class="p">)</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Solve a new test instance</span>
|
||||
<span class="n">solver</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_tsp_model_gurobipy_simplified</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Set parameter Threads to value 1
|
||||
Restricted license - for non-production use only - expires 2024-10-28
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 1 threads
|
||||
|
||||
Optimize a model with 50 rows, 1225 columns and 2450 nonzeros
|
||||
Model fingerprint: 0x04d7bec1
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+00]
|
||||
Objective range [1e+01, 1e+03]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [2e+00, 2e+00]
|
||||
Presolve time: 0.00s
|
||||
Presolved: 50 rows, 1225 columns, 2450 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 4.0600000e+02 9.700000e+01 0.000000e+00 0s
|
||||
66 5.5880000e+03 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 66 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 5.588000000e+03
|
||||
|
||||
User-callback calls 107, time in user-callback 0.00 sec
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area stderr docutils container">
|
||||
<div class="highlight"><pre>
|
||||
INFO:miplearn.components.cuts.mem:Predicting violated lazy constraints...
|
||||
INFO:miplearn.components.lazy.mem:Enforcing 19 constraints ahead-of-time...
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Enforcing 19 subtour elimination constraints
|
||||
Set parameter PreCrush to value 1
|
||||
Set parameter LazyConstraints to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 1 threads
|
||||
|
||||
Optimize a model with 69 rows, 1225 columns and 6091 nonzeros
|
||||
Model fingerprint: 0x09bd34d6
|
||||
Variable types: 0 continuous, 1225 integer (1225 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+00]
|
||||
Objective range [1e+01, 1e+03]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [2e+00, 2e+00]
|
||||
Found heuristic solution: objective 29853.000000
|
||||
Presolve time: 0.00s
|
||||
Presolved: 69 rows, 1225 columns, 6091 nonzeros
|
||||
Variable types: 0 continuous, 1225 integer (1225 binary)
|
||||
|
||||
Root relaxation: objective 6.139000e+03, 93 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 6139.00000 0 6 29853.0000 6139.00000 79.4% - 0s
|
||||
H 0 0 6390.0000000 6139.00000 3.93% - 0s
|
||||
0 0 6165.50000 0 10 6390.00000 6165.50000 3.51% - 0s
|
||||
Enforcing 3 subtour elimination constraints
|
||||
0 0 6165.50000 0 6 6390.00000 6165.50000 3.51% - 0s
|
||||
0 0 6198.50000 0 16 6390.00000 6198.50000 3.00% - 0s
|
||||
* 0 0 0 6219.0000000 6219.00000 0.00% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 11
|
||||
MIR: 1
|
||||
Zero half: 4
|
||||
Lazy constraints: 3
|
||||
|
||||
Explored 1 nodes (222 simplex iterations) in 0.03 seconds (0.02 work units)
|
||||
Thread count was 1 (of 20 available processors)
|
||||
|
||||
Solution count 3: 6219 6390 29853
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 6.219000000000e+03, best bound 6.219000000000e+03, gap 0.0000%
|
||||
|
||||
User-callback calls 141, time in user-callback 0.00 sec
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>Finally, we solve the same instance, but using a regular solver, without ML prediction. We can see that a much larger number of lazy constraints are added during the optimization process itself. Additionally, the solver requires a larger number of iterations to find the optimal solution. There is not a significant difference in running time because of the small size of these instances.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[5]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">solver</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[])</span> <span class="c1"># empty set of ML components</span>
|
||||
<span class="n">solver</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_tsp_model_gurobipy_simplified</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 1 threads
|
||||
|
||||
Optimize a model with 50 rows, 1225 columns and 2450 nonzeros
|
||||
Model fingerprint: 0x04d7bec1
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+00]
|
||||
Objective range [1e+01, 1e+03]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [2e+00, 2e+00]
|
||||
Presolve time: 0.00s
|
||||
Presolved: 50 rows, 1225 columns, 2450 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 4.0600000e+02 9.700000e+01 0.000000e+00 0s
|
||||
66 5.5880000e+03 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 66 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 5.588000000e+03
|
||||
|
||||
User-callback calls 107, time in user-callback 0.00 sec
|
||||
Set parameter PreCrush to value 1
|
||||
Set parameter LazyConstraints to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 1 threads
|
||||
|
||||
Optimize a model with 50 rows, 1225 columns and 2450 nonzeros
|
||||
Model fingerprint: 0x77a94572
|
||||
Variable types: 0 continuous, 1225 integer (1225 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+00]
|
||||
Objective range [1e+01, 1e+03]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [2e+00, 2e+00]
|
||||
Found heuristic solution: objective 29695.000000
|
||||
Presolve time: 0.00s
|
||||
Presolved: 50 rows, 1225 columns, 2450 nonzeros
|
||||
Variable types: 0 continuous, 1225 integer (1225 binary)
|
||||
|
||||
Root relaxation: objective 5.588000e+03, 68 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 5588.00000 0 12 29695.0000 5588.00000 81.2% - 0s
|
||||
Enforcing 9 subtour elimination constraints
|
||||
Enforcing 11 subtour elimination constraints
|
||||
H 0 0 27241.000000 5588.00000 79.5% - 0s
|
||||
0 0 5898.00000 0 8 27241.0000 5898.00000 78.3% - 0s
|
||||
Enforcing 4 subtour elimination constraints
|
||||
Enforcing 3 subtour elimination constraints
|
||||
0 0 6066.00000 0 - 27241.0000 6066.00000 77.7% - 0s
|
||||
Enforcing 2 subtour elimination constraints
|
||||
0 0 6128.00000 0 - 27241.0000 6128.00000 77.5% - 0s
|
||||
0 0 6139.00000 0 6 27241.0000 6139.00000 77.5% - 0s
|
||||
H 0 0 6368.0000000 6139.00000 3.60% - 0s
|
||||
0 0 6154.75000 0 15 6368.00000 6154.75000 3.35% - 0s
|
||||
Enforcing 2 subtour elimination constraints
|
||||
0 0 6154.75000 0 6 6368.00000 6154.75000 3.35% - 0s
|
||||
0 0 6165.75000 0 11 6368.00000 6165.75000 3.18% - 0s
|
||||
Enforcing 3 subtour elimination constraints
|
||||
0 0 6204.00000 0 6 6368.00000 6204.00000 2.58% - 0s
|
||||
* 0 0 0 6219.0000000 6219.00000 0.00% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 5
|
||||
MIR: 1
|
||||
Zero half: 4
|
||||
Lazy constraints: 4
|
||||
|
||||
Explored 1 nodes (224 simplex iterations) in 0.10 seconds (0.03 work units)
|
||||
Thread count was 1 (of 20 available processors)
|
||||
|
||||
Solution count 4: 6219 6368 27241 29695
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 6.219000000000e+03, best bound 6.219000000000e+03, gap 0.0000%
|
||||
|
||||
User-callback calls 170, time in user-callback 0.01 sec
|
||||
</pre></div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Learning-user-cuts">
|
||||
<h2><span class="section-number">4.4. </span>Learning user cuts<a class="headerlink" href="#Learning-user-cuts" title="Link to this heading">¶</a></h2>
|
||||
<p>The example above focused on lazy constraints. To enforce user cuts instead, the procedure is very similar, with the following changes:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Instead of <code class="docutils literal notranslate"><span class="pre">lazy_separate</span></code> and <code class="docutils literal notranslate"><span class="pre">lazy_enforce</span></code>, use <code class="docutils literal notranslate"><span class="pre">cuts_separate</span></code> and <code class="docutils literal notranslate"><span class="pre">cuts_enforce</span></code></p></li>
|
||||
<li><p>Instead of <code class="docutils literal notranslate"><span class="pre">m.inner.cbGetSolution</span></code>, use <code class="docutils literal notranslate"><span class="pre">m.inner.cbGetNodeRel</span></code></p></li>
|
||||
</ul>
|
||||
<p>For a complete example, see <code class="docutils literal notranslate"><span class="pre">build_stab_model_gurobipy</span></code>, <code class="docutils literal notranslate"><span class="pre">build_stab_model_pyomo</span></code> and <code class="docutils literal notranslate"><span class="pre">build_stab_model_jump</span></code>, which solves the maximum-weight stable set problem using user cut callbacks.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[ ]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class='prev-next-bottom'>
|
||||
|
||||
<a class='left-prev' id="prev-link" href="../getting-started-jump/" title="previous page"><span class="section-number">3. </span>Getting started (JuMP)</a>
|
||||
<a class='right-next' id="next-link" href="../../guide/problems/" title="next page"><span class="section-number">5. </span>Benchmark Problems</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer mt-5 mt-md-0">
|
||||
<div class="container">
|
||||
<p>
|
||||
|
||||
© Copyright 2020-2023, UChicago Argonne, LLC.<br/>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../_static/js/index.1c5a1a01449ed65a7b51.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
837
0.4/tutorials/getting-started-gurobipy.ipynb
Normal file
837
0.4/tutorials/getting-started-gurobipy.ipynb
Normal file
@@ -0,0 +1,837 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6b8983b1",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"# Getting started (Gurobipy)\n",
|
||||
"\n",
|
||||
"## Introduction\n",
|
||||
"\n",
|
||||
"**MIPLearn** is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:\n",
|
||||
"\n",
|
||||
"1. Install the Python/Gurobipy version of MIPLearn\n",
|
||||
"2. Model a simple optimization problem using Gurobipy\n",
|
||||
"3. Generate training data and train the ML models\n",
|
||||
"4. Use the ML models together Gurobi to solve new instances\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"Note\n",
|
||||
" \n",
|
||||
"The Python/Gurobipy version of MIPLearn is only compatible with the Gurobi Optimizer. For broader solver compatibility, see the Python/Pyomo and Julia/JuMP versions of the package.\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-warning\">\n",
|
||||
"Warning\n",
|
||||
" \n",
|
||||
"MIPLearn is still in early development stage. If run into any bugs or issues, please submit a bug report in our GitHub repository. Comments, suggestions and pull requests are also very welcome!\n",
|
||||
" \n",
|
||||
"</div>\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "02f0a927",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"MIPLearn is available in two versions:\n",
|
||||
"\n",
|
||||
"- Python version, compatible with the Pyomo and Gurobipy modeling languages,\n",
|
||||
"- Julia version, compatible with the JuMP modeling language.\n",
|
||||
"\n",
|
||||
"In this tutorial, we will demonstrate how to use and install the Python/Gurobipy version of the package. The first step is to install Python 3.8+ in your computer. See the [official Python website for more instructions](https://www.python.org/downloads/). After Python is installed, we proceed to install MIPLearn using `pip`:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"$ pip install MIPLearn==0.3\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"In addition to MIPLearn itself, we will also install Gurobi 10.0, a state-of-the-art commercial MILP solver. This step also install a demo license for Gurobi, which should able to solve the small optimization problems in this tutorial. A license is required for solving larger-scale problems.\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"$ pip install 'gurobipy>=10,<10.1'\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a14e4550",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Note\n",
|
||||
" \n",
|
||||
"In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Python projects.\n",
|
||||
" \n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "16b86823",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modeling a simple optimization problem\n",
|
||||
"\n",
|
||||
"To illustrate how can MIPLearn be used, we will model and solve a small optimization problem related to power systems optimization. The problem we discuss below is a simplification of the **unit commitment problem,** a practical optimization problem solved daily by electric grid operators around the world. \n",
|
||||
"\n",
|
||||
"Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. The total amount of power to be produced needs to be exactly equal to the total demand $d$ (in megawatts).\n",
|
||||
"\n",
|
||||
"This simple problem can be modeled as a *mixed-integer linear optimization* problem as follows. For each generator $g_i$, let $x_i \\in \\{0,1\\}$ be a decision variable indicating whether $g_i$ is online, and let $y_i \\geq 0$ be a decision variable indicating how much power does $g_i$ produce. The problem is then given by:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f12c3702",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"$$\n",
|
||||
"\\begin{align}\n",
|
||||
"\\text{minimize } \\quad & \\sum_{i=1}^n \\left( c^\\text{fix}_i x_i + c^\\text{var}_i y_i \\right) \\\\\n",
|
||||
"\\text{subject to } \\quad & y_i \\leq p^\\text{max}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq p^\\text{min}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& \\sum_{i=1}^n y_i = d \\\\\n",
|
||||
"& x_i \\in \\{0,1\\} & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq 0 & i=1,\\ldots,n\n",
|
||||
"\\end{align}\n",
|
||||
"$$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be3989ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"\n",
|
||||
"Note\n",
|
||||
"\n",
|
||||
"We use a simplified version of the unit commitment problem in this tutorial just to make it easier to follow. MIPLearn can also handle realistic, large-scale versions of this problem.\n",
|
||||
"\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a5fd33f6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Python and Pyomo. We start by defining a data class `UnitCommitmentData`, which holds all the input data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "22a67170-10b4-43d3-8708-014d91141e73",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:18:25.442346786Z",
|
||||
"start_time": "2023-06-06T20:18:25.329017476Z"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class UnitCommitmentData:\n",
|
||||
" demand: float\n",
|
||||
" pmin: List[float]\n",
|
||||
" pmax: List[float]\n",
|
||||
" cfix: List[float]\n",
|
||||
" cvar: List[float]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "29f55efa-0751-465a-9b0a-a821d46a3d40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we write a `build_uc_model` function, which converts the input data into a concrete Pyomo model. The function accepts `UnitCommitmentData`, the data structure we previously defined, or the path to a compressed pickle file containing this data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "2f67032f-0d74-4317-b45c-19da0ec859e9",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:48:05.953902842Z",
|
||||
"start_time": "2023-06-06T20:48:05.909747925Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gurobipy as gp\n",
|
||||
"from gurobipy import GRB, quicksum\n",
|
||||
"from typing import Union\n",
|
||||
"from miplearn.io import read_pkl_gz\n",
|
||||
"from miplearn.solvers.gurobi import GurobiModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def build_uc_model(data: Union[str, UnitCommitmentData]) -> GurobiModel:\n",
|
||||
" if isinstance(data, str):\n",
|
||||
" data = read_pkl_gz(data)\n",
|
||||
"\n",
|
||||
" model = gp.Model()\n",
|
||||
" n = len(data.pmin)\n",
|
||||
" x = model._x = model.addVars(n, vtype=GRB.BINARY, name=\"x\")\n",
|
||||
" y = model._y = model.addVars(n, name=\"y\")\n",
|
||||
" model.setObjective(\n",
|
||||
" quicksum(data.cfix[i] * x[i] + data.cvar[i] * y[i] for i in range(n))\n",
|
||||
" )\n",
|
||||
" model.addConstrs(y[i] <= data.pmax[i] * x[i] for i in range(n))\n",
|
||||
" model.addConstrs(y[i] >= data.pmin[i] * x[i] for i in range(n))\n",
|
||||
" model.addConstr(quicksum(y[i] for i in range(n)) == data.demand)\n",
|
||||
" return GurobiModel(model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c22714a3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"At this point, we can already use Pyomo and any mixed-integer linear programming solver to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2a896f47",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:14.266758244Z",
|
||||
"start_time": "2023-06-06T20:49:14.223514806Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Restricted license - for non-production use only - expires 2024-10-28\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 7 rows, 6 columns and 15 nonzeros\n",
|
||||
"Model fingerprint: 0x58dfdd53\n",
|
||||
"Variable types: 3 continuous, 3 integer (3 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 7e+01]\n",
|
||||
" Objective range [2e+00, 7e+02]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [1e+02, 1e+02]\n",
|
||||
"Presolve removed 2 rows and 1 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 5 rows, 5 columns, 13 nonzeros\n",
|
||||
"Variable types: 0 continuous, 5 integer (3 binary)\n",
|
||||
"Found heuristic solution: objective 1400.0000000\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s\n",
|
||||
" 0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s\n",
|
||||
"* 0 0 0 1320.0000000 1320.00000 0.00% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 2: 1320 1400 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%\n",
|
||||
"obj = 1320.0\n",
|
||||
"x = [-0.0, 1.0, 1.0]\n",
|
||||
"y = [0.0, 60.0, 40.0]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = build_uc_model(\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" demand=100.0,\n",
|
||||
" pmin=[10, 20, 30],\n",
|
||||
" pmax=[50, 60, 70],\n",
|
||||
" cfix=[700, 600, 500],\n",
|
||||
" cvar=[1.5, 2.0, 2.5],\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model.optimize()\n",
|
||||
"print(\"obj =\", model.inner.objVal)\n",
|
||||
"print(\"x =\", [model.inner._x[i].x for i in range(3)])\n",
|
||||
"print(\"y =\", [model.inner._y[i].x for i in range(3)])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "41b03bbc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Running the code above, we found that the optimal solution for our small problem instance costs \\$1320. It is achieve by keeping generators 2 and 3 online and producing, respectively, 60 MW and 40 MW of power."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "01f576e1-1790-425e-9e5c-9fa07b6f4c26",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Note\n",
|
||||
"\n",
|
||||
"- In the example above, `GurobiModel` is just a thin wrapper around a standard Gurobi model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as `optimize`. For more control, and to query the solution, the original Gurobi model can be accessed through `model.inner`, as illustrated above.\n",
|
||||
"- To ensure training data consistency, MIPLearn requires all decision variables to have names.\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf60c1dd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generating training data\n",
|
||||
"\n",
|
||||
"Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a **trained** solver, which can optimize new instances (similar to the ones it was trained on) faster.\n",
|
||||
"\n",
|
||||
"In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a random instance generator:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5eb09fab",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:22.758192368Z",
|
||||
"start_time": "2023-06-06T20:49:22.724784572Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from scipy.stats import uniform\n",
|
||||
"from typing import List\n",
|
||||
"import random\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def random_uc_data(samples: int, n: int, seed: int = 42) -> List[UnitCommitmentData]:\n",
|
||||
" random.seed(seed)\n",
|
||||
" np.random.seed(seed)\n",
|
||||
" pmin = uniform(loc=100_000.0, scale=400_000.0).rvs(n)\n",
|
||||
" pmax = pmin * uniform(loc=2.0, scale=2.5).rvs(n)\n",
|
||||
" cfix = pmin * uniform(loc=100.0, scale=25.0).rvs(n)\n",
|
||||
" cvar = uniform(loc=1.25, scale=0.25).rvs(n)\n",
|
||||
" return [\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" demand=pmax.sum() * uniform(loc=0.5, scale=0.25).rvs(),\n",
|
||||
" pmin=pmin,\n",
|
||||
" pmax=pmax,\n",
|
||||
" cfix=cfix,\n",
|
||||
" cvar=cvar,\n",
|
||||
" )\n",
|
||||
" for _ in range(samples)\n",
|
||||
" ]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a03a7ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.\n",
|
||||
"\n",
|
||||
"Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple machines. The code below generates the files `uc/train/00000.pkl.gz`, `uc/train/00001.pkl.gz`, etc., which contain the input data in compressed (gzipped) pickle format."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "6156752c",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:24.811192929Z",
|
||||
"start_time": "2023-06-06T20:49:24.575639142Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from miplearn.io import write_pkl_gz\n",
|
||||
"\n",
|
||||
"data = random_uc_data(samples=500, n=500)\n",
|
||||
"train_data = write_pkl_gz(data[0:450], \"uc/train\")\n",
|
||||
"test_data = write_pkl_gz(data[450:500], \"uc/test\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b17af877",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we use `BasicCollector` to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files `uc/train/00000.h5`, `uc/train/00001.h5`, etc. The optimization models are also exported to compressed MPS files `uc/train/00000.mps.gz`, `uc/train/00001.mps.gz`, etc."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "7623f002",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:34.936729253Z",
|
||||
"start_time": "2023-06-06T20:49:25.936126612Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from miplearn.collectors.basic import BasicCollector\n",
|
||||
"\n",
|
||||
"bc = BasicCollector()\n",
|
||||
"bc.collect(train_data, build_uc_model, n_jobs=4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c42b1be1-9723-4827-82d8-974afa51ef9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Training and solving test instances"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a33c6aa4-f0b8-4ccb-9935-01f7d7de2a1c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using $k$-nearest neighbors. More specifically, the strategy is to:\n",
|
||||
"\n",
|
||||
"1. Memorize the optimal solutions of all training instances;\n",
|
||||
"2. Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;\n",
|
||||
"3. Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.\n",
|
||||
"4. Provide this partial solution to the solver as a warm start.\n",
|
||||
"\n",
|
||||
"This simple strategy can be implemented as shown below, using `MemorizingPrimalComponent`. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "435f7bf8-4b09-4889-b1ec-b7b56e7d8ed2",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:38.997939600Z",
|
||||
"start_time": "2023-06-06T20:49:38.968261432Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from sklearn.neighbors import KNeighborsClassifier\n",
|
||||
"from miplearn.components.primal.actions import SetWarmStart\n",
|
||||
"from miplearn.components.primal.mem import (\n",
|
||||
" MemorizingPrimalComponent,\n",
|
||||
" MergeTopSolutions,\n",
|
||||
")\n",
|
||||
"from miplearn.extractors.fields import H5FieldsExtractor\n",
|
||||
"\n",
|
||||
"comp = MemorizingPrimalComponent(\n",
|
||||
" clf=KNeighborsClassifier(n_neighbors=25),\n",
|
||||
" extractor=H5FieldsExtractor(\n",
|
||||
" instance_fields=[\"static_constr_rhs\"],\n",
|
||||
" ),\n",
|
||||
" constructor=MergeTopSolutions(25, [0.0, 1.0]),\n",
|
||||
" action=SetWarmStart(),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9536e7e4-0b0d-49b0-bebd-4a848f839e94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Having defined the ML strategy, we next construct `LearningSolver`, train the ML component and optimize one of the test instances."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "9d13dd50-3dcf-4673-a757-6f44dcc0dedf",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:42.072345411Z",
|
||||
"start_time": "2023-06-06T20:49:41.294040974Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xa8b70287\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.01s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.6166537e+09 5.648803e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2906219e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.290621916e+09\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xcf27855a\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 8.29153e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.29153e+09 (0.01s)\n",
|
||||
"Loaded user MIP start with objective 8.29153e+09\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 1\n",
|
||||
" Flow cover: 2\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (565 simplex iterations) in 0.03 seconds (0.01 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 1: 8.29153e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'WS: Count': 1, 'WS: Number of variables set': 482.0}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from miplearn.solvers.learning import LearningSolver\n",
|
||||
"\n",
|
||||
"solver_ml = LearningSolver(components=[comp])\n",
|
||||
"solver_ml.fit(train_data)\n",
|
||||
"solver_ml.optimize(test_data[0], build_uc_model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "61da6dad-7f56-4edb-aa26-c00eb5f946c0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"By examining the solve log above, specifically the line `Loaded user MIP start with objective...`, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "2ff391ed-e855-4228-aa09-a7641d8c2893",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:49:44.012782276Z",
|
||||
"start_time": "2023-06-06T20:49:43.813974362Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xa8b70287\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.6166537e+09 5.648803e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2906219e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.290621916e+09\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x4cbbf7c7\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Found heuristic solution: objective 9.757128e+09\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2906e+09 0 1 9.7571e+09 8.2906e+09 15.0% - 0s\n",
|
||||
"H 0 0 8.298273e+09 8.2906e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
"H 0 0 8.293980e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 5 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 2 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 1 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
"H 0 0 8.291465e+09 8.2908e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 2\n",
|
||||
" MIR: 1\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (1031 simplex iterations) in 0.15 seconds (0.03 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.291465302389e+09, best bound 8.290781665333e+09, gap 0.0082%\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solver_baseline = LearningSolver(components=[])\n",
|
||||
"solver_baseline.fit(train_data)\n",
|
||||
"solver_baseline.optimize(test_data[0], build_uc_model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b6d37b88-9fcc-43ee-ac1e-2a7b1e51a266",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In the log above, the `MIP start` line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "eec97f06",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"## Accessing the solution\n",
|
||||
"\n",
|
||||
"In the example above, we used `LearningSolver.solve` together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a Pyomo model entirely in-memory, using our trained solver."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "67a6cd18",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:50:12.869892930Z",
|
||||
"start_time": "2023-06-06T20:50:12.509410473Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x19042f12\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.5917580e+09 5.627453e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2535968e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.253596777e+09\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xf97cde91\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 8.25814e+09 (0.00s)\n",
|
||||
"User MIP start produced solution with objective 8.25512e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25459e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25459e+09 (0.01s)\n",
|
||||
"Loaded user MIP start with objective 8.25459e+09\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.253597e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2536e+09 0 1 8.2546e+09 8.2536e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 3 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 1 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 4 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 5 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 6 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Cover: 1\n",
|
||||
" MIR: 2\n",
|
||||
" StrongCG: 1\n",
|
||||
" Flow cover: 1\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (575 simplex iterations) in 0.05 seconds (0.01 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%\n",
|
||||
"obj = 8254590409.969726\n",
|
||||
"x = [1.0, 1.0, 0.0]\n",
|
||||
"y = [935662.0949262811, 1604270.0218116897, 0.0]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data = random_uc_data(samples=1, n=500)[0]\n",
|
||||
"model = build_uc_model(data)\n",
|
||||
"solver_ml.optimize(model)\n",
|
||||
"print(\"obj =\", model.inner.objVal)\n",
|
||||
"print(\"x =\", [model.inner._x[i].x for i in range(3)])\n",
|
||||
"print(\"y =\", [model.inner._y[i].x for i in range(3)])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5593d23a-83bd-4e16-8253-6300f5e3f63b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
888
0.4/tutorials/getting-started-gurobipy/index.html
Normal file
888
0.4/tutorials/getting-started-gurobipy/index.html
Normal file
@@ -0,0 +1,888 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>2. Getting started (Gurobipy) — MIPLearn 0.4</title>
|
||||
|
||||
<link href="../../_static/css/theme.css" rel="stylesheet" />
|
||||
<link href="../../_static/css/index.c5995385ac14fb8791e8eb36b4908be2.css" rel="stylesheet" />
|
||||
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="../../_static/vendor/fontawesome/5.13.0/css/all.min.css">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-solid-900.woff2">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-brands-400.woff2">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=362ab14a" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css?v=b0dfe17c" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css?v=2aa19091" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/custom.css?v=f8244a84" />
|
||||
|
||||
<link rel="preload" as="script" href="../../_static/js/index.1c5a1a01449ed65a7b51.js">
|
||||
|
||||
<script src="../../_static/documentation_options.js?v=751a5dd3"></script>
|
||||
<script src="../../_static/doctools.js?v=888ff710"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script crossorigin="anonymous" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"></script>
|
||||
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js?v=7c4c3336"></script>
|
||||
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"inlineMath": [["\\(", "\\)"]], "displayMath": [["\\[", "\\]"]], "processRefs": false, "processEnvironments": false}})</script>
|
||||
<script>window.MathJax = {"tex": {"inlineMath": [["$", "$"], ["\\(", "\\)"]], "processEscapes": true}, "options": {"ignoreHtmlClass": "tex2jax_ignore|mathjax_ignore|document", "processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}</script>
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<link rel="index" title="Index" href="../../genindex/" />
|
||||
<link rel="search" title="Search" href="../../search/" />
|
||||
<link rel="next" title="3. Getting started (JuMP)" href="../getting-started-jump/" />
|
||||
<link rel="prev" title="1. Getting started (Pyomo)" href="../getting-started-pyomo/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="docsearch:language" content="en" />
|
||||
|
||||
</head>
|
||||
<body data-spy="scroll" data-target="#bd-toc-nav" data-offset="80">
|
||||
|
||||
<div class="container-fluid" id="banner"></div>
|
||||
|
||||
|
||||
|
||||
<div class="container-xl">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 col-md-3 bd-sidebar site-navigation show" id="site-navigation">
|
||||
|
||||
<div class="navbar-brand-box">
|
||||
<a class="navbar-brand text-wrap" href="../../">
|
||||
|
||||
|
||||
<h1 class="site-logo" id="site-title">MIPLearn 0.4</h1>
|
||||
|
||||
</a>
|
||||
</div><form class="bd-search d-flex align-items-center" action="../../search/" method="get">
|
||||
<i class="icon fas fa-search"></i>
|
||||
<input type="search" class="form-control" name="q" id="search-input" placeholder="Search the docs ..." aria-label="Search the docs ..." autocomplete="off" >
|
||||
</form><nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
|
||||
<div class="bd-toc-item active">
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Tutorials
|
||||
</span>
|
||||
</p>
|
||||
<ul class="current nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-pyomo/">
|
||||
1. Getting started (Pyomo)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current active">
|
||||
<a class="current reference internal" href="#">
|
||||
2. Getting started (Gurobipy)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-jump/">
|
||||
3. Getting started (JuMP)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../cuts-gurobipy/">
|
||||
4. User cuts and lazy constraints
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
User Guide
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/problems/">
|
||||
5. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/collectors/">
|
||||
6. Training Data Collectors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/features/">
|
||||
7. Feature Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/primal/">
|
||||
8. Primal Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/solvers/">
|
||||
9. Learning Solver
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Python API Reference
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/problems/">
|
||||
10. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/collectors/">
|
||||
11. Collectors & Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/components/">
|
||||
12. Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/solvers/">
|
||||
13. Solvers
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/helpers/">
|
||||
14. Helpers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav> <!-- To handle the deprecated key -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="col py-md-3 pl-md-4 bd-content overflow-auto" role="main">
|
||||
|
||||
<div class="topbar container-xl fixed-top">
|
||||
<div class="topbar-contents row">
|
||||
<div class="col-12 col-md-3 bd-topbar-whitespace site-navigation show"></div>
|
||||
<div class="col pl-md-4 topbar-main">
|
||||
|
||||
<button id="navbar-toggler" class="navbar-toggler ml-0" type="button" data-toggle="collapse"
|
||||
data-toggle="tooltip" data-placement="bottom" data-target=".site-navigation" aria-controls="navbar-menu"
|
||||
aria-expanded="true" aria-label="Toggle navigation" aria-controls="site-navigation"
|
||||
title="Toggle navigation" data-toggle="tooltip" data-placement="left">
|
||||
<i class="fas fa-bars"></i>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="dropdown-buttons-trigger">
|
||||
<button id="dropdown-buttons-trigger" class="btn btn-secondary topbarbtn" aria-label="Download this page"><i
|
||||
class="fas fa-download"></i></button>
|
||||
|
||||
<div class="dropdown-buttons">
|
||||
<!-- ipynb file if we had a myst markdown file -->
|
||||
|
||||
<!-- Download raw file -->
|
||||
<a class="dropdown-buttons" href="../../_sources/tutorials/getting-started-gurobipy.ipynb.txt"><button type="button"
|
||||
class="btn btn-secondary topbarbtn" title="Download source file" data-toggle="tooltip"
|
||||
data-placement="left">.ipynb</button></a>
|
||||
<!-- Download PDF via print -->
|
||||
<button type="button" id="download-print" class="btn btn-secondary topbarbtn" title="Print to PDF"
|
||||
onClick="window.print()" data-toggle="tooltip" data-placement="left">.pdf</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source interaction buttons -->
|
||||
|
||||
<!-- Full screen (wrap in <a> to have style consistency -->
|
||||
|
||||
<a class="full-screen-button"><button type="button" class="btn btn-secondary topbarbtn" data-toggle="tooltip"
|
||||
data-placement="bottom" onclick="toggleFullScreen()" aria-label="Fullscreen mode"
|
||||
title="Fullscreen mode"><i
|
||||
class="fas fa-expand"></i></button></a>
|
||||
|
||||
<!-- Launch buttons -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table of contents -->
|
||||
<div class="d-none d-md-block col-md-2 bd-toc show">
|
||||
|
||||
<div class="tocsection onthispage pt-5 pb-3">
|
||||
<i class="fas fa-list"></i> Contents
|
||||
</div>
|
||||
<nav id="bd-toc-nav">
|
||||
<ul class="visible nav section-nav flex-column">
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Introduction">
|
||||
2.1. Introduction
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Installation">
|
||||
2.2. Installation
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Modeling-a-simple-optimization-problem">
|
||||
2.3. Modeling a simple optimization problem
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Generating-training-data">
|
||||
2.4. Generating training data
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Training-and-solving-test-instances">
|
||||
2.5. Training and solving test instances
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Accessing-the-solution">
|
||||
2.6. Accessing the solution
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-content" class="row">
|
||||
<div class="col-12 col-md-9 pl-md-3 pr-md-0">
|
||||
|
||||
<div>
|
||||
|
||||
<section id="Getting-started-(Gurobipy)">
|
||||
<h1><span class="section-number">2. </span>Getting started (Gurobipy)<a class="headerlink" href="#Getting-started-(Gurobipy)" title="Link to this heading">¶</a></h1>
|
||||
<section id="Introduction">
|
||||
<h2><span class="section-number">2.1. </span>Introduction<a class="headerlink" href="#Introduction" title="Link to this heading">¶</a></h2>
|
||||
<p><strong>MIPLearn</strong> is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Install the Python/Gurobipy version of MIPLearn</p></li>
|
||||
<li><p>Model a simple optimization problem using Gurobipy</p></li>
|
||||
<li><p>Generate training data and train the ML models</p></li>
|
||||
<li><p>Use the ML models together Gurobi to solve new instances</p></li>
|
||||
</ol>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The Python/Gurobipy version of MIPLearn is only compatible with the Gurobi Optimizer. For broader solver compatibility, see the Python/Pyomo and Julia/JuMP versions of the package.</p>
|
||||
</div>
|
||||
<div class="admonition warning">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Installation">
|
||||
<h2><span class="section-number">2.2. </span>Installation<a class="headerlink" href="#Installation" title="Link to this heading">¶</a></h2>
|
||||
<p>MIPLearn is available in two versions:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Python version, compatible with the Pyomo and Gurobipy modeling languages,</p></li>
|
||||
<li><p>Julia version, compatible with the JuMP modeling language.</p></li>
|
||||
</ul>
|
||||
<p>In this tutorial, we will demonstrate how to use and install the Python/Gurobipy version of the package. The first step is to install Python 3.8+ in your computer. See the <a class="reference external" href="https://www.python.org/downloads/">official Python website for more instructions</a>. After Python is installed, we proceed to install MIPLearn using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install MIPLearn==0.3
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>In addition to MIPLearn itself, we will also install Gurobi 10.0, a state-of-the-art commercial MILP solver. This step also install a demo license for Gurobi, which should able to solve the small optimization problems in this tutorial. A license is required for solving larger-scale problems.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install 'gurobipy>=10,<10.1'
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Python projects.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Modeling-a-simple-optimization-problem">
|
||||
<h2><span class="section-number">2.3. </span>Modeling a simple optimization problem<a class="headerlink" href="#Modeling-a-simple-optimization-problem" title="Link to this heading">¶</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>Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. 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).</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">
|
||||
\[\begin{split}\begin{align}
|
||||
\text{minimize } \quad & \sum_{i=1}^n \left( c^\text{fix}_i x_i + c^\text{var}_i y_i \right) \\
|
||||
\text{subject to } \quad & y_i \leq p^\text{max}_i x_i & i=1,\ldots,n \\
|
||||
& y_i \geq p^\text{min}_i x_i & i=1,\ldots,n \\
|
||||
& \sum_{i=1}^n y_i = d \\
|
||||
& x_i \in \{0,1\} & i=1,\ldots,n \\
|
||||
& y_i \geq 0 & i=1,\ldots,n
|
||||
\end{align}\end{split}\]</div>
|
||||
<div class="admonition note">
|
||||
<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.</p>
|
||||
</div>
|
||||
<p>Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Python and Pyomo. We start by defining a data class <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, which holds all the input data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[1]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
|
||||
|
||||
|
||||
<span class="nd">@dataclass</span>
|
||||
<span class="k">class</span> <span class="nc">UnitCommitmentData</span><span class="p">:</span>
|
||||
<span class="n">demand</span><span class="p">:</span> <span class="nb">float</span>
|
||||
<span class="n">pmin</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">pmax</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">cfix</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">cvar</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Next, we write a <code class="docutils literal notranslate"><span class="pre">build_uc_model</span></code> function, which converts the input data into a concrete Pyomo model. The function accepts <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, the data structure we previously defined, or the path to a compressed pickle file containing this data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">gurobipy</span> <span class="k">as</span> <span class="nn">gp</span>
|
||||
<span class="kn">from</span> <span class="nn">gurobipy</span> <span class="kn">import</span> <span class="n">GRB</span><span class="p">,</span> <span class="n">quicksum</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Union</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">read_pkl_gz</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.solvers.gurobi</span> <span class="kn">import</span> <span class="n">GurobiModel</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">build_uc_model</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">UnitCommitmentData</span><span class="p">])</span> <span class="o">-></span> <span class="n">GurobiModel</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">read_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="n">model</span> <span class="o">=</span> <span class="n">gp</span><span class="o">.</span><span class="n">Model</span><span class="p">()</span>
|
||||
<span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">)</span>
|
||||
<span class="n">x</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">_x</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">addVars</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">vtype</span><span class="o">=</span><span class="n">GRB</span><span class="o">.</span><span class="n">BINARY</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">"x"</span><span class="p">)</span>
|
||||
<span class="n">y</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">_y</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">addVars</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">"y"</span><span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">setObjective</span><span class="p">(</span>
|
||||
<span class="n">quicksum</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">cfix</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">data</span><span class="o">.</span><span class="n">cvar</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">addConstrs</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o"><=</span> <span class="n">data</span><span class="o">.</span><span class="n">pmax</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">addConstrs</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">>=</span> <span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">addConstr</span><span class="p">(</span><span class="n">quicksum</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="o">==</span> <span class="n">data</span><span class="o">.</span><span class="n">demand</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">GurobiModel</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>At this point, we can already use Pyomo and any mixed-integer linear programming solver to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></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">demand</span><span class="o">=</span><span class="mf">100.0</span><span class="p">,</span>
|
||||
<span class="n">pmin</span><span class="o">=</span><span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">],</span>
|
||||
<span class="n">pmax</span><span class="o">=</span><span class="p">[</span><span class="mi">50</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">70</span><span class="p">],</span>
|
||||
<span class="n">cfix</span><span class="o">=</span><span class="p">[</span><span class="mi">700</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="mi">500</span><span class="p">],</span>
|
||||
<span class="n">cvar</span><span class="o">=</span><span class="p">[</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">2.5</span><span class="p">],</span>
|
||||
<span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">optimize</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"obj ="</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">objVal</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"x ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">_x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"y ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">_y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Restricted license - for non-production use only - expires 2024-10-28
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 7 rows, 6 columns and 15 nonzeros
|
||||
Model fingerprint: 0x58dfdd53
|
||||
Variable types: 3 continuous, 3 integer (3 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 7e+01]
|
||||
Objective range [2e+00, 7e+02]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [1e+02, 1e+02]
|
||||
Presolve removed 2 rows and 1 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 5 rows, 5 columns, 13 nonzeros
|
||||
Variable types: 0 continuous, 5 integer (3 binary)
|
||||
Found heuristic solution: objective 1400.0000000
|
||||
|
||||
Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s
|
||||
0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s
|
||||
* 0 0 0 1320.0000000 1320.00000 0.00% - 0s
|
||||
|
||||
Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 2: 1320 1400
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%
|
||||
obj = 1320.0
|
||||
x = [-0.0, 1.0, 1.0]
|
||||
y = [0.0, 60.0, 40.0]
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>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.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<ul class="simple">
|
||||
<li><p>In the example above, <code class="docutils literal notranslate"><span class="pre">GurobiModel</span></code> is just a thin wrapper around a standard Gurobi model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as <code class="docutils literal notranslate"><span class="pre">optimize</span></code>. For more control, and to query the solution, the original Gurobi model can be accessed through <code class="docutils literal notranslate"><span class="pre">model.inner</span></code>, as illustrated above.</p></li>
|
||||
<li><p>To ensure training data consistency, MIPLearn requires all decision variables to have names.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Generating-training-data">
|
||||
<h2><span class="section-number">2.4. </span>Generating training data<a class="headerlink" href="#Generating-training-data" title="Link to this heading">¶</a></h2>
|
||||
<p>Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a <strong>trained</strong> solver, which can optimize new instances (similar to the ones it was trained on) faster.</p>
|
||||
<p>In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a
|
||||
random instance generator:</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">uniform</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
|
||||
<span class="kn">import</span> <span class="nn">random</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">seed</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="n">UnitCommitmentData</span><span class="p">]:</span>
|
||||
<span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
|
||||
<span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
|
||||
<span class="n">pmin</span> <span class="o">=</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">100_000.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">400_000.0</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">pmax</span> <span class="o">=</span> <span class="n">pmin</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">2.5</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">cfix</span> <span class="o">=</span> <span class="n">pmin</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">100.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">25.0</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">cvar</span> <span class="o">=</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">1.25</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.25</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[</span>
|
||||
<span class="n">UnitCommitmentData</span><span class="p">(</span>
|
||||
<span class="n">demand</span><span class="o">=</span><span class="n">pmax</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.25</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(),</span>
|
||||
<span class="n">pmin</span><span class="o">=</span><span class="n">pmin</span><span class="p">,</span>
|
||||
<span class="n">pmax</span><span class="o">=</span><span class="n">pmax</span><span class="p">,</span>
|
||||
<span class="n">cfix</span><span class="o">=</span><span class="n">cfix</span><span class="p">,</span>
|
||||
<span class="n">cvar</span><span class="o">=</span><span class="n">cvar</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">samples</span><span class="p">)</span>
|
||||
<span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.</p>
|
||||
<p>Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple
|
||||
machines. The code below generates the files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.pkl.gz</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.pkl.gz</span></code>, etc., which contain the input data in compressed (gzipped) pickle format.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[5]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">write_pkl_gz</span>
|
||||
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
|
||||
<span class="n">train_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">450</span><span class="p">],</span> <span class="s2">"uc/train"</span><span class="p">)</span>
|
||||
<span class="n">test_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">450</span><span class="p">:</span><span class="mi">500</span><span class="p">],</span> <span class="s2">"uc/test"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Finally, we use <code class="docutils literal notranslate"><span class="pre">BasicCollector</span></code> to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.h5</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.h5</span></code>, etc. The optimization models are also exported to compressed MPS files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.mps.gz</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.mps.gz</span></code>, etc.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[6]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.collectors.basic</span> <span class="kn">import</span> <span class="n">BasicCollector</span>
|
||||
|
||||
<span class="n">bc</span> <span class="o">=</span> <span class="n">BasicCollector</span><span class="p">()</span>
|
||||
<span class="n">bc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">train_data</span><span class="p">,</span> <span class="n">build_uc_model</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Training-and-solving-test-instances">
|
||||
<h2><span class="section-number">2.5. </span>Training and solving test instances<a class="headerlink" href="#Training-and-solving-test-instances" title="Link to this heading">¶</a></h2>
|
||||
<p>With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using <span class="math notranslate nohighlight">\(k\)</span>-nearest neighbors. More specifically, the strategy is to:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Memorize the optimal solutions of all training instances;</p></li>
|
||||
<li><p>Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;</p></li>
|
||||
<li><p>Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.</p></li>
|
||||
<li><p>Provide this partial solution to the solver as a warm start.</p></li>
|
||||
</ol>
|
||||
<p>This simple strategy can be implemented as shown below, using <code class="docutils literal notranslate"><span class="pre">MemorizingPrimalComponent</span></code>. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[7]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">sklearn.neighbors</span> <span class="kn">import</span> <span class="n">KNeighborsClassifier</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.components.primal.actions</span> <span class="kn">import</span> <span class="n">SetWarmStart</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.components.primal.mem</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">MemorizingPrimalComponent</span><span class="p">,</span>
|
||||
<span class="n">MergeTopSolutions</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.extractors.fields</span> <span class="kn">import</span> <span class="n">H5FieldsExtractor</span>
|
||||
|
||||
<span class="n">comp</span> <span class="o">=</span> <span class="n">MemorizingPrimalComponent</span><span class="p">(</span>
|
||||
<span class="n">clf</span><span class="o">=</span><span class="n">KNeighborsClassifier</span><span class="p">(</span><span class="n">n_neighbors</span><span class="o">=</span><span class="mi">25</span><span class="p">),</span>
|
||||
<span class="n">extractor</span><span class="o">=</span><span class="n">H5FieldsExtractor</span><span class="p">(</span>
|
||||
<span class="n">instance_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">"static_constr_rhs"</span><span class="p">],</span>
|
||||
<span class="p">),</span>
|
||||
<span class="n">constructor</span><span class="o">=</span><span class="n">MergeTopSolutions</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">]),</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="n">SetWarmStart</span><span class="p">(),</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Having defined the ML strategy, we next construct <code class="docutils literal notranslate"><span class="pre">LearningSolver</span></code>, train the ML component and optimize one of the test instances.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.solvers.learning</span> <span class="kn">import</span> <span class="n">LearningSolver</span>
|
||||
|
||||
<span class="n">solver_ml</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[</span><span class="n">comp</span><span class="p">])</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_uc_model</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xa8b70287
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.01s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.6166537e+09 5.648803e+04 0.000000e+00 0s
|
||||
1 8.2906219e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.290621916e+09
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xcf27855a
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
|
||||
User MIP start produced solution with objective 8.29153e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.29153e+09 (0.01s)
|
||||
Loaded user MIP start with objective 8.29153e+09
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 1
|
||||
Flow cover: 2
|
||||
|
||||
Explored 1 nodes (565 simplex iterations) in 0.03 seconds (0.01 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 1: 8.29153e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
{'WS: Count': 1, 'WS: Number of variables set': 482.0}
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>By examining the solve log above, specifically the line <code class="docutils literal notranslate"><span class="pre">Loaded</span> <span class="pre">user</span> <span class="pre">MIP</span> <span class="pre">start</span> <span class="pre">with</span> <span class="pre">objective...</span></code>, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[9]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">solver_baseline</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[])</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_uc_model</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xa8b70287
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.6166537e+09 5.648803e+04 0.000000e+00 0s
|
||||
1 8.2906219e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.290621916e+09
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x4cbbf7c7
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Found heuristic solution: objective 9.757128e+09
|
||||
|
||||
Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2906e+09 0 1 9.7571e+09 8.2906e+09 15.0% - 0s
|
||||
H 0 0 8.298273e+09 8.2906e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
H 0 0 8.293980e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 5 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 2 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 1 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
H 0 0 8.291465e+09 8.2908e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 2
|
||||
MIR: 1
|
||||
|
||||
Explored 1 nodes (1031 simplex iterations) in 0.15 seconds (0.03 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.291465302389e+09, best bound 8.290781665333e+09, gap 0.0082%
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[9]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
{}
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>In the log above, the <code class="docutils literal notranslate"><span class="pre">MIP</span> <span class="pre">start</span></code> line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems.</p>
|
||||
</section>
|
||||
<section id="Accessing-the-solution">
|
||||
<h2><span class="section-number">2.6. </span>Accessing the solution<a class="headerlink" href="#Accessing-the-solution" title="Link to this heading">¶</a></h2>
|
||||
<p>In the example above, we used <code class="docutils literal notranslate"><span class="pre">LearningSolver.solve</span></code> together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a Pyomo model entirely in-memory, using our trained solver.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[10]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">data</span> <span class="o">=</span> <span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)[</span><span class="mi">0</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">data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"obj ="</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">objVal</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"x ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">_x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"y ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">_y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x19042f12
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.5917580e+09 5.627453e+04 0.000000e+00 0s
|
||||
1 8.2535968e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.253596777e+09
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xf97cde91
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
|
||||
User MIP start produced solution with objective 8.25814e+09 (0.00s)
|
||||
User MIP start produced solution with objective 8.25512e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25459e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25459e+09 (0.01s)
|
||||
Loaded user MIP start with objective 8.25459e+09
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 8.253597e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2536e+09 0 1 8.2546e+09 8.2536e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 3 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 1 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 4 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 5 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 6 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Cover: 1
|
||||
MIR: 2
|
||||
StrongCG: 1
|
||||
Flow cover: 1
|
||||
|
||||
Explored 1 nodes (575 simplex iterations) in 0.05 seconds (0.01 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%
|
||||
obj = 8254590409.969726
|
||||
x = [1.0, 1.0, 0.0]
|
||||
y = [935662.0949262811, 1604270.0218116897, 0.0]
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[ ]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class='prev-next-bottom'>
|
||||
|
||||
<a class='left-prev' id="prev-link" href="../getting-started-pyomo/" title="previous page"><span class="section-number">1. </span>Getting started (Pyomo)</a>
|
||||
<a class='right-next' id="next-link" href="../getting-started-jump/" title="next page"><span class="section-number">3. </span>Getting started (JuMP)</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer mt-5 mt-md-0">
|
||||
<div class="container">
|
||||
<p>
|
||||
|
||||
© Copyright 2020-2023, UChicago Argonne, LLC.<br/>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../_static/js/index.1c5a1a01449ed65a7b51.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
680
0.4/tutorials/getting-started-jump.ipynb
Normal file
680
0.4/tutorials/getting-started-jump.ipynb
Normal file
@@ -0,0 +1,680 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6b8983b1",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"# Getting started (JuMP)\n",
|
||||
"\n",
|
||||
"## Introduction\n",
|
||||
"\n",
|
||||
"**MIPLearn** is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:\n",
|
||||
"\n",
|
||||
"1. Install the Julia/JuMP version of MIPLearn\n",
|
||||
"2. Model a simple optimization problem using JuMP\n",
|
||||
"3. Generate training data and train the ML models\n",
|
||||
"4. Use the ML models together Gurobi to solve new instances\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-warning\">\n",
|
||||
"Warning\n",
|
||||
" \n",
|
||||
"MIPLearn is still in early development stage. If run into any bugs or issues, please submit a bug report in our GitHub repository. Comments, suggestions and pull requests are also very welcome!\n",
|
||||
" \n",
|
||||
"</div>\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "02f0a927",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"MIPLearn is available in two versions:\n",
|
||||
"\n",
|
||||
"- Python version, compatible with the Pyomo and Gurobipy modeling languages,\n",
|
||||
"- Julia version, compatible with the JuMP modeling language.\n",
|
||||
"\n",
|
||||
"In this tutorial, we will demonstrate how to use and install the Python/Pyomo version of the package. The first step is to install Julia in your machine. See the [official Julia website for more instructions](https://julialang.org/downloads/). After Julia is installed, launch the Julia REPL, type `]` to enter package mode, then install MIPLearn:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"pkg> add MIPLearn@0.3\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8274543",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In addition to MIPLearn itself, we will also install:\n",
|
||||
"\n",
|
||||
"- the JuMP modeling language\n",
|
||||
"- Gurobi, a state-of-the-art commercial MILP solver\n",
|
||||
"- Distributions, to generate random data\n",
|
||||
"- PyCall, to access ML model from Scikit-Learn\n",
|
||||
"- Suppressor, to make the output cleaner\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"pkg> add JuMP@1, Gurobi@1, Distributions@0.25, PyCall@1, Suppressor@0.2\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a14e4550",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Note\n",
|
||||
"\n",
|
||||
"- If you do not have a Gurobi license available, you can also follow the tutorial by installing an open-source solver, such as `HiGHS`, and replacing `Gurobi.Optimizer` by `HiGHS.Optimizer` in all the code examples.\n",
|
||||
"- In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Julia projects.\n",
|
||||
" \n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "16b86823",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modeling a simple optimization problem\n",
|
||||
"\n",
|
||||
"To illustrate how can MIPLearn be used, we will model and solve a small optimization problem related to power systems optimization. The problem we discuss below is a simplification of the **unit commitment problem,** a practical optimization problem solved daily by electric grid operators around the world. \n",
|
||||
"\n",
|
||||
"Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. The total amount of power to be produced needs to be exactly equal to the total demand $d$ (in megawatts).\n",
|
||||
"\n",
|
||||
"This simple problem can be modeled as a *mixed-integer linear optimization* problem as follows. For each generator $g_i$, let $x_i \\in \\{0,1\\}$ be a decision variable indicating whether $g_i$ is online, and let $y_i \\geq 0$ be a decision variable indicating how much power does $g_i$ produce. The problem is then given by:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f12c3702",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"$$\n",
|
||||
"\\begin{align}\n",
|
||||
"\\text{minimize } \\quad & \\sum_{i=1}^n \\left( c^\\text{fix}_i x_i + c^\\text{var}_i y_i \\right) \\\\\n",
|
||||
"\\text{subject to } \\quad & y_i \\leq p^\\text{max}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq p^\\text{min}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& \\sum_{i=1}^n y_i = d \\\\\n",
|
||||
"& x_i \\in \\{0,1\\} & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq 0 & i=1,\\ldots,n\n",
|
||||
"\\end{align}\n",
|
||||
"$$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be3989ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"\n",
|
||||
"Note\n",
|
||||
"\n",
|
||||
"We use a simplified version of the unit commitment problem in this tutorial just to make it easier to follow. MIPLearn can also handle realistic, large-scale versions of this problem.\n",
|
||||
"\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a5fd33f6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Julia and JuMP. We start by defining a data class `UnitCommitmentData`, which holds all the input data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "c62ebff1-db40-45a1-9997-d121837f067b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"struct UnitCommitmentData\n",
|
||||
" demand::Float64\n",
|
||||
" pmin::Vector{Float64}\n",
|
||||
" pmax::Vector{Float64}\n",
|
||||
" cfix::Vector{Float64}\n",
|
||||
" cvar::Vector{Float64}\n",
|
||||
"end;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "29f55efa-0751-465a-9b0a-a821d46a3d40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we write a `build_uc_model` function, which converts the input data into a concrete JuMP model. The function accepts `UnitCommitmentData`, the data structure we previously defined, or the path to a JLD2 file containing this data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "79ef7775-18ca-4dfa-b438-49860f762ad0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"using MIPLearn\n",
|
||||
"using JuMP\n",
|
||||
"using Gurobi\n",
|
||||
"\n",
|
||||
"function build_uc_model(data)\n",
|
||||
" if data isa String\n",
|
||||
" data = read_jld2(data)\n",
|
||||
" end\n",
|
||||
" model = Model(Gurobi.Optimizer)\n",
|
||||
" G = 1:length(data.pmin)\n",
|
||||
" @variable(model, x[G], Bin)\n",
|
||||
" @variable(model, y[G] >= 0)\n",
|
||||
" @objective(model, Min, sum(data.cfix[g] * x[g] + data.cvar[g] * y[g] for g in G))\n",
|
||||
" @constraint(model, eq_max_power[g in G], y[g] <= data.pmax[g] * x[g])\n",
|
||||
" @constraint(model, eq_min_power[g in G], y[g] >= data.pmin[g] * x[g])\n",
|
||||
" @constraint(model, eq_demand, sum(y[g] for g in G) == data.demand)\n",
|
||||
" return JumpModel(model)\n",
|
||||
"end;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c22714a3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"At this point, we can already use Gurobi to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "dd828d68-fd43-4d2a-a058-3e2628d99d9e",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:01:10.993801745Z",
|
||||
"start_time": "2023-06-06T20:01:10.887580927Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n",
|
||||
"Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 7 rows, 6 columns and 15 nonzeros\n",
|
||||
"Model fingerprint: 0x55e33a07\n",
|
||||
"Variable types: 3 continuous, 3 integer (3 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 7e+01]\n",
|
||||
" Objective range [2e+00, 7e+02]\n",
|
||||
" Bounds range [0e+00, 0e+00]\n",
|
||||
" RHS range [1e+02, 1e+02]\n",
|
||||
"Presolve removed 2 rows and 1 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 5 rows, 5 columns, 13 nonzeros\n",
|
||||
"Variable types: 0 continuous, 5 integer (3 binary)\n",
|
||||
"Found heuristic solution: objective 1400.0000000\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s\n",
|
||||
" 0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s\n",
|
||||
"* 0 0 0 1320.0000000 1320.00000 0.00% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (5 simplex iterations) in 0.00 seconds (0.00 work units)\n",
|
||||
"Thread count was 32 (of 32 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 2: 1320 1400 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%\n",
|
||||
"\n",
|
||||
"User-callback calls 371, time in user-callback 0.00 sec\n",
|
||||
"objective_value(model.inner) = 1320.0\n",
|
||||
"Vector(value.(model.inner[:x])) = [-0.0, 1.0, 1.0]\n",
|
||||
"Vector(value.(model.inner[:y])) = [0.0, 60.0, 40.0]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = build_uc_model(\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" 100.0, # demand\n",
|
||||
" [10, 20, 30], # pmin\n",
|
||||
" [50, 60, 70], # pmax\n",
|
||||
" [700, 600, 500], # cfix\n",
|
||||
" [1.5, 2.0, 2.5], # cvar\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"model.optimize()\n",
|
||||
"@show objective_value(model.inner)\n",
|
||||
"@show Vector(value.(model.inner[:x]))\n",
|
||||
"@show Vector(value.(model.inner[:y]));"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "41b03bbc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Running the code above, we found that the optimal solution for our small problem instance costs \\$1320. It is achieve by keeping generators 2 and 3 online and producing, respectively, 60 MW and 40 MW of power."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "01f576e1-1790-425e-9e5c-9fa07b6f4c26",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Notes\n",
|
||||
" \n",
|
||||
"- In the example above, `JumpModel` is just a thin wrapper around a standard JuMP model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as `optimize`. For more control, and to query the solution, the original JuMP model can be accessed through `model.inner`, as illustrated above.\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf60c1dd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generating training data\n",
|
||||
"\n",
|
||||
"Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a **trained** solver, which can optimize new instances (similar to the ones it was trained on) faster.\n",
|
||||
"\n",
|
||||
"In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a random instance generator:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "1326efd7-3869-4137-ab6b-df9cb609a7e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"using Distributions\n",
|
||||
"using Random\n",
|
||||
"\n",
|
||||
"function random_uc_data(; samples::Int, n::Int, seed::Int=42)::Vector\n",
|
||||
" Random.seed!(seed)\n",
|
||||
" pmin = rand(Uniform(100_000, 500_000), n)\n",
|
||||
" pmax = pmin .* rand(Uniform(2, 2.5), n)\n",
|
||||
" cfix = pmin .* rand(Uniform(100, 125), n)\n",
|
||||
" cvar = rand(Uniform(1.25, 1.50), n)\n",
|
||||
" return [\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" sum(pmax) * rand(Uniform(0.5, 0.75)),\n",
|
||||
" pmin,\n",
|
||||
" pmax,\n",
|
||||
" cfix,\n",
|
||||
" cvar,\n",
|
||||
" )\n",
|
||||
" for _ in 1:samples\n",
|
||||
" ]\n",
|
||||
"end;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a03a7ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.\n",
|
||||
"\n",
|
||||
"Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple machines. The code below generates the files `uc/train/00001.jld2`, `uc/train/00002.jld2`, etc., which contain the input data in JLD2 format."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "6156752c",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:03:04.782830561Z",
|
||||
"start_time": "2023-06-06T20:03:04.530421396Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = random_uc_data(samples=500, n=500)\n",
|
||||
"train_data = write_jld2(data[1:450], \"uc/train\")\n",
|
||||
"test_data = write_jld2(data[451:500], \"uc/test\");"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b17af877",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we use `BasicCollector` to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files `uc/train/00001.h5`, `uc/train/00002.h5`, etc. The optimization models are also exported to compressed MPS files `uc/train/00001.mps.gz`, `uc/train/00002.mps.gz`, etc."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "7623f002",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:03:35.571497019Z",
|
||||
"start_time": "2023-06-06T20:03:25.804104036Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"using Suppressor\n",
|
||||
"@suppress_out begin\n",
|
||||
" bc = BasicCollector()\n",
|
||||
" bc.collect(train_data, build_uc_model)\n",
|
||||
"end"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c42b1be1-9723-4827-82d8-974afa51ef9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Training and solving test instances"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a33c6aa4-f0b8-4ccb-9935-01f7d7de2a1c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using $k$-nearest neighbors. More specifically, the strategy is to:\n",
|
||||
"\n",
|
||||
"1. Memorize the optimal solutions of all training instances;\n",
|
||||
"2. Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;\n",
|
||||
"3. Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.\n",
|
||||
"4. Provide this partial solution to the solver as a warm start.\n",
|
||||
"\n",
|
||||
"This simple strategy can be implemented as shown below, using `MemorizingPrimalComponent`. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "435f7bf8-4b09-4889-b1ec-b7b56e7d8ed2",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:20.497772794Z",
|
||||
"start_time": "2023-06-06T20:05:20.484821405Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load kNN classifier from Scikit-Learn\n",
|
||||
"using PyCall\n",
|
||||
"KNeighborsClassifier = pyimport(\"sklearn.neighbors\").KNeighborsClassifier\n",
|
||||
"\n",
|
||||
"# Build the MIPLearn component\n",
|
||||
"comp = MemorizingPrimalComponent(\n",
|
||||
" clf=KNeighborsClassifier(n_neighbors=25),\n",
|
||||
" extractor=H5FieldsExtractor(\n",
|
||||
" instance_fields=[\"static_constr_rhs\"],\n",
|
||||
" ),\n",
|
||||
" constructor=MergeTopSolutions(25, [0.0, 1.0]),\n",
|
||||
" action=SetWarmStart(),\n",
|
||||
");"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9536e7e4-0b0d-49b0-bebd-4a848f839e94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Having defined the ML strategy, we next construct `LearningSolver`, train the ML component and optimize one of the test instances."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "9d13dd50-3dcf-4673-a757-6f44dcc0dedf",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:22.672002339Z",
|
||||
"start_time": "2023-06-06T20:05:21.447466634Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n",
|
||||
"Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xd2378195\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [0e+00, 0e+00]\n",
|
||||
" RHS range [2e+08, 2e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 1.02165e+10 (0.00s)\n",
|
||||
"Loaded user MIP start with objective 1.02165e+10\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 1.021568e+10, 510 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 1.0216e+10 0 1 1.0217e+10 1.0216e+10 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (510 simplex iterations) in 0.01 seconds (0.00 work units)\n",
|
||||
"Thread count was 32 (of 32 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 1: 1.02165e+10 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 1.021651058978e+10, best bound 1.021567971257e+10, gap 0.0081%\n",
|
||||
"\n",
|
||||
"User-callback calls 169, time in user-callback 0.00 sec\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solver_ml = LearningSolver(components=[comp])\n",
|
||||
"solver_ml.fit(train_data)\n",
|
||||
"solver_ml.optimize(test_data[1], build_uc_model);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "61da6dad-7f56-4edb-aa26-c00eb5f946c0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"By examining the solve log above, specifically the line `Loaded user MIP start with objective...`, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "2ff391ed-e855-4228-aa09-a7641d8c2893",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:46.969575966Z",
|
||||
"start_time": "2023-06-06T20:05:46.420803286Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n",
|
||||
"Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0xb45c0594\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [0e+00, 0e+00]\n",
|
||||
" RHS range [2e+08, 2e+08]\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Found heuristic solution: objective 1.071463e+10\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 1.021568e+10, 510 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 1.0216e+10 0 1 1.0715e+10 1.0216e+10 4.66% - 0s\n",
|
||||
"H 0 0 1.025162e+10 1.0216e+10 0.35% - 0s\n",
|
||||
" 0 0 1.0216e+10 0 1 1.0252e+10 1.0216e+10 0.35% - 0s\n",
|
||||
"H 0 0 1.023090e+10 1.0216e+10 0.15% - 0s\n",
|
||||
"H 0 0 1.022335e+10 1.0216e+10 0.07% - 0s\n",
|
||||
"H 0 0 1.022281e+10 1.0216e+10 0.07% - 0s\n",
|
||||
"H 0 0 1.021753e+10 1.0216e+10 0.02% - 0s\n",
|
||||
"H 0 0 1.021752e+10 1.0216e+10 0.02% - 0s\n",
|
||||
" 0 0 1.0216e+10 0 3 1.0218e+10 1.0216e+10 0.02% - 0s\n",
|
||||
" 0 0 1.0216e+10 0 1 1.0218e+10 1.0216e+10 0.02% - 0s\n",
|
||||
"H 0 0 1.021651e+10 1.0216e+10 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (764 simplex iterations) in 0.03 seconds (0.02 work units)\n",
|
||||
"Thread count was 32 (of 32 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 7: 1.02165e+10 1.02175e+10 1.02228e+10 ... 1.07146e+10\n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 1.021651058978e+10, best bound 1.021573363741e+10, gap 0.0076%\n",
|
||||
"\n",
|
||||
"User-callback calls 204, time in user-callback 0.00 sec\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solver_baseline = LearningSolver(components=[])\n",
|
||||
"solver_baseline.fit(train_data)\n",
|
||||
"solver_baseline.optimize(test_data[1], build_uc_model);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b6d37b88-9fcc-43ee-ac1e-2a7b1e51a266",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In the log above, the `MIP start` line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "eec97f06",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"## Accessing the solution\n",
|
||||
"\n",
|
||||
"In the example above, we used `LearningSolver.solve` together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a JuMP model entirely in-memory, using our trained solver."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "67a6cd18",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:06:26.913448568Z",
|
||||
"start_time": "2023-06-06T20:06:26.169047914Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n",
|
||||
"Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x974a7fba\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 1e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [0e+00, 0e+00]\n",
|
||||
" RHS range [2e+08, 2e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 9.86729e+09 (0.00s)\n",
|
||||
"User MIP start produced solution with objective 9.86675e+09 (0.00s)\n",
|
||||
"User MIP start produced solution with objective 9.86654e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 9.8661e+09 (0.01s)\n",
|
||||
"Loaded user MIP start with objective 9.8661e+09\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 9.865344e+09, 510 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 9.8653e+09 0 1 9.8661e+09 9.8653e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (510 simplex iterations) in 0.02 seconds (0.01 work units)\n",
|
||||
"Thread count was 32 (of 32 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 9.8661e+09 9.86654e+09 9.86675e+09 9.86729e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 9.866096485614e+09, best bound 9.865343669936e+09, gap 0.0076%\n",
|
||||
"\n",
|
||||
"User-callback calls 182, time in user-callback 0.00 sec\n",
|
||||
"objective_value(model.inner) = 9.866096485613789e9\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data = random_uc_data(samples=1, n=500)[1]\n",
|
||||
"model = build_uc_model(data)\n",
|
||||
"solver_ml.optimize(model)\n",
|
||||
"@show objective_value(model.inner);"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Julia 1.9.0",
|
||||
"language": "julia",
|
||||
"name": "julia-1.9"
|
||||
},
|
||||
"language_info": {
|
||||
"file_extension": ".jl",
|
||||
"mimetype": "application/julia",
|
||||
"name": "julia",
|
||||
"version": "1.9.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
755
0.4/tutorials/getting-started-jump/index.html
Normal file
755
0.4/tutorials/getting-started-jump/index.html
Normal file
@@ -0,0 +1,755 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>3. Getting started (JuMP) — MIPLearn 0.4</title>
|
||||
|
||||
<link href="../../_static/css/theme.css" rel="stylesheet" />
|
||||
<link href="../../_static/css/index.c5995385ac14fb8791e8eb36b4908be2.css" rel="stylesheet" />
|
||||
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="../../_static/vendor/fontawesome/5.13.0/css/all.min.css">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-solid-900.woff2">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-brands-400.woff2">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=362ab14a" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css?v=b0dfe17c" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css?v=2aa19091" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/custom.css?v=f8244a84" />
|
||||
|
||||
<link rel="preload" as="script" href="../../_static/js/index.1c5a1a01449ed65a7b51.js">
|
||||
|
||||
<script src="../../_static/documentation_options.js?v=751a5dd3"></script>
|
||||
<script src="../../_static/doctools.js?v=888ff710"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script crossorigin="anonymous" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"></script>
|
||||
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js?v=7c4c3336"></script>
|
||||
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"inlineMath": [["\\(", "\\)"]], "displayMath": [["\\[", "\\]"]], "processRefs": false, "processEnvironments": false}})</script>
|
||||
<script>window.MathJax = {"tex": {"inlineMath": [["$", "$"], ["\\(", "\\)"]], "processEscapes": true}, "options": {"ignoreHtmlClass": "tex2jax_ignore|mathjax_ignore|document", "processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}</script>
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<link rel="index" title="Index" href="../../genindex/" />
|
||||
<link rel="search" title="Search" href="../../search/" />
|
||||
<link rel="next" title="4. User cuts and lazy constraints" href="../cuts-gurobipy/" />
|
||||
<link rel="prev" title="2. Getting started (Gurobipy)" href="../getting-started-gurobipy/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="docsearch:language" content="en" />
|
||||
|
||||
</head>
|
||||
<body data-spy="scroll" data-target="#bd-toc-nav" data-offset="80">
|
||||
|
||||
<div class="container-fluid" id="banner"></div>
|
||||
|
||||
|
||||
|
||||
<div class="container-xl">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 col-md-3 bd-sidebar site-navigation show" id="site-navigation">
|
||||
|
||||
<div class="navbar-brand-box">
|
||||
<a class="navbar-brand text-wrap" href="../../">
|
||||
|
||||
|
||||
<h1 class="site-logo" id="site-title">MIPLearn 0.4</h1>
|
||||
|
||||
</a>
|
||||
</div><form class="bd-search d-flex align-items-center" action="../../search/" method="get">
|
||||
<i class="icon fas fa-search"></i>
|
||||
<input type="search" class="form-control" name="q" id="search-input" placeholder="Search the docs ..." aria-label="Search the docs ..." autocomplete="off" >
|
||||
</form><nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
|
||||
<div class="bd-toc-item active">
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Tutorials
|
||||
</span>
|
||||
</p>
|
||||
<ul class="current nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-pyomo/">
|
||||
1. Getting started (Pyomo)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-gurobipy/">
|
||||
2. Getting started (Gurobipy)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1 current active">
|
||||
<a class="current reference internal" href="#">
|
||||
3. Getting started (JuMP)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../cuts-gurobipy/">
|
||||
4. User cuts and lazy constraints
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
User Guide
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/problems/">
|
||||
5. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/collectors/">
|
||||
6. Training Data Collectors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/features/">
|
||||
7. Feature Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/primal/">
|
||||
8. Primal Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/solvers/">
|
||||
9. Learning Solver
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Python API Reference
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/problems/">
|
||||
10. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/collectors/">
|
||||
11. Collectors & Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/components/">
|
||||
12. Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/solvers/">
|
||||
13. Solvers
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/helpers/">
|
||||
14. Helpers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav> <!-- To handle the deprecated key -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="col py-md-3 pl-md-4 bd-content overflow-auto" role="main">
|
||||
|
||||
<div class="topbar container-xl fixed-top">
|
||||
<div class="topbar-contents row">
|
||||
<div class="col-12 col-md-3 bd-topbar-whitespace site-navigation show"></div>
|
||||
<div class="col pl-md-4 topbar-main">
|
||||
|
||||
<button id="navbar-toggler" class="navbar-toggler ml-0" type="button" data-toggle="collapse"
|
||||
data-toggle="tooltip" data-placement="bottom" data-target=".site-navigation" aria-controls="navbar-menu"
|
||||
aria-expanded="true" aria-label="Toggle navigation" aria-controls="site-navigation"
|
||||
title="Toggle navigation" data-toggle="tooltip" data-placement="left">
|
||||
<i class="fas fa-bars"></i>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="dropdown-buttons-trigger">
|
||||
<button id="dropdown-buttons-trigger" class="btn btn-secondary topbarbtn" aria-label="Download this page"><i
|
||||
class="fas fa-download"></i></button>
|
||||
|
||||
<div class="dropdown-buttons">
|
||||
<!-- ipynb file if we had a myst markdown file -->
|
||||
|
||||
<!-- Download raw file -->
|
||||
<a class="dropdown-buttons" href="../../_sources/tutorials/getting-started-jump.ipynb.txt"><button type="button"
|
||||
class="btn btn-secondary topbarbtn" title="Download source file" data-toggle="tooltip"
|
||||
data-placement="left">.ipynb</button></a>
|
||||
<!-- Download PDF via print -->
|
||||
<button type="button" id="download-print" class="btn btn-secondary topbarbtn" title="Print to PDF"
|
||||
onClick="window.print()" data-toggle="tooltip" data-placement="left">.pdf</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source interaction buttons -->
|
||||
|
||||
<!-- Full screen (wrap in <a> to have style consistency -->
|
||||
|
||||
<a class="full-screen-button"><button type="button" class="btn btn-secondary topbarbtn" data-toggle="tooltip"
|
||||
data-placement="bottom" onclick="toggleFullScreen()" aria-label="Fullscreen mode"
|
||||
title="Fullscreen mode"><i
|
||||
class="fas fa-expand"></i></button></a>
|
||||
|
||||
<!-- Launch buttons -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table of contents -->
|
||||
<div class="d-none d-md-block col-md-2 bd-toc show">
|
||||
|
||||
<div class="tocsection onthispage pt-5 pb-3">
|
||||
<i class="fas fa-list"></i> Contents
|
||||
</div>
|
||||
<nav id="bd-toc-nav">
|
||||
<ul class="visible nav section-nav flex-column">
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Introduction">
|
||||
3.1. Introduction
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Installation">
|
||||
3.2. Installation
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Modeling-a-simple-optimization-problem">
|
||||
3.3. Modeling a simple optimization problem
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Generating-training-data">
|
||||
3.4. Generating training data
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Training-and-solving-test-instances">
|
||||
3.5. Training and solving test instances
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Accessing-the-solution">
|
||||
3.6. Accessing the solution
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-content" class="row">
|
||||
<div class="col-12 col-md-9 pl-md-3 pr-md-0">
|
||||
|
||||
<div>
|
||||
|
||||
<section id="Getting-started-(JuMP)">
|
||||
<h1><span class="section-number">3. </span>Getting started (JuMP)<a class="headerlink" href="#Getting-started-(JuMP)" title="Link to this heading">¶</a></h1>
|
||||
<section id="Introduction">
|
||||
<h2><span class="section-number">3.1. </span>Introduction<a class="headerlink" href="#Introduction" title="Link to this heading">¶</a></h2>
|
||||
<p><strong>MIPLearn</strong> is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Install the Julia/JuMP version of MIPLearn</p></li>
|
||||
<li><p>Model a simple optimization problem using JuMP</p></li>
|
||||
<li><p>Generate training data and train the ML models</p></li>
|
||||
<li><p>Use the ML models together Gurobi to solve new instances</p></li>
|
||||
</ol>
|
||||
<div class="admonition warning">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Installation">
|
||||
<h2><span class="section-number">3.2. </span>Installation<a class="headerlink" href="#Installation" title="Link to this heading">¶</a></h2>
|
||||
<p>MIPLearn is available in two versions:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Python version, compatible with the Pyomo and Gurobipy modeling languages,</p></li>
|
||||
<li><p>Julia version, compatible with the JuMP modeling language.</p></li>
|
||||
</ul>
|
||||
<p>In this tutorial, we will demonstrate how to use and install the Python/Pyomo version of the package. The first step is to install Julia in your machine. See the <a class="reference external" href="https://julialang.org/downloads/">official Julia website for more instructions</a>. After Julia is installed, launch the Julia REPL, type <code class="docutils literal notranslate"><span class="pre">]</span></code> to enter package mode, then install MIPLearn:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>pkg> add MIPLearn@0.3
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>In addition to MIPLearn itself, we will also install:</p>
|
||||
<ul class="simple">
|
||||
<li><p>the JuMP modeling language</p></li>
|
||||
<li><p>Gurobi, a state-of-the-art commercial MILP solver</p></li>
|
||||
<li><p>Distributions, to generate random data</p></li>
|
||||
<li><p>PyCall, to access ML model from Scikit-Learn</p></li>
|
||||
<li><p>Suppressor, to make the output cleaner</p></li>
|
||||
</ul>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>pkg> add JuMP@1, Gurobi@1, Distributions@0.25, PyCall@1, Suppressor@0.2
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<ul class="simple">
|
||||
<li><p>If you do not have a Gurobi license available, you can also follow the tutorial by installing an open-source solver, such as <code class="docutils literal notranslate"><span class="pre">HiGHS</span></code>, and replacing <code class="docutils literal notranslate"><span class="pre">Gurobi.Optimizer</span></code> by <code class="docutils literal notranslate"><span class="pre">HiGHS.Optimizer</span></code> in all the code examples.</p></li>
|
||||
<li><p>In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Julia projects.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Modeling-a-simple-optimization-problem">
|
||||
<h2><span class="section-number">3.3. </span>Modeling a simple optimization problem<a class="headerlink" href="#Modeling-a-simple-optimization-problem" title="Link to this heading">¶</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>Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. 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).</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">
|
||||
\[\begin{split}\begin{align}
|
||||
\text{minimize } \quad & \sum_{i=1}^n \left( c^\text{fix}_i x_i + c^\text{var}_i y_i \right) \\
|
||||
\text{subject to } \quad & y_i \leq p^\text{max}_i x_i & i=1,\ldots,n \\
|
||||
& y_i \geq p^\text{min}_i x_i & i=1,\ldots,n \\
|
||||
& \sum_{i=1}^n y_i = d \\
|
||||
& x_i \in \{0,1\} & i=1,\ldots,n \\
|
||||
& y_i \geq 0 & i=1,\ldots,n
|
||||
\end{align}\end{split}\]</div>
|
||||
<div class="admonition note">
|
||||
<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.</p>
|
||||
</div>
|
||||
<p>Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Julia and JuMP. We start by defining a data class <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, which holds all the input data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[1]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="k">struct</span> <span class="kt">UnitCommitmentData</span>
|
||||
<span class="w"> </span><span class="n">demand</span><span class="o">::</span><span class="kt">Float64</span>
|
||||
<span class="w"> </span><span class="n">pmin</span><span class="o">::</span><span class="kt">Vector</span><span class="p">{</span><span class="kt">Float64</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="n">pmax</span><span class="o">::</span><span class="kt">Vector</span><span class="p">{</span><span class="kt">Float64</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="n">cfix</span><span class="o">::</span><span class="kt">Vector</span><span class="p">{</span><span class="kt">Float64</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="n">cvar</span><span class="o">::</span><span class="kt">Vector</span><span class="p">{</span><span class="kt">Float64</span><span class="p">}</span>
|
||||
<span class="k">end</span><span class="p">;</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Next, we write a <code class="docutils literal notranslate"><span class="pre">build_uc_model</span></code> function, which converts the input data into a concrete JuMP model. The function accepts <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, the data structure we previously defined, or the path to a JLD2 file containing this data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="k">using</span><span class="w"> </span><span class="n">MIPLearn</span>
|
||||
<span class="k">using</span><span class="w"> </span><span class="n">JuMP</span>
|
||||
<span class="k">using</span><span class="w"> </span><span class="n">Gurobi</span>
|
||||
|
||||
<span class="k">function</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="k">isa</span><span class="w"> </span><span class="kt">String</span>
|
||||
<span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_jld2</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="k">end</span>
|
||||
<span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Model</span><span class="p">(</span><span class="n">Gurobi</span><span class="o">.</span><span class="n">Optimizer</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="n">G</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="o">:</span><span class="n">length</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="nd">@variable</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">[</span><span class="n">G</span><span class="p">],</span><span class="w"> </span><span class="n">Bin</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="nd">@variable</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">[</span><span class="n">G</span><span class="p">]</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="nd">@objective</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">Min</span><span class="p">,</span><span class="w"> </span><span class="n">sum</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">cfix</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">data</span><span class="o">.</span><span class="n">cvar</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">y</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">g</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">G</span><span class="p">))</span>
|
||||
<span class="w"> </span><span class="nd">@constraint</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">eq_max_power</span><span class="p">[</span><span class="n">g</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">G</span><span class="p">],</span><span class="w"> </span><span class="n">y</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">data</span><span class="o">.</span><span class="n">pmax</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="p">[</span><span class="n">g</span><span class="p">])</span>
|
||||
<span class="w"> </span><span class="nd">@constraint</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">eq_min_power</span><span class="p">[</span><span class="n">g</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">G</span><span class="p">],</span><span class="w"> </span><span class="n">y</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="p">[</span><span class="n">g</span><span class="p">])</span>
|
||||
<span class="w"> </span><span class="nd">@constraint</span><span class="p">(</span><span class="n">model</span><span class="p">,</span><span class="w"> </span><span class="n">eq_demand</span><span class="p">,</span><span class="w"> </span><span class="n">sum</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">g</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">G</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">data</span><span class="o">.</span><span class="n">demand</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">JumpModel</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
<span class="k">end</span><span class="p">;</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>At this point, we can already use Gurobi to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="n">model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">UnitCommitmentData</span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="c"># demand</span>
|
||||
<span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">],</span><span class="w"> </span><span class="c"># pmin</span>
|
||||
<span class="w"> </span><span class="p">[</span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">60</span><span class="p">,</span><span class="w"> </span><span class="mi">70</span><span class="p">],</span><span class="w"> </span><span class="c"># pmax</span>
|
||||
<span class="w"> </span><span class="p">[</span><span class="mi">700</span><span class="p">,</span><span class="w"> </span><span class="mi">600</span><span class="p">,</span><span class="w"> </span><span class="mi">500</span><span class="p">],</span><span class="w"> </span><span class="c"># cfix</span>
|
||||
<span class="w"> </span><span class="p">[</span><span class="mf">1.5</span><span class="p">,</span><span class="w"> </span><span class="mf">2.0</span><span class="p">,</span><span class="w"> </span><span class="mf">2.5</span><span class="p">],</span><span class="w"> </span><span class="c"># cvar</span>
|
||||
<span class="w"> </span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">optimize</span><span class="p">()</span>
|
||||
<span class="nd">@show</span><span class="w"> </span><span class="n">objective_value</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="p">)</span>
|
||||
<span class="nd">@show</span><span class="w"> </span><span class="kt">Vector</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="p">[</span><span class="ss">:x</span><span class="p">]))</span>
|
||||
<span class="nd">@show</span><span class="w"> </span><span class="kt">Vector</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="p">[</span><span class="ss">:y</span><span class="p">]));</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
|
||||
|
||||
CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]
|
||||
Thread count: 16 physical cores, 32 logical processors, using up to 32 threads
|
||||
|
||||
Optimize a model with 7 rows, 6 columns and 15 nonzeros
|
||||
Model fingerprint: 0x55e33a07
|
||||
Variable types: 3 continuous, 3 integer (3 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 7e+01]
|
||||
Objective range [2e+00, 7e+02]
|
||||
Bounds range [0e+00, 0e+00]
|
||||
RHS range [1e+02, 1e+02]
|
||||
Presolve removed 2 rows and 1 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 5 rows, 5 columns, 13 nonzeros
|
||||
Variable types: 0 continuous, 5 integer (3 binary)
|
||||
Found heuristic solution: objective 1400.0000000
|
||||
|
||||
Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s
|
||||
0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s
|
||||
* 0 0 0 1320.0000000 1320.00000 0.00% - 0s
|
||||
|
||||
Explored 1 nodes (5 simplex iterations) in 0.00 seconds (0.00 work units)
|
||||
Thread count was 32 (of 32 available processors)
|
||||
|
||||
Solution count 2: 1320 1400
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%
|
||||
|
||||
User-callback calls 371, time in user-callback 0.00 sec
|
||||
objective_value(model.inner) = 1320.0
|
||||
Vector(value.(model.inner[:x])) = [-0.0, 1.0, 1.0]
|
||||
Vector(value.(model.inner[:y])) = [0.0, 60.0, 40.0]
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>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.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Notes</p>
|
||||
<ul class="simple">
|
||||
<li><p>In the example above, <code class="docutils literal notranslate"><span class="pre">JumpModel</span></code> is just a thin wrapper around a standard JuMP model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as <code class="docutils literal notranslate"><span class="pre">optimize</span></code>. For more control, and to query the solution, the original JuMP model can be accessed through <code class="docutils literal notranslate"><span class="pre">model.inner</span></code>, as illustrated above.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Generating-training-data">
|
||||
<h2><span class="section-number">3.4. </span>Generating training data<a class="headerlink" href="#Generating-training-data" title="Link to this heading">¶</a></h2>
|
||||
<p>Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a <strong>trained</strong> solver, which can optimize new instances (similar to the ones it was trained on) faster.</p>
|
||||
<p>In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a
|
||||
random instance generator:</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="k">using</span><span class="w"> </span><span class="n">Distributions</span>
|
||||
<span class="k">using</span><span class="w"> </span><span class="n">Random</span>
|
||||
|
||||
<span class="k">function</span><span class="w"> </span><span class="n">random_uc_data</span><span class="p">(;</span><span class="w"> </span><span class="n">samples</span><span class="o">::</span><span class="kt">Int</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="o">::</span><span class="kt">Int</span><span class="p">,</span><span class="w"> </span><span class="n">seed</span><span class="o">::</span><span class="kt">Int</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span><span class="o">::</span><span class="kt">Vector</span>
|
||||
<span class="w"> </span><span class="n">Random</span><span class="o">.</span><span class="n">seed!</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="n">pmin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span><span class="p">(</span><span class="n">Uniform</span><span class="p">(</span><span class="mi">100_000</span><span class="p">,</span><span class="w"> </span><span class="mi">500_000</span><span class="p">),</span><span class="w"> </span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="n">pmax</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pmin</span><span class="w"> </span><span class="o">.*</span><span class="w"> </span><span class="n">rand</span><span class="p">(</span><span class="n">Uniform</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mf">2.5</span><span class="p">),</span><span class="w"> </span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="n">cfix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pmin</span><span class="w"> </span><span class="o">.*</span><span class="w"> </span><span class="n">rand</span><span class="p">(</span><span class="n">Uniform</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">125</span><span class="p">),</span><span class="w"> </span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="n">cvar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rand</span><span class="p">(</span><span class="n">Uniform</span><span class="p">(</span><span class="mf">1.25</span><span class="p">,</span><span class="w"> </span><span class="mf">1.50</span><span class="p">),</span><span class="w"> </span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="n">UnitCommitmentData</span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">sum</span><span class="p">(</span><span class="n">pmax</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">rand</span><span class="p">(</span><span class="n">Uniform</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)),</span>
|
||||
<span class="w"> </span><span class="n">pmin</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">pmax</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">cfix</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="n">cvar</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">1</span><span class="o">:</span><span class="n">samples</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="k">end</span><span class="p">;</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.</p>
|
||||
<p>Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple
|
||||
machines. The code below generates the files <code class="docutils literal notranslate"><span class="pre">uc/train/00001.jld2</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00002.jld2</span></code>, etc., which contain the input data in JLD2 format.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[5]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
|
||||
<span class="n">train_data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">write_jld2</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">450</span><span class="p">],</span><span class="w"> </span><span class="s">"uc/train"</span><span class="p">)</span>
|
||||
<span class="n">test_data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">write_jld2</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">451</span><span class="o">:</span><span class="mi">500</span><span class="p">],</span><span class="w"> </span><span class="s">"uc/test"</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Finally, we use <code class="docutils literal notranslate"><span class="pre">BasicCollector</span></code> to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files <code class="docutils literal notranslate"><span class="pre">uc/train/00001.h5</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00002.h5</span></code>, etc. The optimization models are also exported to compressed MPS files <code class="docutils literal notranslate"><span class="pre">uc/train/00001.mps.gz</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00002.mps.gz</span></code>, etc.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[6]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="k">using</span><span class="w"> </span><span class="n">Suppressor</span>
|
||||
<span class="nd">@suppress_out</span><span class="w"> </span><span class="k">begin</span>
|
||||
<span class="w"> </span><span class="n">bc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BasicCollector</span><span class="p">()</span>
|
||||
<span class="w"> </span><span class="n">bc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">train_data</span><span class="p">,</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">)</span>
|
||||
<span class="k">end</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Training-and-solving-test-instances">
|
||||
<h2><span class="section-number">3.5. </span>Training and solving test instances<a class="headerlink" href="#Training-and-solving-test-instances" title="Link to this heading">¶</a></h2>
|
||||
<p>With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using <span class="math notranslate nohighlight">\(k\)</span>-nearest neighbors. More specifically, the strategy is to:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Memorize the optimal solutions of all training instances;</p></li>
|
||||
<li><p>Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;</p></li>
|
||||
<li><p>Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.</p></li>
|
||||
<li><p>Provide this partial solution to the solver as a warm start.</p></li>
|
||||
</ol>
|
||||
<p>This simple strategy can be implemented as shown below, using <code class="docutils literal notranslate"><span class="pre">MemorizingPrimalComponent</span></code>. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[7]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="c"># Load kNN classifier from Scikit-Learn</span>
|
||||
<span class="k">using</span><span class="w"> </span><span class="n">PyCall</span>
|
||||
<span class="n">KNeighborsClassifier</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pyimport</span><span class="p">(</span><span class="s">"sklearn.neighbors"</span><span class="p">)</span><span class="o">.</span><span class="n">KNeighborsClassifier</span>
|
||||
|
||||
<span class="c"># Build the MIPLearn component</span>
|
||||
<span class="n">comp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MemorizingPrimalComponent</span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">clf</span><span class="o">=</span><span class="n">KNeighborsClassifier</span><span class="p">(</span><span class="n">n_neighbors</span><span class="o">=</span><span class="mi">25</span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="n">extractor</span><span class="o">=</span><span class="n">H5FieldsExtractor</span><span class="p">(</span>
|
||||
<span class="w"> </span><span class="n">instance_fields</span><span class="o">=</span><span class="p">[</span><span class="s">"static_constr_rhs"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="n">constructor</span><span class="o">=</span><span class="n">MergeTopSolutions</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]),</span>
|
||||
<span class="w"> </span><span class="n">action</span><span class="o">=</span><span class="n">SetWarmStart</span><span class="p">(),</span>
|
||||
<span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Having defined the ML strategy, we next construct <code class="docutils literal notranslate"><span class="pre">LearningSolver</span></code>, train the ML component and optimize one of the test instances.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="n">solver_ml</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[</span><span class="n">comp</span><span class="p">])</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
|
||||
|
||||
CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]
|
||||
Thread count: 16 physical cores, 32 logical processors, using up to 32 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xd2378195
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [0e+00, 0e+00]
|
||||
RHS range [2e+08, 2e+08]
|
||||
|
||||
User MIP start produced solution with objective 1.02165e+10 (0.00s)
|
||||
Loaded user MIP start with objective 1.02165e+10
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 1.021568e+10, 510 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 1.0216e+10 0 1 1.0217e+10 1.0216e+10 0.01% - 0s
|
||||
|
||||
Explored 1 nodes (510 simplex iterations) in 0.01 seconds (0.00 work units)
|
||||
Thread count was 32 (of 32 available processors)
|
||||
|
||||
Solution count 1: 1.02165e+10
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 1.021651058978e+10, best bound 1.021567971257e+10, gap 0.0081%
|
||||
|
||||
User-callback calls 169, time in user-callback 0.00 sec
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>By examining the solve log above, specifically the line <code class="docutils literal notranslate"><span class="pre">Loaded</span> <span class="pre">user</span> <span class="pre">MIP</span> <span class="pre">start</span> <span class="pre">with</span> <span class="pre">objective...</span></code>, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[9]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="n">solver_baseline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[])</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
|
||||
|
||||
CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]
|
||||
Thread count: 16 physical cores, 32 logical processors, using up to 32 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0xb45c0594
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [0e+00, 0e+00]
|
||||
RHS range [2e+08, 2e+08]
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Found heuristic solution: objective 1.071463e+10
|
||||
|
||||
Root relaxation: objective 1.021568e+10, 510 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 1.0216e+10 0 1 1.0715e+10 1.0216e+10 4.66% - 0s
|
||||
H 0 0 1.025162e+10 1.0216e+10 0.35% - 0s
|
||||
0 0 1.0216e+10 0 1 1.0252e+10 1.0216e+10 0.35% - 0s
|
||||
H 0 0 1.023090e+10 1.0216e+10 0.15% - 0s
|
||||
H 0 0 1.022335e+10 1.0216e+10 0.07% - 0s
|
||||
H 0 0 1.022281e+10 1.0216e+10 0.07% - 0s
|
||||
H 0 0 1.021753e+10 1.0216e+10 0.02% - 0s
|
||||
H 0 0 1.021752e+10 1.0216e+10 0.02% - 0s
|
||||
0 0 1.0216e+10 0 3 1.0218e+10 1.0216e+10 0.02% - 0s
|
||||
0 0 1.0216e+10 0 1 1.0218e+10 1.0216e+10 0.02% - 0s
|
||||
H 0 0 1.021651e+10 1.0216e+10 0.01% - 0s
|
||||
|
||||
Explored 1 nodes (764 simplex iterations) in 0.03 seconds (0.02 work units)
|
||||
Thread count was 32 (of 32 available processors)
|
||||
|
||||
Solution count 7: 1.02165e+10 1.02175e+10 1.02228e+10 ... 1.07146e+10
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 1.021651058978e+10, best bound 1.021573363741e+10, gap 0.0076%
|
||||
|
||||
User-callback calls 204, time in user-callback 0.00 sec
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>In the log above, the <code class="docutils literal notranslate"><span class="pre">MIP</span> <span class="pre">start</span></code> line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems.</p>
|
||||
</section>
|
||||
<section id="Accessing-the-solution">
|
||||
<h2><span class="section-number">3.6. </span>Accessing the solution<a class="headerlink" href="#Accessing-the-solution" title="Link to this heading">¶</a></h2>
|
||||
<p>In the example above, we used <code class="docutils literal notranslate"><span class="pre">LearningSolver.solve</span></code> together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a JuMP model entirely in-memory, using our trained solver.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[10]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-julia notranslate"><div class="highlight"><pre><span></span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
|
||||
<span class="n">model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">build_uc_model</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
<span class="nd">@show</span><span class="w"> </span><span class="n">objective_value</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)
|
||||
|
||||
CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]
|
||||
Thread count: 16 physical cores, 32 logical processors, using up to 32 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x974a7fba
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 1e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [0e+00, 0e+00]
|
||||
RHS range [2e+08, 2e+08]
|
||||
|
||||
User MIP start produced solution with objective 9.86729e+09 (0.00s)
|
||||
User MIP start produced solution with objective 9.86675e+09 (0.00s)
|
||||
User MIP start produced solution with objective 9.86654e+09 (0.01s)
|
||||
User MIP start produced solution with objective 9.8661e+09 (0.01s)
|
||||
Loaded user MIP start with objective 9.8661e+09
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 9.865344e+09, 510 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 9.8653e+09 0 1 9.8661e+09 9.8653e+09 0.01% - 0s
|
||||
|
||||
Explored 1 nodes (510 simplex iterations) in 0.02 seconds (0.01 work units)
|
||||
Thread count was 32 (of 32 available processors)
|
||||
|
||||
Solution count 4: 9.8661e+09 9.86654e+09 9.86675e+09 9.86729e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 9.866096485614e+09, best bound 9.865343669936e+09, gap 0.0076%
|
||||
|
||||
User-callback calls 182, time in user-callback 0.00 sec
|
||||
objective_value(model.inner) = 9.866096485613789e9
|
||||
</pre></div></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class='prev-next-bottom'>
|
||||
|
||||
<a class='left-prev' id="prev-link" href="../getting-started-gurobipy/" title="previous page"><span class="section-number">2. </span>Getting started (Gurobipy)</a>
|
||||
<a class='right-next' id="next-link" href="../cuts-gurobipy/" title="next page"><span class="section-number">4. </span>User cuts and lazy constraints</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer mt-5 mt-md-0">
|
||||
<div class="container">
|
||||
<p>
|
||||
|
||||
© Copyright 2020-2023, UChicago Argonne, LLC.<br/>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../_static/js/index.1c5a1a01449ed65a7b51.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
858
0.4/tutorials/getting-started-pyomo.ipynb
Normal file
858
0.4/tutorials/getting-started-pyomo.ipynb
Normal file
@@ -0,0 +1,858 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6b8983b1",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"# Getting started (Pyomo)\n",
|
||||
"\n",
|
||||
"## Introduction\n",
|
||||
"\n",
|
||||
"**MIPLearn** is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:\n",
|
||||
"\n",
|
||||
"1. Install the Python/Pyomo version of MIPLearn\n",
|
||||
"2. Model a simple optimization problem using Pyomo\n",
|
||||
"3. Generate training data and train the ML models\n",
|
||||
"4. Use the ML models together Gurobi to solve new instances\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"Note\n",
|
||||
" \n",
|
||||
"The Python/Pyomo version of MIPLearn is currently only compatible with Pyomo persistent solvers (Gurobi, CPLEX and XPRESS). For broader solver compatibility, see the Julia/JuMP version of the package.\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"<div class=\"alert alert-warning\">\n",
|
||||
"Warning\n",
|
||||
" \n",
|
||||
"MIPLearn is still in early development stage. If run into any bugs or issues, please submit a bug report in our GitHub repository. Comments, suggestions and pull requests are also very welcome!\n",
|
||||
" \n",
|
||||
"</div>\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "02f0a927",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"MIPLearn is available in two versions:\n",
|
||||
"\n",
|
||||
"- Python version, compatible with the Pyomo and Gurobipy modeling languages,\n",
|
||||
"- Julia version, compatible with the JuMP modeling language.\n",
|
||||
"\n",
|
||||
"In this tutorial, we will demonstrate how to use and install the Python/Pyomo version of the package. The first step is to install Python 3.8+ in your computer. See the [official Python website for more instructions](https://www.python.org/downloads/). After Python is installed, we proceed to install MIPLearn using `pip`:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"$ pip install MIPLearn==0.3\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"In addition to MIPLearn itself, we will also install Gurobi 10.0, a state-of-the-art commercial MILP solver. This step also install a demo license for Gurobi, which should able to solve the small optimization problems in this tutorial. A license is required for solving larger-scale problems.\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"$ pip install 'gurobipy>=10,<10.1'\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a14e4550",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Note\n",
|
||||
" \n",
|
||||
"In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Python projects.\n",
|
||||
" \n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "16b86823",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modeling a simple optimization problem\n",
|
||||
"\n",
|
||||
"To illustrate how can MIPLearn be used, we will model and solve a small optimization problem related to power systems optimization. The problem we discuss below is a simplification of the **unit commitment problem,** a practical optimization problem solved daily by electric grid operators around the world. \n",
|
||||
"\n",
|
||||
"Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. The total amount of power to be produced needs to be exactly equal to the total demand $d$ (in megawatts).\n",
|
||||
"\n",
|
||||
"This simple problem can be modeled as a *mixed-integer linear optimization* problem as follows. For each generator $g_i$, let $x_i \\in \\{0,1\\}$ be a decision variable indicating whether $g_i$ is online, and let $y_i \\geq 0$ be a decision variable indicating how much power does $g_i$ produce. The problem is then given by:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f12c3702",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"$$\n",
|
||||
"\\begin{align}\n",
|
||||
"\\text{minimize } \\quad & \\sum_{i=1}^n \\left( c^\\text{fix}_i x_i + c^\\text{var}_i y_i \\right) \\\\\n",
|
||||
"\\text{subject to } \\quad & y_i \\leq p^\\text{max}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq p^\\text{min}_i x_i & i=1,\\ldots,n \\\\\n",
|
||||
"& \\sum_{i=1}^n y_i = d \\\\\n",
|
||||
"& x_i \\in \\{0,1\\} & i=1,\\ldots,n \\\\\n",
|
||||
"& y_i \\geq 0 & i=1,\\ldots,n\n",
|
||||
"\\end{align}\n",
|
||||
"$$"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be3989ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
"\n",
|
||||
"Note\n",
|
||||
"\n",
|
||||
"We use a simplified version of the unit commitment problem in this tutorial just to make it easier to follow. MIPLearn can also handle realistic, large-scale versions of this problem.\n",
|
||||
"\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a5fd33f6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Python and Pyomo. We start by defining a data class `UnitCommitmentData`, which holds all the input data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "22a67170-10b4-43d3-8708-014d91141e73",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:00:03.278853343Z",
|
||||
"start_time": "2023-06-06T20:00:03.123324067Z"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class UnitCommitmentData:\n",
|
||||
" demand: float\n",
|
||||
" pmin: List[float]\n",
|
||||
" pmax: List[float]\n",
|
||||
" cfix: List[float]\n",
|
||||
" cvar: List[float]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "29f55efa-0751-465a-9b0a-a821d46a3d40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we write a `build_uc_model` function, which converts the input data into a concrete Pyomo model. The function accepts `UnitCommitmentData`, the data structure we previously defined, or the path to a compressed pickle file containing this data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "2f67032f-0d74-4317-b45c-19da0ec859e9",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:00:45.890126754Z",
|
||||
"start_time": "2023-06-06T20:00:45.637044282Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pyomo.environ as pe\n",
|
||||
"from typing import Union\n",
|
||||
"from miplearn.io import read_pkl_gz\n",
|
||||
"from miplearn.solvers.pyomo import PyomoModel\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def build_uc_model(data: Union[str, UnitCommitmentData]) -> PyomoModel:\n",
|
||||
" if isinstance(data, str):\n",
|
||||
" data = read_pkl_gz(data)\n",
|
||||
"\n",
|
||||
" model = pe.ConcreteModel()\n",
|
||||
" n = len(data.pmin)\n",
|
||||
" model.x = pe.Var(range(n), domain=pe.Binary)\n",
|
||||
" model.y = pe.Var(range(n), domain=pe.NonNegativeReals)\n",
|
||||
" model.obj = pe.Objective(\n",
|
||||
" expr=sum(\n",
|
||||
" data.cfix[i] * model.x[i] + data.cvar[i] * model.y[i] for i in range(n)\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" model.eq_max_power = pe.ConstraintList()\n",
|
||||
" model.eq_min_power = pe.ConstraintList()\n",
|
||||
" for i in range(n):\n",
|
||||
" model.eq_max_power.add(model.y[i] <= data.pmax[i] * model.x[i])\n",
|
||||
" model.eq_min_power.add(model.y[i] >= data.pmin[i] * model.x[i])\n",
|
||||
" model.eq_demand = pe.Constraint(\n",
|
||||
" expr=sum(model.y[i] for i in range(n)) == data.demand,\n",
|
||||
" )\n",
|
||||
" return PyomoModel(model, \"gurobi_persistent\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c22714a3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"At this point, we can already use Pyomo and any mixed-integer linear programming solver to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2a896f47",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:01:10.993801745Z",
|
||||
"start_time": "2023-06-06T20:01:10.887580927Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Restricted license - for non-production use only - expires 2024-10-28\n",
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 7 rows, 6 columns and 15 nonzeros\n",
|
||||
"Model fingerprint: 0x15c7a953\n",
|
||||
"Variable types: 3 continuous, 3 integer (3 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 7e+01]\n",
|
||||
" Objective range [2e+00, 7e+02]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [1e+02, 1e+02]\n",
|
||||
"Presolve removed 2 rows and 1 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 5 rows, 5 columns, 13 nonzeros\n",
|
||||
"Variable types: 0 continuous, 5 integer (3 binary)\n",
|
||||
"Found heuristic solution: objective 1400.0000000\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s\n",
|
||||
" 0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s\n",
|
||||
"* 0 0 0 1320.0000000 1320.00000 0.00% - 0s\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 2: 1320 1400 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%\n",
|
||||
"WARNING: Cannot get reduced costs for MIP.\n",
|
||||
"WARNING: Cannot get duals for MIP.\n",
|
||||
"obj = 1320.0\n",
|
||||
"x = [-0.0, 1.0, 1.0]\n",
|
||||
"y = [0.0, 60.0, 40.0]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = build_uc_model(\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" demand=100.0,\n",
|
||||
" pmin=[10, 20, 30],\n",
|
||||
" pmax=[50, 60, 70],\n",
|
||||
" cfix=[700, 600, 500],\n",
|
||||
" cvar=[1.5, 2.0, 2.5],\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model.optimize()\n",
|
||||
"print(\"obj =\", model.inner.obj())\n",
|
||||
"print(\"x =\", [model.inner.x[i].value for i in range(3)])\n",
|
||||
"print(\"y =\", [model.inner.y[i].value for i in range(3)])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "41b03bbc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Running the code above, we found that the optimal solution for our small problem instance costs \\$1320. It is achieve by keeping generators 2 and 3 online and producing, respectively, 60 MW and 40 MW of power."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "01f576e1-1790-425e-9e5c-9fa07b6f4c26",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"alert alert-info\">\n",
|
||||
" \n",
|
||||
"Notes\n",
|
||||
" \n",
|
||||
"- In the example above, `PyomoModel` is just a thin wrapper around a standard Pyomo model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as `optimize`. For more control, and to query the solution, the original Pyomo model can be accessed through `model.inner`, as illustrated above. \n",
|
||||
"- To use CPLEX or XPRESS, instead of Gurobi, replace `gurobi_persistent` by `cplex_persistent` or `xpress_persistent` in the `build_uc_model`. Note that only persistent Pyomo solvers are currently supported. Pull requests adding support for other types of solver are very welcome.\n",
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf60c1dd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generating training data\n",
|
||||
"\n",
|
||||
"Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a **trained** solver, which can optimize new instances (similar to the ones it was trained on) faster.\n",
|
||||
"\n",
|
||||
"In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a random instance generator:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5eb09fab",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:02:27.324208900Z",
|
||||
"start_time": "2023-06-06T20:02:26.990044230Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from scipy.stats import uniform\n",
|
||||
"from typing import List\n",
|
||||
"import random\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def random_uc_data(samples: int, n: int, seed: int = 42) -> List[UnitCommitmentData]:\n",
|
||||
" random.seed(seed)\n",
|
||||
" np.random.seed(seed)\n",
|
||||
" pmin = uniform(loc=100_000.0, scale=400_000.0).rvs(n)\n",
|
||||
" pmax = pmin * uniform(loc=2.0, scale=2.5).rvs(n)\n",
|
||||
" cfix = pmin * uniform(loc=100.0, scale=25.0).rvs(n)\n",
|
||||
" cvar = uniform(loc=1.25, scale=0.25).rvs(n)\n",
|
||||
" return [\n",
|
||||
" UnitCommitmentData(\n",
|
||||
" demand=pmax.sum() * uniform(loc=0.5, scale=0.25).rvs(),\n",
|
||||
" pmin=pmin,\n",
|
||||
" pmax=pmax,\n",
|
||||
" cfix=cfix,\n",
|
||||
" cvar=cvar,\n",
|
||||
" )\n",
|
||||
" for _ in range(samples)\n",
|
||||
" ]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a03a7ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.\n",
|
||||
"\n",
|
||||
"Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple machines. The code below generates the files `uc/train/00000.pkl.gz`, `uc/train/00001.pkl.gz`, etc., which contain the input data in compressed (gzipped) pickle format."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "6156752c",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:03:04.782830561Z",
|
||||
"start_time": "2023-06-06T20:03:04.530421396Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from miplearn.io import write_pkl_gz\n",
|
||||
"\n",
|
||||
"data = random_uc_data(samples=500, n=500)\n",
|
||||
"train_data = write_pkl_gz(data[0:450], \"uc/train\")\n",
|
||||
"test_data = write_pkl_gz(data[450:500], \"uc/test\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b17af877",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we use `BasicCollector` to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files `uc/train/00000.h5`, `uc/train/00001.h5`, etc. The optimization models are also exported to compressed MPS files `uc/train/00000.mps.gz`, `uc/train/00001.mps.gz`, etc."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "7623f002",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:03:35.571497019Z",
|
||||
"start_time": "2023-06-06T20:03:25.804104036Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from miplearn.collectors.basic import BasicCollector\n",
|
||||
"\n",
|
||||
"bc = BasicCollector()\n",
|
||||
"bc.collect(train_data, build_uc_model, n_jobs=4)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c42b1be1-9723-4827-82d8-974afa51ef9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Training and solving test instances"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a33c6aa4-f0b8-4ccb-9935-01f7d7de2a1c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using $k$-nearest neighbors. More specifically, the strategy is to:\n",
|
||||
"\n",
|
||||
"1. Memorize the optimal solutions of all training instances;\n",
|
||||
"2. Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;\n",
|
||||
"3. Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.\n",
|
||||
"4. Provide this partial solution to the solver as a warm start.\n",
|
||||
"\n",
|
||||
"This simple strategy can be implemented as shown below, using `MemorizingPrimalComponent`. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "435f7bf8-4b09-4889-b1ec-b7b56e7d8ed2",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:20.497772794Z",
|
||||
"start_time": "2023-06-06T20:05:20.484821405Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from sklearn.neighbors import KNeighborsClassifier\n",
|
||||
"from miplearn.components.primal.actions import SetWarmStart\n",
|
||||
"from miplearn.components.primal.mem import (\n",
|
||||
" MemorizingPrimalComponent,\n",
|
||||
" MergeTopSolutions,\n",
|
||||
")\n",
|
||||
"from miplearn.extractors.fields import H5FieldsExtractor\n",
|
||||
"\n",
|
||||
"comp = MemorizingPrimalComponent(\n",
|
||||
" clf=KNeighborsClassifier(n_neighbors=25),\n",
|
||||
" extractor=H5FieldsExtractor(\n",
|
||||
" instance_fields=[\"static_constr_rhs\"],\n",
|
||||
" ),\n",
|
||||
" constructor=MergeTopSolutions(25, [0.0, 1.0]),\n",
|
||||
" action=SetWarmStart(),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9536e7e4-0b0d-49b0-bebd-4a848f839e94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Having defined the ML strategy, we next construct `LearningSolver`, train the ML component and optimize one of the test instances."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "9d13dd50-3dcf-4673-a757-6f44dcc0dedf",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:22.672002339Z",
|
||||
"start_time": "2023-06-06T20:05:21.447466634Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x5e67c6ee\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.6166537e+09 5.648803e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2906219e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.290621916e+09\n",
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x4a7cfe2b\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 8.29153e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.29153e+09 (0.01s)\n",
|
||||
"Loaded user MIP start with objective 8.29153e+09\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 1\n",
|
||||
" Flow cover: 2\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (565 simplex iterations) in 0.04 seconds (0.01 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 1: 8.29153e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%\n",
|
||||
"WARNING: Cannot get reduced costs for MIP.\n",
|
||||
"WARNING: Cannot get duals for MIP.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{}"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from miplearn.solvers.learning import LearningSolver\n",
|
||||
"\n",
|
||||
"solver_ml = LearningSolver(components=[comp])\n",
|
||||
"solver_ml.fit(train_data)\n",
|
||||
"solver_ml.optimize(test_data[0], build_uc_model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "61da6dad-7f56-4edb-aa26-c00eb5f946c0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"By examining the solve log above, specifically the line `Loaded user MIP start with objective...`, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "2ff391ed-e855-4228-aa09-a7641d8c2893",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:05:46.969575966Z",
|
||||
"start_time": "2023-06-06T20:05:46.420803286Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x5e67c6ee\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.6166537e+09 5.648803e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2906219e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.290621916e+09\n",
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x8a0f9587\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Found heuristic solution: objective 9.757128e+09\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2906e+09 0 1 9.7571e+09 8.2906e+09 15.0% - 0s\n",
|
||||
"H 0 0 8.298273e+09 8.2906e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s\n",
|
||||
"H 0 0 8.293980e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 5 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 1 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2907e+09 0 2 8.2940e+09 8.2907e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 1 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
" 0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s\n",
|
||||
"H 0 0 8.291465e+09 8.2908e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Gomory: 2\n",
|
||||
" MIR: 1\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (1025 simplex iterations) in 0.12 seconds (0.03 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.291465302389e+09, best bound 8.290781665333e+09, gap 0.0082%\n",
|
||||
"WARNING: Cannot get reduced costs for MIP.\n",
|
||||
"WARNING: Cannot get duals for MIP.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{}"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"solver_baseline = LearningSolver(components=[])\n",
|
||||
"solver_baseline.fit(train_data)\n",
|
||||
"solver_baseline.optimize(test_data[0], build_uc_model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b6d37b88-9fcc-43ee-ac1e-2a7b1e51a266",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In the log above, the `MIP start` line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "eec97f06",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"## Accessing the solution\n",
|
||||
"\n",
|
||||
"In the example above, we used `LearningSolver.solve` together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a Pyomo model entirely in-memory, using our trained solver."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "67a6cd18",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2023-06-06T20:06:26.913448568Z",
|
||||
"start_time": "2023-06-06T20:06:26.169047914Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x2dfe4e1c\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"Presolve removed 1000 rows and 500 columns\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1 rows, 500 columns, 500 nonzeros\n",
|
||||
"\n",
|
||||
"Iteration Objective Primal Inf. Dual Inf. Time\n",
|
||||
" 0 6.5917580e+09 5.627453e+04 0.000000e+00 0s\n",
|
||||
" 1 8.2535968e+09 0.000000e+00 0.000000e+00 0s\n",
|
||||
"\n",
|
||||
"Solved in 1 iterations and 0.01 seconds (0.00 work units)\n",
|
||||
"Optimal objective 8.253596777e+09\n",
|
||||
"Set parameter QCPDual to value 1\n",
|
||||
"Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
|
||||
"\n",
|
||||
"CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n",
|
||||
"Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n",
|
||||
"\n",
|
||||
"Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n",
|
||||
"Model fingerprint: 0x0f0924a1\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"Coefficient statistics:\n",
|
||||
" Matrix range [1e+00, 2e+06]\n",
|
||||
" Objective range [1e+00, 6e+07]\n",
|
||||
" Bounds range [1e+00, 1e+00]\n",
|
||||
" RHS range [3e+08, 3e+08]\n",
|
||||
"\n",
|
||||
"User MIP start produced solution with objective 8.25814e+09 (0.00s)\n",
|
||||
"User MIP start produced solution with objective 8.25512e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25483e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25459e+09 (0.01s)\n",
|
||||
"User MIP start produced solution with objective 8.25459e+09 (0.01s)\n",
|
||||
"Loaded user MIP start with objective 8.25459e+09\n",
|
||||
"\n",
|
||||
"Presolve time: 0.00s\n",
|
||||
"Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n",
|
||||
"Variable types: 500 continuous, 500 integer (500 binary)\n",
|
||||
"\n",
|
||||
"Root relaxation: objective 8.253597e+09, 512 iterations, 0.00 seconds (0.00 work units)\n",
|
||||
"\n",
|
||||
" Nodes | Current Node | Objective Bounds | Work\n",
|
||||
" Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n",
|
||||
"\n",
|
||||
" 0 0 8.2536e+09 0 1 8.2546e+09 8.2536e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 3 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 1 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 4 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 5 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
" 0 0 8.2538e+09 0 6 8.2546e+09 8.2538e+09 0.01% - 0s\n",
|
||||
"\n",
|
||||
"Cutting planes:\n",
|
||||
" Cover: 1\n",
|
||||
" MIR: 2\n",
|
||||
" StrongCG: 1\n",
|
||||
" Flow cover: 1\n",
|
||||
"\n",
|
||||
"Explored 1 nodes (575 simplex iterations) in 0.09 seconds (0.01 work units)\n",
|
||||
"Thread count was 20 (of 20 available processors)\n",
|
||||
"\n",
|
||||
"Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09 \n",
|
||||
"\n",
|
||||
"Optimal solution found (tolerance 1.00e-04)\n",
|
||||
"Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%\n",
|
||||
"WARNING: Cannot get reduced costs for MIP.\n",
|
||||
"WARNING: Cannot get duals for MIP.\n",
|
||||
"obj = 8254590409.96973\n",
|
||||
" x = [1.0, 1.0, 0.0, 1.0, 1.0]\n",
|
||||
" y = [935662.0949262811, 1604270.0218116897, 0.0, 1369560.835229226, 602828.5321028307]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data = random_uc_data(samples=1, n=500)[0]\n",
|
||||
"model = build_uc_model(data)\n",
|
||||
"solver_ml.optimize(model)\n",
|
||||
"print(\"obj =\", model.inner.obj())\n",
|
||||
"print(\" x =\", [model.inner.x[i].value for i in range(5)])\n",
|
||||
"print(\" y =\", [model.inner.y[i].value for i in range(5)])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5593d23a-83bd-4e16-8253-6300f5e3f63b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
909
0.4/tutorials/getting-started-pyomo/index.html
Normal file
909
0.4/tutorials/getting-started-pyomo/index.html
Normal file
@@ -0,0 +1,909 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../../">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>1. Getting started (Pyomo) — MIPLearn 0.4</title>
|
||||
|
||||
<link href="../../_static/css/theme.css" rel="stylesheet" />
|
||||
<link href="../../_static/css/index.c5995385ac14fb8791e8eb36b4908be2.css" rel="stylesheet" />
|
||||
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="../../_static/vendor/fontawesome/5.13.0/css/all.min.css">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-solid-900.woff2">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin
|
||||
href="../../_static/vendor/fontawesome/5.13.0/webfonts/fa-brands-400.woff2">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=362ab14a" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css?v=b0dfe17c" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css?v=2aa19091" />
|
||||
<link rel="stylesheet" type="text/css" href="../../_static/custom.css?v=f8244a84" />
|
||||
|
||||
<link rel="preload" as="script" href="../../_static/js/index.1c5a1a01449ed65a7b51.js">
|
||||
|
||||
<script src="../../_static/documentation_options.js?v=751a5dd3"></script>
|
||||
<script src="../../_static/doctools.js?v=888ff710"></script>
|
||||
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script crossorigin="anonymous" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"></script>
|
||||
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js?v=7c4c3336"></script>
|
||||
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"inlineMath": [["\\(", "\\)"]], "displayMath": [["\\[", "\\]"]], "processRefs": false, "processEnvironments": false}})</script>
|
||||
<script>window.MathJax = {"tex": {"inlineMath": [["$", "$"], ["\\(", "\\)"]], "processEscapes": true}, "options": {"ignoreHtmlClass": "tex2jax_ignore|mathjax_ignore|document", "processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}</script>
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<link rel="index" title="Index" href="../../genindex/" />
|
||||
<link rel="search" title="Search" href="../../search/" />
|
||||
<link rel="next" title="2. Getting started (Gurobipy)" href="../getting-started-gurobipy/" />
|
||||
<link rel="prev" title="MIPLearn" href="../../" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="docsearch:language" content="en" />
|
||||
|
||||
</head>
|
||||
<body data-spy="scroll" data-target="#bd-toc-nav" data-offset="80">
|
||||
|
||||
<div class="container-fluid" id="banner"></div>
|
||||
|
||||
|
||||
|
||||
<div class="container-xl">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 col-md-3 bd-sidebar site-navigation show" id="site-navigation">
|
||||
|
||||
<div class="navbar-brand-box">
|
||||
<a class="navbar-brand text-wrap" href="../../">
|
||||
|
||||
|
||||
<h1 class="site-logo" id="site-title">MIPLearn 0.4</h1>
|
||||
|
||||
</a>
|
||||
</div><form class="bd-search d-flex align-items-center" action="../../search/" method="get">
|
||||
<i class="icon fas fa-search"></i>
|
||||
<input type="search" class="form-control" name="q" id="search-input" placeholder="Search the docs ..." aria-label="Search the docs ..." autocomplete="off" >
|
||||
</form><nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
|
||||
<div class="bd-toc-item active">
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Tutorials
|
||||
</span>
|
||||
</p>
|
||||
<ul class="current nav bd-sidenav">
|
||||
<li class="toctree-l1 current active">
|
||||
<a class="current reference internal" href="#">
|
||||
1. Getting started (Pyomo)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-gurobipy/">
|
||||
2. Getting started (Gurobipy)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../getting-started-jump/">
|
||||
3. Getting started (JuMP)
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../cuts-gurobipy/">
|
||||
4. User cuts and lazy constraints
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
User Guide
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/problems/">
|
||||
5. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/collectors/">
|
||||
6. Training Data Collectors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/features/">
|
||||
7. Feature Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/primal/">
|
||||
8. Primal Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../guide/solvers/">
|
||||
9. Learning Solver
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="caption" role="heading">
|
||||
<span class="caption-text">
|
||||
Python API Reference
|
||||
</span>
|
||||
</p>
|
||||
<ul class="nav bd-sidenav">
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/problems/">
|
||||
10. Benchmark Problems
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/collectors/">
|
||||
11. Collectors & Extractors
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/components/">
|
||||
12. Components
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/solvers/">
|
||||
13. Solvers
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference internal" href="../../api/helpers/">
|
||||
14. Helpers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav> <!-- To handle the deprecated key -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="col py-md-3 pl-md-4 bd-content overflow-auto" role="main">
|
||||
|
||||
<div class="topbar container-xl fixed-top">
|
||||
<div class="topbar-contents row">
|
||||
<div class="col-12 col-md-3 bd-topbar-whitespace site-navigation show"></div>
|
||||
<div class="col pl-md-4 topbar-main">
|
||||
|
||||
<button id="navbar-toggler" class="navbar-toggler ml-0" type="button" data-toggle="collapse"
|
||||
data-toggle="tooltip" data-placement="bottom" data-target=".site-navigation" aria-controls="navbar-menu"
|
||||
aria-expanded="true" aria-label="Toggle navigation" aria-controls="site-navigation"
|
||||
title="Toggle navigation" data-toggle="tooltip" data-placement="left">
|
||||
<i class="fas fa-bars"></i>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="dropdown-buttons-trigger">
|
||||
<button id="dropdown-buttons-trigger" class="btn btn-secondary topbarbtn" aria-label="Download this page"><i
|
||||
class="fas fa-download"></i></button>
|
||||
|
||||
<div class="dropdown-buttons">
|
||||
<!-- ipynb file if we had a myst markdown file -->
|
||||
|
||||
<!-- Download raw file -->
|
||||
<a class="dropdown-buttons" href="../../_sources/tutorials/getting-started-pyomo.ipynb.txt"><button type="button"
|
||||
class="btn btn-secondary topbarbtn" title="Download source file" data-toggle="tooltip"
|
||||
data-placement="left">.ipynb</button></a>
|
||||
<!-- Download PDF via print -->
|
||||
<button type="button" id="download-print" class="btn btn-secondary topbarbtn" title="Print to PDF"
|
||||
onClick="window.print()" data-toggle="tooltip" data-placement="left">.pdf</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source interaction buttons -->
|
||||
|
||||
<!-- Full screen (wrap in <a> to have style consistency -->
|
||||
|
||||
<a class="full-screen-button"><button type="button" class="btn btn-secondary topbarbtn" data-toggle="tooltip"
|
||||
data-placement="bottom" onclick="toggleFullScreen()" aria-label="Fullscreen mode"
|
||||
title="Fullscreen mode"><i
|
||||
class="fas fa-expand"></i></button></a>
|
||||
|
||||
<!-- Launch buttons -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table of contents -->
|
||||
<div class="d-none d-md-block col-md-2 bd-toc show">
|
||||
|
||||
<div class="tocsection onthispage pt-5 pb-3">
|
||||
<i class="fas fa-list"></i> Contents
|
||||
</div>
|
||||
<nav id="bd-toc-nav">
|
||||
<ul class="visible nav section-nav flex-column">
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Introduction">
|
||||
1.1. Introduction
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Installation">
|
||||
1.2. Installation
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Modeling-a-simple-optimization-problem">
|
||||
1.3. Modeling a simple optimization problem
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Generating-training-data">
|
||||
1.4. Generating training data
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Training-and-solving-test-instances">
|
||||
1.5. Training and solving test instances
|
||||
</a>
|
||||
</li>
|
||||
<li class="toc-h2 nav-item toc-entry">
|
||||
<a class="reference internal nav-link" href="#Accessing-the-solution">
|
||||
1.6. Accessing the solution
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-content" class="row">
|
||||
<div class="col-12 col-md-9 pl-md-3 pr-md-0">
|
||||
|
||||
<div>
|
||||
|
||||
<section id="Getting-started-(Pyomo)">
|
||||
<h1><span class="section-number">1. </span>Getting started (Pyomo)<a class="headerlink" href="#Getting-started-(Pyomo)" title="Link to this heading">¶</a></h1>
|
||||
<section id="Introduction">
|
||||
<h2><span class="section-number">1.1. </span>Introduction<a class="headerlink" href="#Introduction" title="Link to this heading">¶</a></h2>
|
||||
<p><strong>MIPLearn</strong> is an open source framework that uses machine learning (ML) to accelerate the performance of mixed-integer programming solvers (e.g. Gurobi, CPLEX, XPRESS). In this tutorial, we will:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Install the Python/Pyomo version of MIPLearn</p></li>
|
||||
<li><p>Model a simple optimization problem using Pyomo</p></li>
|
||||
<li><p>Generate training data and train the ML models</p></li>
|
||||
<li><p>Use the ML models together Gurobi to solve new instances</p></li>
|
||||
</ol>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The Python/Pyomo version of MIPLearn is currently only compatible with Pyomo persistent solvers (Gurobi, CPLEX and XPRESS). For broader solver compatibility, see the Julia/JuMP version of the package.</p>
|
||||
</div>
|
||||
<div class="admonition warning">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Installation">
|
||||
<h2><span class="section-number">1.2. </span>Installation<a class="headerlink" href="#Installation" title="Link to this heading">¶</a></h2>
|
||||
<p>MIPLearn is available in two versions:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Python version, compatible with the Pyomo and Gurobipy modeling languages,</p></li>
|
||||
<li><p>Julia version, compatible with the JuMP modeling language.</p></li>
|
||||
</ul>
|
||||
<p>In this tutorial, we will demonstrate how to use and install the Python/Pyomo version of the package. The first step is to install Python 3.8+ in your computer. See the <a class="reference external" href="https://www.python.org/downloads/">official Python website for more instructions</a>. After Python is installed, we proceed to install MIPLearn using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install MIPLearn==0.3
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>In addition to MIPLearn itself, we will also install Gurobi 10.0, a state-of-the-art commercial MILP solver. This step also install a demo license for Gurobi, which should able to solve the small optimization problems in this tutorial. A license is required for solving larger-scale problems.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install 'gurobipy>=10,<10.1'
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>In the code above, we install specific version of all packages to ensure that this tutorial keeps running in the future, even when newer (and possibly incompatible) versions of the packages are released. This is usually a recommended practice for all Python projects.</p>
|
||||
</div>
|
||||
</section>
|
||||
<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="Link to this heading">¶</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>Suppose that a utility company needs to decide which electrical generators should be online at each hour of the day, as well as how much power should each generator produce. More specifically, assume that the 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 the 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. 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).</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">
|
||||
\[\begin{split}\begin{align}
|
||||
\text{minimize } \quad & \sum_{i=1}^n \left( c^\text{fix}_i x_i + c^\text{var}_i y_i \right) \\
|
||||
\text{subject to } \quad & y_i \leq p^\text{max}_i x_i & i=1,\ldots,n \\
|
||||
& y_i \geq p^\text{min}_i x_i & i=1,\ldots,n \\
|
||||
& \sum_{i=1}^n y_i = d \\
|
||||
& x_i \in \{0,1\} & i=1,\ldots,n \\
|
||||
& y_i \geq 0 & i=1,\ldots,n
|
||||
\end{align}\end{split}\]</div>
|
||||
<div class="admonition note">
|
||||
<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.</p>
|
||||
</div>
|
||||
<p>Next, let us convert this abstract mathematical formulation into a concrete optimization model, using Python and Pyomo. We start by defining a data class <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, which holds all the input data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[1]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
|
||||
|
||||
|
||||
<span class="nd">@dataclass</span>
|
||||
<span class="k">class</span> <span class="nc">UnitCommitmentData</span><span class="p">:</span>
|
||||
<span class="n">demand</span><span class="p">:</span> <span class="nb">float</span>
|
||||
<span class="n">pmin</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">pmax</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">cfix</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
<span class="n">cvar</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Next, we write a <code class="docutils literal notranslate"><span class="pre">build_uc_model</span></code> function, which converts the input data into a concrete Pyomo model. The function accepts <code class="docutils literal notranslate"><span class="pre">UnitCommitmentData</span></code>, the data structure we previously defined, or the path to a compressed pickle file containing this data.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pyomo.environ</span> <span class="k">as</span> <span class="nn">pe</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Union</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">read_pkl_gz</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.solvers.pyomo</span> <span class="kn">import</span> <span class="n">PyomoModel</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">build_uc_model</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">UnitCommitmentData</span><span class="p">])</span> <span class="o">-></span> <span class="n">PyomoModel</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">read_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="n">model</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">ConcreteModel</span><span class="p">()</span>
|
||||
<span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">Var</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">),</span> <span class="n">domain</span><span class="o">=</span><span class="n">pe</span><span class="o">.</span><span class="n">Binary</span><span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">Var</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">),</span> <span class="n">domain</span><span class="o">=</span><span class="n">pe</span><span class="o">.</span><span class="n">NonNegativeReals</span><span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">obj</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">Objective</span><span class="p">(</span>
|
||||
<span class="n">expr</span><span class="o">=</span><span class="nb">sum</span><span class="p">(</span>
|
||||
<span class="n">data</span><span class="o">.</span><span class="n">cfix</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">model</span><span class="o">.</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">data</span><span class="o">.</span><span class="n">cvar</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">model</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">eq_max_power</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">ConstraintList</span><span class="p">()</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">eq_min_power</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">ConstraintList</span><span class="p">()</span>
|
||||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">eq_max_power</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o"><=</span> <span class="n">data</span><span class="o">.</span><span class="n">pmax</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">model</span><span class="o">.</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">eq_min_power</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">>=</span> <span class="n">data</span><span class="o">.</span><span class="n">pmin</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">model</span><span class="o">.</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">eq_demand</span> <span class="o">=</span> <span class="n">pe</span><span class="o">.</span><span class="n">Constraint</span><span class="p">(</span>
|
||||
<span class="n">expr</span><span class="o">=</span><span class="nb">sum</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="o">==</span> <span class="n">data</span><span class="o">.</span><span class="n">demand</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">return</span> <span class="n">PyomoModel</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="s2">"gurobi_persistent"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>At this point, we can already use Pyomo and any mixed-integer linear programming solver to find optimal solutions to any instance of this problem. To illustrate this, let us solve a small instance with three generators:</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[3]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></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">demand</span><span class="o">=</span><span class="mf">100.0</span><span class="p">,</span>
|
||||
<span class="n">pmin</span><span class="o">=</span><span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">],</span>
|
||||
<span class="n">pmax</span><span class="o">=</span><span class="p">[</span><span class="mi">50</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">70</span><span class="p">],</span>
|
||||
<span class="n">cfix</span><span class="o">=</span><span class="p">[</span><span class="mi">700</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="mi">500</span><span class="p">],</span>
|
||||
<span class="n">cvar</span><span class="o">=</span><span class="p">[</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">2.5</span><span class="p">],</span>
|
||||
<span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">model</span><span class="o">.</span><span class="n">optimize</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"obj ="</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">obj</span><span class="p">())</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"x ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"y ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Restricted license - for non-production use only - expires 2024-10-28
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 7 rows, 6 columns and 15 nonzeros
|
||||
Model fingerprint: 0x15c7a953
|
||||
Variable types: 3 continuous, 3 integer (3 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 7e+01]
|
||||
Objective range [2e+00, 7e+02]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [1e+02, 1e+02]
|
||||
Presolve removed 2 rows and 1 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 5 rows, 5 columns, 13 nonzeros
|
||||
Variable types: 0 continuous, 5 integer (3 binary)
|
||||
Found heuristic solution: objective 1400.0000000
|
||||
|
||||
Root relaxation: objective 1.035000e+03, 3 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 1035.00000 0 1 1400.00000 1035.00000 26.1% - 0s
|
||||
0 0 1105.71429 0 1 1400.00000 1105.71429 21.0% - 0s
|
||||
* 0 0 0 1320.0000000 1320.00000 0.00% - 0s
|
||||
|
||||
Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 2: 1320 1400
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 1.320000000000e+03, best bound 1.320000000000e+03, gap 0.0000%
|
||||
WARNING: Cannot get reduced costs for MIP.
|
||||
WARNING: Cannot get duals for MIP.
|
||||
obj = 1320.0
|
||||
x = [-0.0, 1.0, 1.0]
|
||||
y = [0.0, 60.0, 40.0]
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>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.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Notes</p>
|
||||
<ul class="simple">
|
||||
<li><p>In the example above, <code class="docutils literal notranslate"><span class="pre">PyomoModel</span></code> is just a thin wrapper around a standard Pyomo model. This wrapper allows MIPLearn to be solver- and modeling-language-agnostic. The wrapper provides only a few basic methods, such as <code class="docutils literal notranslate"><span class="pre">optimize</span></code>. For more control, and to query the solution, the original Pyomo model can be accessed through <code class="docutils literal notranslate"><span class="pre">model.inner</span></code>, as illustrated above.</p></li>
|
||||
<li><p>To use CPLEX or XPRESS, instead of Gurobi, replace <code class="docutils literal notranslate"><span class="pre">gurobi_persistent</span></code> by <code class="docutils literal notranslate"><span class="pre">cplex_persistent</span></code> or <code class="docutils literal notranslate"><span class="pre">xpress_persistent</span></code> in the <code class="docutils literal notranslate"><span class="pre">build_uc_model</span></code>. Note that only persistent Pyomo solvers are currently supported. Pull requests adding support for other types of solver are very welcome.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Generating-training-data">
|
||||
<h2><span class="section-number">1.4. </span>Generating training data<a class="headerlink" href="#Generating-training-data" title="Link to this heading">¶</a></h2>
|
||||
<p>Although Gurobi could solve the small example above in a fraction of a second, it gets slower for larger and more complex versions of the problem. If this is a problem that needs to be solved frequently, as it is often the case in practice, it could make sense to spend some time upfront generating a <strong>trained</strong> solver, which can optimize new instances (similar to the ones it was trained on) faster.</p>
|
||||
<p>In the following, we will use MIPLearn to train machine learning models that is able to predict the optimal solution for instances that follow a given probability distribution, then it will provide this predicted solution to Gurobi as a warm start. Before we can train the model, we need to collect training data by solving a large number of instances. In real-world situations, we may construct these training instances based on historical data. In this tutorial, we will construct them using a
|
||||
random instance generator:</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[4]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">uniform</span>
|
||||
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
|
||||
<span class="kn">import</span> <span class="nn">random</span>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">seed</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="n">UnitCommitmentData</span><span class="p">]:</span>
|
||||
<span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
|
||||
<span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
|
||||
<span class="n">pmin</span> <span class="o">=</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">100_000.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">400_000.0</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">pmax</span> <span class="o">=</span> <span class="n">pmin</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">2.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">2.5</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">cfix</span> <span class="o">=</span> <span class="n">pmin</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">100.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">25.0</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="n">cvar</span> <span class="o">=</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">1.25</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.25</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
|
||||
<span class="k">return</span> <span class="p">[</span>
|
||||
<span class="n">UnitCommitmentData</span><span class="p">(</span>
|
||||
<span class="n">demand</span><span class="o">=</span><span class="n">pmax</span><span class="o">.</span><span class="n">sum</span><span class="p">()</span> <span class="o">*</span> <span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.25</span><span class="p">)</span><span class="o">.</span><span class="n">rvs</span><span class="p">(),</span>
|
||||
<span class="n">pmin</span><span class="o">=</span><span class="n">pmin</span><span class="p">,</span>
|
||||
<span class="n">pmax</span><span class="o">=</span><span class="n">pmax</span><span class="p">,</span>
|
||||
<span class="n">cfix</span><span class="o">=</span><span class="n">cfix</span><span class="p">,</span>
|
||||
<span class="n">cvar</span><span class="o">=</span><span class="n">cvar</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">samples</span><span class="p">)</span>
|
||||
<span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>In this example, for simplicity, only the demands change from one instance to the next. We could also have randomized the costs, production limits or even the number of units. The more randomization we have in the training data, however, the more challenging it is for the machine learning models to learn solution patterns.</p>
|
||||
<p>Now we generate 500 instances of this problem, each one with 50 generators, and we use 450 of these instances for training. After generating the instances, we write them to individual files. MIPLearn uses files during the training process because, for large-scale optimization problems, it is often impractical to hold in memory the entire training data, as well as the concrete Pyomo models. Files also make it much easier to solve multiple instances simultaneously, potentially on multiple
|
||||
machines. The code below generates the files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.pkl.gz</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.pkl.gz</span></code>, etc., which contain the input data in compressed (gzipped) pickle format.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[5]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">write_pkl_gz</span>
|
||||
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
|
||||
<span class="n">train_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">450</span><span class="p">],</span> <span class="s2">"uc/train"</span><span class="p">)</span>
|
||||
<span class="n">test_data</span> <span class="o">=</span> <span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">450</span><span class="p">:</span><span class="mi">500</span><span class="p">],</span> <span class="s2">"uc/test"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Finally, we use <code class="docutils literal notranslate"><span class="pre">BasicCollector</span></code> to collect the optimal solutions and other useful training data for all training instances. The data is stored in HDF5 files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.h5</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.h5</span></code>, etc. The optimization models are also exported to compressed MPS files <code class="docutils literal notranslate"><span class="pre">uc/train/00000.mps.gz</span></code>, <code class="docutils literal notranslate"><span class="pre">uc/train/00001.mps.gz</span></code>, etc.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[6]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.collectors.basic</span> <span class="kn">import</span> <span class="n">BasicCollector</span>
|
||||
|
||||
<span class="n">bc</span> <span class="o">=</span> <span class="n">BasicCollector</span><span class="p">()</span>
|
||||
<span class="n">bc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">train_data</span><span class="p">,</span> <span class="n">build_uc_model</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Training-and-solving-test-instances">
|
||||
<h2><span class="section-number">1.5. </span>Training and solving test instances<a class="headerlink" href="#Training-and-solving-test-instances" title="Link to this heading">¶</a></h2>
|
||||
<p>With training data in hand, we can now design and train a machine learning model to accelerate solver performance. In this tutorial, for illustration purposes, we will use ML to generate a good warm start using <span class="math notranslate nohighlight">\(k\)</span>-nearest neighbors. More specifically, the strategy is to:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Memorize the optimal solutions of all training instances;</p></li>
|
||||
<li><p>Given a test instance, find the 25 most similar training instances, based on constraint right-hand sides;</p></li>
|
||||
<li><p>Merge their optimal solutions into a single partial solution; specifically, only assign values to the binary variables that agree unanimously.</p></li>
|
||||
<li><p>Provide this partial solution to the solver as a warm start.</p></li>
|
||||
</ol>
|
||||
<p>This simple strategy can be implemented as shown below, using <code class="docutils literal notranslate"><span class="pre">MemorizingPrimalComponent</span></code>. For more advanced strategies, and for the usage of more advanced classifiers, see the user guide.</p>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[7]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">sklearn.neighbors</span> <span class="kn">import</span> <span class="n">KNeighborsClassifier</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.components.primal.actions</span> <span class="kn">import</span> <span class="n">SetWarmStart</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.components.primal.mem</span> <span class="kn">import</span> <span class="p">(</span>
|
||||
<span class="n">MemorizingPrimalComponent</span><span class="p">,</span>
|
||||
<span class="n">MergeTopSolutions</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
<span class="kn">from</span> <span class="nn">miplearn.extractors.fields</span> <span class="kn">import</span> <span class="n">H5FieldsExtractor</span>
|
||||
|
||||
<span class="n">comp</span> <span class="o">=</span> <span class="n">MemorizingPrimalComponent</span><span class="p">(</span>
|
||||
<span class="n">clf</span><span class="o">=</span><span class="n">KNeighborsClassifier</span><span class="p">(</span><span class="n">n_neighbors</span><span class="o">=</span><span class="mi">25</span><span class="p">),</span>
|
||||
<span class="n">extractor</span><span class="o">=</span><span class="n">H5FieldsExtractor</span><span class="p">(</span>
|
||||
<span class="n">instance_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">"static_constr_rhs"</span><span class="p">],</span>
|
||||
<span class="p">),</span>
|
||||
<span class="n">constructor</span><span class="o">=</span><span class="n">MergeTopSolutions</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">]),</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="n">SetWarmStart</span><span class="p">(),</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Having defined the ML strategy, we next construct <code class="docutils literal notranslate"><span class="pre">LearningSolver</span></code>, train the ML component and optimize one of the test instances.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">miplearn.solvers.learning</span> <span class="kn">import</span> <span class="n">LearningSolver</span>
|
||||
|
||||
<span class="n">solver_ml</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[</span><span class="n">comp</span><span class="p">])</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_uc_model</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x5e67c6ee
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.6166537e+09 5.648803e+04 0.000000e+00 0s
|
||||
1 8.2906219e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.290621916e+09
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x4a7cfe2b
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
|
||||
User MIP start produced solution with objective 8.29153e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.29153e+09 (0.01s)
|
||||
Loaded user MIP start with objective 8.29153e+09
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 1
|
||||
Flow cover: 2
|
||||
|
||||
Explored 1 nodes (565 simplex iterations) in 0.04 seconds (0.01 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 1: 8.29153e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%
|
||||
WARNING: Cannot get reduced costs for MIP.
|
||||
WARNING: Cannot get duals for MIP.
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[8]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
{}
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>By examining the solve log above, specifically the line <code class="docutils literal notranslate"><span class="pre">Loaded</span> <span class="pre">user</span> <span class="pre">MIP</span> <span class="pre">start</span> <span class="pre">with</span> <span class="pre">objective...</span></code>, we can see that MIPLearn was able to construct an initial solution which turned out to be very close to the optimal solution to the problem. Now let us repeat the code above, but a solver which does not apply any ML strategies. Note that our previously-defined component is not provided.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[9]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">solver_baseline</span> <span class="o">=</span> <span class="n">LearningSolver</span><span class="p">(</span><span class="n">components</span><span class="o">=</span><span class="p">[])</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_data</span><span class="p">)</span>
|
||||
<span class="n">solver_baseline</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">build_uc_model</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x5e67c6ee
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.6166537e+09 5.648803e+04 0.000000e+00 0s
|
||||
1 8.2906219e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.290621916e+09
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x8a0f9587
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Found heuristic solution: objective 9.757128e+09
|
||||
|
||||
Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2906e+09 0 1 9.7571e+09 8.2906e+09 15.0% - 0s
|
||||
H 0 0 8.298273e+09 8.2906e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
0 0 8.2907e+09 0 4 8.2983e+09 8.2907e+09 0.09% - 0s
|
||||
H 0 0 8.293980e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 5 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 1 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2907e+09 0 2 8.2940e+09 8.2907e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 1 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
0 0 8.2908e+09 0 4 8.2940e+09 8.2908e+09 0.04% - 0s
|
||||
H 0 0 8.291465e+09 8.2908e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Gomory: 2
|
||||
MIR: 1
|
||||
|
||||
Explored 1 nodes (1025 simplex iterations) in 0.12 seconds (0.03 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.291465302389e+09, best bound 8.290781665333e+09, gap 0.0082%
|
||||
WARNING: Cannot get reduced costs for MIP.
|
||||
WARNING: Cannot get duals for MIP.
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[9]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
{}
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<p>In the log above, the <code class="docutils literal notranslate"><span class="pre">MIP</span> <span class="pre">start</span></code> line is missing, and Gurobi had to start with a significantly inferior initial solution. The solver was still able to find the optimal solution at the end, but it required using its own internal heuristic procedures. In this example, because we solve very small optimization problems, there was almost no difference in terms of running time, but the difference can be significant for larger problems.</p>
|
||||
</section>
|
||||
<section id="Accessing-the-solution">
|
||||
<h2><span class="section-number">1.6. </span>Accessing the solution<a class="headerlink" href="#Accessing-the-solution" title="Link to this heading">¶</a></h2>
|
||||
<p>In the example above, we used <code class="docutils literal notranslate"><span class="pre">LearningSolver.solve</span></code> together with data files to solve both the training and the test instances. In the following example, we show how to build and solve a Pyomo model entirely in-memory, using our trained solver.</p>
|
||||
<div class="nbinput docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[10]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="n">data</span> <span class="o">=</span> <span class="n">random_uc_data</span><span class="p">(</span><span class="n">samples</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">500</span><span class="p">)[</span><span class="mi">0</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">data</span><span class="p">)</span>
|
||||
<span class="n">solver_ml</span><span class="o">.</span><span class="n">optimize</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"obj ="</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">obj</span><span class="p">())</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">" x ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)])</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">" y ="</span><span class="p">,</span> <span class="p">[</span><span class="n">model</span><span class="o">.</span><span class="n">inner</span><span class="o">.</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nboutput nblast docutils container">
|
||||
<div class="prompt empty docutils container">
|
||||
</div>
|
||||
<div class="output_area docutils container">
|
||||
<div class="highlight"><pre>
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x2dfe4e1c
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
Presolve removed 1000 rows and 500 columns
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1 rows, 500 columns, 500 nonzeros
|
||||
|
||||
Iteration Objective Primal Inf. Dual Inf. Time
|
||||
0 6.5917580e+09 5.627453e+04 0.000000e+00 0s
|
||||
1 8.2535968e+09 0.000000e+00 0.000000e+00 0s
|
||||
|
||||
Solved in 1 iterations and 0.01 seconds (0.00 work units)
|
||||
Optimal objective 8.253596777e+09
|
||||
Set parameter QCPDual to value 1
|
||||
Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)
|
||||
|
||||
CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]
|
||||
Thread count: 10 physical cores, 20 logical processors, using up to 20 threads
|
||||
|
||||
Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros
|
||||
Model fingerprint: 0x0f0924a1
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
Coefficient statistics:
|
||||
Matrix range [1e+00, 2e+06]
|
||||
Objective range [1e+00, 6e+07]
|
||||
Bounds range [1e+00, 1e+00]
|
||||
RHS range [3e+08, 3e+08]
|
||||
|
||||
User MIP start produced solution with objective 8.25814e+09 (0.00s)
|
||||
User MIP start produced solution with objective 8.25512e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25483e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25459e+09 (0.01s)
|
||||
User MIP start produced solution with objective 8.25459e+09 (0.01s)
|
||||
Loaded user MIP start with objective 8.25459e+09
|
||||
|
||||
Presolve time: 0.00s
|
||||
Presolved: 1001 rows, 1000 columns, 2500 nonzeros
|
||||
Variable types: 500 continuous, 500 integer (500 binary)
|
||||
|
||||
Root relaxation: objective 8.253597e+09, 512 iterations, 0.00 seconds (0.00 work units)
|
||||
|
||||
Nodes | Current Node | Objective Bounds | Work
|
||||
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
|
||||
|
||||
0 0 8.2536e+09 0 1 8.2546e+09 8.2536e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 3 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 1 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2537e+09 0 4 8.2546e+09 8.2537e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 4 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 5 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
0 0 8.2538e+09 0 6 8.2546e+09 8.2538e+09 0.01% - 0s
|
||||
|
||||
Cutting planes:
|
||||
Cover: 1
|
||||
MIR: 2
|
||||
StrongCG: 1
|
||||
Flow cover: 1
|
||||
|
||||
Explored 1 nodes (575 simplex iterations) in 0.09 seconds (0.01 work units)
|
||||
Thread count was 20 (of 20 available processors)
|
||||
|
||||
Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09
|
||||
|
||||
Optimal solution found (tolerance 1.00e-04)
|
||||
Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%
|
||||
WARNING: Cannot get reduced costs for MIP.
|
||||
WARNING: Cannot get duals for MIP.
|
||||
obj = 8254590409.96973
|
||||
x = [1.0, 1.0, 0.0, 1.0, 1.0]
|
||||
y = [935662.0949262811, 1604270.0218116897, 0.0, 1369560.835229226, 602828.5321028307]
|
||||
</pre></div></div>
|
||||
</div>
|
||||
<div class="nbinput nblast docutils container">
|
||||
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[ ]:
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class='prev-next-bottom'>
|
||||
|
||||
<a class='left-prev' id="prev-link" href="../../" title="previous page">MIPLearn</a>
|
||||
<a class='right-next' id="next-link" href="../getting-started-gurobipy/" title="next page"><span class="section-number">2. </span>Getting started (Gurobipy)</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer mt-5 mt-md-0">
|
||||
<div class="container">
|
||||
<p>
|
||||
|
||||
© Copyright 2020-2023, UChicago Argonne, LLC.<br/>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../_static/js/index.1c5a1a01449ed65a7b51.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user