<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>VRP archivos &#187; Ingenieria Industrial Online</title>
	<atom:link href="https://ingenieriaindustrialonline.com/tag/vrp/feed/" rel="self" type="application/rss+xml" />
	<link>https://ingenieriaindustrialonline.com/tag/vrp/</link>
	<description>ingenieriaindustriaonline.com</description>
	<lastBuildDate>Fri, 20 Jan 2023 18:31:04 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>

<image>
	<url>https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/cropped-faVicon-32x32.png</url>
	<title>VRP archivos &#187; Ingenieria Industrial Online</title>
	<link>https://ingenieriaindustrialonline.com/tag/vrp/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>¿Cómo calcular una matriz de distancias para modelar un VRP?</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 16 Nov 2021 21:44:14 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Haversine]]></category>
		<category><![CDATA[Matriz de distancias]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Ruteo]]></category>
		<category><![CDATA[VRP]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28858</guid>

					<description><![CDATA[<p>Tal como lo hemos abordado ampliamente, una de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, y dentro de esta categoría, el diseño de rutas de transporte (enrutamiento de vehículos). Los problemas de enrutamiento de vehículos (routing), se encuentran clasificados como problemas de optimización combinatoria, y &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/">¿Cómo calcular una matriz de distancias para modelar un VRP?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Tal como lo hemos abordado ampliamente, u<span>na de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, y dentro de esta categoría, el diseño de rutas de transporte (enrutamiento de vehículos).</span></p>
<p>Los problemas de enrutamiento de vehículos (<em>routing</em>), se encuentran clasificados como problemas de optimización combinatoria, y esto producto de que la cantidad de rutas posibles en un modelo básico, se encuentra determinado por la <span>ecuación (</span><em>n</em><span> – 1)!, donde </span><em>n</em><span>, es igual al número de ubicaciones que componen el problema de enrutamiento.</span></p>
<p>Ahora bien, no solo la resolución de los problemas de enrutamiento es considerada como compleja, también lo es la fase preliminar en la que debemos levantar la información de entrada del modelo (<em>inputs</em>), específicamente, la <strong><em>matriz de distancias</em></strong>.</p>
<p>La forma más básica de un modelo <em>VRP</em>, requiere para su resolución la siguiente información mínima de entrada:</p>

		<div class="plus tie-list-shortcode">
<ul>
<li>Número de vehículos</li>
<li>Un depósito</li>
<li>Matriz de distancias</li>
</ul>

		</div>
	
<p>La matriz de distancias es un tabulado que registra la longitud entre cada uno de los nodos del modelo, incluido el nodo depósito. Su tamaño será <em>n x n </em>(siendo <em>n</em> el número de nodos del modelo). Asumiendo, por ejemplo, que tenemos un problema que se compone de 1 depósito y 19 ubicaciones, el modelo requerirá de una <em>matriz de distancias</em> con 400 datos.</p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>
<p>Recuerdo que cuando abordamos por primera vez este tema en la <em>Universidad</em>, en mi etapa de estudiante, se nos mencionó enfáticamente la complejidad que subyace en este tipo de modelos: El procesamiento, el levantamiento de datos, la consideración de ubicaciones reales, etc.</p>
<p>Pues bien, han pasado algunos años desde entonces, y todo cambió. El modelamiento de este tipo de problemas, la integración con sistemas de información geográfica, la posibilidad de automatizar procesos de captura de información. <em>¡Todo!</em></p>

			</div>
		</div>
	
<p>En artículos anteriores hemos abordado modelos robustos para la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>resolución de problemas VRP</strong></a> y algunas de sus extensiones (<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/"><strong>CVRP</strong></a>, por ejemplo). El objetivo de este artículo será la de utilizar funciones espaciales de <em>Python</em>, para obtener una matriz de distancias de acuerdo a ubicaciones reales, con el propósito de resolver un modelo VRP básico.</p>
<hr />
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>La Secretaría de Educación de Santiago de Cali, tiene dentro de sus funciones, distribuir el material pedagógico a las diferentes instituciones de formación de la ciudad. La distribución del material se realiza todos los lunes, y para ello, la Secretaría cuenta con dos vehículos con capacidad suficiente para transportar todo el material. El depósito del material (lugar desde donde salen y deben regresar los vehículos), se encuentra en la Alcaldía de Cali; y las instituciones que deben visitarse son 60. Tal como podemos apreciar en la siguiente tabla:</p></blockquote>
<table width="576">
<tbody>
<tr>
<td width="80" style="text-align: center;"><strong>Nodo</strong></td>
<td width="224" style="text-align: center;"><strong>Lugar</strong></td>
<td width="127" style="text-align: center;"><strong>Latitud</strong></td>
<td width="145" style="text-align: center;"><strong>Longitud</strong></td>
</tr>
<tr>
<td style="text-align: center;">0</td>
<td style="text-align: center;">Secretaría de Educación &#8211; Alcaldía</td>
<td style="text-align: center;">3,454431875</td>
<td style="text-align: center;">-76,53421433</td>
</tr>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: center;"> Comfandi San Nicolás</td>
<td style="text-align: center;">3,453591118</td>
<td style="text-align: center;">-76,52254886</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: center;"> Mayor de Santiago de Cali</td>
<td style="text-align: center;">3,451577758</td>
<td style="text-align: center;">-76,51023216</td>
</tr>
<tr>
<td style="text-align: center;">3</td>
<td style="text-align: center;"> Municipal Comfandi</td>
<td style="text-align: center;">3,448107915</td>
<td style="text-align: center;">-76,51074714</td>
</tr>
<tr>
<td style="text-align: center;">4</td>
<td style="text-align: center;"> Internado San Carlos</td>
<td style="text-align: center;">3,446994135</td>
<td style="text-align: center;">-76,51525325</td>
</tr>
<tr>
<td style="text-align: center;">5</td>
<td style="text-align: center;"> León de Greiff</td>
<td style="text-align: center;">3,447979402</td>
<td style="text-align: center;">-76,49993247</td>
</tr>
<tr>
<td style="text-align: center;">6</td>
<td style="text-align: center;"> Nuestra Señora de la Anunciación</td>
<td style="text-align: center;">3,445152112</td>
<td style="text-align: center;">-76,49641342</td>
</tr>
<tr>
<td style="text-align: center;">7</td>
<td style="text-align: center;"> Fernando de Aragón</td>
<td style="text-align: center;">3,437355603</td>
<td style="text-align: center;">-76,51383704</td>
</tr>
<tr>
<td style="text-align: center;">8</td>
<td style="text-align: center;"> Casa Evangélica</td>
<td style="text-align: center;">3,437955337</td>
<td style="text-align: center;">-76,52299947</td>
</tr>
<tr>
<td style="text-align: center;">9</td>
<td style="text-align: center;"> San Alberto Magno</td>
<td style="text-align: center;">3,433028941</td>
<td style="text-align: center;">-76,52707643</td>
</tr>
<tr>
<td style="text-align: center;">10</td>
<td style="text-align: center;"> Santa María Goretty</td>
<td style="text-align: center;">3,433414486</td>
<td style="text-align: center;">-76,50720662</td>
</tr>
<tr>
<td style="text-align: center;">11</td>
<td style="text-align: center;"> San Alberto Magno</td>
<td style="text-align: center;">3,433157456</td>
<td style="text-align: center;">-76,5267331</td>
</tr>
<tr>
<td style="text-align: center;">12</td>
<td style="text-align: center;"> San Ignacio de Loyola</td>
<td style="text-align: center;">3,431786629</td>
<td style="text-align: center;">-76,51733464</td>
</tr>
<tr>
<td style="text-align: center;">13</td>
<td style="text-align: center;"> Nuestro Futuro</td>
<td style="text-align: center;">3,430629992</td>
<td style="text-align: center;">-76,50360174</td>
</tr>
<tr>
<td style="text-align: center;">14</td>
<td style="text-align: center;"> Sabio Caldas</td>
<td style="text-align: center;">3,429087807</td>
<td style="text-align: center;">-76,51660508</td>
</tr>
<tr>
<td style="text-align: center;">15</td>
<td style="text-align: center;"> CREAD</td>
<td style="text-align: center;">3,425060978</td>
<td style="text-align: center;">-76,51488847</td>
</tr>
<tr>
<td style="text-align: center;">16</td>
<td style="text-align: center;"> Licomtec</td>
<td style="text-align: center;">3,416664559</td>
<td style="text-align: center;">-76,51673383</td>
</tr>
<tr>
<td style="text-align: center;">17</td>
<td style="text-align: center;">  Nuestra Señora De La Providencia</td>
<td style="text-align: center;">3,419534772</td>
<td style="text-align: center;">-76,49591989</td>
</tr>
<tr>
<td style="text-align: center;">18</td>
<td style="text-align: center;"> Real Suizo</td>
<td style="text-align: center;">3,415208029</td>
<td style="text-align: center;">-76,49323768</td>
</tr>
<tr>
<td style="text-align: center;">19</td>
<td style="text-align: center;"> Nuevo Edén</td>
<td style="text-align: center;">3,415722099</td>
<td style="text-align: center;">-76,53383559</td>
</tr>
<tr>
<td style="text-align: center;">20</td>
<td style="text-align: center;"> Católico</td>
<td style="text-align: center;">3,413066071</td>
<td style="text-align: center;">-76,53984374</td>
</tr>
<tr>
<td style="text-align: center;">21</td>
<td style="text-align: center;"> Santa María Stella</td>
<td style="text-align: center;">3,427031556</td>
<td style="text-align: center;">-76,55134505</td>
</tr>
<tr>
<td style="text-align: center;">22</td>
<td style="text-align: center;"> Santa Isabel</td>
<td style="text-align: center;">3,40805355</td>
<td style="text-align: center;">-76,50817223</td>
</tr>
<tr>
<td style="text-align: center;">23</td>
<td style="text-align: center;"> Compartir</td>
<td style="text-align: center;">3,431957663</td>
<td style="text-align: center;">-76,47495575</td>
</tr>
<tr>
<td style="text-align: center;">24</td>
<td style="text-align: center;"> Lancaster</td>
<td style="text-align: center;">3,400770816</td>
<td style="text-align: center;">-76,55177421</td>
</tr>
<tr>
<td style="text-align: center;">25</td>
<td style="text-align: center;"> Parroquial Divino Salvador</td>
<td style="text-align: center;">3,397086588</td>
<td style="text-align: center;">-76,54259033</td>
</tr>
<tr>
<td style="text-align: center;">26</td>
<td style="text-align: center;"> Reyes Católicos</td>
<td style="text-align: center;">3,393316667</td>
<td style="text-align: center;">-76,53735466</td>
</tr>
<tr>
<td style="text-align: center;">27</td>
<td style="text-align: center;"> Liceo Anglo del Valle</td>
<td style="text-align: center;">3,387318719</td>
<td style="text-align: center;">-76,51975937</td>
</tr>
<tr>
<td style="text-align: center;">28</td>
<td style="text-align: center;"> Laurence</td>
<td style="text-align: center;">3,383420238</td>
<td style="text-align: center;">-76,52078934</td>
</tr>
<tr>
<td style="text-align: center;">29</td>
<td style="text-align: center;"> Los Almendros</td>
<td style="text-align: center;">3,381278208</td>
<td style="text-align: center;">-76,52023144</td>
</tr>
<tr>
<td style="text-align: center;">30</td>
<td style="text-align: center;"> Bautista</td>
<td style="text-align: center;">3,37720834</td>
<td style="text-align: center;">-76,52327843</td>
</tr>
<tr>
<td style="text-align: center;">31</td>
<td style="text-align: center;"> Lacordaire</td>
<td style="text-align: center;">3,378150837</td>
<td style="text-align: center;">-76,54460736</td>
</tr>
<tr>
<td style="text-align: center;">32</td>
<td style="text-align: center;"> General José María Córdoba</td>
<td style="text-align: center;">3,393573314</td>
<td style="text-align: center;">-76,54932805</td>
</tr>
<tr>
<td style="text-align: center;">33</td>
<td style="text-align: center;"> El Hogar</td>
<td style="text-align: center;">3,390745864</td>
<td style="text-align: center;">-76,5503151</td>
</tr>
<tr>
<td style="text-align: center;">34</td>
<td style="text-align: center;"> Americano</td>
<td style="text-align: center;">3,379093255</td>
<td style="text-align: center;">-76,54688187</td>
</tr>
<tr>
<td style="text-align: center;">35</td>
<td style="text-align: center;"> Santa Filomena</td>
<td style="text-align: center;">3,401969935</td>
<td style="text-align: center;">-76,51345082</td>
</tr>
<tr>
<td style="text-align: center;">36</td>
<td style="text-align: center;"> Tomás Vasconi</td>
<td style="text-align: center;">3,403040928</td>
<td style="text-align: center;">-76,5173132</td>
</tr>
<tr>
<td style="text-align: center;">37</td>
<td style="text-align: center;"> República del Salvador</td>
<td style="text-align: center;">3,404454636</td>
<td style="text-align: center;">-76,52143308</td>
</tr>
<tr>
<td style="text-align: center;">38</td>
<td style="text-align: center;"> Los Andes</td>
<td style="text-align: center;">3,429601077</td>
<td style="text-align: center;">-76,53761216</td>
</tr>
<tr>
<td style="text-align: center;">39</td>
<td style="text-align: center;">Villacolombia</td>
<td style="text-align: center;">3,445493943</td>
<td style="text-align: center;">-76,50169202</td>
</tr>
<tr>
<td style="text-align: center;">40</td>
<td style="text-align: center;">Las Américas</td>
<td style="text-align: center;">3,449220822</td>
<td style="text-align: center;">-76,50594064</td>
</tr>
<tr>
<td style="text-align: center;">41</td>
<td style="text-align: center;">Santa Fe</td>
<td style="text-align: center;">3,442238267</td>
<td style="text-align: center;">-76,50988885</td>
</tr>
<tr>
<td style="text-align: center;">42</td>
<td style="text-align: center;">Evaristo García</td>
<td style="text-align: center;">3,440781776</td>
<td style="text-align: center;">-76,51752778</td>
</tr>
<tr>
<td style="text-align: center;">43</td>
<td style="text-align: center;">Alfredo Vásquez Cobo</td>
<td style="text-align: center;">3,435598366</td>
<td style="text-align: center;">-76,5164549</td>
</tr>
<tr>
<td style="text-align: center;">44</td>
<td style="text-align: center;">Ciudad de Cali</td>
<td style="text-align: center;">3,431143181</td>
<td style="text-align: center;">-76,51272126</td>
</tr>
<tr>
<td style="text-align: center;">45</td>
<td style="text-align: center;">INEM</td>
<td style="text-align: center;">3,482761991</td>
<td style="text-align: center;">-76,49976083</td>
</tr>
<tr>
<td style="text-align: center;">46</td>
<td style="text-align: center;">Olaya Herrera</td>
<td style="text-align: center;">3,478178519</td>
<td style="text-align: center;">-76,51280709</td>
</tr>
<tr>
<td style="text-align: center;">47</td>
<td style="text-align: center;">Guillermo Valencia</td>
<td style="text-align: center;">3,47449459</td>
<td style="text-align: center;">-76,5136654</td>
</tr>
<tr>
<td style="text-align: center;">48</td>
<td style="text-align: center;">José Ignacio Rengifo</td>
<td style="text-align: center;">3,471624543</td>
<td style="text-align: center;">-76,5136654</td>
</tr>
<tr>
<td style="text-align: center;">49</td>
<td style="text-align: center;">Santo Tomás</td>
<td style="text-align: center;">3,45830227</td>
<td style="text-align: center;">-76,5164549</td>
</tr>
<tr>
<td style="text-align: center;">50</td>
<td style="text-align: center;">La Merced</td>
<td style="text-align: center;">3,46271449</td>
<td style="text-align: center;">-76,5024645</td>
</tr>
<tr>
<td style="text-align: center;">51</td>
<td style="text-align: center;">Pedro Antonio Molina</td>
<td style="text-align: center;">3,482804827</td>
<td style="text-align: center;">-76,48761579</td>
</tr>
<tr>
<td style="text-align: center;">52</td>
<td style="text-align: center;">Santa Librada</td>
<td style="text-align: center;">3,46228612</td>
<td style="text-align: center;">-76,52302095</td>
</tr>
<tr>
<td style="text-align: center;">53</td>
<td style="text-align: center;">República de Israel</td>
<td style="text-align: center;">3,463656904</td>
<td style="text-align: center;">-76,51053258</td>
</tr>
<tr>
<td style="text-align: center;">54</td>
<td style="text-align: center;">San Vicente Paul</td>
<td style="text-align: center;">3,466227117</td>
<td style="text-align: center;">-76,50950261</td>
</tr>
<tr>
<td style="text-align: center;">55</td>
<td style="text-align: center;">Manuel María Mallarino</td>
<td style="text-align: center;">3,456760129</td>
<td style="text-align: center;">-76,48851701</td>
</tr>
<tr>
<td style="text-align: center;">56</td>
<td style="text-align: center;">Sebastián de Belalcazar</td>
<td style="text-align: center;">3,460229941</td>
<td style="text-align: center;">-76,48521253</td>
</tr>
<tr>
<td style="text-align: center;">57</td>
<td style="text-align: center;">Liceo Departamental</td>
<td style="text-align: center;">3,423860462</td>
<td style="text-align: center;">-76,5385563</td>
</tr>
<tr>
<td style="text-align: center;">58</td>
<td style="text-align: center;">Libardo Madrid</td>
<td style="text-align: center;">3,422061154</td>
<td style="text-align: center;">-76,54383489</td>
</tr>
<tr>
<td style="text-align: center;">59</td>
<td style="text-align: center;">Metropolitano Santa Anita</td>
<td style="text-align: center;">3,401691038</td>
<td style="text-align: center;">-76,54218265</td>
</tr>
<tr>
<td style="text-align: center;">60</td>
<td style="text-align: center;">San José</td>
<td style="text-align: center;">3,396935816</td>
<td style="text-align: center;">-76,55031511</td>
</tr>
</tbody>
</table>
<p>Se desea desarrollar un plan de rutas para cada uno de los vehículos que logre minimizar la distancia total recorrida, al tiempo que asegure la visita de todos los colegios.</p>
<h3>¿Qué necesitaremos?</h3>

		<div id="que-necesitaremos" data-title="¿Qué necesitaremos?" class="index-title"></div>
	
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>:<span> </span></em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>SkLearn</em></strong>: Las librerías son a <em>Python</em>, lo que las <em>apps<span> </span></em>son a un teléfono celular. Esta es quizá una de las características más a tractivas de este lenguaje: Casi que existe una librería para cada necesidad. En este caso, <em>SKLearn</em>, es una librería que integra un conjunto de métodos de aprendizaje automático y minería de datos. En este caso utilizaremos sus funciones para cálculo de distancias entre pares.</li>
<li><strong><em>Pandas</em></strong>:<span> </span><span>Es un paquete de Python que proporciona estructuras de datos rápidas, y flexibles, diseñadas para que el trabajo con datos estructurados (tabulares, multidimensionales, potencialmente heterogéneos) y de series de tiempo sea fácil e intuitivo.</span></li>
<li><em><strong>Numpy</strong>: </em>Es una librería que nos permitirá efectuar operaciones matriciales en Python.</li>
<li><strong><em>Math: </em></strong>Es una librería que contiene un conjunto de funciones matemáticas básicas.</li>
</ul>

		</div>
	
</div>
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title visible full-visible"></div>
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Importar las librerías necesarias</h3>

		<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title"></div>
	
<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title visible full-visible"></div>
<p>Respecto a las librerías, en la introducción del artículo hicimos una descripción de la funcionalidad de cada una, veamos como importarlas en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from sklearn.neighbors import DistanceMetric
from math import radians
import pandas as pd
import numpy as np</code></pre>
</div>
<p><span>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</span></p>
<h3>Paso 3: Importar los datos desde Excel</h3>

		<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title"></div>
	
<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title visible full-visible"></div>
<p>De acuerdo a las necesidades del modelo, podemos desarrollar un código que permita la entrada manual de la información, la captura de los datos desde entornos digitales (Internet, por ejemplo), o podemos, desde luego, alimentar nuestro modelo con información contenida en documentos externos, como es el caso de un archivo de Microsoft Excel.</p>
<p>Esta puede considerarse como una de las ventajas de utilizar <em>Python, </em>su capacidad de integrarse con cualquier fuente de datos. En nuestro caso, toda la información se encuentra contenida en un documento de Excel, el cual presenta el siguiente formato:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio.png" alt="datos_colegio" width="577" height="241" class="size-full wp-image-28867 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio.png 577w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio-300x125.png 300w" sizes="(max-width: 577px) 100vw, 577px" /></p>
<p>Utilizaremos ubicaciones reales, y para eso emplearemos las coordenadas de <em>latitud </em>y <em>longitud</em>.</p>

		<div class="box download  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span><span>Puedes descargar el documento de Excel que utilizamos en este ejemplo: </span><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/colegios.xlsx" target="_blank" rel="noopener"><strong>Base de datos</strong></a>
			</div>
		</div>
	
<p><span>En </span><em>Colaboratory</em><span>, el siguiente fragmento permitirá cargar un archivo al entorno de ejecución:</span></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))</code></pre>
</div>
<p>Al ejecutar este fragmento de código, se abrirá una ventana emergente del explorador que permitirá cargar nuestra base de datos, en nuestro caso el archivo tienen el nombre de <em>colegios.xlsx.</em></p>
<p>La siguiente línea de código permitirá almacenar los datos contenidos en el documento en un <em>Dataframe</em><span> </span>de nuestro entorno, dentro de la variable <em>data</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Leer el documento de Excel y almacenar los datos en la variable data
data = pd.read_excel('colegios.xlsx')</code></pre>
</div>
<p><span>Podemos en cualquier momento confirmar si la carga de los datos se ha realizado correctamente, para eso imprimiremos las primeras cinco filas del  </span><em>DataFrame:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data.head()</code></pre>
</div>
<p><span>Al ejecutar esta instrucción tenemos la siguiente salida (Una vista de las 5 primeras filas del marco de datos):</span></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head.png" alt="data_head" width="534" height="257" class="size-full wp-image-28869 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head.png 534w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head-300x144.png 300w" sizes="(max-width: 534px) 100vw, 534px" /></p>
<p>Podemos observar que los datos han sido perfectamente cargados, y que ahora se encuentran almacenados en la variable (<em>DataFrame</em>): <em><strong>data</strong></em>.</p>
<h3>Paso 4: Convertir las coordenadas de latitud y longitud en radianes</h3>

		<div id="paso-4-convertir-las-coordenadas-de-latitud-y-longitud-en-radianes" data-title="Paso 4: Convertir las coordenadas de latitud y longitud en radianes" class="index-title"></div>
	
<p>La mayor parte de las funciones de cálculo de distancias de <em>Sklearn</em> (<em>Scipy</em>) toman las entradas como radianes. Por esta razón, debemos convertir las coordenadas en radianes.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data['Latitud'] = np.radians(data['Latitud'])
data['Longitud'] = np.radians(data['Longitud'])

#Creamos una matriz bidimensional con la latitud y la longitud en radianes
data[['Latitud','Longitud']].to_numpy()</code></pre>
</div>
<p>Al ejecutar estas líneas, las coordenadas quedarán convertidas en radianes.</p>
<h3>Paso 5: Declarar el tipo de métrica de distancias que se utilizará</h3>

		<div id="paso-5-declarar-el-tipo-de-metrica-de-distancias-que-se-utilizara" data-title="Paso 5: Declarar el tipo de métrica de distancias que se utilizará" class="index-title"></div>
	
<p>En este punto quiero detenerme para mencionar que existen decenas de funciones métricas de distancia rápida. Algunas de las más utilizadas son destinadas a espacios vectoriales de valor real, como: <em>distancias euclidianas, distancias de Manhattan, </em>etc. Prácticas cuando se emplean con coordenadas cartesianas.</p>
<p>En nuestro caso, ya que utilizamos ubicaciones reales y contamos con coordenadas de latitud y de longitud, podemos emplear una función de distancia de vectores bidimensionales que considere la curvatura de la tierra; tal es el caso de las <em>Distancias Haversine (Semiverseno)</em>.</p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>La distancia de Haversine (o gran círculo) es la distancia angular entre dos puntos en la superficie de una esfera. Se supone que la primera coordenada de cada punto es la latitud, la segunda es la longitud, expresada en radianes.</p>
<p>Como la Tierra es casi esférica, la fórmula Haversine proporciona una buena aproximación de la distancia entre dos puntos de la superficie terrestre, con un error de menos del 1% en promedio.
			</div>
		</div>
	
<p>Si quieren conocer la fórmula empleada para el cálculo de cada distancia <em>haversine</em>:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine.png" alt="haversine" width="596" height="54" class="size-full wp-image-28870 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine.png 596w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine-300x27.png 300w" sizes="(max-width: 596px) 100vw, 596px" /></p>
<p>Para efectos de nuestro desarrollo, utilizaremos la librería <em>SKLearn</em> para calcular nuestras distancias. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>dist = DistanceMetric.get_metric('haversine')

dist.pairwise(data[['Latitud','Longitud']].to_numpy())*6373</code></pre>
</div>
<p>El fragmento anterior crea un objeto con métricas <em>haversine</em>, y luego utiliza la función <em>pairwise()</em>, para calcular la distancia entre cada uno de los elementos de la matriz (nodos). Cada distancia calculada multiplica el escalar 6373 (radio esférico de la tierra), para calcular las distancias en kilómetros (Para millas multiplicar por 3798).</p>
<h3>Paso 6: Crear un marco de datos (tabulado) de matriz de distancias</h3>

		<div id="paso-6-crear-un-marco-de-datos-tabulado-de-matriz-de-distancias" data-title="Paso 6: Crear un marco de datos (tabulado) de matriz de distancias" class="index-title"></div>
	
<p>Una vez que ejecutemos el paso anterior, tendremos nuestras distancias entre nodos calculadas; lo que haremos en este paso será organizar dichos valores en forma de marco de datos o tabulado, obteniendo nuestra matriz de distancias.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>distance_matrix = pd.DataFrame(dist.pairwise(data[['Latitud','Longitud']].to_numpy())*6373)

distance_matrix.head()</code></pre>
</div>
<p><span>Al ejecutar esta instrucción tenemos la siguiente salida (Una vista de las 5 primeras filas del marco de datos):</span></p>
<figure id="attachment_28871" aria-describedby="caption-attachment-28871" style="width: 601px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias.png" alt="matriz_distancias" width="601" height="274" class="size-full wp-image-28871" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias.png 601w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias-300x137.png 300w" sizes="(max-width: 601px) 100vw, 601px" /><figcaption id="caption-attachment-28871" class="wp-caption-text">Vista recortada</figcaption></figure>
<p>&nbsp;</p>
<p>Podemos imprimir la matriz completa:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>print(distance_matrix)</code></pre>
</div>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2.png" alt="matriz_distancias_2" width="624" height="304" class="size-full wp-image-28872 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2.png 624w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2-300x146.png 300w" sizes="(max-width: 624px) 100vw, 624px" /></p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Puedes utilizar Google Maps para validar la diferencia entre las distancias obtenidas mediante Haversine y las obtenidas mediante Google.
			</div>
		</div>
	
<hr />
<p>Hasta este punto hemos logrado nuestro objetivo principal, que era <strong>obtener una matriz de distancias</strong> de forma automática tomando como base las coordenadas de longitud y latitud de un conjunto de nodos. Específicamente hemos obtenido 3721 valores de distancia en cuestión de segundos (empleando la métrica <em>haversine</em>).</p>
<p>Lógicamente, estos valores de referencia en la práctica presentan algunas desventajas, como, por ejemplo, la dificultad subyacente de atravesar las ciudades por lugares diferentes que las vías dispuestas para ello. Sin embargo, estos valores pueden ser muy útiles como datos de entrada de un modelo VRP para obtener la secuencia del plan de rutas.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Un método más preciso para obtener una matriz de distancias y tiempos consiste en utilizar la API Distance Matrix de Google Maps. Sin embargo, este es un servicio de pago que abordaremos en artículos posteriores
			</div>
		</div>
	
<hr />
<p>Lo siguiente que haremos, para finalizar nuestro caso de aplicación, será utilizar la matriz de distancias obtenida, en un <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>modelo VRP básico</strong></a> (Si desea profundizar en el modelo, visite el artículo).</p>
<h3>Paso 7: Instalar Google Or Tools</h3>

		<div id="paso-7-instalar-google-or-tools" data-title="Paso 7: Instalar Google Or Tools" class="index-title"></div>
	
<p>Es necesario instalar la librería de Google Or Tools en nuestro entorno de <em>Colaboratory</em> para poder utilizar nuestro modelo VRP.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>!pip install ortools</code></pre>
</div>
<p>Al ejecutar esta instrucción instalaremos nuestro solucionador del modelo de enrutamiento.</p>
<h3>Paso 8: Incorporar el modelo VRP (Previamente formulado)</h3>

		<div id="paso-8-incorporar-el-modelo-vrp-previamente-formulado" data-title="Paso 8: Incorporar el modelo VRP (Previamente formulado)" class="index-title"></div>
	
<p>Como ya lo mencionamos, contamos con un modelo debidamente formulado para resolver problemas VRP básicos. Lo único que modificaremos serán los datos de entrada: <em>Matriz de distancias (distance_matrix), Número de vehículos (2), Depósito (Nodo 0).</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>"""Problema de enrutamiento de vehículos simple (VRP)

Autor: MSc. Ing. Bryan Salazar López 2021

"""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
    """Datos de entrada del modelo"""
    #Llamaremos la matriz de distancia previamente obtenida
    #Emplearemos dos vehículos como lo indica el problema
    #Definiremos el nodo 0 como el depósito (Secretaría)
    data = {}
    data['matriz_distancias'] = distance_matrix
    data['num_vehiculos'] = 2
    data['deposito'] = 0
    return data


def print_solution(data, manager, routing, solution):
    """Imprime la solución sobre la consola"""
    max_route_distance = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -&gt; '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distancia de la ruta: {}km\n'.format(route_distance)
        print(plan_output)
        max_route_distance += route_distance
        max_route_distance = max(route_distance, max_route_distance)
    print('Distancia total de todas las rutas: {}km'.format(max_route_distance))



def main():
    """Punto de entrada del programa"""
    # Invocar la data de entrada.
    data = create_data_model()

    # Crea el administrador del índice de rutas.
    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])

    # Crea el modelo de enrutamiento.
    routing = pywrapcp.RoutingModel(manager)


    # Crea y registra una devolución de llamada de distancia.
    def distance_callback(from_index, to_index):
        """Retorna la distancia entre dos nodos."""
        # Convierte desde la variable de ruta Index hasta la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['matriz_distancias'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define el costo de cada arco.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Adhiere la dimensión de distancia.
    dimension_name = 'Distancia'
    routing.AddDimension(
        transit_callback_index,
        0,  # Sin holgura
        3000,  # Distancia máxima de viaje del vehículo
        True,  # Iniciar el acumulador en cero
        dimension_name)
    distance_dimension = routing.GetDimensionOrDie(dimension_name)
    distance_dimension.SetGlobalSpanCostCoefficient(100)

    # Configurar los parámetros de búsqueda.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)

    # Solucionador del problema.
    solution = routing.SolveWithParameters(search_parameters)

    # Imprimir la solución en la consola.
    if solution:
        print_solution(data, manager, routing, solution)
    else:
        print('No se encuentra solución !')


