Update docs

This commit is contained in:
2024-12-10 12:14:36 -06:00
parent 184af2a869
commit 112963c178
37 changed files with 15544 additions and 3662 deletions

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en" data-content_root="../../">
<html>
<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" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>4. User cuts and lazy constraints &#8212; MIPLearn 0.4</title>
<link href="../../_static/css/theme.css" rel="stylesheet" />
@@ -22,21 +22,26 @@
<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="stylesheet" href="../../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css" type="text/css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/custom.css" />
<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 id="documentation_options" data-url_root="../../" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></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 src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js"></script>
<script async="async" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_HTMLorMML"></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/" />
@@ -68,7 +73,7 @@
<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">
<p class="caption">
<span class="caption-text">
Tutorials
</span>
@@ -95,7 +100,7 @@
</a>
</li>
</ul>
<p class="caption" role="heading">
<p class="caption">
<span class="caption-text">
User Guide
</span>
@@ -127,7 +132,7 @@
</a>
</li>
</ul>
<p class="caption" role="heading">
<p class="caption">
<span class="caption-text">
Python API Reference
</span>
@@ -256,8 +261,8 @@
<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>
<div class="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="Permalink to this headline"></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>
@@ -269,8 +274,8 @@ size; these constraints are added to the formulation only once the solver finds
<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>
<div class="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="Permalink to this headline"></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 Karps 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>
@@ -285,109 +290,109 @@ size; these constraints are added to the formulation only once the solver finds
<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>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>import gurobipy as gp
from gurobipy import quicksum, GRB, tuplelist
from miplearn.solvers.gurobi import GurobiModel
import networkx as nx
import numpy as np
from miplearn.problems.tsp import (
TravelingSalesmanData,
TravelingSalesmanGenerator,
)
from scipy.stats import uniform, randint
from miplearn.io import write_pkl_gz, read_pkl_gz
from miplearn.collectors.basic import BasicCollector
from miplearn.solvers.learning import LearningSolver
from miplearn.components.lazy.mem import MemorizingLazyComponent
from miplearn.extractors.fields import H5FieldsExtractor
from sklearn.neighbors import KNeighborsClassifier
<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>
# Set up random seed to make example more reproducible
np.random.seed(42)
<span class="c1"># Set up Python logging</span>
<span class="kn">import</span> <span class="nn">logging</span>
# Set up Python logging
import logging
<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>
logging.basicConfig(level=logging.WARNING)
<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>
def build_tsp_model_gurobipy_simplified(data):
# Read data from file if a filename is provided
if isinstance(data, str):
data = read_pkl_gz(data)
<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>
# Create empty gurobipy model
model = gp.Model()
<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>
# Create set of edges between every pair of cities, for convenience
edges = tuplelist(
(i, j) for i in range(data.n_cities) for j in range(i + 1, data.n_cities)
)
<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">&quot;x&quot;</span><span class="p">)</span>
# Add binary variable x[e] for each edge e
x = model.addVars(edges, vtype=GRB.BINARY, name=&quot;x&quot;)
<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>
# Add objective function
model.setObjective(quicksum(x[(i, j)] * data.distances[i, j] for (i, j) in edges))
<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">&quot;eq_degree&quot;</span><span class="p">,</span>
<span class="p">)</span>
# Add constraint: must choose two edges adjacent to each city
model.addConstrs(
(
quicksum(x[min(i, j), max(i, j)] for j in range(data.n_cities) if i != j)
== 2
for i in range(data.n_cities)
),
name=&quot;eq_degree&quot;,
)
<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">&quot;&quot;&quot;</span>
<span class="sd"> Callback function that finds subtours in the current solution.</span>
<span class="sd"> &quot;&quot;&quot;</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>
def lazy_separate(m: GurobiModel):
&quot;&quot;&quot;
Callback function that finds subtours in the current solution.
&quot;&quot;&quot;
# Query current value of the x variables
x_val = m.inner.cbGetSolution(x)
<span class="c1"># Initialize empty set of violations</span>
<span class="n">violations</span> <span class="o">=</span> <span class="p">[]</span>
# Initialize empty set of violations
violations = []
<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">&gt;</span> <span class="mf">0.5</span><span class="p">]</span>
# Build set of edges we have currently selected
selected_edges = [e for e in edges if x_val[e] &gt; 0.5]
<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>
# Build a graph containing the selected edges, using networkx
graph = nx.Graph()
graph.add_edges_from(selected_edges)
<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>
# For each component of the graph
for component in list(nx.connected_components(graph)):
<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">&lt;</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>
# If the component is not the entire graph, we found a
# subtour. Add the edge cut to the list of violations.
if len(component) &lt; data.n_cities:
cut_edges = [
[e[0], e[1]]
for e in edges
if (e[0] in component and e[1] not in component)
or (e[0] not in component and e[1] in component)
]
violations.append(cut_edges)
<span class="c1"># Return the list of violations</span>
<span class="k">return</span> <span class="n">violations</span>
# Return the list of violations
return violations
<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">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;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&quot;</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">&gt;=</span> <span class="mi">2</span><span class="p">)</span>
def lazy_enforce(m: GurobiModel, violations) -&gt; None:
&quot;&quot;&quot;
Callback function that, given a list of subtours, adds lazy
constraints to remove them from the feasible region.
&quot;&quot;&quot;
print(f&quot;Enforcing {len(violations)} subtour elimination constraints&quot;)
for violation in violations:
m.add_constr(quicksum(x[e[0], e[1]] for e in violation) &gt;= 2)
<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>
return GurobiModel(
model,
lazy_separate=lazy_separate,
lazy_enforce=lazy_enforce,
)
</pre></div>
</div>
</div>
@@ -398,55 +403,55 @@ the responsbility of the second callback function, <code class="docutils literal
<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>
</div>
<div class="section" id="Generating-training-data">
<h2><span class="section-number">4.2. </span>Generating training data<a class="headerlink" href="#Generating-training-data" title="Permalink to this headline"></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>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span># Configure generator to produce instances with 50 cities located
# in the 1000 x 1000 square, and with slightly perturbed distances.
gen = TravelingSalesmanGenerator(
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
n=randint(low=50, high=51),
gamma=uniform(loc=1.0, scale=0.25),
fix_cities=True,
round=True,
)
<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">&quot;tsp/train&quot;</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">&quot;tsp/test&quot;</span><span class="p">)</span>
# Generate 500 instances and store input data file to .pkl.gz files
data = gen.generate(500)
train_data = write_pkl_gz(data[0:450], &quot;tsp/train&quot;)
test_data = write_pkl_gz(data[450:500], &quot;tsp/test&quot;)
<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>
# Solve the training instances in parallel, collecting the required lazy
# constraints, in addition to other information, such as optimal solution.
bc = BasicCollector()
bc.collect(train_data, build_tsp_model_gurobipy_simplified, n_jobs=10)
</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>
</div>
<div class="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="Permalink to this headline"></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">&quot;static_var_obj_coeffs&quot;</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>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>solver = LearningSolver(
components=[
MemorizingLazyComponent(
extractor=H5FieldsExtractor(instance_fields=[&quot;static_var_obj_coeffs&quot;]),
clf=KNeighborsClassifier(n_neighbors=100),
),
],
)
solver.fit(train_data)
</pre></div>
</div>
</div>
@@ -455,11 +460,11 @@ function only. This ML strategy can be implemented using <code class="docutils l
<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">&quot;miplearn&quot;</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>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span># Increase log verbosity, so that we can see what is MIPLearn doing
logging.getLogger(&quot;miplearn&quot;).setLevel(logging.INFO)
<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>
# Solve a new test instance
solver.optimize(test_data[0], build_tsp_model_gurobipy_simplified);
</pre></div>
</div>
</div>
@@ -565,8 +570,8 @@ User-callback calls 141, time in user-callback 0.00 sec
<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>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>solver = LearningSolver(components=[]) # empty set of ML components
solver.optimize(test_data[0], build_tsp_model_gurobipy_simplified);
</pre></div>
</div>
</div>
@@ -660,9 +665,9 @@ 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>
</div>
<div class="section" id="Learning-user-cuts">
<h2><span class="section-number">4.4. </span>Learning user cuts<a class="headerlink" href="#Learning-user-cuts" title="Permalink to this headline"></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>
@@ -677,8 +682,8 @@ User-callback calls 170, time in user-callback 0.01 sec
</pre></div>
</div>
</div>
</section>
</section>
</div>
</div>
</div>