<?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>Optimización archivos &#187; Ingenieria Industrial Online</title>
	<atom:link href="https://ingenieriaindustrialonline.com/tag/optimizacion/feed/" rel="self" type="application/rss+xml" />
	<link>https://ingenieriaindustrialonline.com/tag/optimizacion/</link>
	<description>ingenieriaindustriaonline.com</description>
	<lastBuildDate>Sun, 16 Apr 2023 16:04:31 +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>Optimización archivos &#187; Ingenieria Industrial Online</title>
	<link>https://ingenieriaindustrialonline.com/tag/optimizacion/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Programación de empleados mediante programación entera</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Sat, 27 Nov 2021 18:27:47 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Asignación]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Programación de empleados]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Scheduling]]></category>
		<category><![CDATA[Turnos]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=29010</guid>

					<description><![CDATA[<p>Las organizaciones que gestionan operaciones cada vez más robustas, requieren en cierto modo de la asignación de personas y recursos a tareas específicas. Desde hace algún tiempo se ha popularizado un planteamiento en torno al objetivo de la logística, generalmente aceptado como: «El objetivo de la logística consiste en llevar el producto correcto, en la &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/">Programación de empleados mediante programación entera</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 organizaciones que gestionan operaciones cada vez más robustas, requieren en cierto modo de la asignación de personas y recursos a tareas específicas. Desde hace algún tiempo se ha popularizado un planteamiento en torno al objetivo de la logística, generalmente aceptado como: «<em>El objetivo de la logística consiste en llevar el producto correcto, en la cantidad correcta, en el lugar correcto, en el momento correcto</em>» (acepta variaciones).</p>
<p>Ahora bien, tendencias de optimización abordan nuevos objetivos y plantean nuevos desafíos alrededor de la intralogística: <em>optimización extrema</em>. Es decir, optimización en la asignación de recursos internos, como por ejemplo: <em>el recurso correcto, asignado a la tarea correcta, en el momento correcto</em>.</p>
<p>Cuando aterrizamos el anterior planteamiento en términos prácticos, entendemos que se hace necesario resolver problemas de programación complejos de forma regular, que permitan la gestión de las operaciones de la organización. Un caso puntual consiste en la <strong>programación del recurso humano</strong>, sujeta a un conjunto complejo de restricciones y requisitos: <em>número de empleados, días laborales, turnos, tiempos inactivos, permisos, políticas internas, etc.</em></p>
<p>El planteamiento de nuevos desafíos de optimización, guarda una estrecha relación con la posibilidad que nos brinda la tecnología de abordar estos retos. Si bien, la complejidad de las organizaciones es cada vez mayor, los algoritmos cada vez son más refinados, y los solucionadores cada vez más robustos e integrados.</p>
<p>El objetivo de este artículo consiste en utilizar las técnicas robustas del solucionador basado en restricciones <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/"><strong>OR-Tools de Google</strong></a>, para resolver un problema de programación de empleados sujeto a un conjunto complejo de restricciones.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Cuando cursaban el tercer año, el Director del Lakeside School, les planteó a Bill Gates y Kent Evans, un problema muy complejo que aun nadie lograba resolver.</p>
<p>Lakeside se había fusionado con una escuela femenil local, por lo tanto, el alumnado había crecido considerablemente, y nadie lograba descifrar cómo ajustar los horarios de clase en las nuevas condiciones. Las restricciones de la programación no eran pocas, por ejemplo:</p>
<ul>
<li>Ningún maestro podía impartir cuatro clases seguidas</li>
<li>Debían considerarse pausas para alimentación</li>
<li>La cantidad de alumnos por sesión de clase no podía pasar de 16</li>
<li>Algunas clases como, por ejemplo, las clases de música no podían asignarse en salones conjuntos.</li>
</ul>
<p>La complejidad del problema hizo que Gates buscara la ayuda de Paul Allen, con quien lograría resolver el modelo. Este fue quizá, el algoritmo que les abrió un camino comercial, puesto que otras organizaciones que tenían problemas de programación, comenzarían a acudir a esta pareja de estudiantes.</p>
<p>Bill Gates y Paul Allen fundaron en 1975 la compañía Microsoft, el resto es historia. 
			</div>
		</div>
	
<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 compañía Stark Lab ha adquirido un nuevo torno para su área de mecanizado. El volumen de trabajo que tiene el área, demanda que esta nueva máquina sea operada en los 3 turnos del día (mañana, tarde y noche).</p>
<p>La compañía ha contratado a 3 trabajadores para operar la nueva máquina, y planea emplear a un aprendiz (estudiante) como cuarto operario.</p>
<p>El supervisor debe diseñar una programación para los cuatro operarios para un periodo de 7 días (una semana), sujeto a las siguientes condiciones:</p>
<ul>
<li>Cada día se divide en tres turnos de 8 horas.</li>
<li>Todos los días, cada turno se asigna a un solo operario, y ningún operario trabajará más de un turno por día.</li>
<li>El último día de la programación se realizan actividades de mantenimiento y limpieza profunda en planta. Por tal razón, solo se trabaja el turno de la mañana.</li>
<li>El operario aprendiz (estudiante), cuenta con la colaboración de la compañía para realizar sus estudios. Por tal razón, no puede trabajar el tercer día de la semana en el turno de la noche.</li>
<li>Como mínimo deben trabajarse 19 turnos en total en la programación. Esta cantidad de turnos ya considera los turnos destinados a mantenimiento y limpieza del último día de la programación.</li>
<li>En el caso en el que no se pueda realizar una distribución de turnos igualitaria, la distribución de los turnos debe realizarse de manera uniforme. Esto quiere decir que no puede existir una diferencia mayor a un turno entre las asignaciones para cada operario.</li>
</ul>
</blockquote>
<p>Se desea desarrollar una programación de empleados (asignación de empleados, turnos y días), que cumpla con las restricciones del planteamiento.</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>ORTools</em></strong>: OR-Tools es un paquete de software portable de código abierto desarrollado por Google, para resolución de problemas de optimización.</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: Instalar Google Or Tools</h3>

		<div id="paso-2-instalar-google-or-tools" data-title="Paso 2: 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 de programación entera.</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 el software del solucionador de Google.</p>
<h3>Paso 3: Importar las librerías necesarias</h3>

		<div id="paso-3-importar-las-librerias-necesarias" data-title="Paso 3: Importar las librerías necesarias" class="index-title"></div>
	
<p>Este modelo empleará programación entera, y por lo tanto instalaremos las librerías dispuestas por Google OR Tools para ello. En este caso utilizaremos un solucionador de programación entera llamado CP-SAT.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Importar la librería para utilizar CP-SAT
from ortools.sat.python import cp_model</code></pre>
</div>
<p><span>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</span></p>
<h3>Paso 4: Crear los datos de entrada del modelo</h3>

		<div id="paso-4-crear-los-datos-de-entrada-del-modelo" data-title="Paso 4: Crear los datos de entrada del modelo" class="index-title"></div>
	
<p>Los datos de entrada de un problema de programación de empleados suelen ser simples, la complejidad recae en el modelamiento de las restricciones. En este caso, los datos de entrada básicos son: <em>número de empleados, número de turnos por día, número de días del plan de programación</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Datos de entrada del modelo
operarios = 4
turnos = 3
dias = 7
total_operarios = range(operarios)
total_turnos = range(turnos)
total_dias = range(dias)

turnos_no_laborales = 2
turnos_laborales = (turnos * dias) - turnos_no_laborales</code></pre>
</div>
<p>Así mismo, definimos el número de turnos no laborales, recordemos que el planteamiento del problema nos indicó que el último día se trabajaría tan solo un turno (por labores de mantenimiento y limpieza), por lo tanto, dos turnos pueden considerarse como no laborales.</p>
<p>¿Por qué utilizamos una variable llamada <em>operarios </em>y otra llamada <em>total_operarios</em>? Además, ¿Para qué se emplea la función <em>range</em>? Bueno, la variable <em>operarios </em>contendrá el valor entero de la cantidad de operarios del modelo; mientras tanto, la variable <em>total_operarios</em> dado que emplea la función <em>range</em>, contendrá la secuencia de números que nos ayudará a definir a cada operario, lo hace de esta manera: range(4) = 0, 1, 2, 3. Como podemos ver, inicia la secuencia en 0, y finaliza en el entero anterior al parámetro dado, en este caso el parámetro dado fue 4, por lo tanto, el entero anterior será 3.</p>
<h3>Paso 5: Crear el modelo de programación</h3>

		<div id="paso-5-crear-el-modelo-de-programacion" data-title="Paso 5: Crear el modelo de programación" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Crear el modelo
model = cp_model.CpModel()</code></pre>
</div>
<h3>Paso 6: Crear las variables de asignación</h3>

		<div id="paso-6-crear-las-variables-de-asignacion" data-title="Paso 6: Crear las variables de asignación" class="index-title"></div>
	
<p>El problema de programación de empleados es un caso de asignación, y por lo tanto, empleamos variables de decisión de asignación. Veamos:</p>
<p>Necesitamos definir <em>qué operario</em> será asignado a <em>qué turno</em> en <em>qué día </em>en específico. Por lo tanto podemos utilizar variables de asignación binarias, que tomen un valor de 1, si esa asignación específica tiene lugar, y tomen valor de 0, en el caso en el que no.</p>
<p>Algebraicamente sería algo así:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra.png" alt="algebra" width="503" height="93" class="size-full wp-image-29015 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra.png 503w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra-300x55.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></p>
<p>En el caso en el que tuviésemos que definir manualmente cada una de las variables de asignación, requeriríamos un total de 84 variables (7 días * 3 turnos * 4 operarios).</p>
<p>Veamos cómo podemos utilizar los ciclos en <em>Python </em>para simplificar la definición de las variables &#8211; Eso sí, en lugar de <em>X </em>llamaremos a la variable <em>asignacion</em>, y en lugar de <em>a, b </em>y<em> c, </em>utilizaremos la primera letra de cada parámetro.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Creamos las variables de asignación
# asignacion[(o, d, t)]: operario 'o' trabaja en el turno 't' el día 'd'.
asignacion = {}
for o in total_operarios:
    for d in total_dias:
        for t in total_turnos:
            asignacion[(o, d, t)] = model.NewBoolVar('turno_n%id%is%i' % (o, d, t))</code></pre>
</div>
<p>De esta manera, haciendo uso de ciclos, creamos todas las variables de asignación del modelo; así mismo, definimos su naturaleza (<em>NewBoolVar = Variable booleana</em>). De manera que en el caso de que una asignación se efectúe su resultado será <em>«verdadero / True»</em>.</p>
<h3>Paso 7: Crear las restricciones del modelo</h3>

		<div id="paso-7-crear-las-restricciones-del-modelo" data-title="Paso 7: Crear las restricciones del modelo" class="index-title"></div>
	
<p>La complejidad de un problema de programación de recursos se define por la naturaleza de sus restricciones. Si bien las restricciones acotan el conjunto solución, y por ende los tiempos de procesamiento, la dificultad subyace del modelamiento. Veamos cómo abordar cada una de las restricciones que nos plantea el caso de aplicación.</p>
<p><strong>Cada turno se asigna a un solo operario</strong></p>
<p>La sumatoria de todas las asignaciones efectuadas en un día <em>d, </em>en el turno <em>t</em>, deben ser menores o iguales a 1. Eso restringe la posibilidad de que un mismo turno, en un mismo día, sea asignado a dos operarios diferentes.</p>
<p>Por ejemplo:</p>
<p style="text-align: center;">X<sub>000</sub> + X<sub>100</sub> + X<sub>200</sub> + X<sub>300</sub> &lt;= 1</p>
<p>Esta restricción nos diría que la sumatoria de todas las asignaciones del día <em>0</em>, para el turno <em>0</em>, de los operarios <em>0, 1, 2 y 3</em>; debe ser menor o igual a <em>1</em>. Es decir que el turno <em>0</em>, del día <em>0</em> no puede ser asignado a más de un operario.</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Cada turno es asignado a un operario en el periodo de programación, o no es asignado
for d in total_dias:
    for t in total_turnos:
        model.Add(sum(asignacion[(o, d, t)] for o in total_operarios) &lt;= 1)</code></pre>
</div>
<p>Esta restricción bien podría formularse como que la sumatoria deba ser igual a 1. Sin embargo, recordemos que existen un par de turnos no laborales, y por lo tanto estos no deben ser asignados.</p>
<p>En el caso en el que tuviésemos que definir manualmente cada una de estas restricciones, requeriríamos un total de 21 restricciones (7 días * 3 turnos).</p>
<p><strong>Cada operario trabaja como máximo un turno por día</strong></p>
<p>La sumatoria de todas las asignaciones efectuadas en un día <em>d, </em>para el operario <em>o</em>, deben ser menores o iguales a 1. Eso restringe la posibilidad de que un operario, en un mismo día, sea asignado a dos turnos diferentes.</p>
<p>Por ejemplo:</p>
<p style="text-align: center;">X<sub>000</sub> + X<sub>001</sub> + X<sub>002</sub> &lt;= 1</p>
<p>Esta restricción nos diría que la sumatoria de todas las asignaciones del día <em>0</em>, para el operario <em>0</em>, de los turnos <em>0, 1, y 2</em>; debe ser menor o igual a <em>1</em>. Es decir que el operario <em>0</em>, del día <em>0</em> no puede ser asignado a más de un turno.</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Cada operario trabaja como máximo un turno por día
for o in total_operarios:
    for d in total_dias:
        model.Add(sum(asignacion[(o, d, t)] for t in total_turnos) &lt;= 1)</code></pre>
</div>
<p>En el caso en el que tuviésemos que definir manualmente cada una de estas restricciones, requeriríamos un total de 28 restricciones (7 días * 4 operarios).</p>
<p><strong>Restricción del operario aprendiz</strong></p>
<p>De acuerdo al caso de aplicación, <em>el operario aprendiz no pude trabajar el tercer día de la semana, en el turno de la noche</em>. Esto nos permite abordar restricciones de requerimientos puntuales sobre el modelo.</p>
<p>Recordemos que el índice, tanto de operarios, turnos y días, comienza en 0. Por lo tanto, para efectos de la formulación de la restricción, el tercer día de la semana será el día 2 (los siete días de la semana son 0, 1, 2, 3, 4, 5 y 6); y el turno de la noche, será el turno 2 (turnos 0, 1 y 2 / mañana, tarde y noche).</p>
<p>Por otro lado, los operarios también son nombrados de acuerdo a sus índices: 0, 1, 2 y 3. Podemos arbitrariamente elegir el índice del operario aprendiz; en nuestro caso diremos que se trata del operario 0.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Restricción del operario (o) 1 para trabajar el día (d) 2, en el turno (t) 2
model.Add(asignacion[(0, 2, 2)] != 1)</code></pre>
</div>
<p>El operador != se utiliza para denotar <em>diferente de</em>, por lo tanto, la restricción indica que la asignación del operario 0, el día 2, en el turno 2, deberá ser diferente de 1; es decir que, al tratarse de una variable booleana, deberá ser <em>Falso = 0.</em></p>
<p><strong>Restricciones de turnos no programados (mantenimiento y limpieza)</strong></p>
<p>De acuerdo al caso de aplicación, el último día de la semana se programan actividades de mantenimiento y limpieza, y que por lo tanto, solo se trabaja en el turno de la mañana. Por esta razón, debemos excluir a los turnos de la tarde y la noche de la programación (turnos <em>1</em> y <em>2</em>) del último día (día 6).</p>
<p>Siguiendo con nuestras denominaciones algebraicas de ejemplo, tendríamos algo así:</p>
<p style="text-align: center;">X<sub>061</sub> + X<sub>161</sub> + X<sub>261</sub> + X<sub>361</sub> == 0</p>
<p style="text-align: center;">X<sub>062</sub> + X<sub>162</sub> + X<sub>262</sub> + X<sub>362</sub> == 0</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Restricción de turnos no programados
model.Add(sum(asignacion[(o, 6, 1)] for o in total_operarios) == 0)
model.Add(sum(asignacion[(o, 6, 2)] for o in total_operarios) == 0)</code></pre>
</div>
<p><strong>Restricción de turnos mínimos programados</strong></p>
<p>De acuerdo al caso de aplicación, debe programarse una cantidad de 19 turnos laborales. Es decir, la cantidad de turnos diarios (3) * la cantidad de días del programa (7); menos la cantidad de turnos no programados por mantenimiento y limpieza (2).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Como mínimo deben trabajarse "turnos_laborales"
min_turnos_totales = []
for o in total_operarios:
    for d in total_dias:
        for t in total_turnos:
            min_turnos_totales.append(asignacion[(o, d, t)])
model.Add(sum(min_turnos_totales) &gt;= turnos_laborales)</code></pre>
</div>
<p>Esta restricción involucra a todas las variables de asignación del modelo, y por lo tanto el uso de ciclos es importante para simplificar el desarrollo de la formulación. Básicamente indica que la sumatoria de todas las variables de decisión (asignación) del modelo (que son booleanas, y para este efecto podemos decir que binarias), debe ser mayor o igual al mínimo de turnos laborales exigidos para programación.</p>
<p>La restricción es 1 sola, pero involucra las 84 variables de asignación.</p>
<p><strong>Restricción de uniformidad en la distribución de turnos</strong></p>
<p>El objetivo de estas restricciones es la de distribuir de la manera más uniforme posible los turnos asignados. En términos prácticos, el caso de aplicación indica que, no puede existir una diferencia mayor a un turno entre las asignaciones de los operarios. Es decir, ningún operario podrá tener asignados dos turnos más que otro operario, por ejemplo.</p>
<p>Para lograr esto es necesario realizar una serie de cálculos intermedios sencillos, con el objetivo de identificar: <em>número de turnos asignables, cantidad mínima de turnos por operario, cantidad máxima de turnos por operario, entre otros</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>min_turnos_por_operario = ((turnos * dias) - turnos_no_laborales) // operarios
if ((turnos * dias) - turnos_no_laborales) % operarios == 0:
    max_turnos_por_operario = min_turnos_por_operario
else:
    max_turnos_por_operario = min_turnos_por_operario + 1
for o in total_operarios:
    num_turnos_trabajados = []
    for d in total_dias:
        for t in total_turnos:
            num_turnos_trabajados.append(asignacion[(o, d, t)])
    model.Add(min_turnos_por_operario &lt;= sum(num_turnos_trabajados))
    model.Add(sum(num_turnos_trabajados) &lt;= max_turnos_por_operario)</code></pre>
</div>
<p>Lo primero que hicimos fue calcular la parte entera de la fracción entre el <em>número de turnos asignables</em> y los operarios; lo que nos indica la <em>cantidad mínima de turnos por operario</em>. Veamos con un ejemplo:</p>
<p style="text-align: center;">(turnos * días) &#8211; turnos no laborales // operarios</p>
<p style="text-align: center;">(3 turnos por día * 7 días) &#8211; 2 turnos // 4 operarios</p>
<p style="text-align: center;">(3 * 7) &#8211; 2 // 4</p>
<p style="text-align: center;">19 // 4 = 4 turnos por operario</p>
<p>Esto indica que existe la posibilidad de asignar al menos 4 turnos por cada operario. Lo siguiente que se debe considerar es la cantidad máxima de turnos por operario.</p>
<p>En el caso en el cual la cantidad que resulte de dividir el número de turnos asignables entre el número de operarios sea entera sin decimales, esto indicaría que la distribución de los turnos puede realizarse en partes iguales, y por lo tanto <em>la cantidad máxima de turnos por operario </em>será equivalente a <em>la cantidad mínima de turnos por operario</em>.</p>
<p>En los casos en los que esto no ocurra, <em>la cantidad máxima de turnos por operario</em> será equivalente a <em>la cantidad mínima de turnos por operario </em>más 1. Recordemos que la restricción nos indica que la diferencia entre asignaciones de turnos a operarios no puede ser mayor de un turno.</p>
<p>Por todo lo demás, las restricciones de este paso consisten en establecer que la cantidad de turnos asignados por operario deberá ser mayor que <em>la cantidad mínima de turnos por operario </em>y deberá ser menor que <em>a cantidad máxima de turnos por operario</em><em>.</em></p>
<h3>Paso 8: Crear el solucionador y enumerar las soluciones</h3>

		<div id="paso-8-crear-el-solucionador-y-enumerar-las-soluciones" data-title="Paso 8: Crear el solucionador y enumerar las soluciones" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Creal (invocar) el solucionador y resolver
solver = cp_model.CpSolver()
solver.parameters.linearization_level = 0
# Enumerar las posibles soluciones
solver.parameters.enumerate_all_solutions = True</code></pre>
</div>
<h3>Paso 9: Configurar las salidas del modelo</h3>

		<div id="paso-9-configurar-las-salidas-del-modelo" data-title="Paso 9: Configurar las salidas del modelo" class="index-title"></div>
	
<p>Este paso consiste en configurar el formato de salida de las soluciones posibles. De forma arbitraria hemos determinado que se impriman las primeras 2 soluciones (<em>solution_limit</em>), de tal manera que el formato de salida tendrá la siguiente estructura:</p>
<ul>
<li>Solución <em>i</em>
<ul>
<li>Día <em>d</em>
<ul>
<li>Operario <em>o, </em>trabaja / no trabaja, en el turno <em>t.</em></li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>class OperariosParcialesSolucion(cp_model.CpSolverSolutionCallback):
    """Print intermediate solutions."""

    def __init__(self, asignacion, operarios, dias, turnos, limit):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self._asignacion = asignacion
        self._operarios = operarios
        self._dias = dias
        self._turnos = turnos
        self._solution_count = 0
        self._solution_limit = limit

    def on_solution_callback(self):
        self._solution_count += 1
        print('Solución %i' % self._solution_count)
        for d in range(self._dias):
            print('Día %i' % d)
            for o in range(self._operarios):
                is_working = False
                for t in range(self._turnos):
                    if self.Value(self._asignacion[(o, d, t)]):
                        is_working = True
                        print('  Operario %i trabaja en el turno %i' % (o, t))
                if not is_working:
                    print('  Operario {} no trabaja'.format(o))
        if self._solution_count &gt;= self._solution_limit:
            print('Detenga la búsqueda después de  %i soluciones' % self._solution_limit)
            self.StopSearch()

    def solution_count(self):
        return self._solution_count

# Muestra las primeras 2 soluciones.
solution_limit = 2
solution_printer = OperariosParcialesSolucion(asignacion, operarios, dias, turnos,
                                                    solution_limit)</code></pre>
</div>
<h3>Paso 10: Invocar al solucionador y configurar algunas estadísticas</h3>

		<div id="paso-10-invocar-al-solucionador-y-configurar-algunas-estadisticas" data-title="Paso 10: Invocar al solucionador y configurar algunas estadísticas" class="index-title"></div>
	
<p>Por último, nos queda invocar al solucionador, y configurar algunos datos estadísticos para enriquecer las salidas del modelo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>solver.Solve(model, solution_printer)

# Estadísticas.
print('\nEstadísticas')
print('  - conflictos      : %i' % solver.NumConflicts())
print('  - ramas de búsqueda       : %i' % solver.NumBranches())
print('  - tiempo de solución      : %f s' % solver.WallTime())
print('  - soluciones encontradas: %i' % solution_printer.solution_count())</code></pre>
</div>
<hr />
<p>Al ejecutar el programa completo, tendremos la siguiente salida:</p>
<ul>
<li><strong><em>Solución 1</em></strong>
<ul>
<li><em>Día 0</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 1</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 0</em></li>
</ul>
</li>
<li><em>Día 1</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 2</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 3</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
<li><em>Día 4</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 5</em>
<ul>
<li><em>Operario 0 trabaja en el turno 2</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 6</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
</ul>
</li>
<li><strong><em>Solución 2</em></strong>
<ul>
<li><em>Día 0</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 1</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 0</em></li>
</ul>
</li>
<li><em>Día 1</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 2</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 3</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
<li><em>Día 4</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 5</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 6</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><em>Detenga la búsqueda después de 2 soluciones</em></p>
<p><em>Estadísticas</em><br />
<em>&#8211; conflictos : 973</em><br />
<em>&#8211; ramas de búsqueda : 1730</em><br />
<em>&#8211; tiempo de búsqueda : 0.044052 s</em><br />
<em>&#8211; soluciones encontradas: 2</em></p>
<hr />
<p>Hemos formulado un modelo con <strong>84 variables de decisión, 55 restricciones y algunos cálculos intermedios</strong>. Podemos observar cómo el modelo ha logrado encontrar soluciones que satisfacen las restricciones planteadas (formuladas). Del mismo modo, podemos observar el tiempo en el cual ha logrado el solucionador hallar la cantidad de soluciones solicitadas (fracciones de segundo).</p>
<p>Es interesante percibir las bondades de este tipo desarrollos, y considerar las posibilidades de aplicación en otros campos diferentes a la programación de empleados; teniendo en cuenta la flexibilidad de la programación basada en restricciones, la potencia de los solucionadores y la capacidad de procesamiento de los equipos en la actualidad.</p>
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/18dIH38IZyioGn7eIRaT8bhMatCyEqasJ?usp=sharing" target="_blank" rel="noopener"><em><strong>Programación de empleados</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/">Programación de empleados mediante programación entera</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/programacion-de-empleados-mediante-programacion-entera/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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>Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Sun, 18 Jul 2021 18:57:10 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Análisis de sensibilidad]]></category>
		<category><![CDATA[Intervalos de factibilidad]]></category>
		<category><![CDATA[Investigación de Operaciones]]></category>
		<category><![CDATA[Método gráfico]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Precio dual]]></category>
		<category><![CDATA[Precio sombra]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Solución gráfica]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26239</guid>

					<description><![CDATA[<p>¿Qué es el análisis de sensibilidad en programación lineal? Quienes se adentren en los conceptos de la investigación de operaciones, propiamente en los conceptos de la programación lineal, deben considerar que existe algo más allá de la solución óptima. En investigación de operaciones, la solución de un modelo matemático establece una base para la toma &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/">Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>¿Qué es el análisis de sensibilidad en programación lineal?</h2>

		<div id="que-es-el-analisis-de-sensibilidad-en-programacion-lineal" data-title="¿Qué es el análisis de sensibilidad en programación lineal?" class="index-title"></div>
	
<p>Quienes se adentren en los conceptos de la investigación de operaciones, propiamente en los conceptos de la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal/"><em><strong>programación lineal</strong></em></a>, deben considerar que existe algo más allá de la solución óptima. En investigación de operaciones, la solución de un modelo matemático establece una base para la toma de decisiones; sin embargo, puede considerarse como esencial el <strong>análisis de los resultados obtenidos</strong>.</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento.png" alt="modelamiento" width="565" height="61" class="aligncenter wp-image-26253 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento.png 565w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento-300x32.png 300w" sizes="(max-width: 565px) 100vw, 565px" /></p>
<p>Los resultados de un modelo de programación lineal pueden ofrecer mucha información adicional, <em>inputs </em>del análisis de resultados. Existen a grandes rasgos, dos tipos de análisis complementarios en PL:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Análisis de sensibilidad</strong>: El cual determina las condiciones que mantendrán la solución actual sin cambios.</li>
<li><strong>Análisis postóptimo</strong>: El cual determina la nueva solución óptima cuando cambian los datos del modelo</li>
</ul>

		</div>
	
<p>En este artículo abordaremos el <strong>análisis de sensibilidad</strong>.</p>
<p>El análisis de sensibilidad en programación lineal corresponde al examen detallado de los límites dentro de los cuales los parámetros del modelo (recursos, utilidad o costo), pueden cambiar sin que esto afecte a la solución óptima y a la capacidad de calcular el impacto que tienen dichos cambios sobre la misma. Es decir, a partir de los resultados obtenidos de un modelo, podemos establecer una solución óptima y tenemos un rango en el cual podemos medir el impacto de los cambios en la disponibilidad de los recursos y los cambios en los coeficientes de la función objetivo. El análisis de dichas variaciones y el cálculo de los límites dentro de los cuales estas son válidas, es lo que se conoce como análisis de sensibilidad. Todo el análisis que se encuentre fuera de ese rango requeriría recalcular el modelo.</p>
<p>Como tal, constituye una herramienta importante en el análisis de los resultados obtenidos, sobre todo, en dos casos en particular:</p>
<ol>
<li><strong>La sensibilidad de la solución óptima ante los cambios de la disponibilidad de los recursos (lado derecho de las restricciones)</strong>. Es decir, cuáles serían los límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima, el aumento o la disminución de la disponibilidad de un recurso.</li>
<li><strong>La sensibilidad de la solución óptima ante los cambios en la utilidad unitaria o el costo unitario (coeficientes de las variables de la función objetivo)</strong>. Es decir, cuáles serían los límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima, el aumento o la disminución de la utilidad unitaria o el costo unitario asociada a las variables que forman parte de la función objetivo.</li>
</ol>
<h3>Análisis de sensibilidad gráfica, caso 1: Cambios en el lado derecho (Disponibilidad de recursos)</h3>

		<div id="analisis-de-sensibilidad-grafica-caso-1-cambios-en-el-lado-derecho" data-title="Análisis de sensibilidad gráfica, caso 1: Cambios en el lado derecho" class="index-title"></div>
	
<p>Con el propósito de evaluar los resultados obtenidos a través del tratamiento de un problema técnicamente formulado y abordado, utilizaremos un caso descrito en el libro Investigación de Operaciones (9na edición), de Hamdy A. Taha (University of Arkansas, Fayetteville), (Ejemplo 3.6-1):</p>
<blockquote class=" quote-simple "><p>JOBCO fabrica dos productos en dos máquinas. Una unidad del producto 1 requiere 2 horas en la máquina 1, y 1 hora en la máquina 2. Una unidad del producto 2 requiere 1 hora en la máquina 1, y 3 horas en la máquina 2. Los ingresos por unidad de los productos 1 y 2 son de $30 y $20, respectivamente. El tiempo de procesamiento diario total disponible en cada máquina es de 8 horas.</p></blockquote>
<p>Si <em><strong>x1</strong></em> y <em><strong>x2</strong></em> son las cantidades diarias de productos 1 y 2, respectivamente, el modelo se da como:</p>
<p style="text-align: center;"><strong>Zmax</strong> = 30<strong>x1</strong> + 20<strong>x2</strong></p>
<p>Sujeto a:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 (Máquina 1)</p>
<p style="text-align: center;"><strong>x1</strong> + 3<strong>x2</strong> &lt;= 8 (Máquina 2)</p>
<p style="text-align: center;"><strong>x1</strong>, <strong>x2</strong> &gt;= 0 (No negatividad)</p>
<p>La forma en la que abordamos la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/"><strong>solución gráfica de un modelo de programación lineal mediante <em>Python</em></strong></a>, está ampliamente documentada. El código en <em>Python </em>que resuelve mediante método gráfico el problema anterior, lo presentamos a continuación:</p>
<hr />
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Caso JOBCO: Investigación de Operaciones (9na edición), de Hamdy A. Taha 
# (University of Arkansas, Fayetteville), (Ejemplo 3.6-1)
# Autor del código en Python: Bryan Salazar López, Ing. M.Sc. (2021)

#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString

#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
plt.plot(x, z, ':', linewidth=1, color='k')


#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6))

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)

#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))

#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices (importante el orden según las manecillas)
plt.fill(m, n, color='silver')

#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice del paso anterior
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de producto X1 a producir: {} unidades'.format(XMAX))
print('Cantidad de producto X2 a producir: {} unidades'.format(YMAX))

#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('JOBCO')

plt.show()
</code></pre>
</div>
<p>Al ejecutar este código, de acuerdo a las instrucciones que pueden encontrar en el artículo de<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/"><strong> introducción al método gráfico mediante Python</strong></a>, obtendrán:</p>
<figure id="attachment_26264" aria-describedby="caption-attachment-26264" style="width: 640px" class="wp-caption aligncenter"><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp" alt="jobco_metodo_grafico" width="640" height="480" class="wp-image-26264 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico-300x225.webp 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><figcaption id="caption-attachment-26264" class="wp-caption-text">Figura 2: Solución gráfica del caso JOBCO obtenida mediante Python</figcaption></figure>
<p>Así mismo, el siguiente resultado en la consola:</p>
<figure id="attachment_26265" aria-describedby="caption-attachment-26265" style="width: 593px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima.png" alt="jobco_solucion_optima" width="593" height="213" class="wp-image-26265 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima.png 593w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima-300x108.png 300w" sizes="(max-width: 593px) 100vw, 593px" /><figcaption id="caption-attachment-26265" class="wp-caption-text">Figura 3: Resultados del caso JOBCO en la consola de Windows (Solución óptima)</figcaption></figure>
<p>&nbsp;</p>
<p>Así entonces, tenemos establecido el código base sobre el cual abordaremos este primer caso de <em>análisis de sensibilidad: cambios en el lado derecho (disponibilidad de recursos)</em>.</p>
<p>La pregunta que pretende responder este caso de análisis de sensibilidad es <em>¿Qué pasaría con la función objetivo si cambia la disponibilidad de alguno de los recursos del problema?</em> El cual corresponde a un planteamiento muy lógico de análisis, puesto que nos ampliaría la información relevante para la toma de decisiones.</p>
<p>Acercando este interrogante a nuestro problema de ejemplo, la cuestión podría ser: <em>¿Qué pasaría con los ingresos totales si la máquina 1 cambia su capacidad?</em></p>
<p>Pensemos por un momento cómo podemos abordar este planteamiento de manera gráfica, pensemos por un momento cómo podemos utilizar nuestro código base para abordar este caso.</p>
<p>Y bien, se nos puede ocurrir en primer lugar, graficar la nueva línea que represente la nueva función de disponibilidad de la máquina 1. Recordemos que si la restricción base es la siguiente:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 (Máquina 1)</p>
<p>Un incremento en la capacidad (variación), por ejemplo, se representaría mediante la siguiente inecuación:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 + 1</p>
<p>Esto quiere decir que, la nueva disponibilidad de la <em><strong>máquina 1</strong></em> pasará de 8 horas a 9 horas. Y nuestro razonamiento nos indica que esta función, tendrá una representación distinta a la anterior, por ende sería de mucha utilidad graficarla. Veamos el código para adicionar en Python:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20
y1v = (8 + 1) - (2 * x) #Variación en la máquina 1 (8 + 1)

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))
sexta_linea = LineString(np.column_stack((x, y1v))) #Nueva línea

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
#plt.plot(x, z, ':', linewidth=1, color='k') #Podemos ocultar esta línea (FO)
plt.plot(x, y1v, ':', linewidth=1, color='b') #Nueva línea
</code></pre>
</div>
<p>Podemos observar cómo agregamos tan solo 3 líneas de código, mediante la inclusión de la variable <em><strong>y1v </strong></em>(variación de la ecuación de la máquina 1).</p>
<p>La ecuación con la cual se representa la restricción de la máquina 1 corresponde a la siguiente:</p>
<p style="text-align: center;"><strong>y1</strong> = 8 &#8211; (2 * <strong>x</strong>)</p>
<p>Por ende, lo que hacemos es aumentar la capacidad de la máquina 1 (8) a una nueva capacidad (9 = 8 + 1):</p>
<p style="text-align: center;"><strong>y1v</strong> = (8 + 1) &#8211; (2 * <strong>x</strong>)</p>
<p>Las dos líneas de código adicionales graficarán esta nueva ecuación.</p>
<p>Al ejecutar nuestro código tendremos:</p>
<figure id="attachment_26266" aria-describedby="caption-attachment-26266" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad.png" alt="jobco_analisis_sensibilidad" width="640" height="480" class="wp-image-26266 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26266" class="wp-caption-text">Figura 3: Representación gráfica de un cambio en la disponibilidad del recurso 1</figcaption></figure>
<p>En este caso, podemos observar cómo una variación en la disponibilidad del recurso 1 (máquina 1), se representa con una línea recta (línea azul punteada) que conserva la misma pendiente de la restricción 1 (línea azul continua), que se mueve paralela a ella, y que en consecuencia, genera una nueva intersección solución (intersección con la restricción 2 &#8211; línea verde).</p>
<p>En este nuevo vértice solución se encuentra un nuevo punto óptimo, es decir, una coordenada diferente en la cual al evaluar la función objetivo, tendremos la respuesta al interrogante planteado: <em>¿Qué pasaría con los ingresos totales si la máquina 1 cambia su capacidad?</em></p>
<p>En consecuencia, utilizaremos nuestro código para:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Establecer gráficamente el nuevo vértice</li>
<li>Nombrarlo gráficamente (Vértice <em>G</em>)</li>
<li>Determinar las coordenadas de este vértice (<em>G</em>)</li>
<li>Evaluar la función objetivo en este nuevo vértice (<em>G</em>)</li>
</ul>

		</div>
	
<p>El siguiente fragmento muestra detalladamente las adiciones al código base:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)
septima_interseccion = sexta_linea.intersection(segunda_linea) #Nuevo vértice

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')
plt.plot(*septima_interseccion.xy, 'o', color='k') #Graficar nuevo vértice

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy
xi7m, yi7m = septima_interseccion.xy #Coordenadas del nuevo vértice

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
xi7 = np.float64(np.array(xi7m)) #Nueva coordenada en x
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))
yi7 = np.float64(np.array(yi7m)) #Nueva coordenada en y

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6)) 
plt.annotate(' G', (xi7, yi7)) #Nombrar el nuevo vértice como G

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)
FOi4 = (xi7 * 30) + (yi7 * 20) #Calcular la FO en el nuevo vértice

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))
print('Función objetivo en punto G: {} '.format(FOi7)) #Mostrar la FO en el punto G
</code></pre>
</div>
<p>Lo que realizamos es muy sencillo, es decir, ya que tenemos una nueva línea, realizamos los mismos procedimientos para identificar y graficar el nuevo vértice generado. Al ejecutar el código obtendremos lo siguiente:</p>
<figure id="attachment_26267" aria-describedby="caption-attachment-26267" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png" alt="jobco_analisis_sensibilidad1" width="640" height="480" class="wp-image-26267 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26267" class="wp-caption-text">Figura 4: Representación gráfica de un cambio en la disponibilidad del recurso 1 (Nuevo vértice)</figcaption></figure>
<p>&nbsp;</p>
<figure id="attachment_26268" aria-describedby="caption-attachment-26268" style="width: 597px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad.png" alt="jobco_solucion_sensibilidad" width="597" height="226" class="wp-image-26268 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad.png 597w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad-300x114.png 300w" sizes="(max-width: 597px) 100vw, 597px" /><figcaption id="caption-attachment-26268" class="wp-caption-text">Figura 5: Resultados del caso JOBCO en la consola de Windows (Función objetivo en punto G)</figcaption></figure>
<p>&nbsp;</p>
<p>La <em>figura 5</em> ilustra el cambio de la solución óptima cuando se cambia la capacidad de la máquina 1. Si la capacidad diaria se incrementa de 8 a 9 horas, el nuevo óptimo se moverá al punto <strong><em>G</em></strong>. Los resultados que se pueden apreciar en la consola, nos permiten conocer que el valor de la función objetivo evaluada en el punto <em><strong>G </strong>(<strong>z</strong> en <strong>G</strong>)</em> equivale a 142,0. Es decir, un valor superior a 128,0 (<em><strong>z</strong></em> en <em><strong>C</strong></em>).</p>
<p>Con los datos obtenidos podemos establecer en este caso la tasa de cambio en la función objetivo a consecuencia del cambio de la capacidad de la máquina 1 de 8 a 9 horas. Esta tasa de cambio se denomina de diversas formas: <em><strong>valor unitario de un recurso </strong></em>(Taha); <em><strong>precio dual </strong></em>(múltiples solucionadores); <em><strong>shadow price </strong></em>(precio sombra).</p>
<p>Esta tasa representa el impacto del incremento unitario en la capacidad de un recurso en la función objetivo; se calcula de la siguiente manera:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual.png" alt="precio_dual" width="517" height="59" class="aligncenter size-full wp-image-26269" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual.png 517w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual-300x34.png 300w" sizes="(max-width: 517px) 100vw, 517px" /></p>
<p>En nuestro ejemplo se calculará así:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price.png" alt="shadow_price" width="346" height="232" class="aligncenter size-full wp-image-26270" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price.png 346w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price-300x201.png 300w" sizes="(max-width: 346px) 100vw, 346px" /></p>
<p>Esto indica que: <em>Un incremento unitario en la capacidad de la máquina 1, aumentará el ingreso en $14,0</em>. O lo que es igual: <em>Una reducción unitaria en la capacidad de la máquina 1, reducirá el ingreso en $14,0.</em></p>
<p>En lo que concierne a nuestro código e Python, es sencillo, resulta que nosotros ya tenemos la función objetivo evaluada en cada uno de los vértices del gráfico. Así entonces, será cuestión de crear una nueva variable que represente el <em><strong>precio dual </strong></em>y la operación correspondiente:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Precio dual (Restricción 1)
Dual1 = (FOi7 - ZMAX) #Calculamos el precio dual para la restricción 1