if __name__ == '__main__':
    main()</code></pre>
</div>
<p>Al ejecutar el modelo tendremos:</p>
<figure id="attachment_28873" aria-describedby="caption-attachment-28873" style="width: 1736px" class="wp-caption aligncenter"><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png" target="_blank" rel="noopener"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png" alt="vrp_rutas" width="1736" height="176" class="wp-image-28873 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png 1736w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-300x30.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-1024x104.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-768x78.png 768w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-1536x156.png 1536w" sizes="(max-width: 1736px) 100vw, 1736px" /></a><figcaption id="caption-attachment-28873" class="wp-caption-text">Clic para ver en una pestaña nueva</figcaption></figure>
<p>&nbsp;</p>
<p>Un plan de rutas para los dos vehículos que parten desde los depósitos (ahí mismo finalizan sus recorridos), y visitan la totalidad de los nodos. La distancia total optimizada es equivalente a 33 km.</p>
<p>Por último, también es posible exportar la <strong>matriz de distancias</strong> que hemos obtenido, para eso utilizaremos el siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>distance_matrix.to_csv('distance_matrix.csv')
files.download('distance_matrix.csv')</code></pre>
</div>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>
<p><span>Los contribuciones de la comunidad han revelado que el modelo VRP de Or Tools maneja valores enteros para las distancias en la matriz. Esto significa que, por ejemplo, una distancia de 0.58 km se trataría como un entero, lo que puede causar pérdida de precisión en los resultados. Por esta razón, recomendamos trabajar con la unidad más pequeña de longitud posible, como metros, y convertir las distancias antes de ejecutar el modelo de ruteo. Por ejemplo, 0.58 km se convertiría en 580 metros, evitando pérdida de precisión. Además, es importante tener en cuenta que esto afectaría la distancia máxima del viaje del vehículo, la cual debemos modificar, en consecuencia.</span></p>

			</div>
		</div>
	
