You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MIPLearn/0.2/api/miplearn/problems/tsp.html

587 lines
27 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.7.0" />
<title>miplearn.problems.tsp API documentation</title>
<meta name="description" content="" />
<link href='https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css' rel='stylesheet'>
<link href='https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/8.0.0/sanitize.min.css' rel='stylesheet'>
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" rel="stylesheet">
<style>.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{font-weight:bold}#index h4 + ul{margin-bottom:.6em}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>miplearn.problems.tsp</code></h1>
</header>
<section id="section-intro">
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python"># MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
import networkx as nx
import numpy as np
import pyomo.environ as pe
from scipy.spatial.distance import pdist, squareform
from scipy.stats import uniform, randint
from scipy.stats.distributions import rv_frozen
from miplearn.instance import Instance
class ChallengeA:
def __init__(
self,
seed=42,
n_training_instances=500,
n_test_instances=50,
):
np.random.seed(seed)
self.generator = TravelingSalesmanGenerator(
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
n=randint(low=350, high=351),
gamma=uniform(loc=0.95, scale=0.1),
fix_cities=True,
round=True,
)
np.random.seed(seed + 1)
self.training_instances = self.generator.generate(n_training_instances)
np.random.seed(seed + 2)
self.test_instances = self.generator.generate(n_test_instances)
class TravelingSalesmanGenerator:
&#34;&#34;&#34;Random generator for the Traveling Salesman Problem.&#34;&#34;&#34;
def __init__(
self,
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
n=randint(low=100, high=101),
gamma=uniform(loc=1.0, scale=0.0),
fix_cities=True,
round=True,
):
&#34;&#34;&#34;Initializes the problem generator.
Initially, the generator creates n cities (x_1,y_1),...,(x_n,y_n) where n, x_i and y_i are
sampled independently from the provided probability distributions `n`, `x` and `y`. For each
(unordered) pair of cities (i,j), the distance d[i,j] between them is set to:
d[i,j] = gamma[i,j] \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}
where gamma is sampled from the provided probability distribution `gamma`.
If fix_cities=True, the list of cities is kept the same for all generated instances. The
gamma values, and therefore also the distances, are still different.
By default, all distances d[i,j] are rounded to the nearest integer. If `round=False`
is provided, this rounding will be disabled.
Arguments
---------
x: rv_continuous
Probability distribution for the x-coordinate of each city.
y: rv_continuous
Probability distribution for the y-coordinate of each city.
n: rv_discrete
Probability distribution for the number of cities.
fix_cities: bool
If False, cities will be resampled for every generated instance. Otherwise, list of
cities will be computed once, during the constructor.
round: bool
If True, distances are rounded to the nearest integer.
&#34;&#34;&#34;
assert isinstance(x, rv_frozen), &#34;x should be a SciPy probability distribution&#34;
assert isinstance(y, rv_frozen), &#34;y should be a SciPy probability distribution&#34;
assert isinstance(n, rv_frozen), &#34;n should be a SciPy probability distribution&#34;
assert isinstance(
gamma,
rv_frozen,
), &#34;gamma should be a SciPy probability distribution&#34;
self.x = x
self.y = y
self.n = n
self.gamma = gamma
self.round = round
if fix_cities:
self.fixed_n, self.fixed_cities = self._generate_cities()
else:
self.fixed_n = None
self.fixed_cities = None
def generate(self, n_samples):
def _sample():
if self.fixed_cities is not None:
n, cities = self.fixed_n, self.fixed_cities
else:
n, cities = self._generate_cities()
distances = squareform(pdist(cities)) * self.gamma.rvs(size=(n, n))
distances = np.tril(distances) + np.triu(distances.T, 1)
if self.round:
distances = distances.round()
return TravelingSalesmanInstance(n, distances)
return [_sample() for _ in range(n_samples)]
def _generate_cities(self):
n = self.n.rvs()
cities = np.array([(self.x.rvs(), self.y.rvs()) for _ in range(n)])
return n, cities
class TravelingSalesmanInstance(Instance):
&#34;&#34;&#34;An instance ot the Traveling Salesman Problem.
Given a list of cities and the distance between each pair of cities, the problem 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&#39;s 21 NP-complete problems.
&#34;&#34;&#34;
def __init__(self, n_cities, distances):
assert isinstance(distances, np.ndarray)
assert distances.shape == (n_cities, n_cities)
self.n_cities = n_cities
self.distances = distances
def to_model(self):
model = pe.ConcreteModel()
model.edges = edges = [
(i, j) for i in range(self.n_cities) for j in range(i + 1, self.n_cities)
]
model.x = pe.Var(edges, domain=pe.Binary)
model.obj = pe.Objective(
expr=sum(model.x[i, j] * self.distances[i, j] for (i, j) in edges),
sense=pe.minimize,
)
model.eq_degree = pe.ConstraintList()
model.eq_subtour = pe.ConstraintList()
for i in range(self.n_cities):
model.eq_degree.add(
sum(
model.x[min(i, j), max(i, j)]
for j in range(self.n_cities)
if i != j
)
== 2
)
return model
def get_instance_features(self):
return np.array([1])
def get_variable_features(self, var_name, index):
return np.array([1])
def get_variable_category(self, var_name, index):
return index
def find_violated_lazy_constraints(self, model):
selected_edges = [e for e in model.edges if model.x[e].value &gt; 0.5]
graph = nx.Graph()
graph.add_edges_from(selected_edges)
components = [frozenset(c) for c in list(nx.connected_components(graph))]
violations = []
for c in components:
if len(c) &lt; self.n_cities:
violations += [c]
return violations
def build_lazy_constraint(self, model, component):
cut_edges = [
e
for e in model.edges
if (e[0] in component and e[1] not in component)
or (e[0] not in component and e[1] in component)
]
return model.eq_subtour.add(sum(model.x[e] for e in cut_edges) &gt;= 2)
def find_violated_user_cuts(self, model):
return self.find_violated_lazy_constraints(model)
def build_user_cut(self, model, violation):
return self.build_lazy_constraint(model, violation)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="miplearn.problems.tsp.ChallengeA"><code class="flex name class">
<span>class <span class="ident">ChallengeA</span></span>
<span>(</span><span>seed=42, n_training_instances=500, n_test_instances=50)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ChallengeA:
def __init__(
self,
seed=42,
n_training_instances=500,
n_test_instances=50,
):
np.random.seed(seed)
self.generator = TravelingSalesmanGenerator(
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
n=randint(low=350, high=351),
gamma=uniform(loc=0.95, scale=0.1),
fix_cities=True,
round=True,
)
np.random.seed(seed + 1)
self.training_instances = self.generator.generate(n_training_instances)
np.random.seed(seed + 2)
self.test_instances = self.generator.generate(n_test_instances)</code></pre>
</details>
</dd>
<dt id="miplearn.problems.tsp.TravelingSalesmanGenerator"><code class="flex name class">
<span>class <span class="ident">TravelingSalesmanGenerator</span></span>
<span>(</span><span>x=<scipy.stats._distn_infrastructure.rv_frozen object>, y=<scipy.stats._distn_infrastructure.rv_frozen object>, n=<scipy.stats._distn_infrastructure.rv_frozen object>, gamma=<scipy.stats._distn_infrastructure.rv_frozen object>, fix_cities=True, round=True)</span>
</code></dt>
<dd>
<section class="desc"><p>Random generator for the Traveling Salesman Problem.</p>
<p>Initializes the problem generator.</p>
<p>Initially, the generator creates n cities (x_1,y_1),&hellip;,(x_n,y_n) where n, x_i and y_i are
sampled independently from the provided probability distributions <code>n</code>, <code>x</code> and <code>y</code>. For each
(unordered) pair of cities (i,j), the distance d[i,j] between them is set to:</p>
<pre><code>d[i,j] = gamma[i,j] \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}
</code></pre>
<p>where gamma is sampled from the provided probability distribution <code>gamma</code>.</p>
<p>If fix_cities=True, the list of cities is kept the same for all generated instances. The
gamma values, and therefore also the distances, are still different.</p>
<p>By default, all distances d[i,j] are rounded to the nearest integer.
If <code>round=False</code>
is provided, this rounding will be disabled.</p>
<h2 id="arguments">Arguments</h2>
<dl>
<dt><strong><code>x</code></strong> :&ensp;<code>rv_continuous</code></dt>
<dd>Probability distribution for the x-coordinate of each city.</dd>
<dt><strong><code>y</code></strong> :&ensp;<code>rv_continuous</code></dt>
<dd>Probability distribution for the y-coordinate of each city.</dd>
<dt><strong><code>n</code></strong> :&ensp;<code>rv_discrete</code></dt>
<dd>Probability distribution for the number of cities.</dd>
<dt><strong><code>fix_cities</code></strong> :&ensp;<code>bool</code></dt>
<dd>If False, cities will be resampled for every generated instance. Otherwise, list of
cities will be computed once, during the constructor.</dd>
<dt><strong><code>round</code></strong> :&ensp;<code>bool</code></dt>
<dd>If True, distances are rounded to the nearest integer.</dd>
</dl></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class TravelingSalesmanGenerator:
&#34;&#34;&#34;Random generator for the Traveling Salesman Problem.&#34;&#34;&#34;
def __init__(
self,
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
n=randint(low=100, high=101),
gamma=uniform(loc=1.0, scale=0.0),
fix_cities=True,
round=True,
):
&#34;&#34;&#34;Initializes the problem generator.
Initially, the generator creates n cities (x_1,y_1),...,(x_n,y_n) where n, x_i and y_i are
sampled independently from the provided probability distributions `n`, `x` and `y`. For each
(unordered) pair of cities (i,j), the distance d[i,j] between them is set to:
d[i,j] = gamma[i,j] \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}
where gamma is sampled from the provided probability distribution `gamma`.
If fix_cities=True, the list of cities is kept the same for all generated instances. The
gamma values, and therefore also the distances, are still different.
By default, all distances d[i,j] are rounded to the nearest integer. If `round=False`
is provided, this rounding will be disabled.
Arguments
---------
x: rv_continuous
Probability distribution for the x-coordinate of each city.
y: rv_continuous
Probability distribution for the y-coordinate of each city.
n: rv_discrete
Probability distribution for the number of cities.
fix_cities: bool
If False, cities will be resampled for every generated instance. Otherwise, list of
cities will be computed once, during the constructor.
round: bool
If True, distances are rounded to the nearest integer.
&#34;&#34;&#34;
assert isinstance(x, rv_frozen), &#34;x should be a SciPy probability distribution&#34;
assert isinstance(y, rv_frozen), &#34;y should be a SciPy probability distribution&#34;
assert isinstance(n, rv_frozen), &#34;n should be a SciPy probability distribution&#34;
assert isinstance(
gamma,
rv_frozen,
), &#34;gamma should be a SciPy probability distribution&#34;
self.x = x
self.y = y
self.n = n
self.gamma = gamma
self.round = round
if fix_cities:
self.fixed_n, self.fixed_cities = self._generate_cities()
else:
self.fixed_n = None
self.fixed_cities = None
def generate(self, n_samples):
def _sample():
if self.fixed_cities is not None:
n, cities = self.fixed_n, self.fixed_cities
else:
n, cities = self._generate_cities()
distances = squareform(pdist(cities)) * self.gamma.rvs(size=(n, n))
distances = np.tril(distances) + np.triu(distances.T, 1)
if self.round:
distances = distances.round()
return TravelingSalesmanInstance(n, distances)
return [_sample() for _ in range(n_samples)]
def _generate_cities(self):
n = self.n.rvs()
cities = np.array([(self.x.rvs(), self.y.rvs()) for _ in range(n)])
return n, cities</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="miplearn.problems.tsp.TravelingSalesmanGenerator.generate"><code class="name flex">
<span>def <span class="ident">generate</span></span>(<span>self, n_samples)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def generate(self, n_samples):
def _sample():
if self.fixed_cities is not None:
n, cities = self.fixed_n, self.fixed_cities
else:
n, cities = self._generate_cities()
distances = squareform(pdist(cities)) * self.gamma.rvs(size=(n, n))
distances = np.tril(distances) + np.triu(distances.T, 1)
if self.round:
distances = distances.round()
return TravelingSalesmanInstance(n, distances)
return [_sample() for _ in range(n_samples)]</code></pre>
</details>
</dd>
</dl>
</dd>
<dt id="miplearn.problems.tsp.TravelingSalesmanInstance"><code class="flex name class">
<span>class <span class="ident">TravelingSalesmanInstance</span></span>
<span>(</span><span>n_cities, distances)</span>
</code></dt>
<dd>
<section class="desc"><p>An instance ot the Traveling Salesman Problem.</p>
<p>Given a list of cities and the distance between each pair of cities, the problem 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.</p></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class TravelingSalesmanInstance(Instance):
&#34;&#34;&#34;An instance ot the Traveling Salesman Problem.
Given a list of cities and the distance between each pair of cities, the problem 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&#39;s 21 NP-complete problems.
&#34;&#34;&#34;
def __init__(self, n_cities, distances):
assert isinstance(distances, np.ndarray)
assert distances.shape == (n_cities, n_cities)
self.n_cities = n_cities
self.distances = distances
def to_model(self):
model = pe.ConcreteModel()
model.edges = edges = [
(i, j) for i in range(self.n_cities) for j in range(i + 1, self.n_cities)
]
model.x = pe.Var(edges, domain=pe.Binary)
model.obj = pe.Objective(
expr=sum(model.x[i, j] * self.distances[i, j] for (i, j) in edges),
sense=pe.minimize,
)
model.eq_degree = pe.ConstraintList()
model.eq_subtour = pe.ConstraintList()
for i in range(self.n_cities):
model.eq_degree.add(
sum(
model.x[min(i, j), max(i, j)]
for j in range(self.n_cities)
if i != j
)
== 2
)
return model
def get_instance_features(self):
return np.array([1])
def get_variable_features(self, var_name, index):
return np.array([1])
def get_variable_category(self, var_name, index):
return index
def find_violated_lazy_constraints(self, model):
selected_edges = [e for e in model.edges if model.x[e].value &gt; 0.5]
graph = nx.Graph()
graph.add_edges_from(selected_edges)
components = [frozenset(c) for c in list(nx.connected_components(graph))]
violations = []
for c in components:
if len(c) &lt; self.n_cities:
violations += [c]
return violations
def build_lazy_constraint(self, model, component):
cut_edges = [
e
for e in model.edges
if (e[0] in component and e[1] not in component)
or (e[0] not in component and e[1] in component)
]
return model.eq_subtour.add(sum(model.x[e] for e in cut_edges) &gt;= 2)
def find_violated_user_cuts(self, model):
return self.find_violated_lazy_constraints(model)
def build_user_cut(self, model, violation):
return self.build_lazy_constraint(model, violation)</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="miplearn.instance.Instance" href="../instance.html#miplearn.instance.Instance">Instance</a></li>
<li>abc.ABC</li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="miplearn.problems.tsp.TravelingSalesmanInstance.build_user_cut"><code class="name flex">
<span>def <span class="ident">build_user_cut</span></span>(<span>self, model, violation)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def build_user_cut(self, model, violation):
return self.build_lazy_constraint(model, violation)</code></pre>
</details>
</dd>
<dt id="miplearn.problems.tsp.TravelingSalesmanInstance.find_violated_user_cuts"><code class="name flex">
<span>def <span class="ident">find_violated_user_cuts</span></span>(<span>self, model)</span>
</code></dt>
<dd>
<section class="desc"></section>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def find_violated_user_cuts(self, model):
return self.find_violated_lazy_constraints(model)</code></pre>
</details>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="miplearn.instance.Instance" href="../instance.html#miplearn.instance.Instance">Instance</a></b></code>:
<ul class="hlist">
<li><code><a title="miplearn.instance.Instance.build_lazy_constraint" href="../instance.html#miplearn.instance.Instance.build_lazy_constraint">build_lazy_constraint</a></code></li>
<li><code><a title="miplearn.instance.Instance.find_violated_lazy_constraints" href="../instance.html#miplearn.instance.Instance.find_violated_lazy_constraints">find_violated_lazy_constraints</a></code></li>
<li><code><a title="miplearn.instance.Instance.get_instance_features" href="../instance.html#miplearn.instance.Instance.get_instance_features">get_instance_features</a></code></li>
<li><code><a title="miplearn.instance.Instance.get_variable_category" href="../instance.html#miplearn.instance.Instance.get_variable_category">get_variable_category</a></code></li>
<li><code><a title="miplearn.instance.Instance.get_variable_features" href="../instance.html#miplearn.instance.Instance.get_variable_features">get_variable_features</a></code></li>
<li><code><a title="miplearn.instance.Instance.to_model" href="../instance.html#miplearn.instance.Instance.to_model">to_model</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="miplearn.problems" href="index.html">miplearn.problems</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="miplearn.problems.tsp.ChallengeA" href="#miplearn.problems.tsp.ChallengeA">ChallengeA</a></code></h4>
</li>
<li>
<h4><code><a title="miplearn.problems.tsp.TravelingSalesmanGenerator" href="#miplearn.problems.tsp.TravelingSalesmanGenerator">TravelingSalesmanGenerator</a></code></h4>
<ul class="">
<li><code><a title="miplearn.problems.tsp.TravelingSalesmanGenerator.generate" href="#miplearn.problems.tsp.TravelingSalesmanGenerator.generate">generate</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="miplearn.problems.tsp.TravelingSalesmanInstance" href="#miplearn.problems.tsp.TravelingSalesmanInstance">TravelingSalesmanInstance</a></code></h4>
<ul class="">
<li><code><a title="miplearn.problems.tsp.TravelingSalesmanInstance.build_user_cut" href="#miplearn.problems.tsp.TravelingSalesmanInstance.build_user_cut">build_user_cut</a></code></li>
<li><code><a title="miplearn.problems.tsp.TravelingSalesmanInstance.find_violated_user_cuts" href="#miplearn.problems.tsp.TravelingSalesmanInstance.find_violated_user_cuts">find_violated_user_cuts</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.7.0</a>.</p>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad()</script>
</body>
</html>