print('\n ANÁLISIS DE SENSIBILIDAD')
print('Precio dual de la máquina 1: {} $/h'.format(Dual1)) #Imprimimos el precio dual 1
</code></pre>
</div>
<p>Recordemos que la función objetivo del punto <em><strong>G </strong></em>(<em><strong>z</strong></em> en <em><strong>G</strong>) </em>se representa por la variable<em><strong> FOi7</strong></em> (función objetivo en la intersección 7). La operación efectúa la diferencia entre este valor y la función objetivo en el vértice óptimo base (<em><strong>ZMAX</strong></em>).</p>
<p>Al ejecutar el código obtendremos lo siguiente en la consola de Windows:</p>
<figure id="attachment_26271" aria-describedby="caption-attachment-26271" style="width: 592px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1.png" alt="precio_dual1" width="592" height="262" class="wp-image-26271 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1.png 592w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1-300x133.png 300w" sizes="(max-width: 592px) 100vw, 592px" /><figcaption id="caption-attachment-26271" class="wp-caption-text">Figura 6: Resultados del caso JOBCO (Precio dual máquina 1)</figcaption></figure>
<p>&nbsp;</p>
<p>Ahora bien, debemos recordar que hemos hecho énfasis en que el análisis de sensibilidad del caso 1 (cambios en el lado derecho de las restricciones &#8211; capacidad de los recursos), precisa de unos límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima. Es decir, para nuestro caso en particular: <em>los límites dentro de los cuales el precio dual para la máquina 1 es 14$/h. </em>Dicho de otra manera, estamos en condiciones de calcular el impacto de una variación en la máquina 1 en los ingresos totales, pero, <em>¿Hasta cuándo esa proyección es válida? </em>Y sí, existen unos límites dentro de los cuales nuestra proyección, tasa o precio dual es válido, por fuera de ellos, sería necesario recalcular.</p>
<p>Revisemos nuevamente la <em>figura 4</em>:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png" alt="jobco_analisis_sensibilidad1" width="640" height="480" class="aligncenter size-full wp-image-26267" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>Veamos cómo la línea que representa los cambios en la capacidad de la <em><strong>máquina 1 </strong></em>(recurso) se mueve paralela a sí misma, y bien puede hacerlo sobre el segmento de la línea que representa la capacidad de la <em><strong>máquina 2</strong></em> (línea verde, o algebraicamente: <em>línea BF, desde el punto B hasta el punto F</em>). Esa es básicamente la clave para determinar los límites de validez de nuestro precio dual, mejor conocidos como <em><strong>intervalos de factibilidad</strong></em>. Pero <em>¿Cuál es la clave? </em>Sí, la ecuación de la <em><strong>máquina 1</strong></em> puede moverse para formar una intersección con la <em><strong>línea verde</strong></em> (a medida que cambie su capacidad) entre <em><strong>B</strong></em> y <em><strong>F</strong></em>, de manera que en estos vértices se encuentran los límites. Y para ser directos, la idea consiste en calcular la capacidad de la máquina 1 en cada uno de estos vértices. <em>¿Cómo lo hacemos? </em>Veamos:</p>
<p>La ecuación que representa la capacidad de la máquina 1 está dada por:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 horas (máquina 1)</p>
<p>Por ende, si retiramos el valor de la capacidad del recurso (8 horas), obtendremos la ecuación de la utilización o el uso del recurso:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2 </strong>= Utilización o uso de la máquina 1</p>
<p>Ahora, teniendo la ecuación de la restricción 1 (máquina 1), podemos evaluar cuál sería su utilización en los vértices <em><strong>B </strong></em>y <em><strong>F</strong></em>. Así encontraríamos los límites de validez para esta restricción (intervalos de factibilidad para nuestro precio dual = 14$/h). Dicho de otro modo, encontraríamos la capacidad mínima y máxima de la máquina 1, para que una variación unitaria en la disponibilidad de la misma (máquina 1), represente una variación de 14$ en la función objetivo. Por fuera de ese intervalo, no podemos asegurar de ninguna manera que el precio dual es de 14$/h, sería necesario recalcular.</p>
<p>Volvamos a la determinación de los límites. Ya tenemos la ecuación de la restricción (la verdad, siempre la hemos tenido), ahora la evaluaremos en los puntos <em><strong>B </strong></em>y <em><strong>F</strong></em>. Para ello es preciso conocer las coordenadas de cada uno de estos puntos. En <em>Python</em> podemos utilizar el siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Intervalos de factibilidad
IFmin1 = (2 * xi2) + yi2
IFmax1 = (2 * xi6) + yi6

print('Límite mínimo de factibilidad (PD máquina 1): {} h'.format(IFmin1))
print('Límite máximo de factibilidad (PD máquina 1): {} h'.format(IFmax1))
</code></pre>
</div>
<p>Veamos: El punto <em><strong>B</strong></em> según nuestro código, corresponde a la segunda intersección, por ende sus coordenadas están definidas por las variables <em><strong>xi1 </strong></em>y <em><strong>yi1</strong></em>. Así entonces, evaluamos la capacidad de la <em><strong>máquina 1</strong></em> en estas coordenadas y tenemos el límite mínimo de factibilidad para el precio dual de la máquina 1 <em>(<strong>IFmin1</strong>).</em></p>
<p>Así mismo, el punto <b><i>F </i></b>según nuestro código, corresponde a la sexta intersección, por ende sus coordenadas están definidas por las variables <em><strong>xi6 </strong></em>y <em><strong>yi6</strong></em>. Así entonces, evaluamos la capacidad de la <em><strong>máquina 1</strong></em> en estas coordenadas y tenemos el límite mínimo de factibilidad para el precio dual de la máquina 1 (<em><strong>IFmax1</strong></em>).</p>
<p>Al ejecutar el código obtendremos lo siguiente en la consola de Windows:</p>
<figure id="attachment_26276" aria-describedby="caption-attachment-26276" style="width: 592px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a.png" alt="intervalo_de_factibilidad1_a" width="592" height="327" class="size-full wp-image-26276" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a.png 592w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a-300x166.png 300w" sizes="(max-width: 592px) 100vw, 592px" /><figcaption id="caption-attachment-26276" class="wp-caption-text">Figura 7: Intervalos de factibilidad para la máquina 1 (Caso JOBCO)</figcaption></figure>
<p>&nbsp;</p>
<p>Podemos efectuar nosotros mismos los cálculos correspondientes:</p>
<p>El punto <em><strong>B</strong></em> tiene la siguientes coordenadas (<em><strong>x </strong></em>= 0, <em><strong>y </strong></em>= 2,67)</p>
<p>Evaluando estas coordenadas en la ecuación de la <strong><em>máquina 1</em></strong>, tenemos:</p>
<p style="text-align: center;">2(<strong>0</strong>)+ (<strong>2,67</strong>) = Límite mínimo de factibilidad (máquina 1)</p>
<p style="text-align: center;">2(<strong>0</strong>)+ (<strong>2,67</strong>) = 2,67 horas</p>
<p>El punto <em><strong>F</strong></em> tiene la siguientes coordenadas (<em><strong>x </strong></em>= 8, <em><strong>y </strong></em>= 0)</p>
<p>Evaluando estas coordenadas en la ecuación de la <strong><em>máquina 1</em></strong>, tenemos:</p>
<p style="text-align: center;">2(<strong>8</strong>)+ (<strong>0</strong>) = Límite máximo de factibilidad (máquina 1)</p>
<p style="text-align: center;">2(<strong>8</strong>)+ (<strong>0</strong>) = 16 horas</p>
<p>Ya efectuamos los procedimientos de manera manual y mediante <em>Python</em>. La conclusión es que el precio dual de $14/h permanece válido en el intervalo:</p>
<p style="text-align: center;"><strong>2,67</strong> h &lt;= Capacidad de la <em><strong>máquina 1</strong></em> &lt;= <strong>16</strong> h</p>
<p>Los cambios que se encuentren fuera de esos <em><strong>intervalos de factibilidad</strong> </em>producen un precio dual diferente, y por lo tanto, precisan nuevos cálculos.</p>
<p>En nuestro código, incluiremos las líneas que permitan llegar al precio dual y a los intervalos de factibilidad de la <em><strong>máquina 2</strong>. </em>Al ejecutarlo, obtendremos lo siguiente:</p>
<figure id="attachment_26273" aria-describedby="caption-attachment-26273" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2.png" alt="jobco_analisis_sensibilidad2" width="640" height="480" class="wp-image-26273 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26273" class="wp-caption-text">Figura 8: Representación gráfica de un cambio en la disponibilidad del recurso 2 (Nuevo vértice H)</figcaption></figure>
<p>&nbsp;</p>
<figure id="attachment_26277" aria-describedby="caption-attachment-26277" style="width: 548px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b.png" alt="intervalo_de_factibilidad2_b" width="548" height="404" class="size-full wp-image-26277" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b.png 548w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b-300x221.png 300w" sizes="(max-width: 548px) 100vw, 548px" /><figcaption id="caption-attachment-26277" class="wp-caption-text">Figura 9: Precio dual e intervalos de factibilidad para la máquina 2 (Caso JOBCO)</figcaption></figure>
<p>&nbsp;</p>
<p>La conclusión es que el precio dual de $2/h permanece válido en el intervalo:</p>
<p style="text-align: center;"><strong>4</strong> h &lt;= Capacidad de la <em><strong>máquina 2</strong></em> &lt;= <strong>24</strong> h</p>
<p>Los precios duales y los intervalos de factibilidad amplían la base que ofrece la solución óptima para la toma de decisiones. <em>Taha </em>propone una serie de preguntas alrededor de este caso (ejercicio 3.6-1), que pueden ser abordadas gracias al análisis de sensibilidad.</p>
<p><em><strong>Pregunta 1</strong></em>: Si JOBCO puede incrementar la capacidad de ambas máquinas, ¿Cuál máquina tendrá la prioridad?</p>
<p><strong>Respuesta: </strong>De acuerdo a los precios duales para las máquinas 1 y 2, cada hora adicional de la máquina 1 incrementa el ingreso total en $14; mientras tanto, cada hora adicional de la máquina 2 incrementa el ingreso total en $2. Por lo tanto, la máquina 1 debe tener la prioridad.</p>
<p><em><strong>Pregunta 2</strong></em>: Se sugiere incrementar las capacidades de las máquinas 1 y 2 al costo adicional de $10/h para cada máquina. ¿Es esto aconsejable?</p>
<p><strong>Respuesta: </strong>De acuerdo a los precios duales para las máquinas 1 y 2.  Los ingresos netos adicionales por hora serían de la siguiente manera:</p>
<p><em>Máquina 1:</em></p>
<p>14$/h (Precio dual máquina 1) &#8211; 10$/h (Costo para aumentar capacidad) = 4 $/h (Ingreso neto)</p>
<p><em>Máquina 2:</em></p>
<p>2$/h (Precio dual máquina 2) &#8211; 10$/h (Costo para aumentar capacidad) = &#8211; 8 $/h (Ingreso neto)</p>
<p>Por consiguiente, solo la <em>máquina 1</em> debe considerarse para el incremento de capacidad.</p>
<p><em><strong>Pregunta 3</strong></em>: Si la capacidad de la máquina 1 se incrementa de 8 a 13 horas, ¿Cómo impactará este incremento al ingreso óptimo?</p>
<p><strong>Respuesta</strong>: Lo primero que debe evaluarse es que la nueva capacidad de la máquina 1 se encuentre dentro del intervalo de factibilidad (2,67 h &#8211; 16 h). Ya que sí se encuentra en dicho intervalo (13 h), el siguiente paso consiste en calcular el <em>cambio en la disponibilidad</em>:</p>
<p style="text-align: center;"><em>Cambio en la capacidad</em> = 13 (Nueva capacidad) &#8211; 8 (Capacidad inicial)</p>
<p style="text-align: center;"><em>Cambio en la capacidad</em> = 5 horas</p>
<p>Por ende, multiplicamos dicho cambio por la tasa de cambio del ingreso (precio dual):</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = Precio dual  * Cambio en la capacidad</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = 14 $/h * (5 horas)</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = 70 $</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = Ingreso base (Solución óptima) + Cambio en el ingreso</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = $ 128 + $ 70</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = $ 198</p>
<p>En el caso de que la nueva capacidad de la máquina se encuentre por fuera del intervalo de factibilidad para dicho recurso, no se dispondría de información suficiente para llegar a una conclusión válida.</p>
<hr />
<p>A continuación, dejamos a disposición el código completo del análisis de sensibilidad gráfica desarrollado en <em>Python</em>:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Caso 1: Investigación de Operaciones (9na edición), de Hamdy A. Taha 
# (University of Arkansas, Fayetteville), (Ejemplo 3.6-1)
# Autor del código en Python: Bryan Salazar López, Ing. M.Sc. (2021)

#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString

#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20
y1v = (8 + 1) - (2 * x) #Variación en la máquina 1 (8 + 1)
y2v = ((8 + 1) - x) / 3 #Variación en la máquina 2 (8 + 1)

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))
sexta_linea = LineString(np.column_stack((x, y1v))) #Nueva línea Máquina 1 (M1)
septima_linea = LineString(np.column_stack((x, y2v))) #Nueva línea Máquina 2 (M2)

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
#plt.plot(x, z, ':', linewidth=1, color='k')
plt.plot(x, y1v, ':', linewidth=1, color='b') #Nueva línea M1
plt.plot(x, y2v, ':', linewidth=1, color='g') #Nueva línea M2