<hr />
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1YZIpYWOrTGBeGwRl9iOhWAwNH5WAVpWw?usp=sharing" target="_blank" rel="noopener"><em><strong>Matriz de distancias para modelar un VRP</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/">¿Cómo calcular una matriz de distancias para modelar un VRP?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Problema de Enrutamiento de Vehículos Capacitados (CVRP) con Google OR-Tools</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Wed, 07 Jul 2021 19:33:37 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[CVRP]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Problema de enrutamiento de vehículos]]></category>
		<category><![CDATA[Problemas de ruteo]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruteo]]></category>
		<category><![CDATA[TSP]]></category>
		<category><![CDATA[VRP]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26107</guid>

					<description><![CDATA[<p>Las variaciones del problema de enrutamiento de vehículos simple (VRP), tienen como objetivo adherir al modelo base restricciones que le permitan ajustarse con mayor rigurosidad a un contexto operacional real. ¿Qué es un CVRP? El problema de enrutamiento de vehículos capacitados (CVRP), también conocido como VRP con restricciones de capacidad; es una variación del VRP &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/">Problema de Enrutamiento de Vehículos Capacitados (CVRP) con Google OR-Tools</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Las variaciones del <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>problema de enrutamiento de vehículos simple (VRP)</strong></a>, tienen como objetivo adherir al modelo base restricciones que le permitan ajustarse con mayor rigurosidad a un contexto operacional real.</p>
<h2>¿Qué es un CVRP?</h2>
<p>El problema de enrutamiento de vehículos capacitados (CVRP), también conocido como <strong>VRP con restricciones de capacidad</strong>; es una variación del VRP básico, en el que los vehículos con capacidad de carga limitada necesitan recoger o entregar artículos en varios lugares. Los artículos tienen un valor (cantidad) asociado a magnitudes como peso o volumen, y los vehículos tienen una capacidad máxima que pueden transportar (en las mismas magnitudes). El problema consiste en recoger o entregar los artículos de forma óptima (según el criterio de optimización), sin exceder la capacidad de los vehículos (<em>Braekers et al., 2016</em>).</p>
<figure id="attachment_26109" aria-describedby="caption-attachment-26109" style="width: 481px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP.png" alt="CVRP" width="481" height="353" class="wp-image-26109" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP.png 828w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-300x220.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-768x563.png 768w" sizes="(max-width: 481px) 100vw, 481px" /><figcaption id="caption-attachment-26109" class="wp-caption-text">Figura 1: Esquema general de un modelo CVRP</figcaption></figure>
<p>De acuerdo a lo anterior, cada nodo que debe visitarse tendrá asociado una cantidad (demanda) que se acumulará en cada vehículo en la medida en que este lo visite (<em>Figura 1</em>); de tal manera que se hace necesario considerar una nueva dimensión en el modelo. Del mismo modo, cada vehículo contará con una capacidad limitada. En el caso en que todos los vehículos que componen la flota cuenten con la misma capacidad, el problema se denomina de capacidad homogénea, y en la caso de que esta capacidad sea diferente, se considera un problema de capacidad heterogénea (HFVRP).</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>El modelamiento de rutas de vehículos se encuentra entre los problemas de optimización más estudiados en la literatura académica. Los primeros estudios datan de 1959, cuando Dantzig y Ramser abordaron por primera vez el problema de despacho de camiones homogéneos para atender estaciones de servicio (Dantzig &amp; Ramser, 1959). Posteriormente, Clarke y Wright en 1964, abordaron el problema que consistía en atender un número de clientes geográficamente dispersos, por medio de una flota de vehículos con capacidades heterogéneas, modelo denominado VRP (Vehicle Routing Problem), o Problema de Enrutamiento de Vehículos (Clarke &amp; Wright, 1964). Desde entonces, el modelamiento de rutas de vehículos ha sido uno de los temas más abordados dentro del marco de la investigación de operaciones, la ingeniería industrial, la logística y el transporte.
			</div>
		</div>
	
<p>El objetivo de este artículo consiste en utilizar las librerías del software Google OR-Tools para abordar problemas de enrutamiento de vehículos con restricciones de capacidad (CVRP).</p>
<hr />
<h2>El problema (CVRP)
		<div id="el-problema" data-title="El problema" class="index-title"></div>
	</h2>
<p>Puppis PetShop suministra a las veterinarias de Ciudad de México, diversos productos de cuidado y aseo para mascotas. Cuentan con un pequeño centro de distribución desde el cual abastecen periódicamente a sus clientes, los cuales se localizan tal como se muestra tentativamente en la figura 2.</p>
<figure id="attachment_26109" aria-describedby="caption-attachment-26109" style="width: 552px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP.png" alt="CVRP" width="552" height="404" class="wp-image-26109" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP.png 828w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-300x220.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-768x563.png 768w" sizes="(max-width: 552px) 100vw, 552px" /><figcaption id="caption-attachment-26109" class="wp-caption-text">Figura 2: Mapa representativo del Centro de Distribución, los clientes y la demanda asociada</figcaption></figure>
<p>Desde el Centro de Distribución se consolidan los diferentes productos que demanda cada cliente en pallets o estibas. La demanda asociada a cada cliente puede apreciarse en la Figura 2 (estibas / carga paletizada).</p>
<p>Para efectos de resolver el problema con mayor rapidez, el encargado de levantar la información ha considerado que las distancias entre dos puntos son iguales sin importar el sentido de estos (distancias simétricas).</p>
<p>Las distancias entre el centro distribución (0) y los 16 clientes que deben abastecer se detallan en la siguiente matriz de distancias (metros):</p>
<table width="1044">
<tbody>
<tr>
<td width="58" style="text-align: center;"></td>
<td width="58" style="text-align: center;"><strong>0</strong></td>
<td width="58" style="text-align: center;"><strong>1</strong></td>
<td width="58" style="text-align: center;"><strong>2</strong></td>
<td width="58" style="text-align: center;"><strong>3</strong></td>
<td width="58" style="text-align: center;"><strong>4</strong></td>
<td width="58" style="text-align: center;"><strong>5</strong></td>
<td width="58" style="text-align: center;"><strong>6</strong></td>
<td width="58" style="text-align: center;"><strong>7</strong></td>
<td width="58" style="text-align: center;"><strong>8</strong></td>
<td width="58" style="text-align: center;"><strong>9</strong></td>
<td width="58" style="text-align: center;"><strong>10</strong></td>
<td width="58" style="text-align: center;"><strong>11</strong></td>
<td width="58" style="text-align: center;"><strong>12</strong></td>
<td width="58" style="text-align: center;"><strong>13</strong></td>
<td width="58" style="text-align: center;"><strong>14</strong></td>
<td width="58" style="text-align: center;"><strong>15</strong></td>
<td width="58" style="text-align: center;"><strong>16</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>0</strong></td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">548</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">662</td>
</tr>
<tr>
<td style="text-align: center;"><strong>1</strong></td>
<td style="text-align: center;">548</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">684</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">594</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1016</td>
<td style="text-align: center;">868</td>
<td style="text-align: center;">1210</td>
</tr>
<tr>
<td style="text-align: center;"><strong>2</strong></td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">684</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">992</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">1130</td>
<td style="text-align: center;">788</td>
<td style="text-align: center;">1552</td>
<td style="text-align: center;">754</td>
</tr>
<tr>
<td style="text-align: center;"><strong>3</strong></td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">992</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">1232</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">822</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">560</td>
<td style="text-align: center;">1358</td>
</tr>
<tr>
<td style="text-align: center;"><strong>4</strong></td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">1118</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1244</td>
</tr>
<tr>
<td style="text-align: center;"><strong>5</strong></td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">228</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">240</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">708</td>
</tr>
<tr>
<td style="text-align: center;"><strong>6</strong></td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">228</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">1004</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">480</td>
</tr>
<tr>
<td style="text-align: center;"><strong>7</strong></td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">856</td>
</tr>
<tr>
<td style="text-align: center;"><strong>8</strong></td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">514</td>
</tr>
<tr>
<td style="text-align: center;"><strong>9</strong></td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">240</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">468</td>
</tr>
<tr>
<td style="text-align: center;"><strong>10</strong></td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">1232</td>
<td style="text-align: center;">1118</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">1152</td>
<td style="text-align: center;">354</td>
</tr>
<tr>
<td style="text-align: center;"><strong>11</strong></td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">594</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">1004</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">844</td>
</tr>
<tr>
<td style="text-align: center;"><strong>12</strong></td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
</tr>
<tr>
<td style="text-align: center;"><strong>13</strong></td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1130</td>
<td style="text-align: center;">822</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">536</td>
</tr>
<tr>
<td style="text-align: center;"><strong>14</strong></td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">1016</td>
<td style="text-align: center;">788</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">194</td>
</tr>
<tr>
<td style="text-align: center;"><strong>15</strong></td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">868</td>
<td style="text-align: center;">1552</td>
<td style="text-align: center;">560</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">1152</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">798</td>
</tr>
<tr>
<td style="text-align: center;"><strong>16</strong></td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">1210</td>
<td style="text-align: center;">754</td>
<td style="text-align: center;">1358</td>
<td style="text-align: center;">1244</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">798</td>
<td style="text-align: center;">0</td>
</tr>
</tbody>
</table>
<p>La compañía cuenta con 4 camiones, cada uno de los cuales tiene una capacidad máxima de 15 estibas. Es deseable desarrollar un plan de rutas en el cual se determine cuántos camiones utilizar para minimizar la distancia total recorrida.</p>
<hr />
<h2>Resolución del modelo CVRP mediante Google OR-Tools
		<div id="resolucion-del-modelo-cvrp-mediante-google-or-tools" data-title="Resolución del modelo CVRP mediante Google OR-Tools" class="index-title"></div>
	</h2>
<p><span>De acuerdo a lo mencionado en el artículo de </span><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/"><strong>introducción a Google OR-Tools</strong></a><span>, esta herramienta soporta múltiples lenguajes de programación, en esta ocasión, haremos uso del lenguaje de programación Python.</span></p>
<h3><em>Paso 1: Importar la librería</em></h3>

		<div id="paso-1-importar-la-libreria" data-title="Paso 1: Importar la librería" class="index-title"></div>
	
El siguiente fragmento de código importa las librerías necesarias para resolver un VRP:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Importar la librería de Google OR-Tools
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
</code></pre>
</div>
<h3><em>Paso 2: Crear la data del modelo CVRP</em></h3>

		<div id="paso-2-crear-la-data-del-modelo" data-title="Paso 2: Crear la data del modelo" class="index-title"></div>
	
La data necesaria para modelar un VRP consiste en:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>matriz_distancias</strong>: Una matriz de distancias entre nodos (de acuerdo a una misma unidad de medida)</li>
<li><strong>num_vehiculos:</strong> Número de vehículos disponibles en la flota.</li>
<li><strong>deposito</strong>: Cuál es el índice que identifica al depósito (lugar en el cual todos los vehículos inician y terminan su ruta).</li>
<li><strong>demanda:</strong> Cada ubicación tiene una demanda correspondiente a la cantidad, por ejemplo, peso o volumen, del artículo a recoger. En el caso de nuestro problema serán pallets o estibas.</li>
<li><strong>capacidad_vehiculos:</strong> Cada vehículo tiene una capacidad: la cantidad máxima que puede contener el vehículo. A medida que un vehículo viaja a lo largo de su ruta, la cantidad total de artículos que transporta nunca puede exceder su capacidad. En el caso de nuestro problema serán <strong>15</strong> pallets o estibas.</li>
</ul>

		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def create_data_model():
    """Almacena los datos de entrada del problema"""
    data = {}
    data['matriz_distancias'] = [
        [
            0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
            468, 776, 662
        ],
        [
            548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
            1016, 868, 1210
        ],
        [
            776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
            1130, 788, 1552, 754
        ],
        [
            696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
            1164, 560, 1358
        ],
        [
            582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
            1050, 674, 1244
        ],
        [
            274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
            514, 1050, 708
        ],
        [
            502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
            514, 1278, 480
        ],
        [
            194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
            662, 742, 856
        ],
        [
            308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
            320, 1084, 514
        ],
        [
            194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
            274, 810, 468
        ],
        [
            536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
            730, 388, 1152, 354
        ],
        [
            502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
            308, 650, 274, 844
        ],
        [
            388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
            536, 388, 730
        ],
        [
            354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
            342, 422, 536
        ],
        [
            468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
            342, 0, 764, 194
        ],
        [
            776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
            388, 422, 764, 0, 798
        ],
        [
            662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
            536, 194, 798, 0
        ],
    ]
    data['num_vehiculos'] = 4
    data['deposito'] = 0
    data['demanda'] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]
    data['capacidad_vehiculos'] = [15, 15, 15, 15]
    return data</span></code></pre>
</div>
<p>Cada fila y columna de la matriz de distancias tiene un índice de a cuerdo a su posición iniciando desde 0. Así entonces, el índice 0 se reserva en este caso para el depósito. El orden de la data es importante, así entonces, podemos apreciar que la demanda asociada a cada nodo se ordena de acuerdo al índice de cada cliente, partiendo con una demanda de 0 pallets asociada al depósito.</p>
<p>Ya que el problema considera la disponibilidad de 4 vehículos, el vector de entrada de la capacidad de la flota tendrá 4 datos correspondientes a la capacidad de cada uno de ellos. Para efectos de nuestro ejemplo se trata de capacidad homogénea.</p>
<h3><em>Paso 2: Definir las salidas del solucionador</em></h3>

		<div id="paso-2-definir-las-salidas-del-solucionador" data-title="Paso 2: Definir las salidas del solucionador" class="index-title"></div>
	
El siguiente fragmento de código define la función que imprime la solución del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def print_solution(data, manager, routing, solution):
    """Imprimir la solución en la consola."""
    total_distance = 0
    total_load = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        route_load = 0
        while not routing.IsEnd(index):
            node_index = manager.IndexToNode(index)
            route_load += data['demanda'][node_index]
            plan_output += ' {0} Pallets({1}) -&gt; '.format(node_index, route_load)
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += ' {0} Pallets({1})\n'.format(manager.IndexToNode(index),
                                                 route_load)
        plan_output += 'Distancia de la ruta: {}m\n'.format(route_distance)
        plan_output += 'Pallets entregados en la ruta: {}\n'.format(route_load)
        print(plan_output)
        total_distance += route_distance
        total_load += route_load
    print('Distancia total de todas las rutas: {}m'.format(total_distance))
    print('Pallets entregados en todas las rutas: {}'.format(total_load))
</span></code></pre>
</div>
<h3><em>Paso 4: Crear el administrador de índice de rutas</em></h3>

		<div id="paso-4-crear-el-administrador-de-indice-de-rutas" data-title="Paso 4: Crear el administrador de índice de rutas" class="index-title"></div>
	
El siguiente código en la sección principal de los programas crea el administrador de índices (administrador) y el modelo de enrutamiento (enrutamiento). El método manager.IndexToNode convierte los índices internos del solucionador (que puede ignorar con seguridad) en números para ubicaciones. Los números de ubicación corresponden a los índices de la matriz de distancias.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])
</span></code></pre>
</div>
<h3><em>Paso 5: Crear el modelo de enrutamiento</em></h3>

		<div id="paso-5-crear-el-modelo-de-enrutamiento" data-title="Paso 5: Crear el modelo de enrutamiento" class="index-title"></div>
	
