Jekyll2021-07-17T10:43:38+00:00https://alternate.parts/feed.xmlAlternate PartsWe solve your everyday problems and make tools from scarce resourcesNomad Cyclist Problem - Ancient Silk Road on Bicycle2020-10-19T19:05:01+00:002020-10-19T19:05:01+00:00https://alternate.parts/blog/nomad-cyclist-problem<h2 id="an-step-by-step-guide-to-traveling-salesman-problem-and-its-application-in-cycle-tour-planning">An step by step guide to Traveling Salesman Problem and it’s application in cycle tour planning</h2>
<p>Silk Road is an ancient network of routes encompassing dozens of cities in Western China, Central, West and South Asia. In the past, there were no roads for ancient traders and travelers to travel on. The shortest known path from a city to another city usually became part of the route. Exceptions were made in order to avoid thugs, tough weather and the risk of running out of food and water.</p>
<p><img src="/images/nomad-cyclist/ancient-silk-road-path.png" alt="Ancient Silk Road Path" /></p>
<p>A few friends of mine are planning a bicycle tour through the ancient Silk Road next year. This is going to be a human powered trip similar to how most people traveled through these routes in old times. My friends are not pro-cyclists. So, every paddle must be stroked in the right direction in this journey of thousands of Kilometers.</p>
<p>I initially thought it’s a crazy idea but then I realized it is going to be a trip of a lifetime. So, I decided to give programming a try to plan the tour.</p>
<h2 id="cities">Cities</h2>
<p>These cyclists have planned that the following major ancient cities cannot be missed on the Silk Road:</p>
<p>Xi’an, China 🇨🇳 (origin place of silk trade)</p>
<p>Lanzhou, China 🇨🇳</p>
<p>Dunhuang, China 🇨🇳</p>
<p>Gaochang, China 🇨🇳</p>
<p>Urumqi, China 🇨🇳</p>
<p>Almaty, Kazakhstan 🇰🇿</p>
<p>Bishkek, Kyrgyzstan 🇰🇬</p>
<p>Tashkent, Uzbekistan 🇺🇿</p>
<p>Dushanbe, Tajikistan 🇹🇯</p>
<p>Baysun, Uzbekistan 🇺🇿</p>
<p>Samarkand, Uzbekistan 🇺🇿</p>
<p>Bukhara, Uzbekistan 🇺🇿</p>
<p>Merv, Turkmenistan 🇹🇲</p>
<p>Ashgabat, Turkmenistan 🇹🇲</p>
<p>Tehran, Iran 🇮🇷</p>
<p>Tabriz, Iran 🇮🇷</p>
<p>Ankara, Turkey 🇹🇷</p>
<p>Istanbul, Turkey 🇹🇷</p>
<p>There are cities along the route that are very rich in historical sites and have significance in the ancient trade but it is not possible to include all of them in a human powered bicycle tour. For example, Taxila in Pakistan and Kashgar in China.</p>
<h2 id="traveling-salesman-problem">Traveling Salesman problem</h2>
<p>In order to find a shortest route through all of these cities, let’s use the classic Traveling Salesman Problem (hereafter TSP) algorithm. TSP is an optimization problem. TSP says</p>
<blockquote>
<p>“Given a list of cities to visit and distances between each of them, what is the shortest path to visit all these locations and return back to our starting location”</p>
</blockquote>
<p>In the original TSP, the route we calculate is a closed route and our start and end locations must be the same.</p>
<p><img src="/images/nomad-cyclist/travelling_salesman_problem.png" alt="Traveling Salesman Problem example, Wikipedia" /></p>
<p>But as cyclists, we are interested in visiting the cities on our way, we are not interested in going back to the starting location. TSP has many variations, let’s call this variation of TSP as “Nomad Cyclist Problem”.</p>
<p><img src="/images/nomad-cyclist/open-tsp-route.png" alt="Open Tour TSP" /></p>
<h2 id="distance-matrix">Distance Matrix</h2>
<p>Distance Matrix is the starting point to formulating almost any optimization problem involving a path. E.g. Shortest Path Problem, Traveling Salesman Problem, Vehicle Routing Problem and Route Optimization for logistics.</p>
<p>Have a look at the following Distance Matrix:</p>
<p><img src="/images/nomad-cyclist/silk-road-distance-matrix-a.png" alt="Silk Road Distance Matrix" /></p>
<p>The cell in 2nd row and 1st column has value 626. This is the distance in kilometers from Lanzhou to Xi’an. The distance from Xi’an to Lanzhou however, is 627 indicated by the cell in the first row’s 2nd column.</p>
<p>The value of each cell in the above table is the length of the road in kilometers, from the city on the left to the city in the top header.</p>
<p>We will use a 2D array (or a 2D list in Python) to store Distance Matrix values.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># distance matrix
</span><span class="n">D</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">627</span><span class="p">,</span> <span class="mi">1724</span><span class="p">,</span> <span class="mi">2396</span><span class="p">,</span> <span class="mi">2531</span><span class="p">,</span> <span class="mi">3179</span><span class="p">],</span>
<span class="p">[</span><span class="mi">626</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1096</span><span class="p">,</span> <span class="mi">1768</span><span class="p">,</span> <span class="mi">1903</span><span class="p">,</span> <span class="mi">2551</span><span class="p">],</span>
<span class="p">[</span><span class="mi">1723</span><span class="p">,</span> <span class="mi">1095</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">929</span><span class="p">,</span> <span class="mi">1064</span><span class="p">,</span> <span class="mi">1713</span><span class="p">],</span>
<span class="p">[</span><span class="mi">2395</span><span class="p">,</span> <span class="mi">1766</span><span class="p">,</span> <span class="mi">849</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">163</span><span class="p">,</span> <span class="mi">811</span><span class="p">],</span>
<span class="p">[</span><span class="mi">2531</span><span class="p">,</span> <span class="mi">1903</span><span class="p">,</span> <span class="mi">985</span><span class="p">,</span> <span class="mi">163</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">656</span><span class="p">],</span>
<span class="p">[</span><span class="mi">3186</span><span class="p">,</span> <span class="mi">2557</span><span class="p">,</span> <span class="mi">1640</span><span class="p">,</span> <span class="mi">817</span><span class="p">,</span> <span class="mi">664</span><span class="p">,</span> <span class="mi">0</span><span class="p">]]</span>
</code></pre></div></div>
<p>For real world locations, we can easily calculate the distance matrix for our locations using
<a href="https://developers.google.com/maps/documentation/distance-matrix/overview">Google Maps Distance Matrix API</a> and <a href="https://docs.mapbox.com/help/glossary/matrix-api/">Mapbox’s Matrix API</a> using simple API calls.</p>
<h3 id="compute-distance-matrix">Compute Distance Matrix</h3>
<p>Calculating distance matrix by hand for more than a couple of cities becomes tiresome. Let’s write some scripts to compute the distance matrix for us.</p>
<p>To compute the distance matrix using Google Maps Matrix API, we can simply provide the name of the places. But I decided to hardcode longitude, latitude coordinates of the cities. I think coordinates avoid confusion when more than one place has a similar name.</p>
<p>The <code class="language-plaintext highlighter-rouge">getCities</code> method gives a list of cities + coordinates.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">getCities</span><span class="p">():</span>
<span class="c1"># locations to visit
</span> <span class="n">cities</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s">"name"</span><span class="p">:</span> <span class="s">"Xi'an, Shaanxi, China"</span><span class="p">,</span>
<span class="s">"location"</span><span class="p">:</span> <span class="p">[</span><span class="mf">34.341576</span><span class="p">,</span> <span class="mf">108.939774</span><span class="p">]</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s">"name"</span><span class="p">:</span> <span class="s">"Lanzhou, China"</span><span class="p">,</span>
<span class="s">"location"</span><span class="p">:</span> <span class="p">[</span><span class="mf">36.061089</span><span class="p">,</span> <span class="mf">103.834305</span><span class="p">]</span>
<span class="p">},</span>
<span class="p">...</span>
<span class="p">{</span>
<span class="s">"name"</span><span class="p">:</span> <span class="s">"Istanbul, Turkey"</span><span class="p">,</span>
<span class="s">"location"</span><span class="p">:</span> <span class="p">[</span><span class="mf">41.022576</span><span class="p">,</span> <span class="mf">28.961477</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="k">return</span> <span class="n">cities</span>
</code></pre></div></div>
<h3 id="compute-distance-matrix-1">Compute Distance Matrix</h3>
<p>Computing the Distance Matrix is easy now. We just have to call a gmap API endpoint, provide the two lists: origins & destinations, between which we want to compute distances and get back our distance matrix as JSON that will be parsed and converted into a 2D list <code class="language-plaintext highlighter-rouge">D</code> we saw above.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span><span class="p">,</span> <span class="n">traceback</span>
<span class="k">def</span> <span class="nf">gmapMatrix</span><span class="p">(</span><span class="n">origins</span><span class="p">,</span> <span class="n">destinations</span><span class="p">):</span>
<span class="n">API_KEY</span> <span class="o">=</span> <span class="n">GET_GMAP_API_KEY</span><span class="p">()</span> <span class="c1"># <<= put your gmap api key
</span> <span class="n">URL</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=</span><span class="si">{</span><span class="n">origins</span><span class="si">}</span><span class="s">&destinations=</span><span class="si">{</span><span class="n">destinations</span><span class="si">}</span><span class="s">&key=</span><span class="si">{</span><span class="n">API_KEY</span><span class="si">}</span><span class="s">"</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span><span class="p">)</span>
<span class="n">jsn</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">return</span> <span class="n">jsn</span>
<span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span>
<span class="n">traceback</span><span class="p">.</span><span class="n">print_exc</span><span class="p">()</span>
<span class="k">return</span> <span class="bp">None</span>
</code></pre></div></div>
<p>Let’s find our distance matrix. Welp, not so easy. It turns out Google’s Matrix APIs has <a href="https://developers.google.com/maps/documentation/distance-matrix/usage-and-billing#other-usage-limits">a few limits</a>.</p>
<p>According to Google Maps Matrix API:</p>
<ol>
<li>“Each query sent to the Distance Matrix API generates elements, where the number of origins times the number of destinations equals the number of elements.”</li>
<li>Maximum of 25 origins or 25 destinations per request are allowed</li>
<li>Maximum 100 elements per server-side request are allowed</li>
</ol>
<h3 id="compute-distance-matrix-for-more-than-100-elements">Compute Distance Matrix for more than 100 elements</h3>
<p>We have about 20 cities. 20*20 equals 400. We are above the 100 elements limit. We have to divide our request in batches of 100, fetch the distances and construct our <code class="language-plaintext highlighter-rouge">20 x 20</code> distance matrix.</p>
<p>If you are familiar with Convolutional Neural Networks in Deep Learning, you might know the convolution technique in which we <em>take a small part of matrix, do some operation and calculate another matrix</em>. This process is step by step and works on a small part of the matrix at a time.</p>
<p><img src="/images/nomad-cyclist/convolution-operation.png" alt="Convolution Operation in Deep Learning" /></p>
<p>Don’t worry, our calculations for takling the 100 elements limit are much simpler than Convolutional Neural Nets.</p>
<p>Take a look at <a href="https://github.com/emadehsan/nomad-cyclist/blob/0f42ef8a06bdf2be0b7c0fdec32661d16efded38/distance_matrix.py#L26"><code class="language-plaintext highlighter-rouge">convolveAndComputeDistMatrix</code></a> method in the project code. This method goes over the distance matrix, picks only 100 items to find distance and constructs a distance matrix of <code class="language-plaintext highlighter-rouge">20 x 20</code>… our desired Distance Matrix.</p>
<h2 id="implementation">Implementation</h2>
<p>Let’s formulate our Nomad Cyclist Problem (TSP) as a Linear Programming problem.
To solve an Linear Programming problem, we usually need 4 things</p>
<ul>
<li>Decision Variables</li>
<li>Constraints</li>
<li>Objective</li>
<li>Solver</li>
</ul>
<h2 id="solver">Solver</h2>
<p>A (optimization) solver is a software or library which takes an optimization problem defined (modeled) in a certain way and tries to find an optimum solution to that problem. Our job is to model the problem properly, the solver does the heavy duty.</p>
<p>We will use OR-Tools which is an open source library by Google AI team for tackling optimization problems.</p>
<p>Let’s see how to model our problem for OR-Tools Initialize OR-Tools’ Linear Solver</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ortools.linear_solver</span> <span class="kn">import</span> <span class="n">pywraplp</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">pywraplp</span><span class="p">.</span><span class="n">Solver</span><span class="p">(</span><span class="s">'TSP'</span><span class="p">,</span> <span class="n">pywraplp</span><span class="p">.</span><span class="n">Solver</span><span class="p">.</span><span class="n">GLOP_LINEAR_PROGRAMMING</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="decision-variable">Decision Variable</h2>
<p>In simple words, decision variables are the variables that will contain the output of the Linear Programming algorithm. The decision made by our algorithm!</p>
<p>For TSP, a decision variable will be a 2D list of the same size as Distance Matrix. It will contain <code class="language-plaintext highlighter-rouge">1</code> in the cells whose corresponding roads must be visited and hence part of the output route. And <code class="language-plaintext highlighter-rouge">0</code> at roads that must not be traveled.</p>
<p><img src="/images/nomad-cyclist/decision-variable-tsp.png" alt="Decision Variable for Traveling Salesman Problem" /></p>
<p>According to OR-Tools <a href="https://developers.google.com/optimization/reference/python/linear_solver/pywraplp#intvar">Linear Solver documentation</a>, we define an integer decision variable using <code class="language-plaintext highlighter-rouge">IntVar</code>.</p>
<p>So, let’s say we want to create a variable named <code class="language-plaintext highlighter-rouge">count</code> that will hold the number of times a certain task is done. We will probably start it with 0 and will keep incrementing with each task until the end of our program, where it would contain some useful value of our interest.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></div>
<p>If we want to define the same variable but to be used by the OR-Tools Linear Solver, we will define it as follows (and it will now be called a decision variable).</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># IntVar(lowerBound, upperBound, name)
</span><span class="n">count</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="n">IntVar</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="s">'count_var'</span><span class="p">)</span>
</code></pre></div></div>
<p>Notice that we have to provide bounds too. This tells the solver to find an integer value that is from lowerBound to upperBound. This helps narrow down the possible outputs and the solver can find the solution faster.</p>
<p>Now we define our decision variable for TSP. It is going to be a 2d Python list of <code class="language-plaintext highlighter-rouge">IntVar</code>s. But here, the bounds of cities will be <code class="language-plaintext highlighter-rouge">0</code> to <code class="language-plaintext highlighter-rouge">1</code> except for the cities whose inner distances were not found, for them bounds will be <code class="language-plaintext highlighter-rouge">0</code> to <code class="language-plaintext highlighter-rouge">0</code>.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="p">[[</span><span class="n">s</span><span class="p">.</span><span class="n">IntVar</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">D</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">is</span> <span class="bp">None</span> <span class="k">else</span> <span class="mi">1</span><span class="p">,</span> <span class="s">''</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">n</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>
</code></pre></div></div>
<h2 id="constraints">Constraints</h2>
<p>Think of constraints as the limits or rules that must be respected. According to TSP,</p>
<p>“We must visit every city in a single closed path exactly once.”</p>
<p>First we will solve TSP for a closed tour. Then we’ll modify it to give an open shortest path for our Nomad Cyclists.
So, in our solution there must be only one road (used for) going to a city and only one road going out of the city.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><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="c1"># in i-th row, only one element can be used in the tour
</span> <span class="c1"># i.e. only 1 outgoing road from i-th city must be used in the tour
</span> <span class="n">s</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="mi">1</span> <span class="o">==</span> <span class="nb">sum</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="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">n</span><span class="p">)))</span>
<span class="c1"># in j-th column, only one element can be used in the tour
</span> <span class="c1"># i.e. only 1 incoming road to j-th city must be used in tour
</span> <span class="n">s</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="mi">1</span> <span class="o">==</span> <span class="nb">sum</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">i</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">n</span><span class="p">)))</span>
<span class="c1"># a city must not be assigned a path to itself in the tour
</span> <span class="c1"># no direct road from i-th city to i-th city again must be taken
</span> <span class="n">s</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="mi">0</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">i</span><span class="p">])</span>
</code></pre></div></div>
<p>The above constraints are not enough. There are sometimes problematic subtours and each of them covers a subset of distinct cities but none of them cover all the cities. We want one (shortest) tour through all cities.</p>
<p><img src="/images/nomad-cyclist/problematic-subtours-in-traveling-salesman-problem.png" alt="Problematic (disconnected) Sub Tours" /></p>
<p>We know that any closed path consisting of 6 cities has 6 arcs (edges / roads) connecting them (like above photo). So, we tell our solver that if a tour is a subtour (i.e. it covers cities less than total cities in distance matrix) the roads connecting these cities must be less than the number of cities in this subtour. This will enforce the solver to find subtours that are not closed, which will eventually lead to successfully finding a closed (shortest) tour connecting all cities.</p>
<p>But we cannot find all possible subtours and include them in our constraint. A way to avoid such subtours is that on the first run of our algorithm, we find a solution and if it consists of subtours, we run the algorithm again and this time we tell it to keep those subtours unclosed. We keep repeating the process until we find a single optimal tour that covers all cities instead of small disconnected subtours.</p>
<p>Let’s model this constraint:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Subtours from previous run (if any)
</span><span class="k">for</span> <span class="n">sub</span> <span class="ow">in</span> <span class="n">Subtours</span><span class="p">:</span>
<span class="c1"># list containing total outgoing+incoming arcs to
</span> <span class="c1"># each city in this subtour
</span> <span class="n">K</span> <span class="o">=</span> <span class="p">[</span> <span class="n">x</span><span class="p">[</span><span class="n">sub</span><span class="p">[</span><span class="n">i</span><span class="p">]][</span><span class="n">sub</span><span class="p">[</span><span class="n">j</span><span class="p">]]</span> <span class="o">+</span> <span class="n">x</span><span class="p">[</span><span class="n">sub</span><span class="p">[</span><span class="n">j</span><span class="p">]][</span><span class="n">sub</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="nb">len</span><span class="p">(</span><span class="n">sub</span><span class="p">)</span><span class="o">-</span><span class="mi">1</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="nb">len</span><span class="p">(</span><span class="n">sub</span><span class="p">))</span> <span class="p">]</span>
<span class="c1"># sum of arcs (roads used) between these cities must
</span> <span class="c1"># be less than number of cities in this subtour
</span> <span class="n">s</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sub</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span> <span class="o">>=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">K</span><span class="p">))</span>
</code></pre></div></div>
<h2 id="objective">Objective</h2>
<p>The goal of our Integer Programming algorithm. In our case, we want to minimize the distance we have to cover to visit all the cities.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># minimize the total distance of the tour
</span><span class="n">s</span><span class="p">.</span><span class="n">Minimize</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">Sum</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="p">(</span><span class="mi">0</span> <span class="k">if</span> <span class="n">D</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">is</span> <span class="bp">None</span> <span class="k">else</span> <span class="n">D</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">n</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">n</span><span class="p">)))</span>
</code></pre></div></div>
<p>In other words, find a route through these cities that is a shortest path, while respecting the above constraints. It is ‘a’ shortest path. Because there can be multiple shortest paths of same the length.</p>
<h2 id="nomad-cyclist-problem">Nomad Cyclist Problem</h2>
<p>TSP with an open route will be our Nomad Cyclist Problem. A simple trick to make our existing algorithm find an open route instead of a closed one is to add a dummy city to the distance matrix. Set the distance of this dummy city to 0 from all the other cities.</p>
<p>When the solver will find the optimum solution, there will be this dummy city somewhere along the closed route. We simply delete this dummy city and it’s incoming & outgoing arcs (roads) to get an open route for our Nomad Cyclists.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># to make the loops run 1 more time then current size of our lists
# so we could add another row / column item
</span><span class="n">n1</span> <span class="o">=</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span>
<span class="c1"># if i or j are equal to n, that means we are in the last row / column
# just add a 0 element here
</span><span class="n">E</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">in</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">else</span> <span class="n">D</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">n1</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">n1</span><span class="p">)]</span>
</code></pre></div></div>
<p>The resulting route will be the shortest one way and open tour covering all the cities.</p>
<h2 id="manual-v-calculated-tour-comparison">Manual v Calculated tour comparison</h2>
<p>According to our solver, we must cycle through cities in this order</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Shortest Path for our Cycling Tour:
Cities: Xi'an Lanzhou Dunhuang Gaochang Urumqi Khorgas Horgos Almaty Bishkek Tashkent Samarqand Dushanbe Baysun Bukhara Merv Ashgabat Tehran Tabriz Ankara Istanbul
City Index 0 1 2 3 4 5 6 7 8 9 12 10 11 13 14 15 16 17 18 19
Distance 0 627 1096 929 163 656 -1 332 237 631 310 292 196 341 353 400 948 632 1463 451
Cumulative 0 627 1723 2652 2815 3471 3470 3802 4039 4670 4980 5272 5468 5809 6162 6562 7510 8142 9605 10056
</code></pre></div></div>
<p>A photo of same output in case the above output does not appear readable:
<img src="/images/nomad-cyclist/output-nomad-cyclist.png" alt="Nomad Cycling Tour" /></p>
<p>After deciding the cities to visit, we made a rough plan about the order in which we will visit these places. On Google Maps, you cannot create a route between more than 10 places (I think).</p>
<p>First leg of our previously planned route for Chinese Silk Road cities, which matches the route suggested by our algorithm:</p>
<p><img src="/images/nomad-cyclist/silk-road-china.png" alt="Chinese Part of Silk Road" /></p>
<p>Last leg of our planned tour through the Middle East to Europe… this also matches with algorithm output:</p>
<p><img src="/images/nomad-cyclist/silk-road-middle-east.png" alt="Silk Road through Middle East" /></p>
<p>2nd (center) leg of our manually planned route for Central Asian cities:</p>
<p><img src="/images/nomad-cyclist/silk-road-central-asia.png" alt="Central Asia - Silk Road" /></p>
<p>The shortest route by our algorithm matches about 90% of our manually planned route except a few cities in Central Asia:</p>
<p><img src="/images/nomad-cyclist/shortest-path-silk-road-tsp.png" alt="Central Asia - Shortest Path - Silk Road" /></p>
<p>When cycling thousands of kilometers, even a couple hundred kilometers less traveled to reach our destination feels like a relief. We hope this has been a great guide to the Traveling Salesman Problem and Nomad Cyclist Problem. An important thing we’ll consider next is the elevation.</p>
<h2 id="plan-your-own-route">Plan your own route</h2>
<p>All the project code is on <a href="https://github.com/emadehsan/nomad-cyclist">GitHub as Nomad Cyclist</a> and is full of helpful comments. Feel free to customize it and make a pull request.</p>
<p>The (TSP) Linear Programming algorithm used here is taken from Serge Kruk’s <a href="https://amzn.to/3iPceJD">Practical Python AI Projects</a> book. Highly recommended if you want to study route optimization algorithms or optimization algorithms in general.</p>
<p>Next, we will be adding the option to consider elevation and climb difficulty too, a very important thing to consider for cyclists while planning tours.</p>An step by step guide to Traveling Salesman Problem and it’s application in cycle tour planningCutting Stock Problem - 1D - How to cut Rods, Paper Rolls from Stock with minimum wastage2020-07-18T19:44:09+00:002020-07-18T19:44:09+00:00https://alternate.parts/cutting-stock-problem<p align="center">
<img alt="Cutting Stock Problem" src="/images/csp-preview.png" />
</p>
<p>If you have seen wide Paper or Fabric Rolls cut into smaller width rolls, or cutting of big metal rods.</p>
<p>You might have wondered:</p>
<blockquote>
<h3 id="how-many-ways-are-there-to-cut-a-big-stock-item-into-small-pieces-of-required-lengths">How many ways are there to cut a big stock item into small pieces of required lengths?</h3>
</blockquote>
<p>The answer is: Too many ways</p>
<p>Perhaps a better question is:</p>
<blockquote>
<h3 id="how-to-cut-required-small-pieces-from-the-stock-item-so-that-waste-is-minimum">How to cut required small pieces from the stock item so that waste is minimum?</h3>
</blockquote>
<p>Another question you could ask:</p>
<blockquote>
<h3 id="how-to-cut-the-required-small-pieces-from-the-stock-item-so-that-minimum-possible-stock-items-are-used">How to cut the required small pieces from the stock item so that minimum possible stock items are used?</h3>
</blockquote>
<p>All these questions are addressed with the name ‘Cutting Stock Problem’ (CSP) and are studied under a sub-field of Applied Mathematics called Operations Research. People in the Metal, Glass, Paper and Textile industry deal with this problem everyday.</p>
<h2 id="1d-vs-2d-cutting-stock-problem">1D vs 2D Cutting Stock Problem</h2>
<p>The are a couple of variations of CSP. If each next piece that we want requires a single cut to get, it’s called 1D or One Dimensional Cutting Stock Problem. Examples include cutting of Paper Rolls, Fabric Rolls and Metal Rods.</p>
<p align="center">
<img alt="1D Cutting Stock Problem example" src="/images/1d-example.png" />
<p align="center">
1D Cutting Stock Problem
</p>
</p>
<p>If the cutting involves a rectangular sheet cut into small rectangular sheets of required sizes, it’s called 2D or Two Dimensional Cutting Stock Problem
Examples includes Cutting of Glass Sheets and Metal Sheets.</p>
<p align="center">
<img alt="1D Cutting Stock Problem example" src="/images/2d-example.png" />
<p align="center">
2D Cutting Stock Problem example
</p>
</p>
<p>This article discusses 1D Problem in depth and in the next article, we’ll discuss the 2D Problem.
For the sake of example, let us assume that we have standard rods of size 89 cm in our stock.</p>
<p align="center">
<img alt="stock rod 89cm" src="/images/stock-rod.PNG" />
<p align="center">
</p>
</p>
<p>A customer order arrives and it says, they need:</p>
<ul>
<li>1 rod of 45 cm</li>
<li>1 of 25 cm</li>
<li>3 rods of 20 cm and</li>
<li>1 of 35 cm</li>
</ul>
<p align="center">
<img alt="Customer Order" src="/images/customer-order.PNG" />
<p align="center">
Customer Order
</p>
</p>
<h2 id="cutting-without-a-plan">Cutting without a plan</h2>
<p>We immediately start cutting. We don’t think of rearranging the rods and we cut them in the manner that they were mentioned in the customer order.</p>
<p>From 1st Stock rod, we cut: 45 cm & 25 cm and we are left with 19 cm which does not satisfy any of our customer demands.</p>
<p>We move to the next rod for cutting. we cut: 20 cm, another 20 and another 20, we are left with 29 cm. Our last required rod is 35 cm that cannot be cut from 29 cm so we have to cut another stock rod</p>
<p>We start cutting the 3rd rod. we cut: 35 cm and we are left with 54 cm</p>
<p align="center">
<img alt="Cut summery" src="/images/cut-summery.PNG" />
<p align="center">
Cut summary
</p>
</p>
<p>So with no planning for cuts, we ended up using 3 stock rods to satisfy the customer demand and our leftover is 19 cm, 29 cm and 54 cm. We might be able to use the leftover 29 and 54 cm rods in some future orders but 19 cm leftover will most likely go to waste because seemingly no customer needs rods less than 20 cm.</p>
<p>This no planning for cuts approach is certainly not ideal. What if the next order requires a 60 cm rod. We cannot satisfy that with 29 and 54 cm rods.</p>
<p>So, is there a way to satisfy this customer demand So that we use minimum possible stock rods? and our waste is minimum too?</p>
<h2 id="csp-tool---to-plan-the-cuts">CSP Tool - to plan the cuts</h2>
<p>Let us plan for cuts with the help a tool. We designed this simple open source tool to solve Cutting Stock Problem which is free to use and available at</p>
<blockquote>
<h3 id="httpsalternatepartscsp"><a href="https://alternate.parts/csp">https://alternate.parts/csp</a></h3>
</blockquote>
<p>This CSP Tool can plan both 1D and 2D Cutting Stock Problem. (The 2D part is not complete yet.)</p>
<p>Let us focus on 1D tool which helps us in finding an optimal plan to cut your stock rods or stock fabric rolls so that minimum possible stock is used and waste is minimum too.</p>
<p align="center">
<img src="/images/csp-tool.png" />
<p align="center">
<a href="https://alternate.parts/csp">CSP Tool</a> in action. Plan for cuts is in the diagram and table on the right
</p>
</p>
<p>Here you see 2 tables.
In the top table, you specify the details of customer rods to cut. In the bottom table, you enter stock rods details.</p>
<p>Notice that you cannot specify the quantities of the stock rods for now. Actually this tool will tell you how many stock rods will be required to completely satisfy this demand.
(We might add this feature soon so that you could limit the number of stock rods to be used and satisfy maximum possible customer demand while staying in the limit.)</p>
<p>Let’s enter the sizes of customer rolls in the top table
45 - 1
25 - 1
20 - 3
35 - 1</p>
<p>Enter size of stock rods in the bottom table: 89</p>
<p>Notice that you don’t need to write the units like cm or inch. You only specify size. click “Cut”. You can see the plan:</p>
<p align="center">
<img src="/images/solution.PNG" />
<p align="center">
</p>
</p>
<p>In the diagram on the right, we see the plan of how to cut the rods. Each row specifies 1 stock rod and each box represents 1 small customer rod. Rods with the same width or size have the same color. And the blackish color specifies the waste or the leftover part of the stock rod.</p>
<p>In the bottom right table, we can see the Usage or Utilization of each stock rod. 1st was utilized 95.5% and 89.9% of the 2nd was used. And here is the details of the cuts. You can also download these cut details as a CSV file and import to Microsoft Excel. Or simply copy and paste to Google sheets.</p>
<h2 id="how-it-works">How it works</h2>
<p>Let us see how CSP Tool works behind the scenes. It uses Google’s OR-Tools library which is a fantastic library to solve problems like Cutting Stock Problem.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pip <span class="nb">install </span>ortools
</code></pre></div></div>
<p>OR-Tools is very straightforward to use. You specify the variables. Variables will contain the result of your problem. In our case, they will contain the number of stock rods used and the plan of cutting</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># array of boolean declared as int, if y[i] is 1,
</span> <span class="c1"># then y[i] Big roll is used, else it was not used
</span> <span class="n">y</span> <span class="o">=</span> <span class="p">[</span> <span class="n">solver</span><span class="p">.</span><span class="n">IntVar</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="sa">f</span><span class="s">'y_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">'</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">k</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="p">]</span>
<span class="c1"># x[i][j] = 3 means that small-roll width specified by i-th order
</span> <span class="c1"># must be cut from j-th order, 3 times
</span> <span class="n">x</span> <span class="o">=</span> <span class="p">[[</span><span class="n">solver</span><span class="p">.</span><span class="n">IntVar</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="sa">f</span><span class="s">'x_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">_</span><span class="si">{</span><span class="n">j</span><span class="si">}</span><span class="s">'</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">k</span><span class="p">[</span><span class="mi">1</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">num_orders</span><span class="p">)]</span>
<span class="n">unused_widths</span> <span class="o">=</span> <span class="p">[</span> <span class="n">solver</span><span class="p">.</span><span class="n">NumVar</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">parent_width</span><span class="p">,</span> <span class="sa">f</span><span class="s">'w_</span><span class="si">{</span><span class="n">j</span><span class="si">}</span><span class="s">'</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">k</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="p">]</span>
<span class="c1"># will contain the number of big rolls used
</span> <span class="n">nb</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="n">IntVar</span><span class="p">(</span><span class="n">k</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">k</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">'nb'</span><span class="p">)</span>
</code></pre></div></div>
<p>Then you specify constraints. Constraints are limits within which your algorithm must find a solution. In our case the constraints are that</p>
<ol>
<li>All the customer orders must be satisfied
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># constraint: demand fulfillment
</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">num_orders</span><span class="p">):</span>
<span class="c1"># small rolls from i-th order must be at least as many in quantity
</span> <span class="c1"># as specified by the i-th order
</span> <span class="n">solver</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="nb">sum</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="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">k</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span> <span class="o">>=</span> <span class="n">demands</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span>
</code></pre></div> </div>
</li>
<li>And Sum of sizes of small rods cut from a big stock rod cannot exceed the size of the big rod.
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># constraint: max size limit
</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">k</span><span class="p">[</span><span class="mi">1</span><span class="p">]):</span>
<span class="c1"># total width of small rolls cut from j-th big roll,
</span> <span class="c1"># must not exceed big rolls width
</span> <span class="n">solver</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span> \
<span class="nb">sum</span><span class="p">(</span><span class="n">demands</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="mi">1</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="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">num_orders</span><span class="p">))</span> \
<span class="o"><=</span> <span class="n">parent_width</span><span class="o">*</span><span class="n">y</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> \
<span class="p">)</span>
</code></pre></div> </div>
</li>
</ol>
<p>And then specify the objective. The objective is what is your main goal with this algorithm. Like our goal with CSP is to minimize the number of stock items used.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">Cost</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="n">Sum</span><span class="p">((</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">y</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">k</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
<span class="n">solver</span><span class="p">.</span><span class="n">Minimize</span><span class="p">(</span><span class="n">Cost</span><span class="p">)</span>
</code></pre></div></div>
<p>The code for this algorithm is available at on <a href="https://github.com/emadehsan/csp">GitHub</a></p>
<h2 id="dont-know-programming-no-worries">Don’t know programming? No worries!</h2>
<p>If you don’t understand this programming part, no worries. You don’t need to know programming to use the <a href="https://alternate.parts/csp">CSP Tool</a>.</p>
<p>You can watch the explanation of CSP, demo of CSP Tool and some glass cutting in action in this accompanying video</p>
<p align="center">
<iframe width="560" height="315" src="https://www.youtube.com/embed/4WXtfO9JB20" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</p>
<h3 id="thinks-to-keep-in-mind">Thinks to keep in mind</h3>
<ul>
<li>Works with integers only: IP (Integer Programming) problems working with integers only. If you have some values that have decimal part, you can multiply all of your inputs with some number that will make them integers (or close estimation).</li>
<li>You cannot specify units: Whether your input is in Inches or Meters, you have to keep a record of that yourself and conversions if any.</li>
</ul>
<h2 id="further-readings">Further Readings</h2>
<h2 id="googles-or-tools">Google’s OR Tools</h2>
<p><a href="https://developers.google.com/optimization">Google’s OR Tools</a> library was used in making this tool. They have great tutorials and examples that are easy to follow without any background in Operations Research. It is available for Python, C++, Java and C#.</p>
<h3 id="practical-python-ai-projects">Practical Python AI Projects</h3>
<p>We also learned a lot from Professor Serge Kruk’s book: <a href="https://amzn.to/3iPceJD">Practical Python AI Projects</a>
It is very easy to understand and totally recommended. In fact most of the code used in our CSP Tool is taken from this book.</p>
<p>The 2D Cutting Stock Problem is even more interesting and challenging to solve. See you next time with 2D CSP. :)</p>