#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)
septima_interseccion = sexta_linea.intersection(segunda_linea) #Nuevo vértice M1
octava_interseccion = septima_linea.intersection(primera_linea) #Nuevo vértice M2

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')
plt.plot(*septima_interseccion.xy, 'o', color='k') #Graficar nuevo vértice M1
plt.plot(*octava_interseccion.xy, 'o', color='k') #Graficar nuevo vértice M2

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy
xi7m, yi7m = septima_interseccion.xy #Coordenadas del nuevo vértice M1
xi8m, yi8m = octava_interseccion.xy #Coordenadas del nuevo vértice M2

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
xi7 = np.float64(np.array(xi7m)) #Nueva coordenada en x (Máquina 1)
xi8 = np.float64(np.array(xi8m)) #Nueva coordenada en x (Máquina 2)
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))
yi7 = np.float64(np.array(yi7m)) #Nueva coordenada en y (Máquina 1)
yi8 = np.float64(np.array(yi8m)) #Nueva coordenada en y (Máquina 2)

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6)) 
plt.annotate(' G', (xi7, yi7)) #Nombrar el nuevo vértice como G
plt.annotate(' H', (xi8, yi8)) #Nombrar el nuevo vértice como H

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)
FOi7 = (xi7 * 30) + (yi7 * 20) #Calcular la FO en el nuevo vértice G
FOi8 = (xi8 * 30) + (yi8 * 20) #Calcular la FO en el nuevo vértice H

#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))
print('Función objetivo en punto G: {} '.format(FOi7))
print('Función objetivo en punto H: {} '.format(FOi8))

#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices (importante el orden según las manecillas)
plt.fill(m, n, color='silver')

#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice del paso anterior
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de producto X1 a producir: {} unidades'.format(XMAX))
print('Cantidad de producto X2 a producir: {} unidades'.format(YMAX))

#Precio dual (Restricción 1)
Dual1 = (FOi7 - ZMAX) #Calculamos el precio dual para la restricción 1
Dual2 = (FOi8 - ZMAX) #Calculamos el precio dual para la restricción 1

#Imprimir los precios duales
print('\n ANÁLISIS DE SENSIBILIDAD')
print('Precio dual de la máquina 1: {} $/h'.format(Dual1)) #Imprimimos el precio dual 1
print('Precio dual de la máquina 2: {} $/h'.format(Dual2)) #Imprimimos el precio dual 2

#Intervalos de factibilidad (Máquina 1)
IFmin1 = (2 * xi1) + yi1
IFmax1 = (2 * xi6) + yi6

#Intervalos de factibilidad (Máquina 2)
IFmin2 = xi3 + (3 * yi3)
IFmax2 = xi5 + (3 * yi5)

#Imprimir los intervalos de factibilidad
print('\n INTERVALOS DE FACTIBILIDAD')
print('Límite mínimo de factibilidad (PD máquina 1): {} h'.format(IFmin1))
print('Límite máximo de factibilidad (PD máquina 1): {} h'.format(IFmax1))
print('Límite mínimo de factibilidad (PD máquina 2): {} h'.format(IFmin2))
print('Límite máximo de factibilidad (PD máquina 2): {} h'.format(IFmax2))


#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('JOBCO')