El siguiente código crea el modelo de enrutamiento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    routing = pywrapcp.RoutingModel(manager)
</span></code></pre>
</div>
<h3><em>Paso 6: Definir la devolución del llamado de distancia</em></h3>

		<div id="paso-6-definir-la-devolucion-del-llamado-de-distancia" data-title="Paso 6: Definir la devolución del llamado de distancia" class="index-title"></div>
	
El siguiente fragmento de código permite crear una función que devuelve la llamada de una distancia entre dos nodos y los pasa al solucionador para su consideración. Es decir, de acuerdo a la matriz de distancias dada, esta función establece, de acuerdo a dos ubicaciones, cual es su distancia correspondiente.</p>
<p>Así mismo, esta función permite establecer los costos del arco (útil para los casos que aborden dimensiones adicionales a las distancias).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def distance_callback(from_index, to_index):
    """Retorna la distancia entre dos nodos"""
    # Convierte desde la variable de ruta Index hacia
    # la matriz de distancia NodeIndex.
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    return data['matriz_distancias'][from_node][to_node]

transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
</span></code></pre>
</div>
<h3><em>Paso 7: </em><em>Definir la devolución del llamado de demanda y crear la restricción de capacidad</em></h3>

		<div id="paso-7-crear-la-restriccion-de-capacidad" data-title="Paso 7: Crear la restricción de capacidad" class="index-title"></div>
	
Además de la devolución de llamada a distancia, el solucionador también requiere una devolución de llamada de demanda, que devuelve la demanda en cada ubicación y una dimensión para las limitaciones de capacidad.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    def demand_callback(from_index):
        """Retorna la demanda asociada a cada nodo"""
        # Convierte desde la variable de ruta Index hacia
        # la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        return data['demanda'][from_node]

    demand_callback_index = routing.RegisterUnaryTransitCallback(
        demand_callback)
    routing.AddDimensionWithVehicleCapacity(
        demand_callback_index,
        0,  # Sin holgura en la capacidad de los vehículos
        data['capacidad_vehiculos'],  # Capacidad máxima de los vehículos
        True,  # Iniciar el acumulador en cero
        'Capacity')
</span></code></pre>
</div>
<h3><em>Paso 8: Configurar los parámetros de búsqueda</em></h3>

		<div id="paso-8-configurar-los-parametros-de-busqueda" data-title="Paso 8: Configurar los parámetros de búsqueda" class="index-title"></div>
	