plt.show()
</code></pre>
</div>
<hr />
<p>En el próximo artículo desarrollaremos el segundo caso de análisis de sensibilidad: <em>cambios en la utilidad unitaria o el costo unitario.</em></p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/">Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</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/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Método gráfico de la programación lineal mediante el uso de Python</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Sat, 17 Jul 2021 19:50:16 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Investigación de Operaciones]]></category>
		<category><![CDATA[Método gráfico]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Solución gráfica]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26194</guid>

					<description><![CDATA[<p>Tal como lo mencionamos en el artículo en el que abordamos inicialmente los pasos de resolución gráfica de los modelos de programación lineal; dada la limitación en la cantidad de variables que puede soportar el método gráfico (2 variables), y dada la forma manual de resolución del mismo, este es difícilmente útil en la práctica. &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/">Método gráfico de la programación lineal mediante el uso de Python</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 mencionamos en el artículo en el que abordamos inicialmente los pasos de <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico/"><em><strong>resolución gráfica</strong></em></a> de los modelos de <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal/"><strong>programación lineal</strong></a>; dada la limitación en la cantidad de variables que puede soportar el método gráfico (2 variables), y dada la forma manual de resolución del mismo, este es difícilmente útil en la práctica.</p>
<h3>¿Por qué debería aprender a solucionar gráficamente un modelo de PL?</h3>

		<div id="por-que-deberia-aprender-a-solucionar-graficamente-un-modelo-de-pl" data-title="¿Por qué debería aprender a solucionar gráficamente un modelo de PL?" class="index-title"></div>
	
<p>La solución gráfica de los modelos de programación lineal, puede proporcionar una perspectiva que permita un entendimiento más amplio de los métodos generales de resolución (<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-simplex/"><strong>método simplex</strong></a> o solucionadores). Del mismo modo, permite una mayor comprensión de los diversos elementos que componen un modelo de PL (dado su componente visual), siendo especialmente útil en el aprendizaje de diferentes tópicos como pueden ser: <em>tipos de solución, tipos de restricciones y análisis de sensibilidad</em>.</p>
<h3>¿Solo puedo solucionar gráficamente un modelo de PL de manera manual?</h3>
<p>Si bien hemos reconocido las bondades del aprendizaje de la solución gráfica, razón por la cual su enseñanza perdura en la academia; también hemos de reconocer que la naturaleza manual de sus cálculos y procedimientos gráficos, pueden hacer tedioso su aprendizaje.</p>
<p>Ahora bien, es preciso considerar que gran parte de los programas solucionadores de modelos PL cuentan con algún módulo gráfico interactivo (TORA, WinQSB, por ejemplo), que puede remplazar los procedimientos manuales. Así entonces, existen otras alternativas a los procedimientos y cálculos manuales, que permiten lograr un acercamiento más ágil al método de solución gráfica de PL.</p>
<h2>Solución gráfica de programación lineal mediante Python</h2>
<p>Ya hemos justificado de manera suficiente la permanencia del método gráfico en los programas de enseñanza de <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/"><strong>Investigación de Operaciones</strong></a>; sin embargo, es necesario reconocer la posibilidad actual de integrar diversas disciplinas a través de los procesos de aprendizaje que enriquezcan la formación en la materia.</p>
<p>El objetivo de este artículo consiste precisamente en abordar el método gráfico como alternativa de solución de modelos de programación lineal, al tiempo que generamos un código en Python (programación), que nos permita automatizar procedimientos gráficos y cálculos.</p>
<p>El propósito de integrar un lenguaje de programación como lo es Python, consiste en acercar a las personas a la posibilidad de integrar métodos tradicionales de la investigación de operaciones con herramientas tecnológicas vigentes. <em>¿Por qué Python? </em>Porque es gratuito; está respaldado por los desarrollos de una gran comunidad (casi que existe una línea de código desarrollado para cada requerimiento); sus aplicaciones no se limitan a un área en concreto, y por lo tanto podemos integrar diversos desarrollos a nuestros métodos.</p>
<p>En síntesis, queremos aportar nuevas propuestas en la enseñanza de la investigación de operaciones a través de la programación.</p>
<hr />
<h3>Requisitos técnicos</h3>

		<div id="requisitos-tecnicos" data-title="Requisitos técnicos" class="index-title"></div>
	
<p>Los requisitos técnicos varían de acuerdo a si queremos ejecutar los códigos en nuestro equipo, o si queremos utilizar un entorno colaborativo (<em>recomendado</em>).</p>
<h4><em>Requisitos en nuestro equipo</em></h4>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Tener una versión de Python instalada (preferiblemente una versión posterior a 3.6): <a href="https://www.python.org/downloads/release/python-380/"><strong>Descargar Python 3.8x 64-bit</strong></a></li>
<li>Tener una versión de <em>pip</em> (paquetes de instalación) de Python superior a 9.01 (Python 3.4 o superiores vienen con <em>pip</em> incorporado). Esto le permitirá instalar librerías con un comando simple y directo: <em>pip</em>.</li>
<li>Tener un editor para código, nosotros recomendamos un editor simple, por ejemplo: Sublime Text: <a href="https://www.sublimetext.com/3"><strong>Descargar Sublime Text</strong></a><strong>. </strong>También puede utilizar un IDE (Entorno de Desarrollo Integrado), como <em><strong>Spyder.</strong></em> Todo dependerá del alcance de su proyecto.</li>
</ul>

		</div>
	
<h4><em>Requisitos en un entorno colaborativo</em></h4>
<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 />
<h3>Librerías necesarias</h3>

		<div id="librerias-necesarias" data-title="Librerías necesarias" class="index-title"></div>
	
<p>Con la instalación de los programas, o el uso de un entorno colaborativo, ya estamos listos para empezar a programar, sin embargo, cada desarrollo, por pequeño que este sea, tiene sus particularidades; y existen herramientas (librerías) que nos facilitan considerablemente nuestros desarrollos, de acuerdo a nuestras necesidades. Para colocarlo en perspectiva, piense en se teléfono celular, tenemos diferentes <em>apps</em>, cada una de ellas nos facilita algunas tareas, de acuerdo a nuestras necesidades; pues bien, así funcionan las librerías en Python.</p>
<p>El desarrollo que queremos lograr requiere de las siguientes librerías:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Numpy</strong>: Numpy es una librería (biblioteca) que contiene un conjunto amplio de soluciones especializadas en cálculos matemáticos de alto nivel. Numpy nos facilita la vida a la hora de trabajar con matrices, vectores, rangos, etc.</li>
</ul>

		</div>
	
<p>Instalar una librería en Python es muy sencillo, de ahí la importancia de contar con <em>pip</em> (paquetes de instalación). <em>Pip </em>nos permite instalar cualquier librería con un comando escrito en la <span class="tie-highlight tie-highlight-black"><em><strong>Lista de comandos de Windows (CMD)</strong></em></span>. Recuerde que si utiliza un entorno colaborativo, este incluye gran parte de las librerías necesarias.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span> Para abrir la <em><strong>Lista de comandos de Windows </strong></em>pulsa a la vez la tecla de Windows (normalmente tiene el logotipo de Microsoft, está ubicada cerca de la tecla Alt) y la letra R. Se te abrirá una pequeña ventana con el título de «Ejecutar». Dentro del rectángulo para introducir texto que verás en la ventana, escribe cmd y pulsa sobre el botón de «Aceptar».
			</div>
		</div>
	
<p>Para instalar Numpy tan solo debemos escribir el siguiente comando en la lista de comandos: <span>pip install numpy</span></p>
<figure id="attachment_26196" aria-describedby="caption-attachment-26196" style="width: 559px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy.jpg" alt="Numpy" width="559" height="159" class="wp-image-26196 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy.jpg 559w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy-300x85.jpg 300w" sizes="(max-width: 559px) 100vw, 559px" /><figcaption id="caption-attachment-26196" class="wp-caption-text">Figura 1: Comando para instalar librería Numpy</figcaption></figure>
<p>Damos <em><strong>ENTER </strong></em>y veremos lo siguiente:</p>
<figure id="attachment_26197" aria-describedby="caption-attachment-26197" style="width: 575px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy2.jpg" alt="Numpy2" width="575" height="301" class="wp-image-26197 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy2.jpg 575w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Numpy2-300x157.jpg 300w" sizes="(max-width: 575px) 100vw, 575px" /><figcaption id="caption-attachment-26197" class="wp-caption-text">Figura 2: Proceso de descarga de librería Numpy finalizado</figcaption></figure>
<p>Así de sencillo es instalar una librería en Python.</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Matplotlib</strong>: Matplotlib es una librería (biblioteca) completa para crear visualizaciones estáticas, animadas e interactivas en Python. Y bien, si queremos abordar el método gráfico, necesitamos una librería que nos ayude con las gráficas.</li>
</ul>

		</div>
	
<p>Para instalar Matplotlib tan solo debemos escribir el siguiente comando en la lista de comandos: pip install matplotlib</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Shapely</strong>: Shapely es una librería (biblioteca) completa que nos permite la manipulación y análisis de objetos geométricos. Nos resultará de mucha ayuda al momento de manipular nuestras gráficas.</li>
</ul>

		</div>
	
<p>Para instalar Shapely tan solo debemos escribir el siguiente comando en la lista de comandos: pip install Shapely</p>
<p>Así de sencillo es instalar librerías en Python. Con estas librerías estamos listos para crear nuestro desarrollo.</p>
<hr />
<h2>El problema</h2>
<blockquote class=" quote-simple "><p>Un autobús que hace el recorrido Cali-Buga, ofrece asientos para fumadores al precio de 10.000 pesos y a no fumadores al precio de 6.000 pesos. Al no fumador se le deja llevar 50 Kg. de peso y al fumador 20 Kg. Si el autobús tiene 90 asientos y admite un equipaje de hasta 3.000 Kg. ¿Cuál ha de ser la oferta de asientos de la compañía para cada tipo de pasajeros, con la finalidad de optimizar el beneficio? Además, debe considerarse que por políticas de la empresa, deben ofrecerse cómo mínimo 10 asientos para pasajeros no fumadores.</p></blockquote>
<h3>Modelamiento mediante programación lineal</h3>
<p><em><strong>Variables</strong></em></p>
<p><strong>x</strong>: Cantidad de asientos reservados a fumadores.</p>
<p><strong>y</strong>: Cantidad de asientos reservados a no fumadores.</p>
<p><em><strong>Restricciones</strong></em></p>
<p>20<strong>x</strong> + 50<strong>y</strong> &lt;= 3000 <em>(Equipaje permitido)</em></p>
<p><strong>x</strong> + <strong>y</strong> &lt;= 90 <em>(Cantidad de asientos disponibles)</em></p>
<p><strong>y</strong> &gt;= 10 <em>(Política de asientos mínimos para no fumadores)</em></p>
<p><strong>y</strong> &gt;= 0 <em>(No negatividad)</em></p>
<p><strong>x</strong> &gt;= 0 <em>(No negatividad)</em></p>
<p><em><strong>Función objetivo </strong></em></p>
<p><em><strong>z = </strong></em>10000<strong>x</strong> + 6000<strong>y  </strong><em>(Maximizar)</em></p>
<hr />
<h2>Método gráfico mediante Python</h2>
<h3>Paso 1: Importar las librerías</h3>

		<div id="paso-1-importar-las-librerias" data-title="Paso 1: Importar las librerías" class="index-title"></div>
	
<p>El siguiente fragmento de código importa las librerías necesarias para nuestro desarrollo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString
</code></pre>
</div>
<h3>Paso 2: Ecuaciones e intervalos</h3>

		<div id="paso-2-ecuaciones-e-intervalos" data-title="Paso 2: Ecuaciones e intervalos" class="index-title"></div>
	
<p>Para poder graficar las restricciones (paso esencial del método gráfico), es necesario representar las ecuaciones por medio de líneas en nuestra gráfica. Cada restricción estará representada por una línea recta. El paso que antecede trazar una línea recta consiste en la determinación de un mínimo de dos puntos que al unirse la conformen.</p>
<p>Cada punto en el plano cartesiano se encuentra conformado por una coordenada en <em><strong>x</strong> </em>y una coordenada en <em><strong>y</strong> </em>(en el caso que así definamos llamar a la abscisa y a la ordenada). Recordemos que a partir del modelo algebraico inicial, nuestras restricciones se encuentran representadas por ecuaciones. Así entonces, la tarea consiste en, a partir de una ecuación, obtener un conjunto mínimo de dos coordenadas.</p>
<p>Manualmente este proceso se realiza tabulando, es decir, despejando el valor de una de las variables de la ecuación, a partir de la asignación arbitraria de un valor a la variable restante.</p>
<p>La tarea que tenemos mediante la programación en Python consistirá entonces en automatizar este proceso.</p>
<p>Lo primero que debemos hacer es despejar cada una de las inecuaciones convertidas en ecuaciones del problema, para eso, en nuestro caso, vamos a despejar en función de <em><strong>y</strong></em>:</p>
<p>Por ejemplo, para nuestra <strong>primera restricción</strong>:</p>
<p>20<strong>x</strong> + 50<strong>y</strong> &lt;= 3000 <em>(Inecuación)</em></p>
<p>20<strong>x</strong> + 50<strong>y</strong> = 3000 <em>(Ecuación)</em></p>
<p><strong>y</strong> = (3000 &#8211; 20<strong>x</strong>) / 50 <em>(Despejamos <strong>y</strong>)</em></p>
<p><strong>y1</strong> = (3000 &#8211; 20<strong>x</strong>) / 50 <em>(Asignamos un identificador único a <strong>y</strong>)</em></p>
<p>Repetimos el procedimiento para nuestra <strong>segunda restricción</strong>:</p>
<p><strong>x</strong> + <strong>y</strong> &lt;= 90 <em>(Inecuación)</em></p>
<p><strong>x</strong> + <strong>y</strong> = 90 <em>(Ecuación)</em></p>
<p><strong>y</strong> = 90 &#8211; <strong>x</strong> <em>(Despejamos <strong>y</strong>)</em></p>
<p><strong>y2</strong> = 90 &#8211; <strong>x</strong> <em>(Asignamos un identificador único a <strong>y</strong>)</em></p>
<p>En el caso de la <strong>tercera restricción</strong>:</p>
<p><strong>y</strong> &gt;= 10 <em>(Inecuación)</em></p>
<p><strong>y</strong> = 10 <em>(Ecuación)</em></p>
<p>En este caso, dado que la intención es graficar, es fundamental que cada ecuación contenga las dos variables. Dado que en la ecuación original la variable <em><strong>x </strong></em>no hace parte, podemos incluirla multiplicándola por 0. Es decir, para todos los valores que tome <em><strong>x </strong></em>la ecuación permanecerá inalterable.</p>
<p><strong>y</strong> = 10 + (0 * <strong>x</strong>) <em>(Ecuación, <strong>y</strong> ya se encuentra despejada)</em></p>
<p><strong>y3</strong> = 10 + (0 * <strong>x</strong>) <em>(Asignamos un identificador único a <strong>y</strong>)</em></p>
<p>En el caso de la <strong>cuarta restricción</strong>:</p>
<p><strong>y</strong> &gt;= 0 <em>(Inecuación)</em></p>
<p><strong>y</strong> = 0 <em>(Ecuación)</em></p>
<p>En este caso, dado que la intención es graficar, es fundamental que cada ecuación contenga las dos variables. Dado que en la ecuación original la variable <em><strong>x </strong></em>no hace parte, podemos incluirla multiplicándola por 0. Es decir, para todos los valores que tome <em><strong>x </strong></em>la ecuación permanecerá inalterable.</p>
<p><strong>y</strong> = 0 * <strong>x</strong> <em>(Ecuación, <strong>y</strong> ya se encuentra despejada)</em></p>
<p><strong>y4</strong> = 0 * <strong>x</strong> <em>(Asignamos un identificador único a <strong>y</strong>)</em></p>
<p>Todas las ecuaciones anteriores tienen algo en común: Se encuentran despejadas para obtener el valor de <em><strong>y </strong></em>a partir de <em><strong>x</strong></em><em>, </em>de manera que necesitamos alguna función que nos permita asignar diferentes valores a <em><strong>x </strong></em>en un rango dado. Para eso utilizaremos la función <strong><em>np.arange.</em></strong></p>
<p style="text-align: center;"><strong>x </strong>= <strong>np.arange</strong>(-100, 150, 50)</p>
<p>Esta función permite asignar a <em><strong>x</strong></em> diferentes valores de acuerdo a unos argumentos dados (<em>valor mínimo, valor máximo, intervalo o paso</em>). Es decir que de acuerdo a la anterior línea de código, a la variable <em><strong>x </strong></em>le serán asignados diversos valores desde el valor <em>-100 </em>hasta el valor <em>150 </em>con intervalos de <em>50. </em>Así entonces, tomará los siguientes valores:</p>
<table border="0" cellpadding="0" cellspacing="0" width="80" style="border-collapse: collapse; width: 60pt;" class=" aligncenter">
<tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" width="80" style="height: 15pt; width: 60pt; text-align: center;"><em><strong>x</strong></em></td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">-100</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">-50</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">50</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">100</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">150</td>
</tr>
</tbody>
</table>
<p>Dado que las ecuaciones que pretenden hallar <em><strong>y1, y2, y3 </strong></em>y <em><strong>y4 </strong></em>son dependientes de <em><strong>x</strong></em><em>, </em>tomarán sus valores respectivos de acuerdo a los resultados de cada ecuación, para cada valor de <em><strong>x</strong></em>:</p>
<table border="0" cellpadding="0" cellspacing="0" width="400" style="border-collapse: collapse; width: 300pt;" class=" aligncenter">
<tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl66" width="80" style="height: 15pt; width: 60pt;"><em><strong>x</strong></em></td>
<td class="xl66" width="80" style="width: 60pt;"><em><strong>y1</strong></em></td>
<td class="xl66" width="80" style="width: 60pt;"><em><strong>y2</strong></em></td>
<td class="xl66" width="80" style="width: 60pt;"><em><strong>y3</strong></em></td>
<td class="xl66" width="80" style="width: 60pt;"><em><strong>y4</strong></em></td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-100</td>
<td class="xl65">100</td>
<td class="xl65">190</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-50</td>
<td class="xl65">80</td>
<td class="xl65">140</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">0</td>
<td class="xl65">60</td>
<td class="xl65">90</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">50</td>
<td class="xl65">40</td>
<td class="xl65">40</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">100</td>
<td class="xl65">20</td>
<td class="xl65">-10</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15pt;">150</td>
<td class="xl65">0</td>
<td class="xl65">-60</td>
<td class="xl65">10</td>
<td class="xl65">0</td>
</tr>
</tbody>
</table>
<p>La última de nuestras restricciones (<strong>quinta restricción</strong>), está dada en función de hallar <em><strong>x </strong></em>a partir <em><strong>y</strong></em>, de manera que efectuamos la misma tarea en un sentido inverso:</p>
<p><strong>x</strong> &gt;= 0 <em>(Inecuación)</em></p>
<p><strong>x</strong> = 0 <em>(Ecuación)</em></p>
<p>En este caso, dado que la intención es graficar, es fundamental que cada ecuación contenga las dos variables. Dado que en la ecuación original la variable <em><strong>y </strong></em>no hace parte, podemos incluirla multiplicándola por 0. Es decir, para todos los valores que tome <em><strong>y </strong></em>la ecuación permanecerá inalterable.</p>
<p><strong>x</strong> = 0 * <strong>y</strong> <em>(Ecuación, <strong>x</strong> ya se encuentra despejada)</em></p>
<p><strong>x1</strong> = 0 * <strong>y</strong> <em>(Asignamos un identificador único a <strong>x</strong>)</em></p>
<p>En este caso, ya que requerimos que <em><strong>y </strong></em>tome diversos valores, utilizaremos la función <em><strong>np.arange.</strong></em></p>
<p style="text-align: center;"><strong>y </strong>= <strong>np.arange</strong>(-100, 150, 50)</p>
<p>Esta función permite asignar a <em><strong>y</strong></em> diferentes valores de acuerdo a unos argumentos dados (<em>valor mínimo, valor máximo, intervalo o paso</em>). Es decir que de acuerdo a la anterior línea de código, a la variable <em><strong>y </strong></em>le serán asignados diversos valores desde el valor <em>-100 </em>hasta el valor <em>150 </em>con intervalos de <em>50. </em>Así entonces, tomará los siguientes valores:</p>
<table border="0" cellpadding="0" cellspacing="0" width="80" style="border-collapse: collapse; width: 60pt;" class=" aligncenter">
<tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" width="80" style="height: 15pt; width: 60pt; text-align: center;"><em><strong>y</strong></em></td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">-100</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">-50</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">50</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">100</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" align="right" style="height: 15pt; text-align: center;">150</td>
</tr>
</tbody>
</table>
<p>Dada que nuestra quinta restricción pretende hallar <em><strong>x1</strong></em> a partir de los valores de <em><strong>y, </strong></em>tendríamos lo siguiente:</p>
<table border="0" cellpadding="0" cellspacing="0" width="160" style="border-collapse: collapse; width: 120pt;" class=" aligncenter">
<tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl66" width="80" style="height: 15.0pt; width: 60pt;"><em><strong>y</strong></em></td>
<td class="xl66" width="80" style="width: 60pt;"><em><strong>x1</strong></em></td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-100</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-50</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">0</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">50</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">100</td>
<td class="xl65">0</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">150</td>
<td class="xl65">0</td>
</tr>
</tbody>
</table>
<p>Por último, consideramos la inclusión de la <em><strong>función objetivo</strong></em><strong>, </strong>para ello:</p>
<p>10000<strong>x</strong> + 6000<strong>y</strong> = 0 <em>(Ecuación)</em></p>
<p><strong>y</strong> = (- 10000<strong>x</strong>) / 6000 <em>(Despejamos <strong>y</strong>)</em></p>
<p><strong>y5</strong> = (- 10000<strong>x</strong>) / 6000 <em>(Asignamos un identificador único a <strong>y</strong>)</em></p>
<p>En este caso, la función que ya asignamos a <em><strong>x </strong></em>(np.arange), nos prestará los valores necesarios para tabular <em><strong>y5</strong></em>.</p>
<p>Lo que hicimos hasta ahora consiste en explicar con alto grado de detalle la función de cada una las líneas del código que utilizaremos en Python. Todo lo anterior queda reducido al siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ecuaciones e intervalos (Para tabular)
x = np.arange(-100, 150, 50)
y1 = (3000 - (20 * x))/ 50
y2 = 90 - x
y3 = 10 + (0 * x)
y4 = 0 * x
y5 = (-10000 * x) / 6000
y = np.arange(-100, 150, 50)
x1 = 0 * y
</code></pre>
</div>
<h3>Paso 3: Tabular coordenadas e identificar las líneas</h3>

		<div id="paso-3-tabular-coordenadas-e-identificar-las-lineas" data-title="Paso 3: Tabular coordenadas e identificar las líneas" class="index-title"></div>
	
<p>En el paso anterior desarrollamos unas líneas de código que representan los cálculos correspondientes a cada una de las ecuaciones del modelo. Si bien con fines prácticos mostramos la forma en que se tabularían los datos, es hasta este paso en que la información se organiza en tablas (propiamente matrices de dos columnas: <em><strong>x </strong></em>y <em><strong>y</strong></em>). La información se organiza a partir de una instancia básica de la librería <em>Numpy, </em>tal como se explicará a continuación:</p>
<p style="text-align: center;"><strong>np.column_stack</strong>((x, y1))</p>
<p>Esta instancia tomará los valores del <strong><em>paso 1</em></strong> y los tabulará en una matriz 2D (dos columnas). En este ejemplo:</p>
<table border="0" cellpadding="0" cellspacing="0" width="160" style="border-collapse: collapse; width: 120pt;" class=" aligncenter">
<tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl66" width="80" style="height: 15.0pt; width: 60pt;">x</td>
<td class="xl66" width="80" style="width: 60pt;">y1</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-100</td>
<td class="xl65">100</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">-50</td>
<td class="xl65">80</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">0</td>
<td class="xl65">60</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">50</td>
<td class="xl65">40</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">100</td>
<td class="xl65">20</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" class="xl65" style="height: 15.0pt;">150</td>
<td class="xl65">0</td>
</tr>
</tbody>
</table>
<p>El siguiente paso consiste en, a partir de una instancia básica de la librería <em>Shapely, </em>generar la línea correspondiente a cada ecuación de acuerdo al tabulado generado, tal como se aprecia a continuación:</p>
<p style="text-align: center;"><strong>LineString</strong>(<strong>np.column_stack</strong>((x, y1)))</p>
<p><em>LineString</em> es una instancia de la librería <em>Shapely </em>que permite unir cada punto (coordenada), de manera que genera una línea. Ahora bien, cada línea de código deberá corresponder o asociarse a una variable única que nos permitirá identificar cada una de las líneas, por ejemplo:</p>
<p style="text-align: center;"><strong>primera_línea</strong> = <strong>LineString</strong>(<strong>np.column_stack</strong>((x, y1)))</p>
<p>Realizamos el mismo procedimiento para cada una de las líneas, siendo cuidadosos al momento de asignar las variables asociadas a cada una de las líneas:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, y4)))
sexta_linea = LineString(np.column_stack((x, y5)))
</code></pre>
</div>
<p>Podemos observar como cada conjunto de variables corresponde estrictamente a cada una de las líneas asociadas a cada ecuación en particular.</p>
<h3>Paso 4: Graficar las líneas</h3>

		<div id="paso-4-graficar-las-lineas" data-title="Paso 4: Graficar las líneas" class="index-title"></div>
	
<p>En este paso vamos a utilizar la librería <em>Matplotlib </em>para graficar nuestras líneas (ecuaciones).</p>

		<div class="clearfix"></div>
		<div class="toggle tie-sc-close">
			<h3 class="toggle-head">Colores de línea <span class="fa fa-angle-down" aria-hidden="true"></span></h3>
			<div class="toggle-content">
<table class="table table-bordered table-striped">
<tbody>
<tr>
<th style="text-align: center;">Alias</th>
<th style="text-align: center;">Color</th>
</tr>
</tbody>
<tbody>
<tr>
<td style="text-align: center;">b</td>
<td style="text-align: center;">Azul</td>
</tr>
<tr>
<td style="text-align: center;">g</td>
<td style="text-align: center;">Verde</td>
</tr>
<tr>
<td style="text-align: center;">r</td>
<td style="text-align: center;">Rojo</td>
</tr>
<tr>
<td style="text-align: center;">c</td>
<td style="text-align: center;">Cyan</td>
</tr>
<tr>
<td style="text-align: center;">m</td>
<td style="text-align: center;">Magenta</td>
</tr>
<tr>
<td style="text-align: center;">y</td>
<td style="text-align: center;">Amarillo</td>
</tr>
<tr>
<td style="text-align: center;">k</td>
<td style="text-align: center;">Negro</td>
</tr>
<tr>
<td style="text-align: center;">w</td>
<td style="text-align: center;">Blanco</td>
</tr>
</tbody>
</table>

			</div>
		</div>
	

		<div class="clearfix"></div>
		<div class="toggle tie-sc-close">
			<h3 class="toggle-head">Tipos de línea <span class="fa fa-angle-down" aria-hidden="true"></span></h3>
			<div class="toggle-content">
<table class="table table-bordered table-striped">
<tbody>
<tr>
<th style="text-align: center;">Estilo de línea</th>
</tr>
</tbody>
<tbody>
<tr>
<td style="text-align: center;">&#8211;</td>
<td><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/linea1-150x10.jpg" alt="" width="150" height="10" class="aligncenter size-thumbnail wp-image-26201" /></td>
</tr>
<tr>
<td style="text-align: center;">&#8211; &#8211;</td>
<td><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/linea2-150x10.jpg" alt="" width="150" height="10" class="aligncenter size-thumbnail wp-image-26202" /></td>
</tr>
<tr>
<td style="text-align: center;">:</td>
<td><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/linea3-150x10.jpg" alt="línea3" width="150" height="10" class="aligncenter size-thumbnail wp-image-26203" /></td>
</tr>
<tr>
<td style="text-align: center;">: &#8211;</td>
<td><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/linea4-150x10.jpg" alt="línea4" width="150" height="10" class="aligncenter size-thumbnail wp-image-26204" /></td>
</tr>
</tbody>
</table>

			</div>
		</div>
	
<p>El argumento <em>linewidth </em>determinará el grosor de la línea. Debemos considerar asignar las mismas variables que en el paso anterior.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
plt.plot(x, y4, '-', linewidth=2, color='k')
plt.plot(x, z, ':', linewidth=1, color='k')
</code></pre>
</div>
<p>Ya en este paso tenemos lo suficiente para esbozar nuestras líneas, sin embargo es necesario crear algunas configuraciones generales de nuestra gráfica, y aunque estas líneas de código van al cierre, nos adelantaremos para ir conociendo la evolución parcial de nuestro desarrollo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('Asientos para fumadores')
plt.ylabel('Asientos para no fumadores')
plt.title('Método Gráfico')

plt.show()
</code></pre>
</div>
<p>Estos ajustes permiten asignar los títulos de los ejes, así como el título general del gráfico, así mismo especifica la función que permite que el resultado del código se muestre como imagen.</p>
<p>En este paso queremos ejecutar el código parcial. Nosotros utilizaremos <em>Google Colaboratory</em>, puedes ver la ejecución en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>.</p>
<figure id="attachment_26453" aria-describedby="caption-attachment-26453" style="width: 397px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-4_Ecuaciones-representadas-en-lineas-Metodo-grafico.png" alt="Figura 4: Ecuaciones representadas en líneas (Método gráfico)" width="397" height="278" class="size-full wp-image-26453" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-4_Ecuaciones-representadas-en-lineas-Metodo-grafico.png 397w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-4_Ecuaciones-representadas-en-lineas-Metodo-grafico-300x210.png 300w" sizes="(max-width: 397px) 100vw, 397px" /><figcaption id="caption-attachment-26453" class="wp-caption-text">Figura 4: Ecuaciones representadas en líneas (Método gráfico)</figcaption></figure>
<p>&nbsp;</p>
<p>De manera que, de acuerdo al código ejecutado, nuestro programa es capaz de esbozar gráficamente cada una de nuestras ecuaciones sobre el plano cartesiano. Ya que incluso tenemos una representación de la función objetivo (línea punteada), e infiriendo el polígono factible (de acuerdo a la naturaleza de las restricciones: &lt;= o &gt;=), podemos geométricamente establecer cual es el vértice solución.</p>
<p>Ahora bien, podemos identificar que en la gráfica se pueden apreciar algunos polígonos, pero: <em>¿Cuál es nuestro polígono factible? </em>Bien, para eso debemos considerar la naturaleza de las restricciones; en cuyo caso, cada línea trazada dividirá el cuadrante solución en dos semiplanos: uno a cada lado de la línea trazada. Solo una de estas dos mitades satisface cada desigualdad, de manera que debemos considerar si cada restricción es «menor o igual», «mayor o igual», o en su defecto «igual». Por ejemplo:</p>
<figure id="attachment_26216" aria-describedby="caption-attachment-26216" style="width: 491px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/semiplanos.jpg" alt="semiplanos" width="491" height="322" class="wp-image-26216" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/semiplanos.jpg 563w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/semiplanos-300x197.jpg 300w" sizes="(max-width: 491px) 100vw, 491px" /><figcaption id="caption-attachment-26216" class="wp-caption-text">Figura 5: Semiplanos obtenidos a partir de una línea de ecuación</figcaption></figure>
<p>En la anterior gráfica (Figura 5) podemos observar la línea que representa la segunda restricción. Así mismo, podemos observar el concepto de semiplanos: <em><strong>A</strong> y <strong>B</strong> </em>(Uno a cada lado de la línea trazada). Como se trata de una restricción «menor o igual» (líneas hacia abajo y hacia la izquierda); solo la mitad <em><strong>A </strong></em>satisface la desigualdad.</p>
<figure id="attachment_26209" aria-describedby="caption-attachment-26209" style="width: 614px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico2.jpg" alt="metodo_grafico2" width="614" height="460" class="wp-image-26209 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico2.jpg 614w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico2-300x225.jpg 300w" sizes="(max-width: 614px) 100vw, 614px" /><figcaption id="caption-attachment-26209" class="wp-caption-text">Figura 6: Polígono factible</figcaption></figure>
<p>En este caso representamos con flechas el lado, es decir la mitad que divide cada línea trazada, en el cual se encuentran los valores que satisfacen cada restricción. Las restricciones «menores o igual» apuntarán sus flechas hacia abajo y/o a la izquierda; los «mayores o igual» apuntarán sus flechas hacia arriba y/o a la derecha. Así entonces encontramos con suma facilidad nuestro polígono factible (zona gris).</p>
<h3>Paso 5: Determinar y graficar las intersecciones</h3>

		<div id="paso-5-determinar-y-graficar-las-intersecciones" data-title="Paso 5: Determinar y graficar las intersecciones" class="index-title"></div>
	
<p>Algunos textos, como por ejemplo <em>Investigación de Operaciones de </em><em>Taha</em>, consideran que los dos pasos fundamentales de la solución gráfica consisten en: determinar el espacio de soluciones factibles y determinar la solución óptima entre todos los puntos localizados en el espacio de soluciones.</p>
<p>Consideremos que el área gris de la Figura 6 corresponde al polígono o espacio de soluciones factibles. Todos los puntos (coordenadas en x, y) que se encuentran en este espacio, satisfacen al mismo tiempo todas las restricciones del modelo. Los puntos que se encuentra fuera, satisfacen algunas o ninguna de las restricciones.</p>
<p>Ahora bien, la cantidad de puntos dentro del espacio de soluciones factibles es sumamente grande, sin embargo, hemos de recordar que este método se rige bajo criterios de optimización. En nuestro ejemplo, este criterio es: <em>maximizar</em>. Recordemos que en programación lineal, la solución óptima siempre está asociada con un punto de esquina o vértice del polígono factible. Y que estos vértices se generan en cada punto de intersección de dos o más líneas, es decir, en el punto en que se igualan dos o más ecuaciones.</p>
<p>Lo anterior es sumamente importante, ya que para efectos de nuestro desarrollo en Python, podemos utilizar alguna función básica que, dadas las líneas, nos represente las intersecciones. En la práctica, determinar el punto de intersección de dos líneas requiere de la solución de un sistema de ecuaciones de 2 x 2, ya sea por el método de <em>sustitución, igualación, eliminación</em>, etc.</p>
<p>En nuestro caso, vamos a utilizar la función <strong>intersection</strong>() de Python. Lo que debemos hacer es determinar que intersecciones queremos determinar y graficar. Por ejemplo:</p>
<p>Si queremos determinar la intersección de la línea amarilla (cuarta línea, así la nombramos en el paso anterior) y la línea azul (primera línea), debemos escribir el siguiente código:</p>
<p style="text-align: center;">primera_interseccion = cuarta_linea.<strong>intersection</strong>(primera_linea)</p>
<p>Esta línea de código creará una variable llamada <em>primera_interseccion</em> que estará definida por la intersección entre la <em>cuarta línea</em> y la <em>primera línea</em>. El siguiente fragmento creará las intersecciones restantes del polígono solución (consideramos que el orden es importante, así que las creamos de acuerdo a las manecillas del reloj).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(primera_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = segunda_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
</code></pre>
</div>
<p>El espacio de soluciones factibles tienen cuatro intersecciones, de acuerdo al código anterior quedan definidas de acuerdo a los nombres que le asignamos a las variables. Ahora, el paso siguiente consiste en graficar dichas restricciones.</p>
<p>Acá pueden encontrar el listado con los tipos de marcadores (símbolos) que pueden utilizar: <a href="https://matplotlib.org/stable/api/markers_api.html"><em><strong>marcadores pyplot</strong></em></a>. Nosotros utilizaremos un marcador en círculo (representado así en el código: «o»).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
</code></pre>
</div>
<p>Así entonces, podemos ejecutar nuestro código (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) nuevamente y veremos:</p>
<figure id="attachment_26454" aria-describedby="caption-attachment-26454" style="width: 397px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-7_-Vertices-del-espacio-de-solucion-factible.png" alt="Figura 7: Vértices del espacio de solución factible" width="397" height="278" class="wp-image-26454 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-7_-Vertices-del-espacio-de-solucion-factible.png 397w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-7_-Vertices-del-espacio-de-solucion-factible-300x210.png 300w" sizes="(max-width: 397px) 100vw, 397px" /><figcaption id="caption-attachment-26454" class="wp-caption-text">Figura 7: Vértices del espacio de solución factible</figcaption></figure>
<p>Como podemos observar en la Figura 7, hemos logrado graficar los vértices del polígono factible.</p>
<p>Ahora es cuando debemos preguntarnos: <em>¿Qué más necesitamos? </em>Pues bien, si bien gráficamente podemos determinar el vértice solución (basta con trasladar paralelamente la línea punteada hasta cada vértice e identificar en cual de ellos la línea no corta el polígono solución); sería importante determinar el valor de las coordenadas en cada uno de los vértices, ya que con ello podemos evaluar la ecuación de la función objetivo para cada punto de esquina.</p>
<p>Lo que necesitamos es el valor numérico de cada punto, y la verdad, ya lo tenemos dentro de nuestras variables; lo que debemos hacer es imprimirlo en la consola de Windows, para eso podemos utilizar el siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Imprimiendo las coordenadas de los vértices en la consola
print('\n COORDENADAS DE LAS INTERSECCIONES')
print('Coordenadas de la primera intersección: {} '.format(primera_interseccion))
print('Coordenadas de la segunda intersección: {} '.format(segunda_interseccion))
print('Coordenadas de la tercera intersección: {} '.format(tercera_interseccion))
print('Coordenadas de la cuarta intersección: {} '.format(cuarta_interseccion))
</code></pre>
</div>
<p>Bien podíamos utilizar tan solo la función <strong>print</strong>() en su versión más simple, por ejemplo:</p>
<p style="text-align: center;"><strong>print</strong>(primera_interseccion)</p>
<p>Esto serviría para obtener el punto de la primera intersección con sus dos coordenadas (<em><strong>x</strong></em>, <em><strong>y</strong></em>). Sin embargo, hemos utilizado algún texto que identifique con precisión la información que estamos generando. En este caso utilizamos un poco de texto, dentro de las llaves {} el programa introducirá el valor de cada variable. Esta es una cuestión accesoria y de estilo, siéntanse libres de explorar con el código. Ejecutamos (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) y, junto a la imagen, tendremos el siguiente resultado:</p>
<figure id="attachment_26455" aria-describedby="caption-attachment-26455" style="width: 533px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-8_Valores-de-las-coordenadas-en-cada-vertice.png" alt="Figura 8: Valores de las coordenadas en cada vértice" width="533" height="124" class="wp-image-26455 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-8_Valores-de-las-coordenadas-en-cada-vertice.png 533w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-8_Valores-de-las-coordenadas-en-cada-vertice-300x70.png 300w" sizes="(max-width: 533px) 100vw, 533px" /><figcaption id="caption-attachment-26455" class="wp-caption-text">Figura 8: Valores de las coordenadas en cada vértice</figcaption></figure>
<p>&nbsp;</p>
<p>De esta manera hemos obtenido los valores de los puntos de cada vértice.</p>
<h3>Paso 6: Determinar la solución óptima</h3>

		<div id="paso-6-determinar-la-solucion-optima" data-title="Paso 6: Determinar la solución óptima" class="index-title"></div>
	
<p>Básicamente el objetivo de la solución gráfica se logra en este paso, es decir, hallar la solución óptima en términos de la función objetivo y los valores de las variables de decisión. Con un poco de lógica podemos inferir que en el punto en el que estamos, esta es una cuestión sencilla. Lo único que debemos hacer es, de acuerdo a los valores de cada vértice, evaluar la ecuación de la función objetivo en cada uno de ellos, de manera que podamos determinar cual es el mejor resultado (de acuerdo al criterio de optimización, en este caso: <em>maximizar</em>).</p>
<p>Ya que nos encontramos trabajando en <em>Python</em>, es necesario aclarar que el valor de los vértices obtenidos corresponde a un formato de tipo <em>point </em>(punto), y que básicamente, no es posible operar cada una de las variables (<em>x, y) </em>que contiene sin antes extraerlas. Por esa razón, lo siguiente que debemos hacer es extraer el valor de cada variable contenida en cada punto de manera independiente:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Identificando los valores de las coordenadas (x, y) de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
</code></pre>
</div>
<p>El anterior código acaba de generar un total de 8 variables, cada una de ellas contendrá la información correspondiente a la proyección de cada eje sobre cada punto, por ejemplo: <em><strong>xi1m</strong></em><em> </em>contendrá la información correspondiente al valor que tienen el punto de la primera intersección sobre el eje <em><strong>x</strong></em>. Es decir, acabamos de extraer las variables <em><strong>x</strong></em> y <em><strong>y </strong></em>de cada uno de los puntos de intersección.</p>
<p>Sin embargo, ese valor extraído se encuentra en un formato de matriz y contiene más información que la que necesitamos, de hecho por eso le colocamos la letra <em><strong>m</strong>, </em>para reconocer que es una matriz. Lo que debemos hacer es cambiar el formato de los valores extraídos. Formas existen muchas, nosotros utilizaremos la siguiente:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Cambiamos el formato de la variable de matriz a real
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
</code></pre>
</div>
<p><em>float 64</em> es un formato que corresponde a números reales, es decir: números que pueden ser positivos o negativos con decimales. En este punto tenemos las variables correspondientes a cada uno de los valores de los vértices en los ejes <em><strong>x </strong></em>y <em><strong>y</strong></em>. Lo siguiente será evaluar la ecuación objetivo en cada uno de los vértices, es decir, en las coordenadas de cada intersección. Por ejemplo:</p>
<p style="text-align: center;"><strong>FOi1</strong> = (<strong>xi1</strong> * 10000) + (<strong>yi1</strong> * 6000)</p>
<p>Creamos la variable <em><strong>FOi1</strong></em>, es decir: La función objetivo en la intersección 1 (vértice 1). La cual multiplicará los coeficientes de la ecuación objetivo por el valor de la variable <strong>xi1 </strong>y <strong>yi1</strong>, es decir, las coordenadas <strong>x </strong>y <strong>y </strong>en el vértice 1. En este caso, y ya que conocemos estos valores (los hemos impreso en la consola), generaría el siguiente resultado:</p>
<p style="text-align: center;"><strong>FOi1</strong> = (<strong>0</strong> * 10000) + (<strong>60</strong> * 6000)</p>
<p style="text-align: center;"><strong>FOi1</strong> = 360000</p>
<p>Lo que equivale a decir que el resultado de la función objetivo en el vértice 1 es igual a 360.000. Así entonces, generamos el fragmento de código de estas ecuaciones y variables, así como las funciones que permitan que estos valores se impriman en la consola:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 10000) + (yi1 * 6000)
FOi2 = (xi2 * 10000) + (yi2 * 6000)
FOi3 = (xi3 * 10000) + (yi3 * 6000)
FOi4 = (xi4 * 10000) + (yi4 * 6000)

#Imprimiendo las evaluaciones de la FO en cada vértice (Consola)
print('\n EVALUACIÓN DE LA FO EN LOS VÉRTICES')
print('Función objetivo en la intersección 1: {} pesos'.format(FOi1))
print('Función objetivo en la intersección 2: {} pesos'.format(FOi2))
print('Función objetivo en la intersección 3: {} pesos'.format(FOi3))
print('Función objetivo en la intersección 4: {} pesos'.format(FOi4))
</code></pre>
</div>
<p>Ejecutamos nuestro código (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>), y tendremos, además de la gráfica, el siguiente resultado:</p>
<figure id="attachment_26456" aria-describedby="caption-attachment-26456" style="width: 519px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-9_Funcion-objetivo-en-cada-uno-de-los-vertices.png" alt="Figura 9: Función objetivo en cada uno de los vértices" width="519" height="124" class="size-full wp-image-26456" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-9_Funcion-objetivo-en-cada-uno-de-los-vertices.png 519w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-9_Funcion-objetivo-en-cada-uno-de-los-vertices-300x72.png 300w" sizes="(max-width: 519px) 100vw, 519px" /><figcaption id="caption-attachment-26456" class="wp-caption-text">Figura 9: Función objetivo en cada uno de los vértices</figcaption></figure>
<p>&nbsp;</p>
<p>A esta altura debemos suponer que estamos muy cerca de lograr que nuestro desarrollo determine, según el criterio de optimización, la solución óptima. Ya que tenemos los valores de la función objetivo en cada uno de los vértices, será cuestión de determinar su valor máximo o mínimo, según se el caso. En nuestro problema, requerimos determinar el valor máximo; para eso utilizamos una función básica de Python:</p>
<p style="text-align: center;"><strong>ZMAX </strong>= <strong>max</strong>(FOi1, FOi2, FOi3, FOi4)</p>
<p>Creamos la variable <em><strong>ZMAX</strong></em>, es decir: La variable que contendrá el valor máximo entre las variables que contienen los valores de la función objetivo en cada vértice. Así entonces obtendremos de una muy sencilla, nuestra solución óptima. El siguiente código creará la variable y la función que permitirá que se imprima dicho resultado en la consola:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} pesos'.format(ZMAX))
</code></pre>
</div>
<p>Ejecutamos nuestro código (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) y tendremos, además de la gráfica, el siguiente resultado:</p>
<figure id="attachment_26457" aria-describedby="caption-attachment-26457" style="width: 478px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-10_Solucion-optima.png" alt="Figura 10: Solución óptima" width="478" height="70" class="size-full wp-image-26457" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-10_Solucion-optima.png 478w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Figura-10_Solucion-optima-300x44.png 300w" sizes="(max-width: 478px) 100vw, 478px" /><figcaption id="caption-attachment-26457" class="wp-caption-text">Figura 10: Solución óptima</figcaption></figure>
<p>En este punto nuestro desarrollo permite que obtengamos, además de la gráfica, la solución óptima. <em>¿Qué más podemos necesitar?</em> Pues bien, necesitamos como mínimo imprimir el valor de las variables de decisión, es decir, el valor de las coordenadas <em><strong>x </strong></em>y <em><strong>y </strong></em>en el vértice donde se encuentra la solución óptima (punto óptimo). En <em>Python </em>pueden existir muchas alternativas para obtener esta información, nosotros utilizaremos el concepto de directorio.</p>
<p>Lo primero que vamos a hacer es organizar la información, es decir, crear un vector que almacene los valores de cada variable (sea <em><strong>x</strong> </em>o <strong><em>y</em></strong>) en todos los vértices, por ejemplo:</p>
<p style="text-align: center;"><strong>m</strong> = [<strong>xi1</strong>, <strong>xi2</strong>, <strong>xi3</strong>, <strong>xi4</strong>]
<p>El vector <em><strong>m </strong></em>contendrá los valores (ordenados) de las variables <em><strong>x </strong></em>en cada uno de los vértices, empezando por el vértice 1 y finalizando en el vértice 4. Lo mismo realizamos con las variables <em><strong>y</strong></em>.</p>
<p style="text-align: center;"><strong>n</strong> = [<strong>yi1</strong>, <strong>yi2</strong>, <strong>yi3</strong>, <strong>yi4</strong>]
<p>En este punto, y con la información tal cual como tenemos organizada, podemos realizar un pequeño aporte al gráfico: rellenar nuestro polígono solución. Para eso, utilizamos una función llamada <em>fill</em>, y nuestros vectores recién creados:</p>
<p style="text-align: center;">plt.<strong>fill</strong>(<strong>m</strong>, <strong>n</strong>, color=&#8217;silver&#8217;)</p>
<p>Esta función utiliza los vectores <em><strong>m </strong></em>y <em><strong>n </strong></em>para crear un relleno en el gráfico al desplazarse por los puntos de los vectores (coordenadas), en sentido de las manecillas del reloj. Es decir, iniciará en el vértice 1 y finalizará en el vértice 4. Además, hemos especificado que el color de este relleno sea <em>silver:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices 
plt.fill(m, n, color='silver')
</code></pre>
</div>
<p>Ejecutamos nuestro código (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) y tendremos la siguiente gráfica:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico3.png" alt="método_gráfico3" width="640" height="480" class="aligncenter wp-image-26223 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico3.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico3-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>Ahora podemos apreciar el espacio de soluciones factibles desde la gráfica que nos exporta el programa.</p>
<p>Volvamos a nuestro directorio. Lo que vamos a hacer es crear un arreglo con las variables que contienen los resultados de las funciones objetivo (<em><strong>FOi1, FOi2, FOi3 </strong></em>y <em><strong>FOi4</strong></em>), y a cada uno le asignaremos un índice de acuerdo a su orden (ya veremos para qué). Por ejemplo:</p>
<p style="text-align: center;"><strong>dict1</strong> = {0:<strong>FOi1</strong>, 1:<strong>FOi2</strong>, 2:<strong>FOi3</strong>, 3:<strong>FOi4</strong>}</p>
<p>Así entonces, a cada variable le asignamos un número inicia en 0 y finaliza en 3 (ya veremos para qué). La variable <em><strong>dict</strong></em> hace referencia a nuestro directorio, y dentro de él se encuentra el arreglo con nuestros índices.</p>
<p>Lo siguiente que haremos es extraer el índice, no la variable de este arreglo, de acuerdo al mayor valor de la variable, no del índice. Es decir, una función que evalúe las variables de las funciones objetivo, y en lugar de devolvernos dicho valor, nos traiga el índice que tiene al lado. Nosotros de antemano conocemos que el mayor valor es el de <em><strong>FOi3</strong></em>, por esta razón, la función debe arrojarnos como respuesta el índice <em><strong>2</strong></em> (2:<strong>FOi3</strong>). <em>¿Por qué no iniciamos desde 1 y sí desde 0? </em>En Python, las posiciones dentro de un arreglo inician desde 0.</p>
<p>La siguiente función nos traerá el índice de acuerdo al mayor valor de la variable:</p>
<p style="text-align: center;"><strong>posicion</strong> = <strong>max</strong>(<strong>dict1</strong>, key=dict1.get)</p>
<p>Creamos la variable <em><strong>posicion </strong></em>y en ella se almacenará el índice asociado al mayor valor de la variable del arreglo. <em>¿Y esto para qué nos sirve? </em>Pues bien, recuerdan que tenemos un par de vectores donde se almacenan las variables <em><strong>x </strong></em>y <em><strong>y </strong></em>de cada uno de los vértices (y que hicimos énfasis en ordenarlos). Con la variable <em><strong>posicion</strong></em> podemos acceder a la variable específica dentro de estos vectores, es decir, a la posición en la que se encuentra cada una de las variables asociadas al vértice solución. <em>¿Podemos plantearlo de una manera más sencilla? </em>Vamos a intentarlo:</p>
<p>Según de la manera ordenada en la que hemos venido trabajando el código, podemos inferir que si la función objetivo solución se encuentra en la variable <em><strong>FOi3</strong></em> sus coordenadas serán <em><strong>xi3 </strong></em>y <em><strong>y13</strong></em>. Así entonces, si tenemos la posición de la función objetivo dentro de un arreglo (<em><strong>dict1</strong></em>)<strong></strong><em><strong>,</strong></em> esa misma posición corresponderá a la de cada variable dentro del vector que la contenga (el vector <em><strong>m </strong></em>contiene las variables <em><strong>x</strong></em> de los vértices, mientras el vector <em><strong>n </strong></em>contiene las variables <em><strong>y</strong></em> de los vértices).</p>
<p>Así entonces, el valor de las coordenadas del vértice solución la obtenemos de la siguiente forma:</p>
<p style="text-align: center;"><strong>XMAX</strong> = <strong>m</strong>[<strong>posicion</strong>]
<strong>YMAX</strong> = <strong>n</strong>[<strong>posicion</strong>]
<p>La variable <em><strong>XMAX</strong></em> tomará el valor de la variable <em><strong>x</strong></em> que se encuentra en el vector <em><strong>m </strong></em>en la posición de la variable <em><strong>posicion</strong></em>. Es decir, corresponderá al valor de la variable <em><strong>x</strong></em> en el vértice solución. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de asientos a reservar para fumadores: {} '.format(XMAX))
print('Cantidad de asientos a reservar para no fumadores: {} '.format(YMAX))
</code></pre>
</div>
<p>En el anterior fragmento, bien pudimos utilizar la función <strong>print</strong>(<strong>XMAX</strong>) de una manera sencilla. Sin embargo, le dimos un formato que permitiera darle un nombre a las variables de acuerdo al problema (variables de decisión).</p>
<p>Ejecutamos nuestro código y tendremos, además de la gráfica, el siguiente resultado en la consola:</p>
<figure id="attachment_26458" aria-describedby="caption-attachment-26458" style="width: 591px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Variables-de-decision.png" alt="Variables de decisión" width="591" height="89" class="size-full wp-image-26458" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Variables-de-decision.png 591w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/Variables-de-decision-300x45.png 300w" sizes="(max-width: 591px) 100vw, 591px" /><figcaption id="caption-attachment-26458" class="wp-caption-text">Variables de decisión</figcaption></figure>
<p>En este punto podemos decir que nuestro código tiene la capacidad de esbozar una solución gráfica del problema de programación lineal. Al mismo tiempo nos permite obtener información básica relevante.</p>
<p>Podemos hacer algunos ajustes accesorios, todos a elección del desarrollador. En este caso, vamos a agregar un par de líneas de código que permitan adicionar al gráfico un par de anotaciones con las coordenadas del vértice solución y el valor la solución óptima. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Generando las anotaciones de las coordenadas y solución óptima en el gráfico
plt.annotate('  X: {0} / Y: {1} (Coordenadas)'.format(XMAX, YMAX), (XMAX, YMAX))
plt.annotate('  Solución óptima: {}'.format(ZMAX), (XMAX, YMAX+3))
</code></pre>
</div>
<p>Ejecutamos nuestro código (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) y tendremos la siguiente gráfica:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico4.png" alt="método_gráfico4" width="656" height="563" class="aligncenter wp-image-26225 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico4.png 656w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/metodo_grafico4-300x257.png 300w" sizes="(max-width: 656px) 100vw, 656px" /></p>
<p>También podemos eliminar las líneas rectas, para ello anteponemos # en la línea que ordena imprimir cada una de las ecuaciones. Ejecutamos (<a href="https://colab.research.google.com/drive/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><em><strong>Método Gráfico</strong></em></a>) y tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/descarga.png" alt="poligono solucion factible" width="528" height="278" class="aligncenter size-full wp-image-26459" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/descarga.png 528w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/descarga-300x158.png 300w" sizes="(max-width: 528px) 100vw, 528px" /></p>
<hr />
<p>Así entonces, hemos finalizado con la construcción de nuestro código para efectuar una solución gráfica de un modelo de programación lineal. Este puede, con ligeras modificaciones, ajustarse a cualquier problema susceptible de ser solucionado mediante método gráfico.</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/1EFa3Fb2nAB7ws6faGc73Me8KAhjYKqXM?usp=sharing"><strong>Método Gráfico</strong></a>.</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Autor: Bryan Salazar López, Ing. M.Sc. (2021)
#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString

#Ecuaciones e intervalos (Para tabular)
x = np.arange(-100, 150, 50)
y = np.arange(-100, 150, 50)
y1 = (3000 - (20 * x))/ 50
y2 = 90 - x
y3 = 10 + (0 * x)
x1 = 0 * y
y4 = 0 * x
z = (-10000 * x) / 6000

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, y4)))
sexta_linea = LineString(np.column_stack((x, z)))

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
plt.plot(x, y4, '-', linewidth=2, color='k')
plt.plot(x, z, ':', linewidth=1, color='k')