El siguiente código establece los parámetros de búsqueda predeterminados, un método heurístico para encontrar la primera solución y la metaheurística del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    search_parameters.local_search_metaheuristic = (
        routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
    search_parameters.time_limit.FromSeconds(15)
</span></code></pre>
</div>
<p>El solucionador considera 14 estrategias de primera solución. En este caso, utilizaremos la estrategia de <em><strong>ruta más corta: </strong></em><em>PATH_CHEAPEST_ARC.</em></p>
<p>El solucionador considera 6 opciones de búsqueda local:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Automática: <em>AUTOMATIC</em></li>
<li>Algoritmo voraz: <em>GREEDY_DESCENT</em></li>
<li>Búsqueda local guiada: <em>GUIDED_LOCAL_SEARCH</em></li>
<li>Algoritmo de recocido simulado: <em>SIMULATED_ANNEALING</em></li>
<li>Búsqueda tabú: <em>TABU_SEARCH</em></li>
<li>Búsqueda de objetivos tabú: <em>OBJECTIVE_TABU_SEARCH</em></li>
</ul>

		</div>
	
<p>Generalmente, la metaheurística que presenta mejores resultados en modelos de enrutamiento corresponde a la búsqueda local guiada, razón por la cual utilizaremos esta opción en nuestro solucionador.</p>
<p>Respecto a los parámetros de búsqueda, limitaremos al solucionador a 15 segundos de ejecución.</p>
<h3><em>Paso 9: Invocar el solucionador</em></h3>

		<div id="paso-9-invocar-el-solucionador" data-title="Paso 9: Invocar el solucionador " class="index-title"></div>
	
El siguiente fragmento de código sirve para invocar el solucionador del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"> solution = routing.SolveWithParameters(search_parameters)
</span></code></pre>
</div>
<h3><em>Paso 10: Imprimir la solución</em></h3>

		<div id="paso-10-imprimir-la-solucion" data-title="Paso 10: Imprimir la solución" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"> if solution:
print_solution(data, manager, routing, solution)
else:
print('No se encuentra solución !')
</span></code></pre>
</div>
<p>&nbsp;</p>
<hr />
<p>Es posible que el desarrollo de los diez pasos anteriores demande algún grado de complejidad subyacente del uso de un lenguaje de programación; sin embargo, es preciso mencionar que, el modelo anterior queda perfectamente configurado, y puede replicarse con modificaciones menores en múltiples problemas de enrutamiento de vehículos básicos (VRP).</p>
<h2>¿Cómo ejecutar el modelo?
		<div id="como-ejecutar-el-modelo" data-title="¿Cómo ejecutar el modelo?" class="index-title"></div>
	</h2>
<p><em><strong>Alternativa 1, ejecución en nuestro equipo:</strong></em></p>
<p>Lo primero que debemos considerar, en el caso de que queramos ejecutar este código en nuestro equipo, es que es preciso contar con la instalación de <em>Python </em>en nuestro equipo de cómputo, así mismo debemos contar con la última versión del comando <em>pip</em> y por supuesto, el software <em>OR-Tools.</em> Una guía detallada de la instalación de estos requerimientos la podrás encontrar en el siguiente enlace:</p>
<p style="text-align: center;"><a href="https://developers.google.com/optimization/install/python/windows" target="_blank" class="shortc-button medium blue">Instalación de OR-Tools para Python</a>
<p>Ahora, lo recomendable es trabajar con algún editor de código práctico (IDE), por ejemplo: <a href="https://www.sublimetext.com/3"><strong><em>Sublime Text</em></strong></a>, o <em><strong>Spyder</strong></em> (Una herramienta más completa y por ende más robusta y pesada).</p>
<p><em><strong>Alternativa 2, ejecución en un entorno virtual (Recomendado):</strong></em></p>
<p>Podemos utilizar del mismo modo, un entorno virtual. En este caso recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google.</p>
<hr />
<p>El código completo de nuestro desarrollo lo presentamos a continuación. También puedes ver el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1A17CZTJ19OoEUOSsM8fm2wyq5bay46cG?usp=sharing"><strong>Problema de Enrutamiento de Vehículos Capacitados (CVRP)</strong></a>.</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">"""Problema de enrutamiento de vehículos capacitados (CVRP)

Ejercicio de ejemplo: MSc. Ing. Bryan Salazar López 2021

"""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
    """Almacena los datos de entrada del problema"""
    data = {}
    data['matriz_distancias'] = [
        [
            0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
            468, 776, 662
        ],
        [
            548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
            1016, 868, 1210
        ],
        [
            776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
            1130, 788, 1552, 754
        ],
        [
            696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
            1164, 560, 1358
        ],
        [
            582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
            1050, 674, 1244
        ],
        [
            274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
            514, 1050, 708
        ],
        [
            502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
            514, 1278, 480
        ],
        [
            194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
            662, 742, 856
        ],
        [
            308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
            320, 1084, 514
        ],
        [
            194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
            274, 810, 468
        ],
        [
            536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
            730, 388, 1152, 354
        ],
        [
            502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
            308, 650, 274, 844
        ],
        [
            388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
            536, 388, 730
        ],
        [
            354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
            342, 422, 536
        ],
        [
            468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
            342, 0, 764, 194
        ],
        [
            776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
            388, 422, 764, 0, 798
        ],
        [
            662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
            536, 194, 798, 0
        ],
    ]
    data['num_vehiculos'] = 4
    data['deposito'] = 0
    data['demanda'] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8] 
    data['capacidad_vehiculos'] = [15, 15, 15, 15]
    return data


def print_solution(data, manager, routing, solution):
    """Imprimir la solución en la consola."""
    total_distance = 0
    total_load = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        route_load = 0
        while not routing.IsEnd(index):
            node_index = manager.IndexToNode(index)
            route_load += data['demanda'][node_index]
            plan_output += ' {0} Pallets({1}) -&gt; '.format(node_index, route_load)
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += ' {0} Pallets({1})\n'.format(manager.IndexToNode(index),
                                                 route_load)
        plan_output += 'Distancia de la ruta: {}m\n'.format(route_distance)
        plan_output += 'Pallets entregados en la ruta: {}\n'.format(route_load)
        print(plan_output)
        total_distance += route_distance
        total_load += route_load
    print('Distancia total de todas las rutas: {}m'.format(total_distance))
    print('Pallets entregados en todas las rutas: {}'.format(total_load))



def main():
    """Punto de entrada del programa"""
    # Invocar la data de entrada.
    data = create_data_model()

    # Crea el administrador del índice de rutas.
    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])

    # Crea el modelo de enrutamiento.
    routing = pywrapcp.RoutingModel(manager)


    # Crea y registra una devolución de llamada de distancia.
    def distance_callback(from_index, to_index):
        """Retorna la distancia entre dos nodos."""
        # Convierte desde la variable de ruta Index hasta la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['matriz_distancias'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define el costo de cada arco.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Adhiere la restricción de capacidad.
    def demand_callback(from_index):
        """Retorna la demanda asociada a cada nodo"""
        # Convierte desde la variable de ruta Index hacia
        # la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        return data['demanda'][from_node]

    demand_callback_index = routing.RegisterUnaryTransitCallback(
        demand_callback)
    routing.AddDimensionWithVehicleCapacity(
        demand_callback_index,
        0,  # Sin holgura en la capacidad de los vehículos
        data['capacidad_vehiculos'],  # Capacidad máxima de los vehículos
        True,  # Iniciar el acumulador en cero
        'Capacity')

    # Configurar los parámetros de búsqueda.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    search_parameters.local_search_metaheuristic = (
        routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
    search_parameters.time_limit.FromSeconds(15)

    # Solucionador del problema.
    solution = routing.SolveWithParameters(search_parameters)

    # Imprimir la solución en la consola.
    if solution:
        print_solution(data, manager, routing, solution)
    else:
        print('No se encuentra solución !')


if __name__ == '__main__':
    main()</span></code></pre>
</div>
<p>Al ejecutar nuestro desarrollo en <em><a href="https://colab.research.google.com/drive/1A17CZTJ19OoEUOSsM8fm2wyq5bay46cG?usp=sharing">Colaboratory</a>, </em>tenemos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion.png" alt="CVRP_solucion" width="875" height="389" class="aligncenter size-full wp-image-26436" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion.png 875w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion-300x133.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion-768x341.png 768w" sizes="(max-width: 875px) 100vw, 875px" /></p>
<p>El siguiente diagrama muestra las rutas asignadas:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-2.png" alt="CVRP" width="468" height="333" class="aligncenter wp-image-26112" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-2.png 1012w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-2-300x213.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP-2-768x546.png 768w" sizes="(max-width: 468px) 100vw, 468px" /></p>
<h2>¿Qué pasaría si se dispone de un vehículo menos?</h2>
<p>En el artículo de VRP simple abordamos esta misma pregunta, ya que no existían restricciones de capacidad. Así entonces, pudimos observar el impacto en el modelo de disminuir una unidad de transporte de la flota. Ahora bien, en el caso del presente modelo es preciso que la sumatoria de las demandas sea igual o inferior a la sumatoria de las capacidades de la flota de transporte; en caso contrario el modelo no hallará solución.</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_no_solucion.png" alt="CVRP_no_solucion" width="549" height="38" class="aligncenter size-full wp-image-26437" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_no_solucion.png 549w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_no_solucion-300x21.png 300w" sizes="(max-width: 549px) 100vw, 549px" /></p>
<p>Esta es quizá la principal limitación del modelo CVRP tal como se ha abordado hasta el momento, puesto que si bien adiciona al modelo básico del VRP las restricciones de capacidad, no considera una situación normal de cualquier contexto operacional: que la demanda sea superior a la capacidad de la flota de transporte.</p>
<p>Ahora bien, existen variaciones del modelo CVRP que considera la posibilidad de que la demanda supere la capacidad de la flota de transporte: <em>CVRP con penalizaciones y abandonos</em>, modelos que abordaremos en próximos artículos.</p>
<h3>¿Qué pasaría si uno de los vehículos cuenta con una mayor capacidad de transporte?</h3>
<p>Consideremos el hecho de que uno de los vehículos de la flota tenga una capacidad de carga superior al resto: 25 pallets para ser exacto. En este caso nuestro modelo corresponde a un problema de enrutamiento de vehículos capacitados con flota de transporte heterogénea (HFVRP). Tal como se ha formulado el modelo, podemos evidenciar los resultados de este planteamiento, por lo tanto modificamos la capacidad de uno de los vehículos (vehículo 3).</p>
<p>Los resultados obtenidos son:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_ii.png" alt="CVRP_solucion_ii" width="1277" height="397" class="aligncenter size-full wp-image-26438" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_ii.png 1277w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_ii-300x93.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_ii-1024x318.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_ii-768x239.png 768w" sizes="(max-width: 1277px) 100vw, 1277px" /></p>
<p>Podemos evidenciar que la consideración de un vehículo de mayor capacidad (25 pallets), permite que el modelo evalúe rutas más eficientes, ya que, incluso considerando el uso de las 4 unidades de transporte, logra una distancia total recorrida menor (5844 m) a la obtenida en el problema original (6208m).</p>
<h3>¿Qué pasaría si empleo una metaheurística alternativa?</h3>
<p>Tal como lo mencionamos en el artículo de <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><em><strong>VRP</strong></em></a>, los problemas de ruteo corresponden a la categoría de optimización combinatoria, esto implica que requieran de metaheurísticas dada la cantidad de posibles soluciones. De acuerdo a lo citado en el paso 8, es posible utilizar diversas metaheurísticas en Google Or-Tools, algunas de las cuales pueden arrojar resultados diferentes.</p>
<p>Siguiendo con el planteamiento del anterior interrogante (<em>¿Qué pasaría si uno de los vehículos cuenta con mayor capacidad de transporte?</em>), vamos a abordar el modelo haciendo uso de la metaheurística de algoritmo voraz.</p>
<p>Modificamos los parámetros de búsqueda:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    search_parameters.local_search_metaheuristic = (
        routing_enums_pb2.LocalSearchMetaheuristic.GREEDY_DESCENT)
    search_parameters.time_limit.FromSeconds(15)