#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(primera_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = segunda_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o')
plt.plot(*segunda_interseccion.xy, 'o')
plt.plot(*tercera_interseccion.xy, 'o')
plt.plot(*cuarta_interseccion.xy, 'o')

#Imprimiendo las coordenadas de los vértices en la consola
print('\n COORDENADAS DE LAS INTERSECCIONES')
print('Coordenadas de la primera intersección: {} '.format(primera_interseccion))
print('Coordenadas de la segunda intersección: {} '.format(segunda_interseccion))
print('Coordenadas de la tercera intersección: {} '.format(tercera_interseccion))
print('Coordenadas de la cuarta intersección: {} '.format(cuarta_interseccion))

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 10000) + (yi1 * 6000)
FOi2 = (xi2 * 10000) + (yi2 * 6000)
FOi3 = (xi3 * 10000) + (yi3 * 6000)
FOi4 = (xi4 * 10000) + (yi4 * 6000)

#Imprimiendo las evaluaciones de la FO en cada vértice (Consola)
print('\n EVALUACIÓN DE LA FO EN LOS VÉRTICES')
print('Función objetivo en la intersección 1: {} pesos'.format(FOi1))
print('Función objetivo en la intersección 2: {} pesos'.format(FOi2))
print('Función objetivo en la intersección 3: {} pesos'.format(FOi3))
print('Función objetivo en la intersección 4: {} pesos'.format(FOi4))