</span></code></pre>
</div>
<p>La solución obtenida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_Greedy.png" alt="CVRP_solucion_Greedy" width="873" height="392" class="aligncenter size-full wp-image-26439" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_Greedy.png 873w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_Greedy-300x135.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/CVRP_solucion_Greedy-768x345.png 768w" sizes="(max-width: 873px) 100vw, 873px" /></p>
<p>Podemos observar que esta metaheurística arroja un resultado (6208 m) menos satisfactorio que el logrado por medio de <em>búsqueda local guiada</em> (5844 m).</p>
<hr />
<p>El modelo de <em><strong>problema de enrutamiento de vehículos capacitados (CVRP)</strong></em> y el <em>script </em>del solucionador quedaron desarrollados en un lenguaje de programación estándar y ampliamente utilizado. Desde luego, las posibilidades de integrar datos de entrada y procesar los datos de salidas son interesantes. Por ejemplo, es posible desarrollar un<span> </span><em>script </em>mediante el cual el código ya desarrollado tome los datos de entrada desde un archivo de Excel, o desde un servidor externo.</p>
<p>También, es posible desarrollar una interfaz amigable desde la cual se ingrese la información; o vincular los datos de salida con algún modelo o documento determinado.</p>
<p>En próximos artículos abordaremos las distintas variaciones del modelo de enrutamiento de transporte, como es el caso de los VRPTW (ventanas de tiempo) y los modelos CVRP con penalizaciones y abandonos.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/">Problema de Enrutamiento de Vehículos Capacitados (CVRP) con Google OR-Tools</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Problema de Enrutamiento de Vehículos (VRP) con Google OR-Tools</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 06 Jul 2021 22:59:48 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[CVRP]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Problema de enrutamiento de vehículos]]></category>
		<category><![CDATA[Problemas de ruteo]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruteo]]></category>
		<category><![CDATA[TSP]]></category>
		<category><![CDATA[VRP]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26091</guid>

					<description><![CDATA[<p>Una de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, en el cual, el diseño de rutas de transporte (enrutamiento de vehículos) cumple un rol importante. Su objetivo es encontrar las mejores rutas para una flota de vehículos que visitan un conjunto de ubicaciones. Por lo &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/">Problema de Enrutamiento de Vehículos (VRP) con Google OR-Tools</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Una de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, en el cual, el diseño de rutas de transporte (enrutamiento de vehículos) cumple un rol importante. Su objetivo es encontrar las mejores rutas para una flota de vehículos que visitan un conjunto de ubicaciones. Por lo general, el objetivo de la optimización se centra en determinar una menor distancia, un menor tiempo o un menor costo total (Ballou, 2004).</p>
<h2>¿Qué es un VRP?</h2>
<p>Una versión más general del <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-del-agente-viajero-tsp/"><strong>Problema del Agente Viajero básico (TSP)</strong></a>, es el problema de enrutamiento de vehículos, ampliamente conocido como VRP, por sus siglas en inglés. La principal diferencia entre el TSP y el VRP consiste en la consideración de varios vehículos en el modelo de enrutamiento (Ver Figura 1), es decir, cómo atender óptimamente a un conjunto de clientes, geográficamente dispersos alrededor de un depósito central, a través de una flota de vehículos homogénea (en su versión más básica &#8211; VRP puro) <em>(Clarke &amp; Wright, 1964) (Asghari &amp; Mirzapour Al-e-hashem, 2021)</em>.</p>
<figure id="attachment_26092" aria-describedby="caption-attachment-26092" style="width: 383px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP.png" alt="VRP" width="383" height="312" class=" wp-image-26092" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP.png 884w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-300x244.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-768x626.png 768w" sizes="(max-width: 383px) 100vw, 383px" /><figcaption id="caption-attachment-26092" class="wp-caption-text">Figura 1: Esquema general de un modelo VRP</figcaption></figure>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>El modelamiento de rutas de vehículos se encuentra entre los problemas de optimización más estudiados en la literatura académica. Los primeros estudios datan de 1959, cuando Dantzig y Ramser abordaron por primera vez el problema de despacho de camiones homogéneos para atender estaciones de servicio (Dantzig &amp; Ramser, 1959). Posteriormente, Clarke y Wright en 1964, abordaron el problema que consistía en atender un número de clientes geográficamente dispersos, por medio de una flota de vehículos con capacidades heterogéneas, modelo denominado VRP (Vehicle Routing Problem), o Problema de Enrutamiento de Vehículos (Clarke &amp; Wright, 1964). Desde entonces, el modelamiento de rutas de vehículos ha sido uno de los temas más abordados dentro del marco de la investigación de operaciones, la ingeniería industrial, la logística y el transporte.
			</div>
		</div>
	
<h2>¿Cuál es la complejidad matemática de un VRP?</h2>
<p>Es necesario considerar que, los problemas de enrutamiento de vehículos (VRP) y sus extensiones, están clasificados como problemas de optimización combinatoria.</p>
<p>El número de rutas posibles está determinado por la ecuación (<em>n</em> – 1)!, donde <em>n</em>, es igual al número de ubicaciones que componen el problema de enrutamiento (Ver Figura 2). Un problema con 10 ubicaciones (sin contar el depósito o punto de partida), cuenta con 362880 rutas posibles; mientras un problema con 20 ubicaciones cuenta con 2432902008176640000 rutas posibles. Una búsqueda exhaustiva, que evalúe cada una de las posibles soluciones, garantizaría encontrar la ruta óptima; sin embargo, computacionalmente esta es una cuestión intratable, salvo para los conjuntos de pequeñas soluciones (<em>Google OR-Tools, 2020</em>). En la mayor parte de los casos prácticos se requiere de la consideración de técnicas de optimización de búsqueda inteligente, que puedan arrojar soluciones óptimas, o casi óptimas.</p>
<figure id="attachment_26093" aria-describedby="caption-attachment-26093" style="width: 436px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-2.png" alt="Problemas de ruteo nodos rutas" width="436" height="267" class="wp-image-26093" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-2.png 648w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-2-300x184.png 300w" sizes="(max-width: 436px) 100vw, 436px" /><figcaption id="caption-attachment-26093" class="wp-caption-text">Figura 2: Cantidad de rutas por número de ubicaciones</figcaption></figure>
<hr />
<p>La formulación matemática para abordar problemas de enrutamiento de vehículos ha sido ampliamente divulgada. La modelación requiere de la consideración de restricciones de flujo, de balance, de limitación de formación de sub-ciclos, por citar algunas. Hoy por hoy, para efectos de aplicaciones prácticas, lo ideal consiste en utilizar programación basada en restricciones, de manera que los modelos no se aborden en notación algebraica.</p>
<p>El objetivo de este artículo consiste en utilizar las librerías del software Google OR-Tools para abordar problemas de enrutamiento de vehículos (VRP).</p>
<hr />
<h2>El problema (VRP)
		<div id="el-problema" data-title="El problema" class="index-title"></div>
	</h2>
<p>Puppis PetShop suministra a las veterinarias de Ciudad de México, diversos productos de cuidado y aseo para mascotas. Cuentan con un pequeño centro de distribución desde el cual abastecen periódicamente a sus clientes, los cuales se localizan tal como se muestra tentativamente en la figura 3.</p>
<figure id="attachment_26094" aria-describedby="caption-attachment-26094" style="width: 425px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-3.png" alt="Problema de enrutamiento de vehículos" width="425" height="310" class="wp-image-26094" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-3.png 824w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-3-300x219.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-3-768x561.png 768w" sizes="(max-width: 425px) 100vw, 425px" /><figcaption id="caption-attachment-26094" class="wp-caption-text">Figura 3: Mapa representativo del Centro de Distribución y los clientes</figcaption></figure>
<p>Para efectos de resolver el problema con mayor rapidez, el encargado de levantar la información ha considerado que las distancias entre dos puntos son iguales sin importar el sentido de estos (distancias simétricas).</p>
<p>Las distancias entre el centro distribución (0) y los 16 clientes que deben abastecer se detallan en la siguiente matriz de distancias (metros):</p>
<table width="1044">
<tbody>
<tr>
<td width="58" style="text-align: center;"></td>
<td width="58" style="text-align: center;"><strong>0</strong></td>
<td width="58" style="text-align: center;"><strong>1</strong></td>
<td width="58" style="text-align: center;"><strong>2</strong></td>
<td width="58" style="text-align: center;"><strong>3</strong></td>
<td width="58" style="text-align: center;"><strong>4</strong></td>
<td width="58" style="text-align: center;"><strong>5</strong></td>
<td width="58" style="text-align: center;"><strong>6</strong></td>
<td width="58" style="text-align: center;"><strong>7</strong></td>
<td width="58" style="text-align: center;"><strong>8</strong></td>
<td width="58" style="text-align: center;"><strong>9</strong></td>
<td width="58" style="text-align: center;"><strong>10</strong></td>
<td width="58" style="text-align: center;"><strong>11</strong></td>
<td width="58" style="text-align: center;"><strong>12</strong></td>
<td width="58" style="text-align: center;"><strong>13</strong></td>
<td width="58" style="text-align: center;"><strong>14</strong></td>
<td width="58" style="text-align: center;"><strong>15</strong></td>
<td width="58" style="text-align: center;"><strong>16</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>0</strong></td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">548</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">662</td>
</tr>
<tr>
<td style="text-align: center;"><strong>1</strong></td>
<td style="text-align: center;">548</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">684</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">594</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1016</td>
<td style="text-align: center;">868</td>
<td style="text-align: center;">1210</td>
</tr>
<tr>
<td style="text-align: center;"><strong>2</strong></td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">684</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">992</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">1130</td>
<td style="text-align: center;">788</td>
<td style="text-align: center;">1552</td>
<td style="text-align: center;">754</td>
</tr>
<tr>
<td style="text-align: center;"><strong>3</strong></td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">992</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">1232</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">822</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">560</td>
<td style="text-align: center;">1358</td>
</tr>
<tr>
<td style="text-align: center;"><strong>4</strong></td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">1118</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1244</td>
</tr>
<tr>
<td style="text-align: center;"><strong>5</strong></td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">228</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">240</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">708</td>
</tr>
<tr>
<td style="text-align: center;"><strong>6</strong></td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">228</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">1004</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">480</td>
</tr>
<tr>
<td style="text-align: center;"><strong>7</strong></td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">856</td>
</tr>
<tr>
<td style="text-align: center;"><strong>8</strong></td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">514</td>
</tr>
<tr>
<td style="text-align: center;"><strong>9</strong></td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">240</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">468</td>
</tr>
<tr>
<td style="text-align: center;"><strong>10</strong></td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">1232</td>
<td style="text-align: center;">1118</td>
<td style="text-align: center;">582</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">1152</td>
<td style="text-align: center;">354</td>
</tr>
<tr>
<td style="text-align: center;"><strong>11</strong></td>
<td style="text-align: center;">502</td>
<td style="text-align: center;">594</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">400</td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">1004</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">878</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">844</td>
</tr>
<tr>
<td style="text-align: center;"><strong>12</strong></td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">890</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">696</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">114</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
</tr>
<tr>
<td style="text-align: center;"><strong>13</strong></td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1130</td>
<td style="text-align: center;">822</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">628</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">308</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">536</td>
</tr>
<tr>
<td style="text-align: center;"><strong>14</strong></td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">1016</td>
<td style="text-align: center;">788</td>
<td style="text-align: center;">1164</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">320</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">650</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">342</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">194</td>
</tr>
<tr>
<td style="text-align: center;"><strong>15</strong></td>
<td style="text-align: center;">776</td>
<td style="text-align: center;">868</td>
<td style="text-align: center;">1552</td>
<td style="text-align: center;">560</td>
<td style="text-align: center;">674</td>
<td style="text-align: center;">1050</td>
<td style="text-align: center;">1278</td>
<td style="text-align: center;">742</td>
<td style="text-align: center;">1084</td>
<td style="text-align: center;">810</td>
<td style="text-align: center;">1152</td>
<td style="text-align: center;">274</td>
<td style="text-align: center;">388</td>
<td style="text-align: center;">422</td>
<td style="text-align: center;">764</td>
<td style="text-align: center;">0</td>
<td style="text-align: center;">798</td>
</tr>
<tr>
<td style="text-align: center;"><strong>16</strong></td>
<td style="text-align: center;">662</td>
<td style="text-align: center;">1210</td>
<td style="text-align: center;">754</td>
<td style="text-align: center;">1358</td>
<td style="text-align: center;">1244</td>
<td style="text-align: center;">708</td>
<td style="text-align: center;">480</td>
<td style="text-align: center;">856</td>
<td style="text-align: center;">514</td>
<td style="text-align: center;">468</td>
<td style="text-align: center;">354</td>
<td style="text-align: center;">844</td>
<td style="text-align: center;">730</td>
<td style="text-align: center;">536</td>
<td style="text-align: center;">194</td>
<td style="text-align: center;">798</td>
<td style="text-align: center;">0</td>
</tr>
</tbody>
</table>
<p>Si la compañía tiene 4 camiones, es deseable desarrollar un plan de rutas en el cual se determine cuántos camiones utilizar para minimizar la distancia total recorrida.</p>
<hr />
<h2>Resolución del modelo VRP mediante Google OR-Tools
		<div id="resolucion-del-modelo-vrp-mediante-google-or-tools" data-title="Resolución del modelo VRP mediante Google OR-Tools" class="index-title"></div>
	</h2>
<p><span>De acuerdo a lo mencionado en el artículo de </span><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/"><strong>introducción a Google OR-Tools</strong></a><span>, esta herramienta soporta múltiples lenguajes de programación, en esta ocasión, haremos uso del lenguaje de programación Python.</span></p>
<h3><em>Paso 1: Importar la librería</em></h3>

		<div id="paso-1-importar-la-libreria" data-title="Paso 1: Importar la librería" class="index-title"></div>
	
El siguiente fragmento de código importa las librerías necesarias para resolver un VRP:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Importar la librería de Google OR-Tools
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
</code></pre>
</div>
<h3><em>Paso 2: Crear la data del modelo VRP</em></h3>

		<div id="paso-2-crear-la-data-del-modelo" data-title="Paso 2: Crear la data del modelo" class="index-title"></div>
	
La data necesaria para modelar un VRP consiste en:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>matriz_distancias</strong>: Una matriz de distancias entre nodos (de acuerdo a una misma unidad de medida)</li>
<li><strong>num_vehiculos:</strong> Número de vehículos disponibles en la flota.</li>
<li><strong>deposito</strong>: Cuál es el índice que identifica al depósito (lugar en el cual todos los vehículos inician y terminan su ruta).</li>
</ul>

		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def create_data_model():
    """Almacena los datos de entrada del problema"""
    data = {}
    data['matriz_distancias'] = [
        [
            0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
            468, 776, 662
        ],
        [
            548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
            1016, 868, 1210
        ],
        [
            776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
            1130, 788, 1552, 754
        ],
        [
            696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
            1164, 560, 1358
        ],
        [
            582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
            1050, 674, 1244
        ],
        [
            274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
            514, 1050, 708
        ],
        [
            502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
            514, 1278, 480
        ],
        [
            194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
            662, 742, 856
        ],
        [
            308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
            320, 1084, 514
        ],
        [
            194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
            274, 810, 468
        ],
        [
            536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
            730, 388, 1152, 354
        ],
        [
            502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
            308, 650, 274, 844
        ],
        [
            388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
            536, 388, 730
        ],
        [
            354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
            342, 422, 536
        ],
        [
            468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
            342, 0, 764, 194
        ],
        [
            776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
            388, 422, 764, 0, 798
        ],
        [
            662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
            536, 194, 798, 0
        ],
    ]
    data['num_vehiculos'] = 4
    data['deposito'] = 0
    return data</span></code></pre>
</div>
<p>Cada fila y columna de la matriz de distancias tiene un índice de a cuerdo a su posición iniciando desde 0. Así entonces, el índice 0 se reserva en este caso para el depósito.</p>
<h3><em>Paso 2: Definir las salidas del solucionador</em></h3>

		<div id="paso-2-definir-las-salidas-del-solucionador" data-title="Paso 2: Definir las salidas del solucionador" class="index-title"></div>
	
El siguiente fragmento de código define la función que imprime la solución del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def print_solution(data, manager, routing, solution):
    """Imprime la solución sobre la consola"""
    max_route_distance = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -&gt; '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distancia de la ruta: {}m\n'.format(route_distance)
        print(plan_output)
        max_route_distance += route_distance
        max_route_distance = max(route_distance, max_route_distance)
    print('Distancia total de todas las rutas: {}m'.format(max_route_distance))
</span></code></pre>
</div>
<h3><em>Paso 3: Definir el punto de entrada del programa</em></h3>

		<div id="paso-3-definir-el-punto-de-entrada-del-programa" data-title="Paso 3: Definir el punto de entrada del programa" class="index-title"></div>
	
El siguiente fragmento de código define la función principal del programa, e invoca la data de entrada:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def main():
    """Punto de entrada del programa."""
    # Invocar la data de entrada.
    data = create_data_model()
</span></code></pre>
</div>
<h3><em>Paso 4: Crear el administrador de índice de rutas</em></h3>

		<div id="paso-4-crear-el-administrador-de-indice-de-rutas" data-title="Paso 4: Crear el administrador de índice de rutas" class="index-title"></div>
	
El siguiente código en la sección principal de los programas crea el administrador de índices (administrador) y el modelo de enrutamiento (enrutamiento). El método manager.IndexToNode convierte los índices internos del solucionador (que puede ignorar con seguridad) en números para ubicaciones. Los números de ubicación corresponden a los índices de la matriz de distancias.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])
</span></code></pre>
</div>
<h3><em>Paso 5: Crear el modelo de enrutamiento</em></h3>

		<div id="paso-5-crear-el-modelo-de-enrutamiento" data-title="Paso 5: Crear el modelo de enrutamiento" class="index-title"></div>
	
El siguiente código crea el modelo de enrutamiento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    routing = pywrapcp.RoutingModel(manager)
</span></code></pre>
</div>
<h3><em>Paso 6: Definir la devolución del llamado de distancia</em></h3>

		<div id="paso-6-definir-la-devolucion-del-llamado-de-distancia" data-title="Paso 6: Definir la devolución del llamado de distancia" class="index-title"></div>
	
El siguiente fragmento de código permite crear una función que devuelve la llamada de una distancia entre dos nodos y los pasa al solucionador para su consideración. Es decir, de acuerdo a la matriz de distancias dada, esta función establece, de acuerdo a dos ubicaciones, cual es su distancia correspondiente.</p>
<p>Así mismo, esta función permite establecer los costos del arco (útil para los casos que aborden dimensiones adicionales a las distancias).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">def distance_callback(from_index, to_index):
    """Retorna la distancia entre dos nodos"""
    # Convierte desde la variable de ruta Index hacia
    # la matriz de distancia NodeIndex.
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    return data['matriz_distancias'][from_node][to_node]

transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
</span></code></pre>
</div>
<h3><em>Paso 7: Adherir una dimensión de distancia</em></h3>

		<div id="paso-7-adherir-una-dimension-de-distancia" data-title="Paso 7: Adherir una dimensión de distancia" class="index-title"></div>
	
Para resolver un VRP es necesario crear una dimensión que represente la distancia acumulada recorrida por cada vehículo a lo largo de su ruta.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">dimension_name = 'Distancia'
routing.AddDimension(
    transit_callback_index,
    0,  # Sin holgura
    3000,  # Distancia máxima de viaje para un vehículo
    True,  # Iniciar el acumulador en cero
    dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
distance_dimension.SetGlobalSpanCostCoefficient(100)
</span></code></pre>
</div>
<h3><em>Paso 8: Configurar los parámetros de búsqueda</em></h3>

		<div id="paso-8-configurar-los-parametros-de-busqueda" data-title="Paso 8: Configurar los parámetros de búsqueda" class="index-title"></div>
	
El siguiente código establece los parámetros de búsqueda predeterminados y un método heurístico para encontrar la primera solución:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
</span></code></pre>
</div>
<p>El solucionador considera 14 estrategias de primera solución. En este caso, utilizaremos la estrategia de <em><strong>ruta más corta: </strong></em><em>PATH_CHEAPEST_ARC.</em></p>
<h3><em>Paso 9: Invocar el solucionador</em></h3>

		<div id="paso-9-invocar-el-solucionador" data-title="Paso 9: Invocar el solucionador " class="index-title"></div>
	
El siguiente fragmento de código sirve para invocar el solucionador del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"> solution = routing.SolveWithParameters(search_parameters)
</span></code></pre>
</div>
<h3><em>Paso 10: Imprimir la solución</em></h3>

		<div id="paso-10-imprimir-la-solucion" data-title="Paso 10: Imprimir la solución" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"> if solution:
print_solution(data, manager, routing, solution)
else:
print('No se encuentra solución !')
</span></code></pre>
</div>
<p>&nbsp;</p>
<hr />
<p>Es posible que el desarrollo de los diez pasos anteriores demande algún grado de complejidad subyacente del uso de un lenguaje de programación; sin embargo, es preciso mencionar que, el modelo anterior queda perfectamente configurado, y puede replicarse con modificaciones menores en múltiples problemas de enrutamiento de vehículos básicos (VRP).</p>
<h2>¿Cómo ejecutar el modelo?
		<div id="como-ejecutar-el-modelo" data-title="¿Cómo ejecutar el modelo?" class="index-title"></div>
	</h2>
<p><em><strong>Alternativa 1, ejecución en nuestro equipo:</strong></em></p>
<p>Lo primero que debemos considerar, en el caso de que queramos ejecutar este código en nuestro equipo, es que es preciso contar con la instalación de <em>Python </em>en nuestro equipo de cómputo, así mismo debemos contar con la última versión del comando <em>pip</em> y por supuesto, el software <em>OR-Tools.</em> Una guía detallada de la instalación de estos requerimientos la podrás encontrar en el siguiente enlace:</p>
<p style="text-align: center;"><a href="https://developers.google.com/optimization/install/python/windows" target="_blank" class="shortc-button medium blue">Instalación de OR-Tools para Python</a>
<p>Ahora, lo recomendable es trabajar con algún editor de código práctico (IDE), por ejemplo: <a href="https://www.sublimetext.com/3"><strong><em>Sublime Text</em></strong></a>, o <em><strong>Spyder</strong></em> (Una herramienta más completa y por ende más robusta y pesada).</p>
<p><em><strong>Alternativa 2, ejecución en un entorno virtual (Recomendado):</strong></em></p>
<p>Podemos utilizar del mismo modo, un entorno virtual. En este caso recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google.</p>
<hr />
<p>El código completo de nuestro desarrollo lo presentamos a continuación. También puedes ver el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1W2aOdLv-3pYxaXx00ME1o6-miKkBnZ3K?usp=sharing"><strong>Problema de Enrutamiento de Vehículos (VRP)</strong></a>.</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">"""Problema de enrutamiento de vehículos simple (VRP)

Ejercicio de ejemplo: MSc. Ing. Bryan Salazar López 2021

"""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
    """Almacena los datos de entrada del problema"""
    data = {}
    data['matriz_distancias'] = [
        [
            0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
            468, 776, 662
        ],
        [
            548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
            1016, 868, 1210
        ],
        [
            776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
            1130, 788, 1552, 754
        ],
        [
            696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
            1164, 560, 1358
        ],
        [
            582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
            1050, 674, 1244
        ],
        [
            274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
            514, 1050, 708
        ],
        [
            502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
            514, 1278, 480
        ],
        [
            194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
            662, 742, 856
        ],
        [
            308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
            320, 1084, 514
        ],
        [
            194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
            274, 810, 468
        ],
        [
            536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
            730, 388, 1152, 354
        ],
        [
            502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
            308, 650, 274, 844
        ],
        [
            388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
            536, 388, 730
        ],
        [
            354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
            342, 422, 536
        ],
        [
            468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
            342, 0, 764, 194
        ],
        [
            776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
            388, 422, 764, 0, 798
        ],
        [
            662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
            536, 194, 798, 0
        ],
    ]
    data['num_vehiculos'] = 4
    data['deposito'] = 0
    return data


def print_solution(data, manager, routing, solution):
    """Imprime la solución sobre la consola"""
    max_route_distance = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -&gt; '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distancia de la ruta: {}m\n'.format(route_distance)
        print(plan_output)
        max_route_distance += route_distance
        max_route_distance = max(route_distance, max_route_distance)
    print('Distancia total de todas las rutas: {}m'.format(max_route_distance))



def main():
    """Punto de entrada del programa"""
    # Invocar la data de entrada.
    data = create_data_model()

    # Crea el administrador del índice de rutas.
    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])

    # Crea el modelo de enrutamiento.
    routing = pywrapcp.RoutingModel(manager)


    # Crea y registra una devolución de llamada de distancia.
    def distance_callback(from_index, to_index):
        """Retorna la distancia entre dos nodos."""
        # Convierte desde la variable de ruta Index hasta la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['matriz_distancias'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define el costo de cada arco.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Adhiere la dimensión de distancia.
    dimension_name = 'Distancia'
    routing.AddDimension(
        transit_callback_index,
        0,  # Sin holgura
        3000,  # Distancia máxima de viaje del vehículo
        True,  # Iniciar el acumulador en cero
        dimension_name)
    distance_dimension = routing.GetDimensionOrDie(dimension_name)
    distance_dimension.SetGlobalSpanCostCoefficient(100)

    # Configurar los parámetros de búsqueda.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)

    # Solucionador del problema.
    solution = routing.SolveWithParameters(search_parameters)

    # Imprimir la solución en la consola.
    if solution:
        print_solution(data, manager, routing, solution)
    else:
        print('No se encuentra solución !')


if __name__ == '__main__':
    main()</span></code></pre>
</div>
<p>Al ejecutar nuestro desarrollo en <em><a href="https://colab.research.google.com/drive/1W2aOdLv-3pYxaXx00ME1o6-miKkBnZ3K?usp=sharing">Colaboratory</a>, </em>tenemos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion.png" alt="VRP_solucion" width="561" height="313" class="aligncenter size-full wp-image-26433" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion.png 561w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion-300x167.png 300w" sizes="(max-width: 561px) 100vw, 561px" /></p>
<p>El siguiente diagrama muestra las rutas asignadas:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-5.png" alt="VRP" width="490" height="355" class="aligncenter wp-image-26097" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-5.png 832w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-5-300x217.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP-5-768x557.png 768w" sizes="(max-width: 490px) 100vw, 490px" /></p>
<h2>¿Qué pasaría si se dispone de un vehículo menos?</h2>
<p>Las bondades de la programación basada en restricciones nos permiten efectuar este tipo de modificaciones con suma facilidad. Así entonces, modificamos la cantidad de vehículos en los datos de entrada para evidenciar los resultados:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion_II.png" alt="VRP_solucion_II" width="581" height="243" class="aligncenter size-full wp-image-26434" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion_II.png 581w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/VRP_solucion_II-300x125.png 300w" sizes="(max-width: 581px) 100vw, 581px" /></p>
<p>Los resultados evidencian que, de acuerdo a las condiciones del ejercicio, disponer de un vehículo menos en la flota de transporte, representaría una menor distancia total recorrida. <em>¿Cómo explicamos este fenómeno?</em> Pues bien, el solucionador con las condiciones actuales, determina que los clientes que deberían ser visitados por el cuarto vehículo se distribuyan en los vehículos restantes; esto implica que por lo menos, un vehículo no tendrá que desplazarse desde y hacia el depósito, lo cual puede incidir en la distancia total recorrida.</p>
<p>Ahora bien, en la práctica los vehículos presentan una capacidad limitada, ya sea por volumen, peso, tiempo, combustible, entre otros, lo cual suele restringir aún más el modelo de transporte. Es muy probable que en la práctica no fuese posible reasignar los clientes desatendidos ante la disponibilidad de un vehículo menos en la flota de transporte.</p>
<hr />
<p>El modelo de <em><strong>problema de enrutamiento de vehículos simple (VRP)</strong></em> y el <em>script </em>del solucionador quedaron desarrollados en un lenguaje de programación estándar y ampliamente utilizado. Desde luego, las posibilidades de integrar datos de entrada y procesar los datos de salidas son interesantes. Por ejemplo, es posible desarrollar un<span> </span><em>script </em>mediante el cual el código ya desarrollado tome los datos de entrada desde un archivo de Excel, o desde un servidor externo.</p>
<p>También, es posible desarrollar una interfaz amigable desde la cual se ingrese la información; o vincular los datos de salida con algún modelo o documento determinado.</p>
<p>En próximos artículos abordaremos las distintas variaciones del modelo general de enrutamiento de transporte, como es el caso de los <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/"><strong>CVRP</strong></a>, VRPTW y muchos más, como por ejemplo, aplicaciones desde las cuales se integre Google Maps a un modelo de enrutamiento.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/">Problema de Enrutamiento de Vehículos (VRP) con Google OR-Tools</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>¿Qué es y para qué sirve Google OR-Tools?</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Wed, 14 Apr 2021 00:25:27 +0000</pubDate>
				<category><![CDATA[Actualidad]]></category>
		<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Investigación de Operaciones]]></category>
		<category><![CDATA[Programación de restricciones]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Solucionadores]]></category>
		<category><![CDATA[VRP]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=25340</guid>

					<description><![CDATA[<p>Google OR-Tools es un paquete de software portable de código abierto para resolución de problemas de optimización. Así mismo, cuenta con metaheurísticas que buscan encontrar la mejor solución a un problema entre un conjunto de posibles soluciones. Or-Tools es una herramienta potente, diseñada para abordar los problemas más difíciles del mundo en el enrutamiento de &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/">¿Qué es y para qué sirve Google OR-Tools?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><strong>
		<div id="que-es-google-or-tools-y-para-que-sirve" data-title="¿Qué es Google Or-Tools y para qué sirve?" class="index-title"></div>
	 </strong></p>
<p><strong>Google OR-Tools es un paquete de software portable de código abierto para resolución de problemas de optimización</strong>. Así mismo, cuenta con metaheurísticas que buscan encontrar la mejor solución a un problema entre un conjunto de posibles soluciones. Or-Tools es una herramienta potente, diseñada para abordar los problemas más difíciles del mundo en el enrutamiento de vehículos, los flujos, la programación de números enteros y lineales y la programación de restricciones.</p>
<p>Una de las ventajas potenciales de Google Or-Tools consiste en que, a pesar de estar desarrollado en lenguaje C++, permite el modelamiento de problemas en múltiples lenguajes de programación, del mismo modo, cuenta con múltiples solucionadores específicos para resolverlos, algunos comerciales y algunos de código abierto.</p>
<table width="422">
<tbody>
<tr>
<td width="102"></td>
<td style="text-align: center;" colspan="4" width="320">Lenguajes de programación</td>
</tr>
<tr>
<td style="text-align: center;" width="102">OR-Tools</td>
<td style="text-align: center;" width="80">Python</td>
<td style="text-align: center;" width="80">C++</td>
<td style="text-align: center;" width="80">Java</td>
<td style="text-align: center;" width="80">C#</td>
</tr>
<tr>
<td style="text-align: center;" width="102">Compatibilidad</td>
<td style="text-align: center;" width="80">✓</td>
<td style="text-align: center;" width="80"> ✓</td>
<td style="text-align: center;" width="80"> ✓</td>
<td style="text-align: center;" width="80"> ✓</td>
</tr>
</tbody>
</table>
<p>Fuente: <a href="https://developers.google.com/optimization"><em><strong>Google OR-Tools</strong></em></a></p>
<p>Centrémonos en la posibilidad de modelar un problema de optimización en un lenguaje de programación, puesto que las oportunidades son cada vez más interesantes. En la academia, <strong>la enseñanza de la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/">investigación de operaciones</a>, el proceso de modelamiento y el mundo de los solucionadores, es tan a menudo, atomizado, que carece de practicidad</strong>.</p>
<p>La obtención, el procesamiento y la disposición de un volumen considerable de datos, su convergencia en un modelo de optimización y su eventual integración hacia un solucionador, son procesos complejos, que implican con regularidad la reunión de herramientas poco compatibles.</p>
<p>Google Or-Tools, al permitir el <strong>modelamiento en diversos lenguajes de programación</strong>, ofrece un sinfín de posibilidades de integración entre herramientas de obtención y tratamiento de información; así mismo, ofrece posibilidades ilimitadas de desarrollo de soluciones, como por ejemplo interfaces de usuario amigables, o integración con sistemas de integración de recursos, app’s, plataformas webs, dispositivos con un sistema de conexión a internet de las cosas (IoT), y mucho más.</p>
<p>Suponga, por ejemplo, que quiere desarrollar un modelo que le permita mejorar la eficiencia del proceso de recogida de paquetes, un típico problema de distribución y recolección de última milla, abordado dentro de la investigación de operaciones como un problema <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>VRP (Vehicle Routing Problem)</strong></a>. En su versión básica, requerirá de un conjunto de nodos y el costo que representa el arco que une a cada par (origen y destino). Sin embargo, asumamos que el criterio de optimización está basado en la distancia entre nodos, y, además, que se pretende, dado el volumen de datos requeridos, integrar una interfaz de programación externa que le permita obtener los tiempos entre nodos desde una plataforma como Google Maps.</p>
<p>Por otro lado, la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/"><strong>flota de vehículos disponibles tiene una capacidad limitada (CVRP)</strong></a>, y la información relacionada al número de vehículos y su capacidad volumétrica, se encuentra actualizada todos los días en archivo <a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="Los archivos CSV (del inglés comma-separated values) son un tipo de documento en formato abierto sencillo para representar datos en forma de tabla, en las que las columnas se separan por comas."><em>csv</em></a> accesible desde el servidor de la empresa.</p>
<p>La información relacionada con los destinos, o puntos de entrega, así mismo la dimensión volumétrica de los paquetes se puede obtener desde una solución desarrollada a la medida por la compañía, la cual mantiene la información actualizada en tiempo real y exporta diariamente estos datos en formato <a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="Documento de Microsoft Excel"><em>xls</em></a>.</p>
<p>Pretender integrar los datos de entrada, que a su vez son salidas desde plataformas diferentes, en formatos diferentes; algunos de ellos, información en tiempo real; procesar y organizar dicha información, integrarlos en un modelo de optimización y luego ejecutar un solucionador, haciendo uso de las herramientas típicas como Solver, Storm, Tora, WinQSB, Crystall Ball, etc., resultaría sumamente complejo, y en algunos casos imposible.</p>
<p>Sin embargo, haciendo uso, por ejemplo, de un lenguaje de programación como <a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="Lenguaje de programación interpretado cuya filosofía hace hincapié en la legibilidad de su código">Python</a>,</em> es posible desarrollar un código (<a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="En informática, un script, secuencia de comandos">script</a>)</em> que automatice las solicitudes hacia una <a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="La interfaz de programación de aplicaciones, conocida también por la sigla API">API</a></em> de Google Maps (<a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="HTML, siglas en inglés de HyperText Markup Language (‘lenguaje de marcado de hipertexto’), hace referencia al lenguaje de marcado para la elaboración de páginas web"><em>HTML</em></a>), decodifique sus respuestas (<a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="JSON (acrónimo de JavaScript Object Notation, «notación de objeto de JavaScript») es un formato de texto sencillo para el intercambio de datos">JSON</a>),</em> permitiendo disponer de la información relacionada con la distancia más probable para una hora de programación de salida de la flota en particular.</p>
<p>Así mismo, es posible ejecutar un script básico que extraiga la información contenida en un archivo <em>csv</em> y un archivo <em>xls</em>. Posteriormente, y haciendo uso de las librerías de enrutamiento de Google Or-Tools, toda esta data puede integrarse en un modelo de programación basada en restricciones que ejecutará una metaheurística como solucionador.<img decoding="async" class="aligncenter wp-image-25346" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/Or-Tools.jpg" alt="Or-Tools" width="481" height="627" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/Or-Tools.jpg 652w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/Or-Tools-230x300.jpg 230w" sizes="(max-width: 481px) 100vw, 481px" /></p>
<p>Como resultado, diariamente es posible mediante la integración de información actualizada en tiempo real en múltiples plataformas, a través de múltiples servidores, poder ejecutar un modelo de programación basado en restricciones que me permita obtener las rutas más eficientes de acuerdo a la metaheurística y a los parámetros del solucionador.</p>
<p>Dicho de otro modo, es posible integrar al modelo y al solucionador cualquier desarrollo que se base en un lenguaje común, por ejemplo: Python.</p>
<p style="text-align: center;"><strong><em>¡Sí, las ventajas potenciales son asombrosas!</em></strong></p>
<h2><strong>¿Qué tipo de modelos se pueden resolver mediante Google OR-Tools?</strong>
		<div id="que-tipo-de-modelos-se-pueden-resolver-mediante-google-or-tools" data-title="<strong>¿Qué tipo de modelos se pueden resolver mediante Google OR-Tools</strong>?" class="index-title"></div>
	</h2>
<p>Google OR-Tools incluye solucionadores para:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Programación de restricciones</li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-en-google-or-tools/"><strong>Programación lineal</strong></a></li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/"><strong>Programación entera y mixta</strong></a></li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problemas-de-asignacion-en-google-or-tools/"><strong>Problemas de asignación</strong></a></li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-la-ruta-mas-corta-en-google-or-tools/"><strong>Problemas de flujos</strong></a></li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>Problemas de enrutamiento de vehículos</strong></a></li>
<li>Algoritmos gráficos</li>
</ul>

		</div>
	
<h2><strong>¿Qué tan potente es el solucionador?</strong>
		<div id="que-tan-potente-es-el-solucionador" data-title="¿Qué tan potente es el solucionador?" class="index-title"></div>
	</h2>
<p>De acuerdo al MiniZinc Challenge 2020, competencia internacional de solucionadores de programación de restricciones, Google OR-Tools obtuvo tres medallas doradas en cuatro categorías de evaluación.</p>
<table width="433">
<tbody>
<tr>
<td style="text-align: center;" width="122"><strong>Categoría</strong></td>
<td style="text-align: center;" width="95"><strong>Oro</strong></td>
<td style="text-align: center;" width="105"><strong>Plata</strong></td>
<td style="text-align: center;" width="111"><strong>Bronce</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>Búsqueda fija</strong></td>
<td style="text-align: center;">SICStus Prolog</td>
<td style="text-align: center;">JaCoP</td>
<td style="text-align: center;">Choco 4</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Búsqueda libre</strong></td>
<td style="text-align: center;"><strong>OR-Tools</strong></td>
<td style="text-align: center;">PicatSAT</td>
<td style="text-align: center;">Mistral 2.0</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Búsqueda paralela</strong></td>
<td style="text-align: center;"><strong>OR-Tools</strong></td>
<td style="text-align: center;">PicatSAT</td>
<td style="text-align: center;">Mistral 2.0</td>
</tr>
<tr>
<td style="text-align: center;" width="122"><strong>Búsqueda abierta</strong></td>
<td style="text-align: center;"><strong>OR-Tools</strong></td>
<td style="text-align: center;">sunny-cp—</td>
<td style="text-align: center;">PicatSAT</td>
</tr>
</tbody>
</table>
<p>Fuente: <a href="https://www.minizinc.org/challenge2020/results2020.html"><em><strong>MiniZinc Challenge (2020)</strong></em></a></p>
<h2><strong>¿Cómo puedo instalar Google OR-Tools?</strong>
		<div id="como-puedo-instalar-google-or-tools" data-title="<strong>¿Cómo puedo instalar Google OR-Tools?</strong>" class="index-title"></div>
	</h2>
<p>Con asombro descubrimos desde esta plataforma (Ingeniería Industrial Online), que en la actualidad muchas personas nos escriben solicitando información para instalar WinQSB en computadores de 64 bits. El asombro es aún mayor cuando descubrimos que algunas instituciones de formación aún utilizan WinQSB como solucionador de modelos de investigación de operaciones.</p>
<p>Pues bien, esas incompatibilidades tecnológicas, no solo se han sorteado con Google OR-Tools, existen además múltiples solucionadores perfectamente compatibles con gran parte de los sistemas operativos de la actualidad. Ahora bien, OR-Tools es compatible con sistemas Linux, Mac y Windows; sin embargo, su instalación varía de acuerdo al lenguaje de programación que quieran utilizar.</p>
<p>Una guía completa de instalación la pueden encontrar en el siguiente enlace:</p>
<p><center><a href="https://developers.google.com/optimization/install" target="_blank" class="shortc-button medium blue">Guía de Instalación de<strong> Google OR-Tools</strong></a></center>&nbsp;</p>
<h2>Ventajas de ser un software de código abierto
		<div id="ventajas-de-ser-un-software-de-codigo-abierto" data-title="Ventajas de ser un software de código abierto" class="index-title"></div>
	</h2>
<p>Una de la mayores ventajas que presenta Google OR-Tools consiste en que es un software de código abierto, de tal manera que su modelo de desarrollo se basa en la colaboración. Esto representa un beneficio potencial, dado que cientos de personas, día a día, se encuentran formulando modelos de optimización en diversos lenguajes de programación.</p>
<p>Una evidencia de ello es la colaboración entorno a la formulación de problemas de enrutamiento de vehículos, ya que a través de las contribuciones que pueden observarse en <a href="http://github.com/google/or-tools">GitHub</a> o un su foro de discusión en <a href="https://groups.google.com/g/or-tools-discuss">Google Groups</a>, se han desarrollado modelos para diversas variaciones del modelo base, dentro de las que podemos encontrar:</p>

		<div class="thumbup tie-list-shortcode">
<ul>
<li>TSP</li>
<li><strong><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/">VRP</a></strong></li>
<li><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/"><strong>CVRP (Problema de enrutamiento de vehículos con restricciones de capacidad)</strong></a></li>
<li>VRPTW (Problema de enrutamiento de vehículos con ventanas de tiempo)</li>
<li>Problemas de enrutamiento de vehículos con restricciones de recursos</li>
<li>Problemas de enrutamiento de vehículos con abandonos y penalizaciones</li>
<li>Problemas de enrutamiento de vehículos con recogidas y entregas</li>
</ul>

		</div>
	
<p>Y mucho más, como colaboraciones a problemas de optimización específicos, integraciones con API&#8217;s desarrolladas por Google, como Distance Matrix API de Google Maps.</p>
<h2>¿Los requerimientos técnicos son un limitante para su uso?</h2>

		<div id="los-requerimientos-tecnicos-son-un-limitante-para-su-aprendizaje" data-title="¿Los requerimientos técnicos son un limitante para su aprendizaje?" class="index-title"></div>
	
<p>De ninguna manera. Y le vamos a explicar la razón.</p>
<p>Si queremos utilizar Google OR Tools en nuestro ordenador, sí tenemos algunos requerimientos técnicos, ahora bien, todos gratuitos. Requerimos la instalación de <em>Python</em>, un entorno de desarrollo integrado (IDE), y de la instalación de su librería (or tools).</p>
<p>Sin embargo, para su aprendizaje y uso básico, podemos utilizar entornos virtuales, como por ejemplo: <a href="https://colab.research.google.com/"><em><strong>Colaboratory de Google</strong></em></a>. ¿En qué consiste?</p>
<p>Colaboratory, también llamado «Colab», te permite ejecutar y programar en Python en tu navegador con las siguientes ventajas:</p>

		<div class="thumbup tie-list-shortcode">
<ul>
<li>No requiere configuración</li>
<li>Da acceso gratuito a GPUs (Unidades de procesamiento)</li>
<li>Permite compartir contenido fácilmente</li>
</ul>

		</div>
	
<p>Es decir, podemos utilizar toda la potencia de las máquinas de Google para ejecutar nuestros modelos&#8230; <em>¡Genial! </em></p>
<p>De hecho, vamos a utilizar esta herramienta en cada modelo que desarrollemos en este portal, compartiremos nuestros códigos y esperamos que les sean de gran utilidad.</p>
<hr />
<p>En próximos artículos desarrollaremos modelos de optimización y metaheurísticas haciendo uso de Google OR-Tools, de tal manera que de la mano descubran esta gran herramienta.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/">¿Qué es y para qué sirve Google OR-Tools?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