#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} pesos'.format(ZMAX))

#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices 
plt.fill(m, n, color='silver')

#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de asientos a reservar para fumadores: {} '.format(XMAX))
print('Cantidad de asientos a reservar para no fumadores: {} '.format(YMAX))

#Generando las anotaciones de las coordenadas y solución óptima en el gráfico
plt.annotate('  X: {0} / Y: {1} (Coordenadas)'.format(XMAX, YMAX), (XMAX, YMAX))
plt.annotate('  Solución óptima: {}'.format(ZMAX), (XMAX, YMAX+3))


#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('Asientos para fumadores')
plt.ylabel('Asientos para no fumadores')
plt.title('Método Gráfico')

plt.show()
</code></pre>
</div>
<p>&nbsp;</p>
<hr />
<p>En artículos posteriores abordaremos <em><strong><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/">Análisis de sensibilidad</a> </strong></em>en la solución gráfica mediante <em>Python, y </em>evaluaremos la pertinencia de la misma.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/">Método gráfico de la programación lineal mediante el uso de Python</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/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/feed/</wfw:commentRss>
			<slash:comments>2</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>Programación lineal mixta con Google OR-Tools</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Mon, 26 Apr 2021 19:53:07 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Optimización lineal]]></category>
		<category><![CDATA[PLE]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Programación lineal entera]]></category>
		<category><![CDATA[Programación lineal mixta]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=25584</guid>

					<description><![CDATA[<p>En artículos anteriores hemos mencionado la diferencia existente entre programación lineal (PL) y programación lineal entera (PLE). Recordamos entonces que, cuando un modelo presenta todas sus variables enteras, se denomina puro. En caso contrario, cuando utiliza una combinación de variables enteras y continuas, se denomina mixto, constituyendo un modelo de programación lineal mixta. En materia de optimización lineal, la programación &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/">Programación lineal mixta 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>En artículos anteriores hemos mencionado la diferencia existente entre <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/"><em><strong>programación lineal (PL)</strong></em></a> y <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/"><em><strong>programación lineal entera (PLE)</strong></em></a>. Recordamos entonces que, cuando un modelo presenta todas sus variables enteras, se denomina <em>puro. </em>En caso contrario, cuando utiliza una combinación de variables enteras y continuas, se denomina <em>mixto, </em>constituyendo un modelo de <em>programación lineal mixta.</em></p>
<p>En materia de optimización lineal, la programación lineal mixta, lógicamente, aborda la mayor cantidad de casos de aplicación práctica. En la medida en que se consideren la mayor cantidad de variables que representen mediante un modelo de optimización, la realidad; es potencialmente adecuado, hacer uso de modelos de programación mixta.</p>
<p>Por ejemplo, pensemos en la producción de televisores, es posible que en un modelo lineal queramos representar por medio de una variable entera, la cantidad de unidades producidas; ahora bien, también es posible que queramos representar por medio de una variable continua, el tiempo empleado en la fabricación. En este escenario, requerimos de programación lineal mixta.<em></em></p>
<hr />
<p><span>El objetivo de este artículo consiste en utilizar las librerías del software Google OR-Tools para abordar problemas de programación lineal mixta (optimización lineal mixta). </span></p>
<p>OR-Tools proporciona una herramienta principal para resolver este tipo de problemas de optimización:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>MPSolver: un contenedor para varios solucionadores de MIP de terceros, que utilizan técnicas estándar de ramificación y vinculación <em>(branch and bound).</em></li>
</ul>

		</div>
	
<hr />
<h2>El problema
		<div id="el-problema" data-title="El problema" class="index-title"></div>
	</h2>
<p>En el artículo de introducción (<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/">programación lineal</a>), abordamos un caso descrito en el libro <em>Applied Mathematical Programming, de Bradley, Hax, and Magnanti (Addison-Wesley, 1977), del MIT (Cápitulo 2 página 50). </em>Con fines prácticos, hemos adaptado dicho problema, incorporando nuevas restricciones y modificando algunos datos del modelo original, con el propósito de evidenciar la diferencia en los resultados obtenidos por medio del uso de variables continuas, enteras y mixtas.</p>
<blockquote class=" quote-simple "><p>El propietario de una tienda que produce remolques para automóviles desea determinar la mejor combinación para sus tres productos: remolques de plataforma plana, remolques económicos y remolques de lujo. Su taller se limita a trabajar 24 días al mes en el trabajo de los metales y 60 días al mes en el trabajo de la madera para estos productos.</p>
<p>Existe un contrato vigente, mediante el cual, el propietario deberá entregar como mínimo 4 remolques tipo económico cada mes.</p>
<p>La siguiente tabla indica los datos de producción de los remolques.</p></blockquote>
<table width="492">
<tbody>
<tr>
<td width="172"></td>
<td colspan="3" width="240" style="text-align: center;">Uso por cada unidad de tráiler</td>
<td rowspan="2" width="80" style="text-align: center;">Recursos disponibles</td>
</tr>
<tr>
<td style="text-align: center;"></td>
<td width="80" style="text-align: center;">Plataforma plana</td>
<td width="80" style="text-align: center;">Económica</td>
<td width="80" style="text-align: center;">Lujosa</td>
</tr>
<tr>
<td style="text-align: center;">Días de trabajo en metales</td>
<td style="text-align: center;">0,6</td>
<td style="text-align: center;">2</td>
<td style="text-align: center;">1</td>
<td style="text-align: center;">24</td>
</tr>
<tr>
<td style="text-align: center;">Días de trabajo en madera</td>
<td style="text-align: center;">1</td>
<td style="text-align: center;">2</td>
<td style="text-align: center;">4</td>
<td style="text-align: center;">60</td>
</tr>
<tr>
<td style="text-align: center;">Contribución ($ x 100)</td>
<td style="text-align: center;">6</td>
<td style="text-align: center;">14</td>
<td style="text-align: center;">13</td>
<td style="text-align: center;"></td>
</tr>
</tbody>
</table>
<h2>Modelamiento del problema
		<div id="modelamiento-del-problema" data-title="Modelamiento del problema" class="index-title"></div>
	</h2>
<p><em>Sean las variables de decisión del problema:</em></p>
<p>x<sub>0</sub> = Número de remolques de plataforma plana producidos por mes</p>
<p>x<sub>1</sub> = Número de remolques económicos producidos por mes</p>
<p>x<sub>2</sub> = Número de remolques de lujo producidos por mes</p>
<p><em>Suponiendo que los costos de la capacidad de trabajo en metal y madera sean fijos, el problema se convierte en un problema de maximización:</em></p>
<p>Zmax = 6<strong>x<sub>0</sub></strong> + 14<strong>x<sub>1</sub></strong> + 13<strong>x<sub>2</sub></strong></p>
<p><em>Sujeto a las siguientes restricciones de capacidad:</em></p>
<p>0,6<strong>x<sub>0</sub></strong> + 2<strong>x<sub>1</sub></strong> + <strong>x<sub>2</sub></strong> &lt;= 24,</p>
<p><strong>x<sub>0</sub></strong> + 2<strong>x<sub>1</sub></strong> + 4<strong>x<sub>2</sub></strong> &lt;= 60,</p>
<p><em>Sujeto a la siguiente restricción de demanda mínima (contrato)</em></p>
<p><strong>x<sub>1</sub></strong> &gt;= 4</p>
<p><em>Sujeto a las siguientes restricciones de no-negatividad:</em></p>
<p><strong>x<sub>0</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>1</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>2</sub></strong> &gt;= 0,</p>
<p><em>Podemos, del mismo modo, establecer un par de variables que correspondan a las horas ociosas para las dos tareas establecidas (metal y madera):</em></p>
<p>x<sub>3</sub> = Número de horas ociosas en el trabajo en metal al mes,</p>
<p>x<sub>4</sub> = Número de horas ociosas en el trabajo en madera al mes,</p>
<p><em>Reescribimos las restricciones (adicionando las variables de horas ociosas). Podemos observar que las inecuaciones ahora serán igualdades, para que de esta forma ahora podamos tener información relacionada a los recursos. En otras palabras, lo que se utiliza (horas productivas) + lo que sobre (horas ociosas) = tiempo disponible:</em></p>
<p>0,6<strong>x<sub>0</sub></strong> + 2<strong>x<sub>1</sub></strong> + <strong>x<sub>2</sub></strong> + <strong>x<sub>3</sub></strong> = 24,</p>
<p><strong>x<sub>0</sub></strong> + 2<strong>x<sub>1</sub></strong> + 4<strong>x<sub>2</sub></strong> + <strong>x<sub>4</sub></strong> = 60,</p>
<p><strong>x<sub>0</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>1</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>2</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>3</sub></strong> &gt;= 0,</p>
<p><strong>x<sub>4</sub></strong> &gt;= 0,</p>
<p>Así entonces, tenemos el problema completamente modelado.</p>
<hr />
<h2>Resolución del modelo mediante Google OR-Tools</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
		<div id="paso-1-importar-la-libreria" data-title="Paso 1: Importar la librería" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código importa las librerías necesarias:</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.linear_solver import pywraplp
</code></pre>
</div>
<h3><em>Paso 2: Declarar el solucionador
		<div id="paso-2-declarar-el-solucionador" data-title="Paso 2: Declarar el solucionador" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código declara el solucionador SCIP <em>(Solving Constraint Integer Programs), un solucionador de código abierto disponible que permite resolver problemas lineales mixtos (Google OR-Tools posee múltiples solucionadores):</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">solver = pywraplp.Solver.CreateSolver('SCIP')</span></code></pre>
</div>
<h3><em>Paso 3: Crear las variables del modelo
		<div id="paso-4-crear-las-variables-del-modelo" data-title="Paso 4: Crear las variables del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código permite crear las variables del modelo. La sintaxis permite declarar la naturaleza de cada una de las variables y el rango de valores permitidos (restricciones de no-negatividad).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    x0 = solver.IntVar(0, solver.infinity(), 'x0')
    x1 = solver.IntVar(0, solver.infinity(), 'x1')
    x2 = solver.IntVar(0, solver.infinity(), 'x2')
    x3 = solver.NumVar(0, solver.infinity(), 'x3')
    x4 = solver.NumVar(0, solver.infinity(), 'x4')
</span></code></pre>
</div>

		<div class="checklist tie-list-shortcode">
<ul>
<li>solver.IntVar = Variables enteras</li>
<li>solver.NumVar = Variables continuas</li>
</ul>

		</div>
	
<p>A partir de la declaración de estas variables, el modelo corresponde a un problema de programación lineal mixta.</p>
<h3><em>Paso 4: Definir las restricciones del modelo
		<div id="paso-5-definir-las-restricciones-del-modelo" data-title="Paso 5: Definir las restricciones del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código define las restricciones del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    # Restricción 0: 0.6x0 + 2x1 + x2 + x3 = 24. (horas metales)
    solver.Add(0.6 * x0 + 2 * x1 + x2 + x3 == 24.0)

    # Restricción 1: x0 + 2x1 + 4x2 + x4 = 60. (horas madera)
    solver.Add(x0 + 2 * x1 + 4 *x2 + x4 == 60.0)

    # Restricción 3: x1 &gt;= 4. (demanda mínima)
    solver.Add(x1 &gt;= 4.0)
</span></code></pre>
</div>
<h3><em>Paso 5: Definir la función objetivo del modelo
		<div id="paso-5-definir-la-funcion-objetivo-del-modelo" data-title="Paso 5: Definir la función objetivo del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código define la función objetivo del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    # Función objetivo (max): 6x0 + 14x1 + 13x2
    solver.Maximize(6 * x0 + 14 * x1 + 13 * x2)
</span></code></pre>
</div>
<h3><em>Paso 6: Invocar el solucionador
		<div id="paso-6-invocar-el-solucionador" data-title="Paso 6: Invocar el solucionador " class="index-title"></div>
	</em></h3>
<p>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">status = solver.Solve()
</span></code></pre>
</div>
<h3><em>Paso 7: Definir las salidas del solucionador
		<div id="paso-7-definir-las-salidas-del-solucionador" data-title="Paso 7: Definir las salidas del solucionador" class="index-title"></div>
	</em></h3>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">    if status == pywraplp.Solver.OPTIMAL:
        print('Solución:')
        print('Valor objetivo =', solver.Objective().Value())
        print('Número de remolques de plataforma plana producidos por mes =', x0.solution_value())
        print('Número de remolques económicos producidos por mes =', x1.solution_value())
        print('Número de remolques de lujo producidos por mes =', x2.solution_value())
        print('Número de horas ociosas en el trabajo en metal al mes =', x3.solution_value())
        print('Número de horas ociosas en el trabajo en madera al mes =', x4.solution_value())
    else:
      if status == solver.FEASIBLE:
        print('Se encontró una solución potencialmente subóptima.')
      else:
        print('El problema no tiene solución óptima.')

    # Información avanzada del solucionador

    print('\nUso avanzado:')
    print('Problema resuelto en %f milisegundos' % solver.wall_time())
    print('Problema resuelto en %d iteraciones' % solver.iterations())
</span></code></pre>
</div>
<p>&nbsp;</p>
<hr />
<p>Es posible que el desarrollo de los siete 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 optimización lineal.</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/1QEYOChKrnjJWk3l9DiaQVzote7jMl_VK?usp=sharing"><strong>Programación Lineal Mixta</strong></a>.</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">#Adaptado de: Bradley, Hax, and Magnanti, 'Applied Mathematical Programming', Chapter 2
#Nuevo caso y modelo: Salazar, ingenieriaindustrialonline.com (Programación lineal mixta)

# Importar la librería de Google OR-Tools
from ortools.linear_solver import pywraplp

def LinearProgrammingExample():
    solver = pywraplp.Solver.CreateSolver('SCIP')

    x0 = solver.IntVar(0, solver.infinity(), 'x0')
    x1 = solver.IntVar(0, solver.infinity(), 'x1')
    x2 = solver.IntVar(0, solver.infinity(), 'x2')
    x3 = solver.NumVar(0, solver.infinity(), 'x3')
    x4 = solver.NumVar(0, solver.infinity(), 'x4')

    print('Número de variables =', solver.NumVariables())

    # Restricción 0: 0.5x0 + 2x1 + x2 + x3 = 24.
    solver.Add(0.6 * x0 + 2 * x1 + x2 + x3 == 24.0)

    # Restricción 1: x0 + 2x1 + 4x2 + x4 = 60.
    solver.Add(x0 + 2 * x1 + 4 *x2 + x4 == 60.0)

    # Restricción 3: x1 &gt;= 4.
    solver.Add(x1 &gt;= 4.0)

    print('Número de restricciones =', solver.NumConstraints())

    # Función objetivo (max): 6x0 + 14x1 + 13x2
    solver.Maximize(6 * x0 + 14 * x1 + 13 * x2)

    # Declarar el solucionador.
    status = solver.Solve()

    # Declarar las salidas del solucionador
    if status == pywraplp.Solver.OPTIMAL:
        print('Solución:')
        print('Valor objetivo =', solver.Objective().Value())
        print('Número de remolques de plataforma plana producidos por mes =', x0.solution_value())
        print('Número de remolques económicos producidos por mes =', x1.solution_value())
        print('Número de remolques de lujo producidos por mes =', x2.solution_value())
        print('Número de horas ociosas en el trabajo en metal al mes =', x3.solution_value())
        print('Número de horas ociosas en el trabajo en madera al mes =', x4.solution_value())
    else:
      if status == solver.FEASIBLE:
        print('Se encontró una solución potencialmente subóptima.')
      else:
        print('El problema no tiene solución óptima.')

    # Información avanzada del solucionador

    print('\nUso avanzado:')
    print('Problema resuelto en %f milisegundos' % solver.wall_time())
    print('Problema resuelto en %d iteraciones' % solver.iterations())

LinearProgrammingExample()</span></code></pre>
</div>
<p>Al ejecutar nuestro desarrollo en <em><a href="https://colab.research.google.com/drive/1QEYOChKrnjJWk3l9DiaQVzote7jMl_VK?usp=sharing">Colaboratory</a>, </em>tenemos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/pl_mixta_solucion.png" alt="pl_mixta_solucion" width="661" height="258" class="aligncenter size-full wp-image-26352" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/pl_mixta_solucion.png 661w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/pl_mixta_solucion-300x117.png 300w" sizes="(max-width: 661px) 100vw, 661px" /></p>
<p>Hemos modificado la naturaleza de las variables con el objetivo de mostrar los resultados a partir de tres escenarios: variables mixtas, enteras y continuas:</p>
<table width="506">
<tbody>
<tr>
<td width="152" style="text-align: center;"></td>
<td width="109" style="text-align: center;">Variables mixtas</td>
<td width="116" style="text-align: center;">Variables enteras</td>
<td width="129" style="text-align: center;">Variables continuas</td>
</tr>
<tr>
<td style="text-align: center;">Valor objetivo</td>
<td style="text-align: center;">247</td>
<td style="text-align: center;">246</td>
<td style="text-align: center;">248,57</td>
</tr>
<tr>
<td style="text-align: center;">Plataforma plana</td>
<td style="text-align: center;">8</td>
<td style="text-align: center;">10</td>
<td style="text-align: center;">8,57</td>
</tr>
<tr>
<td style="text-align: center;">Económicos</td>
<td style="text-align: center;">4</td>
<td style="text-align: center;">4</td>
<td style="text-align: center;">4</td>
</tr>
<tr>
<td style="text-align: center;">Lujosos</td>
<td style="text-align: center;">11</td>
<td style="text-align: center;">10</td>
<td style="text-align: center;">10,86</td>
</tr>
<tr>
<td style="text-align: center;">Horas ociosas (metal)</td>
<td style="text-align: center;">0,19</td>
<td style="text-align: center;">0,00</td>
<td style="text-align: center;">0,00</td>
</tr>
<tr>
<td style="text-align: center;">Horas ociosas (madera)</td>
<td style="text-align: center;">0,00</td>
<td style="text-align: center;">2,00</td>
<td style="text-align: center;">0,00</td>
</tr>
</tbody>
</table>
<p>El anterior, es un problema lineal que representa un caso sencillo, sin embargo, los resultados obtenidos presentan variaciones menores de acuerdo a la naturaleza de las variables consideradas.</p>
<p>Sin embargo, en modelos robustos, estas variaciones pueden ser considerables y determinantes. Por tal razón, la programación lineal mixta ofrece la posibilidad de modelar variables cuya naturaleza refleje con precisión la realidad que se pretende representar.</p>
<hr />
<p>Ahora bien, el modelo de optimización lineal mixta 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>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/">Programación lineal mixta 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/programacion-lineal-mixta-con-google-or-tools/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Programación lineal entera con Google OR-Tools</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Fri, 23 Apr 2021 22:48:25 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Optimización lineal]]></category>
		<category><![CDATA[PLE]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Programación lineal entera]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=25536</guid>

					<description><![CDATA[<p>Básicamente, la diferencia entre programación lineal (PL) y programación lineal entera (PLE) consiste en la naturaleza de sus variables; en el caso de la optimización lineal simple, el uso de variables de naturaleza continua permite el uso de valores fraccionarios en sus variables de decisión; lo cual, de acuerdo al modelo, puede ajustarse a la &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/">Programación lineal entera con Google OR-Tools</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Básicamente, la diferencia entre <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal/"><strong>programación lineal (PL)</strong></a> y programación lineal entera (PLE) consiste en la naturaleza de sus variables; en el caso de la optimización lineal simple, el uso de variables de naturaleza continua permite el uso de valores fraccionarios en sus variables de decisión; lo cual, de acuerdo al modelo, puede ajustarse a la realidad, o no. Por ejemplo, pensemos en la producción de maíz, es posible procesar fácilmente 1,7 toneladas del grano; en cuyo caso, las variables continuas ajustarán el modelo a la realidad.</p>
<p>Ahora bien, existen innumerables casos de aplicación práctica en los cuales las soluciones fraccionarias no se ajustan a la realidad y debemos considerar el uso de variables enteras, así entonces, tendremos un modelo de programación lineal entera (PLE). Por ejemplo, pensemos en la producción de lápices, es posible que queramos determinar la producción en términos de unidades de producto, no tanto así de fracciones.</p>
<p>Es preciso mencionar que, cuando un modelo presenta todas sus variables enteras, se denomina <em>puro. </em>En caso contrario, cuando utiliza una combinación de variables enteras y continuas, se denomina <em>mixto, </em>y se abordará mediante <em><strong><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/">programación lineal mixta</a></strong>.</em></p>
<hr />
<p>Ciertamente, en la práctica, los solucionadores de modelos de optimización han abordado la naturaleza de las variables brindando relativa facilidad; es decir, podemos cambiar el tipo de variable entre continua y entera de una manera muy sencilla, sin que esto afecte considerablemente el modelo.</p>
<p><span>El objetivo de este artículo consiste en utilizar las librerías del software Google OR-Tools para abordar problemas de programación lineal entera (optimización lineal entera). </span></p>
<p>OR-Tools proporciona dos herramientas principales para resolver este tipo de problemas de optimización:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>MPSolver: un contenedor para varios solucionadores de MIP de terceros, que utilizan técnicas estándar de ramificación y vinculación <em>(branch and bound).</em></li>
<li>Solucionador de CP-SAT: Un solucionador de programación de restricciones que utiliza métodos SAT (satisfacibilidad).</li>
</ul>

		</div>
	
<hr />
<h2>El problema</h2>
<p>Con el propósito de evaluar los resultados obtenidos a través del tratamiento de un problema técnicamente formulado y abordado, utilizaremos un caso descrito en el libro Investigación de Operaciones (9na edición), de Hamdy A. Taha (University of Arkansas, Fayetteville), (<em>Conjunto de problemas 9.1A &#8211; Problema 3</em>).</p>
<blockquote class=" quote-simple "><p>Suponga que tiene 7 botellas de vino llenas, 7 a la mitad y 7 vacías. Le gustaría dividir las 21 botellas entre tres individuos de modo que cada uno reciba exactamente 7. Además, cada individuo debe recibir la misma cantidad de vino. Exprese el problema como restricciones del PLE, y halle una solución. <cite>TAHA</cite></p></blockquote>
<table width="439">
<tbody>
<tr>
<td width="172" style="text-align: center;"></td>
<td width="89" style="text-align: center;">Botellas llenas</td>
<td width="89" style="text-align: center;">Botellas a la mitad</td>
<td width="89" style="text-align: center;">Botellas vacías</td>
</tr>
<tr>
<td width="172" style="text-align: center;">Contenido</td>
<td width="89" style="text-align: center;">1</td>
<td width="89" style="text-align: center;">0,5</td>
<td width="89" style="text-align: center;">0</td>
</tr>
<tr>
<td width="172" style="text-align: center;">Cantidad de botellas</td>
<td width="89" style="text-align: center;">7</td>
<td width="89" style="text-align: center;">7</td>
<td width="89" style="text-align: center;">7</td>
</tr>
</tbody>
</table>
<h2>Modelamiento del problema</h2>
<p>El problema plantea un caso de asignación, en el cual debemos determinar la cantidad de botellas de cada tipo (llenas, medias y vacías), asignadas a cada uno de un conjunto de 3 individuos (1, 2 y 3). Por lo tanto, las variables de decisión se definirán de la siguiente manera:</p>
<p><strong>x<em><sub>ij</sub></em></strong><span> = Cantidad de botellas de tipo <em>i </em>asignadas al individuo <em>j</em></span></p>
<p><strong><em>i </em></strong>= {0 = Llena; 1 = Media; 2 = Vacía}</p>
<p><em><strong>j</strong> = </em>{0 = Individuo 1; 1 = individuo 2; 2 = individuo 3}</p>
<p>Donde todos los <strong>x<em><sub>ij</sub></em> son enteros no negativos</strong>. (Ya que queremos determinar cantidad de botellas, es decir que las variables de decisión no están formuladas en función del contenido).</p>
<p>Este modelo puede abordar las restricciones de volumen de líquido (contenido igual para todos los individuos), por medio de coeficientes de contenido, o, definiendo que el contenido corresponde a una variable, la cual debe declararse de igual forma. Ya que el contenido que se encuentra en una botella llena, por ejemplo, es el mismo sea asignado a cualquier individuo, la manera más simple de abordarlo es por medio de coeficientes.</p>
<p>En la formulación del modelo de <em>Python</em> lo abordaremos por medio de variables con fines prácticos.</p>
<p><em>La función objetivo es artificial, dado que el modelo no pretende maximizar o minimizar algún factor. Por lo tanto podemos utilizar cualquier coeficiente, por ejemplo cero.</em></p>
<p><span>Zmax = 0</span><strong>x<em><sub>00</sub></em> + </strong>0<strong>x<em><sub>01</sub></em>  +  </strong>0<strong>x<em><sub>02</sub></em>  + </strong>0<strong>x<em><sub>10</sub></em> + </strong>0<strong>x<em><sub>11</sub></em>  +  </strong>0<strong>x<em><sub>12</sub></em>  + </strong>0<strong>x<em><sub>20</sub></em></strong><span><strong> + </strong>0<strong>x<em><sub>21</sub></em>  +  </strong>0<strong>x<em><sub>22</sub></em></strong>  </span></p>
<p><em>Sujeto a las siguientes restricciones:</em></p>
<p><span>x</span><em><sub>00</sub></em><span> + x<em><sub>01</sub></em>  +  x<em><sub>02</sub></em>  = 7</span></p>
<p><span>x</span><em><sub>10</sub></em><span> + x<em><sub>11</sub></em>  +  x<em><sub>12</sub></em>  = 7</span></p>
<p><span>x</span><em><sub>20</sub></em><span> + x<em><sub>21</sub></em>  +  x<em><sub>22</sub></em>  = 7</span></p>
<p>Las anteriores restricciones limitan la disponibilidad de botellas de cada tipo. Es decir, por ejemplo, la sumatoria de botellas llenas enviadas a los individuos 1, 2 y 3 deberá ser igual a 7; así mismo para el restante tipo de botellas.</p>
<p><span>x</span><em><sub>00</sub></em><span> + x<em><sub>10</sub></em>  +  x<em><sub>20</sub></em>  = 7</span></p>
<p><span>x</span><em><sub>01</sub></em><span> + x<em><sub>11</sub></em>  +  x<em><sub>21</sub></em>  = 7</span></p>
<p><span>x</span><em><sub>02</sub></em><span> + x<em><sub>12</sub></em>  +  x<em><sub>22</sub></em>  = 7</span></p>
<p>Las anteriores restricciones indican que cada individuo deberá recibir exactamente 7 botellas sin importar el tipo de las mismas. Es decir, por ejemplo, la sumatoria de botellas tipo 0, 1 y 2 enviadas al individuo 0, deberá ser igual a 7; así mismo para los individuos restantes.</p>
<p>Respecto a las restricciones de contenido, que limitarán el modelo para que cada individuo reciba la misma cantidad de vino, podemos realizar una operación previa, en la cual determinemos la cantidad total de vino disponible.</p>
<p><em>Vino disponible = 1( 7 botellas llenas) + 0,5( 7 botellas medias)</em></p>
<p><em>Vino disponible = 10,5 </em></p>
<p>Así entonces, para que a cada individuo le corresponda la misma cantidad de vino en la distribución de botellas, deberá dividirse el vino disponible entre el total de individuos.</p>
<p><em>Vino para cada individuo = (10,5 / 3) = 3,5</em></p>
<p>Así entonces, las restricciones de equidad en la distribución (contenido) serán las siguientes:</p>
<p><strong>x<em><sub>00</sub></em></strong><span> + 0,5<strong>x<em><sub>10</sub></em></strong>   = 3,5</span></p>
<p><strong>x<em><sub>01</sub></em></strong><span> + 0,5<strong>x<em><sub>11</sub></em></strong>  = 3,5</span></p>
<p><strong>x<em><sub>02</sub></em></strong><span> + 0,5<strong>x<em><sub>12</sub></em></strong>  = 3,5</span></p>
<p><span>Así entonces, tenemos el problema completamente modelado.</span></p>
<hr />
<h2>Resolución del modelo mediante Google OR-Tools</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
		<div id="paso-1-importar-la-libreria" data-title="Paso 1: Importar la librería" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código importa las librerías necesarias:</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.linear_solver import pywraplp
</code></pre>
</div>
<h3><em>Paso 2: Declarar el solucionador
		<div id="paso-2-declarar-el-solucionador" data-title="Paso 2: Declarar el solucionador" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código declara el solucionador SCIP <em>(Solving Constraint Integer Programs), un solucionador de código abierto disponible (Google OR-Tools posee múltiples solucionadores):</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">solver = pywraplp.Solver.CreateSolver('SCIP')</span></code></pre>
</div>
<h3><em>Paso 3: Crear la data del modelo
		<div id="paso-3-crear-la-data-del-modelo" data-title="Paso 3: Crear la data del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código crea la data del modelo. En este caso, la matriz de contenido de las botellas distribuidas a los 3 individuos (matriz 3 x 3).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Crear la data del modelo de asignación
<span class="pln">contenido = [
    [  1,   1,   1],
    [0.5, 0.5, 0.5],
    [  0,   0,   0],
]
num_botellas = len(contenido)
num_individuos = len(contenido[0])</span></code></pre>
</div>
<h3><em>Paso 4: Crear las variables del modelo
		<div id="paso-4-crear-las-variables-del-modelo" data-title="Paso 4: Crear las variables del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento crea las variables del modelo mediante un bucle (Hace uso número de botellas y el número de individuos), definiendo las variables x [i, j]. Así mismo, se declara el rango de valores que pueden tomar las variables, del mismo modo su naturaleza: <em>variables enteras </em>(solver.IntVar). Esta declaración sustituye las restricciones de no-negatividad (0, solver.infinity()), es decir, mayores a cero.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln">  x = {}
  for i in range(num_botellas):
      for j in range(num_individuos):
          x[i, j] = solver.IntVar(0, solver.infinity(), '')
</span></code></pre>
</div>
<h3><em>Paso 5: Definir las restricciones del modelo
		<div id="paso-5-definir-las-restricciones-del-modelo" data-title="Paso 5: Definir las restricciones del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código define las restricciones del modelo mediante bucles:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"># Restricciones de disponibilidad de botellas de cada tipo = 7
# Para cada i (tipo de botella), suma sus posibles variaciones de j (individuos)
# Por ejemplo, siendo i = 0, sumará X00 + X01 + X02, esta sumatoria
# deberá ser igual a 7 (botellas disponibles de cada tipo)
  for i in range(num_botellas):
      solver.Add(solver.Sum([x[i, j] for j in range(num_individuos)]) == 7)
# Restricciones de cantidad de botellas asignadas a cada individuo
# Para cada j (individuo), suma sus posibles variaciones de i (tipos de botella)
# Por ejemplo, siendo j = 0, sumará X00 + X10 + X20, esta sumatoria
# deberá ser igual a 7 (botellas asignadas a un individuo sin importar el tipo)
  for j in range(num_individuos):
      solver.Add(solver.Sum([x[i, j] for i in range(num_botellas)]) == 7)
# Restricciones de equidad en la distribución (utiliza la matriz de contenido)
# Para cada j (individuo), suma los productos de las posibles
# variaciones de i (tipo de botella) por sus coeficientes de contenido
# Por ejemplo, siendo j = 0 y C[i][j] equivalente al contenido
# de una botella tipo i entregado a un individuo tipo j
# sumará (C00 * X00) + (C10 * X10) + (C20 * X20), los valores de C[i][j]
# los tomará de la matriz de contenido. Esta sumatoria
# deberá ser igual a 3,5 (distribución equitativa por individuo)
  for j in range(num_individuos):
      solver.Add(solver.Sum([contenido[i][j] * x[i, j] for i in range(num_botellas)]) == 3.5)
</span></code></pre>
</div>
<h3><em>Paso 6: Definir la función objetivo del modelo
		<div id="paso-5-definir-la-funcion-objetivo-del-modelo" data-title="Paso 5: Definir la función objetivo del modelo" class="index-title"></div>
	</em></h3>
<p>El siguiente fragmento de código define la función objetivo del modelo. Consideremos que no es importante el criterio de la función, tampoco existe un costo asociado a las variables de decisión. Sin embargo, formularemos una función objetivo basada en bucles (utilizando la matriz de contenido), la cual quizá puede ser de utilidad en modelamientos futuros. Los coeficientes de las variables de decisión se basarán en la matriz de contenido (contenido[i][j]).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"># Función objetivo
  objective_terms = []
  for i in range(num_botellas):
      for j in range(num_individuos):
          objective_terms.append(contenido[i][j] * x[i, j])
  solver.Maximize(solver.Sum(objective_terms))
</span></code></pre>
</div>
<h3><em>Paso 7: Invocar el solucionador
		<div id="paso-6-invocar-el-solucionador" data-title="Paso 6: Invocar el solucionador " class="index-title"></div>
	</em></h3>
<p>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">status = solver.Solve()
</span></code></pre>
</div>
<h3><em>Paso 8: Definir las salidas del solucionador
		<div id="paso-7-definir-las-salidas-del-solucionador" data-title="Paso 7: Definir las salidas del solucionador" class="index-title"></div>
	</em></h3>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"># Configura los parámetros de impresión, las salidas del modelo
botellas_ind0 = 0
botellas_ind1 = 0
botellas_ind2 = 0  
if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
      print('Puntaje total = ', solver.Objective().Value(), '\n')
      for i in range(num_botellas):
          for j in range(num_individuos):
              # Test if x[i,j] is 1 (con tolerancia de punto flotante)
              if x[i, j].solution_value() &gt; 0.5:
                print('Botellas del tipo %d asignadas al individuo %d.  Cantidad = %d' %
                          (i, j, x[i, j].solution_value()))
      for i in range(num_botellas):
          botellas_ind0 = x[i, 0].solution_value() + botellas_ind0
      for i in range(num_botellas):
          botellas_ind1 = x[i, 1].solution_value() + botellas_ind1
      for i in range(num_botellas):
          botellas_ind2 = x[i, 2].solution_value() + botellas_ind2
  print('Número de botellas asignadas al individuo 0:', botellas_ind0)
  print('Número de botellas asignadas al individuo 1:', botellas_ind1)
  print('Número de botellas asignadas al individuo 2:', botellas_ind2)
</span></code></pre>
</div>
<p>En este caso, configuramos las salidas del solucionador. Nos deberá indicar la cantidad de botellas de cada tipo que deberán ser distribuidas a cada individuo. El valor de la función objetivo (contenido total), nos servirá para verificar la solución (10,5).</p>
<p>Como información adicional, hemos configurado algunas impresiones (arbitrarias) para que nos detalle la cantidad de botellas que le deberán ser asignadas a cada individuo.</p>
<hr />
<p>Es posible que el desarrollo de los ocho 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 optimización lineal.</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/1USpjLdh8Iqd5uPmOy7CzpqWPYWPAwq8U?usp=sharing"><strong>Programación Lineal Entera</strong></a>.</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code><span class="pln"># Caso: Investigación de Operaciones (9na edición), de Hamdy A. Taha 
# (University of Arkansas, Fayetteville), (Conjunto de problemas 9.1A - Problema 3)
# Modelo: MSc. Ing. Bryan Salazar López

# Importar la librería de Google OR-Tools
from ortools.linear_solver import pywraplp

# Declarar el solucionador que abordará el modelo
solver = pywraplp.Solver.CreateSolver('SCIP')

# Data del modelo

contenido = [ 
    [  1,   1,   1], 
    [0.5, 0.5, 0.5],
    [  0,   0,   0],      
] 
num_botellas = len(contenido)
num_individuos = len(contenido[0])


def main():

# Variables del modelo
  x = {}
  for i in range(num_botellas):
      for j in range(num_individuos):
          x[i, j] = solver.IntVar(0, solver.infinity(), '')

  # Las sumatoria de botellas de cada tipo es igual a 7
  # Cada curso podrá tener un máximo de n estudiantes
  for i in range(num_botellas):
      solver.Add(solver.Sum([x[i, j] for j in range(num_individuos)]) == 7)
  for j in range(num_individuos):
      solver.Add(solver.Sum([x[i, j] for i in range(num_botellas)]) == 7)
  for j in range(num_individuos):
      solver.Add(solver.Sum([contenido[i][j] * x[i, j] for i in range(num_botellas)]) == 3.5)



  # Función objetivo con criterio de optimización: minimizar
  objective_terms = []
  for i in range(num_botellas):
      for j in range(num_individuos):
          objective_terms.append(contenido[i][j] * x[i, j])
  solver.Maximize(solver.Sum(objective_terms))

  # Invoca el solucionador
  status = solver.Solve()

  # Configura los parámetros de impresión, las salidas del modelo
  botellas_ind0 = 0
  botellas_ind1 = 0
  botellas_ind2 = 0
  if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
      print('Contenido total = ', solver.Objective().Value(), '\n')
      for i in range(num_botellas):
          for j in range(num_individuos):
              # Test if x[i,j] is 1 (con tolerancia de punto flotante)
              if x[i, j].solution_value() &gt; 0.5:
                print('Botellas del tipo %d asignadas al individuo %d.  Cantidad = %d' %
                          (i, j, x[i, j].solution_value()))
      for i in range(num_botellas):
          botellas_ind0 = x[i, 0].solution_value() + botellas_ind0
      for i in range(num_botellas):
          botellas_ind1 = x[i, 1].solution_value() + botellas_ind1
      for i in range(num_botellas):
          botellas_ind2 = x[i, 2].solution_value() + botellas_ind2
  print('Número de botellas asignadas al individuo 0:', botellas_ind0)
  print('Número de botellas asignadas al individuo 1:', botellas_ind1)
  print('Número de botellas asignadas al individuo 2:', botellas_ind2)



if __name__ == '__main__':
  main()</span></code></pre>
</div>
<p>Al ejecutar nuestro desarrollo en <em><a href="https://colab.research.google.com/drive/1USpjLdh8Iqd5uPmOy7CzpqWPYWPAwq8U?usp=sharing">Colaboratory</a>, </em>tenemos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/plentera_solucion.png" alt="plentera_solucion" width="671" height="258" class="aligncenter size-full wp-image-26349" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/plentera_solucion.png 671w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/04/plentera_solucion-300x115.png 300w" sizes="(max-width: 671px) 100vw, 671px" /></p>
<p>De esta manera se ha hallado una solución óptima al modelo formulado. Esta misma respuesta se encuentra consignada en el libro <em>Investigación de Operaciones de TAHA:</em></p>
<table width="439">
<tbody>
<tr>
<td width="172"></td>
<td width="89" style="text-align: center;">Individuo 1</td>
<td width="89" style="text-align: center;">Individuo 2</td>
<td width="89" style="text-align: center;">Individuo 3</td>
</tr>
<tr>
<td width="172" style="text-align: center;">Botellas llenas</td>
<td width="89" style="text-align: center;">3</td>
<td width="89" style="text-align: center;">1</td>
<td width="89" style="text-align: center;">3</td>
</tr>
<tr>
<td width="172" style="text-align: center;">Botellas medio llenas</td>
<td width="89" style="text-align: center;">1</td>
<td width="89" style="text-align: center;">5</td>
<td width="89" style="text-align: center;">1</td>
</tr>
<tr>
<td width="172" style="text-align: center;">Botellas vacías</td>
<td width="89" style="text-align: center;">3</td>
<td width="89" style="text-align: center;">1</td>
<td width="89" style="text-align: center;">3</td>
</tr>
</tbody>
</table>
<hr />
<p>Ahora bien, el modelo de optimización lineal 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 algunos modelos que incorporen la combinación de tipos de variables (continuas y enteras): <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/"><strong><em>programación lineal mixta</em></strong></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-entera-con-google-or-tools/">Programación lineal entera 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/programacion-lineal-entera-con-google-or-tools/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>¿Qué es la Investigación de Operaciones?</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Thu, 06 Jun 2019 20:03:37 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Herramientas]]></category>
		<category><![CDATA[Investigación de Operaciones]]></category>
		<category><![CDATA[Optimización]]></category>
		<guid isPermaLink="false">http://contentlab.co/ingenieria/?p=1093</guid>

					<description><![CDATA[<p>El propósito de  la Investigación de Operaciones consiste en preparar al profesional para decidir entre diferentes medios o métodos disponibles para alcanzar un objetivo propuesto, de modo que se alcance un resultado en relación a determinados criterios de optimización. La relación entre Investigación de Operaciones e Ingeniería Industrial se basa en el objetivo común de alcanzar la &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/">¿Qué es la Investigación de Operaciones?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="que-es-la-investigacion-de-operaciones" data-title="¿Qué es la Investigación de operaciones?" class="index-title"></div>
	El propósito de  la <em><strong>Investigación de Operaciones</strong></em> consiste en preparar al profesional para decidir entre diferentes medios o métodos disponibles para alcanzar un objetivo propuesto, de modo que se alcance un resultado en relación a determinados criterios de optimización.</p>
<p>La relación entre Investigación de Operaciones e Ingeniería Industrial se basa en el objetivo común de alcanzar la productividad en una organización o sistema (La productividad entendida como el grado en el que una organización se acerca hacia su objetivo. La visión de Goldratt de la productividad). El Ingeniero Industrial o el encargado de la gestión de operaciones busca gestionar las restricciones y alcanzar el objetivo general de la organización mediante la toma de decisiones (Las restricciones entendidas como todo aquello que se interpone entre la organización y su objetivo). La Investigación de Operaciones proporciona herramientas y métodos cuantitativos para apoyar estas decisiones.</p>
<p>Si bien, la intuición, la creatividad y la experiencia forman parte de las consideraciones en un proceso de toma de decisiones, algunas decisiones merecen un estudio más profundo, en razón de sus consecuencias y de la complejidad del contexto, haciéndose imprescindible un sustento metodológico para la toma de decisiones, el cual puede hallarse en los procedimientos propios de la investigación de operaciones. También es posible que dichas formas de razonamiento producto de una base cuantitativa, sirvan para reforzar una buena teoría, y como driría Javier Arévalo, pensador sistémico experto en gestión de operaciones: <em>¡Nada es más práctico que una buena teoría!</em></p>

		<div class="box warning  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span> En lugar de decir que el objetivo de la Investigación de Operaciones es evitar decisiones intuitivas del Ingeniero, se podría afirmar que su objetivo es proporcionar al Ingeniero un conjunto de herramientas que lo ayuden a tomar decisiones más informadas y fundamentadas en principios sólidos (complementar su enfoque). La Investigación de Operaciones busca fortalecer la práctica del Ingeniero mediante la aplicación de métodos y técnicas cuantitativas que refuercen su base teórica. 
			</div>
		</div>
	

		<div class="box info  alignleft">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Podría verse que uno de los primeros ejemplos históricos del uso de la investigación operacional es la misión confiada a Arquímedes por Hierón, tirano de Siracusa, de aplicar los mejores medios y métodos para defender a la ciudad contra los ataques y el sitio de los romanos.
			</div>
		</div>
	
<h2>Un poco de historia&#8230;</h2>
<p>La investigación operacional sólo se ha beneficiado de una aplicación sistemática hasta la Segunda Guerra Mundial, principalmente en la conducción de las grandes operaciones militares. La investigación operacional utiliza, en gran medida, a los ordenadores; la invención y comercialización de estas máquinas fueron la condición primordial de su desarrollo en el dominio civil y especialmente en la economía de empresa. Por una feliz coincidencia, sólo en nuestra época <strong>los problemas de gestión de las grandes empresas se han convertido en irremediablemente complejos</strong>. Si bien es indispensable, para el técnico en investigación de operacional, el estudiar los problemas generales que se presentan y los algoritmos clásicos que permiten resolverlos, debe estar también totalmente persuadido de que las situaciones prácticas que encontrará serán mucho más complicadas y que deberá emprender una tarea original para dar satisfacción al encargado de tomar decisiones ofreciéndole la posibilidad de optimizar según su propio criterio.</p>
<blockquote class="aligncenter quote-simple "><p>“Un elemento principal de la investigación de operaciones es el modelado matemático. Aunque la solución del modelo matemático establece una base para tomar una decisión, se deben tener en cuenta factores intangibles o no cuantificables, por ejemplo el comportamiento humano, para poder llegar a una decisión final” <cite>TAHA, Hamdy</cite></p></blockquote>
<p>&nbsp;</p>
<p>Es necesario, pues, en función de las motivaciones del responsable de la decisión que plantea un problema, identificar los fenómenos a estudiar mediante un análisis profundo de la situación. Este análisis se funda sobre la observación de la situación real, mediante conversaciones con las personas que participan en ella directamente y mediante acopio de datos estadísticos o provisionales (resultantes de encuestas, de medidas o de estudios técnicos). El profesional debe entender por sobre todas las cosas que su objeto de estudio es <a href="https://ingenieriaindustrialonline.com/teoria-de-restricciones-toc/las-empresas-como-un-sistema-un-todo-indivisible/"><strong>una organización, una empresa, un todo indivisible que funciona como un sistema</strong></a>.</p>
<p>La <strong>investigación de operaciones </strong>puede definirse como un método científico de resolución de problemas (Es la definición de Ackoff), la cual brinda las herramientas suficientes para que con base en abstracciones de la realidad se puedan generar y resolver modelos matemáticos con el objetivo de elaborar un análisis y concluir de los mismos para así poder sustentar cuantitativamente las decisiones que se tomen respecto a la situación problema.</p>
<p><img decoding="async" class="size-full wp-image-1096 aligncenter" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC.png" alt="Método científico" width="530" height="480" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC.png 530w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-300x272.png 300w" sizes="(max-width: 530px) 100vw, 530px" /></p>
<p>Otra de las muchas definiciones que de la investigación de operaciones se encuentran es la siguiente:</p>
<blockquote class=" quote-simple "><p>«La Investigación de Operaciones es la aplicación, por grupos interdisciplinarios, del método científico a problemas relacionados con el control de las organizaciones o sistemas a fin de que se produzcan soluciones que mejor sirvan a los objetivos de toda organización.» <cite>Ackoff, R. L. y Sasieni M. W.</cite></p></blockquote>
<p>&nbsp;</p>
<h2>
		<div id="como-abordar-un-problema-real-de-optimizacion" data-title="¿Cómo abordar un problema real de optimización?" class="index-title"></div>
	¿Cómo abordar un problema real de optimización?</h2>
<p><em><strong>La optimización puede considerarse como la búsqueda de la mejor solución</strong></em> (solución óptima) de un problema. El mejor término depende del contexto en el que se trabaje. Por ejemplo, en un contexto operativo atinente a las utilidades,la optimización del sistema constituye la maximización de los resultados, todo lo contrario a los costos o las distancias, casos en los cuales la optimización dependerá de la minimización de los resultados.</p>
<p><img decoding="async" class="wp-image-1097 aligncenter" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-1.png" alt="Problema de optimización" width="588" height="291" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-1.png 1010w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-1-300x149.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-1-768x380.png 768w" sizes="(max-width: 588px) 100vw, 588px" /></p>
<h2>
		<div id="modelamiento" data-title="Modelamiento" class="index-title"></div>
	Modelamiento</h2>
<p style="text-align: justify;">Un modelo es una abstracción o una representación de la realidad o un concepto o una idea con el que se pretende aumentar su comprensión, hacer predicciones y/o controlar/analizar un sistema. Cuando el sistema no existe, sirve para definir la estructura ideal de ese sistema futuro indicando las relaciones funcionales entre sus elementos. En la actualidad un modelo se define como un constructo basado en nuestras propias percepciones pasadas y actuales; la anterior representación puede ser <a data-toggle="tooltip" data-placement="top" class="post-tooltip tooltip-top" title="que considera algo como un todo."><em>holista</em></a> o <em>reduccionista</em>.</p>
<p style="text-align: justify;">Los modelos se pueden clasificar según su grado de abstracción en:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Modelos Abstractos (no físicos)</li>
<li>Modelos Concretos (físicos)</li>
</ul>

		</div>
	
<p>Y se pueden clasificar igualmente si son matemáticos en:
		<div class="checklist tie-list-shortcode">
<ul>
<li>Estáticos</li>
<li>Dinámicos</li>
<li>Determinísticos</li>
<li>Estocásticos</li>
</ul>

		</div>
	
<figure id="attachment_1098" aria-describedby="caption-attachment-1098" style="width: 605px" class="wp-caption aligncenter"><img decoding="async" class="wp-image-1098 size-full" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-2.png" alt="Modelos de investigación de operaciones" width="605" height="544" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-2.png 605w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/MC-2-300x270.png 300w" sizes="(max-width: 605px) 100vw, 605px" /><figcaption id="caption-attachment-1098" class="wp-caption-text">Francisco Chediak</figcaption></figure>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span> En la actualidad, no se puede hablar de una clasificación estricta de los algoritmos debido a la variedad en la naturaleza y origen de los datos (por ejemplo, los datos obtenidos en tiempo real), y a las capacidades de los lenguajes de programación. Esto ha permitido la incorporación de elementos probabilísticos en modelos que antes se consideraban determinísticos. 
			</div>
		</div>
	
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-la-investigacion-de-operaciones/">¿Qué es la Investigación de Operaciones?</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-la-investigacion-de-operaciones/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
	</channel>
</rss>
