<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es">
  <title>Marco Pinto-Orellana</title>
  <subtitle>Científico de la computación dedicado al análisis de señales cerebrales, el procesamiento de señales y la estadística.</subtitle>
  <link href="https://pintoorellana.org/es/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="https://pintoorellana.org/es/" rel="alternate" type="text/html"/>
  <id>https://pintoorellana.org/es/atom.xml</id>
  <updated>2026-11-07T00:00:00Z</updated>
  <author><name>Marco Pinto-Orellana</name></author>
  <entry>
    <title>Optimización en Python y PuLP</title>
    <link href="https://pintoorellana.org/es/blog/linear-programming/" rel="alternate" type="text/html"/>
    <id>https://pintoorellana.org/es/blog/linear-programming/</id>
    <published>2026-11-07T00:00:00Z</published>
    <updated>2026-11-07T00:00:00Z</updated>
    <summary>Localización de servicios, el problema de las p-medianas, el vendedor viajero y el ruteo de vehículos, escritos como programas lineales enteros y resueltos en Python.</summary>
    <content type="html" xml:base="https://pintoorellana.org/es/blog/linear-programming/">&lt;p&gt;Este artículo es la versión escrita de un curso corto de Python para optimización y
logística al que fui invitado a dictar en varias ocasiones en la Universidad San Francisco Xavier de Chuquisaca (USFX), en Sucre,
Bolivia. El curso cubre cuatro problemas clásicos en programación lineal: la ubicación de dos hospitales, el
problema de las p-medianas, el problema del vendedor viajero y el ruteo de vehículos.&lt;/p&gt;
&lt;h2 id=&quot;las-herramientas&quot;&gt;Las herramientas&lt;/h2&gt;
&lt;p&gt;Los modelos usan &lt;a href=&quot;https://coin-or.github.io/pulp/&quot;&gt;PuLP&lt;/a&gt;, una librería de Python para
escribir programas lineales, que funciona como un wrapper para &lt;a href=&quot;https://www.gnu.org/software/glpk/&quot;&gt;GLPK&lt;/a&gt;, una excelente herramienta de código abierto para resolver mixed integer linear programming (MILP) problems.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m pip install pulp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PuLP también incluye el optimizador CBC. Si GLPK no está instalado, reemplace
&lt;code&gt;GLPK(options=[&apos;--mipgap&apos;, &apos;0.01&apos;])&lt;/code&gt; en el código de abajo por
&lt;code&gt;PULP_CBC_CMD(gapRel=0.01)&lt;/code&gt;. Ambos se detienen cuando la solución está a menos de un 1 % del punto óptimo.&lt;/p&gt;
&lt;p&gt;Un pequeño archivo auxiliar, &lt;code&gt;utils.py&lt;/code&gt;, crea las variables en gropo y las ejecuta el optimizador (me parece más sencillo leer el código con esos helpers):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pulp import *

def create_variable_matrix(i, j, name=&amp;quot;x_{i}_{j}&amp;quot;, **other_args):
    return [[LpVariable(name.format(i=a, j=b), **other_args) for b in range(j)]
            for a in range(i)]

def create_variable_array(i, name=&amp;quot;y_{i}&amp;quot;, **other_args):
    return [LpVariable(name.format(i=a), **other_args) for a in range(i)]

def euclidian_distance(point1, point2):
    return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) ** 0.5

def create_distance_matrix(points):
    return [[euclidian_distance(a, b) for b in points] for a in points]

def resolve_problem(problem, show_variables=True):
    GLPK(options=[&apos;--mipgap&apos;, &apos;0.01&apos;]).solve(problem)
    if show_variables:
        for v in problem.variables():
            if v.varValue != 0:
                print(v.name, &amp;quot;=&amp;quot;, v.varValue)
    return value(problem.objective)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LpVariable&lt;/code&gt; recibe tres parámetros que son suficientemente relevantes para nosotros: &lt;code&gt;cat&lt;/code&gt;, el tipo de variable
(&lt;code&gt;&apos;Continuous&apos;&lt;/code&gt; o &lt;code&gt;&apos;Integer&apos;&lt;/code&gt;); &lt;code&gt;lowBound&lt;/code&gt;, su valor mínimo (&lt;span class=&quot;math&quot;&gt;\(-\infty\)&lt;/span&gt; si se omite); y
&lt;code&gt;upBound&lt;/code&gt;, su valor máximo (&lt;span class=&quot;math&quot;&gt;\(+\infty\)&lt;/span&gt; si se omite).&lt;/p&gt;
&lt;p&gt;Una variable entera entre 0 y 1 es una decisión de sí o no. Una matriz de &lt;span class=&quot;math&quot;&gt;\(5 \times 10\)&lt;/span&gt; de ellas, y una lista de 17 enteros no negativos se puede describir así:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;f = create_variable_matrix(5, 10, name=&amp;quot;F_{i}_{j}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
l = create_variable_array(17, name=&amp;quot;L_{i}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Un modelo es un objeto de tipo &lt;code&gt;LpProblem&lt;/code&gt;. Tiene el operador &lt;code&gt;+=&lt;/code&gt; overloaded, y permite una interesante sintaxis para añadir restricciones: &lt;code&gt;model += constraint&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;problem = LpProblem(&amp;quot;mi_modelo&amp;quot;, LpMinimize)   # cambiando LpMaximize
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;modelo-a-dos-hospitales&quot;&gt;Modelo A: dos hospitales&lt;/h2&gt;
&lt;p&gt;Siete barrios necesitan dos nuevos hospitales de primer nivel, cada uno construido en uno de los barrios. ¿Dónde deberían ir?&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/two-hospitals.webp&quot; alt=&quot;Tres paneles: una tabla con las coordenadas de los siete barrios, los barrios como puntos en el plano unidos por todas las conexiones posibles, y un mapa de calor de las distancias entre ellos.&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Los siete barrios, todas las conexiones posibles entre ellos y su matriz de distancias.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Una buena respuesta deja a cada barrio cerca del hospital que lo atiende, así que el
objetivo es la suma de las distancias de cada barrio a su hospital. Para eso necesitamos
modelar esas condiciones con dos tipos de decisiones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class=&quot;math&quot;&gt;\(x_{ij} = 1\)&lt;/span&gt; si el barrio &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; es atendido por un hospital en el barrio &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, y &lt;span class=&quot;math&quot;&gt;\(0\)&lt;/span&gt; en otro
caso, para &lt;span class=&quot;math&quot;&gt;\(0 \le i, j \le 6\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;span class=&quot;math&quot;&gt;\(y_j = 1\)&lt;/span&gt; si se construye un hospital en el barrio &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, y &lt;span class=&quot;math&quot;&gt;\(0\)&lt;/span&gt; en otro caso.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;problem = LpProblem(&amp;quot;two_hospital_problem&amp;quot;, LpMinimize)
x = create_variable_matrix(7, 7, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
y = create_variable_array(7, name=&amp;quot;y_{i}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Con &lt;span class=&quot;math&quot;&gt;\(d_{ij}\)&lt;/span&gt; la distancia entre los barrios &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; y &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, la función objetivo es&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\min \sum_{i=0}^{6} \sum_{j=0}^{6} d_{ij}\, x_{ij} = 0.0\,x_{0,0} + 20.0\,x_{0,1} + 20.0\,x_{0,2} + 28.3\,x_{0,3} + \dots + 14.1\,x_{6,5} + 0.0\,x_{6,6}.\]&lt;/div&gt;
&lt;p&gt;Como consecuencia, la función objetivo necesita enlazar todas las &lt;span class=&quot;math&quot;&gt;\(7 * 7\)&lt;/span&gt; combinaciones:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;objective = (0.0 * x[0][0] + 20.0 * x[0][1] + 20.0 * x[0][2] + 28.3 * x[0][3]
             + 14.1 * x[0][4] + 22.4 * x[0][5] + 22.4 * x[0][6]
             + 20.0 * x[1][0] + ...)                 # 49 términos en total
problem += objective
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hay una solución trivial usando solo este objetivo: &lt;span class=&quot;math&quot;&gt;\(x=0\)&lt;/span&gt;. Eso quiere decir que la solución que minimiza la distancia entre los hospitales y los vecindarios es &lt;strong&gt;no construir ningún hospital&lt;/strong&gt;.
Y claramente, esa no es la solución que quisieramos. Así que necesitamos restricciones adicionales para asegurar que una solución sea válida:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cada barrio debe ser atendido por un solo hospital: &lt;span class=&quot;math&quot;&gt;\(\sum_{j} x_{ij} = 1\)&lt;/span&gt; para todo &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt;. Para
el barrio 0, eso es &lt;span class=&quot;math&quot;&gt;\(x_{0,0} + x_{0,1} + \dots + x_{0,6} = 1\)&lt;/span&gt;, y lo mismo vale para los
otros seis.&lt;/li&gt;
&lt;li&gt;Debe haber exactamente dos hospitales: &lt;span class=&quot;math&quot;&gt;\(\sum_{j} y_j = 2\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Un barrio solo puede ser atendido por un hospital que existe: si &lt;span class=&quot;math&quot;&gt;\(x_{ij} = 1\)&lt;/span&gt;, entonces
&lt;span class=&quot;math&quot;&gt;\(y_j\)&lt;/span&gt; debe ser 1. Podemos escribir esto como una restricción lineal, &lt;span class=&quot;math&quot;&gt;\(x_{ij} \le y_j\)&lt;/span&gt; para todo &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; y &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for i in range(7):                              # 1. un hospital por barrio
    problem += lpSum(x[i][j] for j in range(7)) == 1
problem += lpSum(y) == 2                        # 2. dos hospitales
for i in range(7):                              # 3. solo hospitales que existen
    for j in range(7):
        problem += x[i][j] &amp;lt;= y[j]

resolve_problem(problem, show_variables=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Después de ejecutar el código, GLPK muestra su progreso, con las siguientesvariables que no son cero:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;x_0_4 = 1
x_1_5 = 1
x_2_4 = 1
x_3_5 = 1
x_4_4 = 1
x_5_5 = 1
x_6_4 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los hospitales deberían ser construido en los barrios 4 y 5.
Los barrios 0, 2, 4 y 6 usan el hospital del barrio 4, y los barrios 1, 3 y 5 el del barrio 5. La distancia total es 58,2.&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/two-hospitals-solution.webp&quot; alt=&quot;Solución dibujada en negro: dos grupos de conexiones en forma de estrella, centrados en los barrios 4 y 5&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Solución dibujada en negro: dos grupos de conexiones en forma de estrella, centrados en los barrios 4 y 5&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;modelo-b-el-problema-de-las-p-medianas&quot;&gt;Modelo B: el problema de las p-medianas&lt;/h2&gt;
&lt;p&gt;El modelo de los dos hospitales es un caso particular de un problema clásico de
&lt;strong&gt;localización&lt;/strong&gt;. Hay &lt;span class=&quot;math&quot;&gt;\(n\)&lt;/span&gt; clientes que atender y &lt;span class=&quot;math&quot;&gt;\(m\)&lt;/span&gt; lugares donde podría abrirse una agencia.
El problema de las p-medianas abre &lt;span class=&quot;math&quot;&gt;\(p\)&lt;/span&gt; de ellas y asigna cada cliente a una agencia
abierta, de forma que la suma de las distancias de los clientes a sus agencias sea la
menor posible.&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/p-median.webp&quot; alt=&quot;20 puntos aleatorios unidos por todas las conexiones posibles&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;20 puntos aleatorios unidos por todas las conexiones posibles&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Las variables son idénticas a las del modelo A: &lt;span class=&quot;math&quot;&gt;\(y_j = 1\)&lt;/span&gt; si se abre una agencia en &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, y
&lt;span class=&quot;math&quot;&gt;\(x_{ij} = 1\)&lt;/span&gt; si el cliente &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; es atendido desde &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;. El modelo también es el mismo, con
&lt;span class=&quot;math&quot;&gt;\(p\)&lt;/span&gt; en lugar de 2:&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\begin{aligned}
\min\ &amp;amp; \sum_{i=1}^{n} \sum_{j=1}^{m} d_{ij}\, x_{ij} \\
\text{sujeto a}\ &amp;amp; \sum_{j=1}^{m} x_{ij} = 1, &amp;amp;&amp;amp; i = 1, \dots, n, \\
&amp;amp; \sum_{j=1}^{m} y_j = p, \\
&amp;amp; x_{ij} \le y_j, &amp;amp;&amp;amp; i = 1, \dots, n,\ j = 1, \dots, m, \\
&amp;amp; x_{ij}, y_j \in \{0, 1\}
\end{aligned}\]&lt;/div&gt;
&lt;p&gt;Esta vez, podemos escribir la función objetivo con un solo bucle, así que el código sirve para cualquier número de puntos y
cualquier &lt;span class=&quot;math&quot;&gt;\(p\)&lt;/span&gt;. Para hacer el ejemplo simple, vamos a asumir que cada cliente es también es un lugar candidato, así que
&lt;span class=&quot;math&quot;&gt;\(m = n\)&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def p_median_problem(distance_matrix, facilities):
    n = len(distance_matrix)
    problem = LpProblem(&amp;quot;p_median_problem&amp;quot;, LpMinimize)
    x = create_variable_matrix(n, n, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
    y = create_variable_array(n, name=&amp;quot;y_{i}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0, upBound=1)

    problem += lpSum(distance_matrix[i][j] * x[i][j] for i in range(n) for j in range(n))
    for i in range(n):                          # cada cliente atendido una vez
        problem += lpSum(x[i][j] for j in range(n)) == 1
    problem += lpSum(y) == facilities           # exactamente p agencias
    for i in range(n):                          # solo desde agencias abiertas
        for j in range(n):
            problem += x[i][j] &amp;lt;= y[j]

    return resolve_problem(problem)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/p-median-solution.webp&quot; alt=&quot;Las tres mejores agencias para los 20 clientes&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Las tres mejores agencias para los 20 clientes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;modelo-c-el-vendedor-viajero&quot;&gt;Modelo C: el vendedor viajero&lt;/h2&gt;
&lt;p&gt;Este es uno de los modelos más clásicos en programación lineal, teoría de grafos y optimización: dadas &lt;span class=&quot;math&quot;&gt;\(n\)&lt;/span&gt; ciudades donde se conoce las distancias entre ellas, el problema del vendedor viajero
(en inglés, &lt;em&gt;traveling salesman problem&lt;/em&gt;) busca la ruta más corta que parte de una ciudad,
visita cada una de las otras exactamente una vez y vuelve al punto de partida.&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/tsp.webp&quot; alt=&quot;20 ciudades a visitar&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;20 ciudades a visitar&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Sea &lt;span class=&quot;math&quot;&gt;\(x_{ij} = 1\)&lt;/span&gt; si la ruta va directamente de la ciudad &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; a la ciudad &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, y &lt;span class=&quot;math&quot;&gt;\(0\)&lt;/span&gt; en otro
caso. Así qeu la función objetivo es la longitud de la ruta:&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\min \sum_{i=1}^{n} \sum_{j=1}^{n} d_{ij}\, x_{ij}.\]&lt;/div&gt;
&lt;p&gt;A simple vista bastan dos restricciones (que al mismo tiempo resultan intuitivas): a cada ciudad llega un camino, y de cada ciudad
sale un camino.&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\sum_{i=1}^{n} x_{ik} = 1 \quad\text{y}\quad \sum_{j=1}^{n} x_{kj} = 1, \qquad k = 1, \dots, n.\]&lt;/div&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for k in range(n):
    problem += lpSum(x[i][k] for i in range(n)) == 1   # un ruta llega
    problem += lpSum(x[k][j] for j in range(n)) == 1   # un ruta sale
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;¿Son suficientes esas dos condiciones? &lt;strong&gt;¡¡No!!&lt;/strong&gt;. Esto es lo que devuelve el optimizador cuando solamente nos basamos en estas dos restricciones:&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/tsp-subtours.webp&quot; alt=&quot;Una solución válida podría implicar que existen ciclos independientes&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Una solución válida podría implicar que existen ciclos independientes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;La respuesta es correcta para el modelo: a cada ciudad llega exactamente un camino y sale
exactamente uno. Nada informa al modelo que los caminos deban formar un solo ciclo, y
varios ciclos cortos cuestan menos que uno largo. A estos ciclos se les llamar &lt;em&gt;subtours&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Hay varias formas de eliminarlos. Aquí vamos a usar una formulación usando &lt;em&gt;flujos&lt;/em&gt; (Gavish&amp;amp;Graves, 1978)
que solucionó el problema de los subtours usando un truco bastante ingenioso.
Su idea fue añadir una variable más: &lt;span class=&quot;math&quot;&gt;\(f_{ij} \ge 0\)&lt;/span&gt;, una cantidad de flujo enviada por el
camino de &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; a &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;.
Esa cantidad no se mide ni aparece directamente en nuestra función objetivo, y funciona como una variable que es construida &lt;em&gt;mientras el modelo se ejecuta&lt;/em&gt; (como si fuese una variable temporal en un lenguaje de programación).
Esto nos permite modelar que si la ciudad 1 envía &lt;span class=&quot;math&quot;&gt;\(n - 1\)&lt;/span&gt; unidades, y cada una de las demás se queda
exactamente con una:&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\sum_{i=1}^{n} f_{ik} - \sum_{j=1}^{n} f_{kj} = 1, \qquad k = 2, \dots, n.\]&lt;/div&gt;
&lt;p&gt;El flujo solo puede viajar por los caminos que la ruta usa, así que como ningún camino necesita
llevar más de &lt;span class=&quot;math&quot;&gt;\(n - 1\)&lt;/span&gt; unidades,&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[f_{ij} \le (n - 1)\, x_{ij}, \qquad i, j = 1, \dots, n,\ i \ne j.\]&lt;/div&gt;
&lt;p&gt;Un subtour que no pasa por la ciudad 1 no tiene ningún camino que le traiga flujo desde
afuera, y aun así cada una de sus ciudades debe quedarse con una unidad. Eso es imposible,
así que todas las ciudades tienen que estar en el mismo ciclo que la ciudad 1. Por la
misma razón, una ciudad no puede formar un ciclo consigo misma (&lt;span class=&quot;math&quot;&gt;\(x_{kk} = 1\)&lt;/span&gt;), y por eso el
código no necesita excluir esas variables.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def travelling_salesman_problem(distance_matrix):
    n = len(distance_matrix)
    problem = LpProblem(&amp;quot;travelling_salesman_problem&amp;quot;, LpMinimize)
    x = create_variable_matrix(n, n, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
    f = create_variable_matrix(n, n, name=&amp;quot;f_{i}_{j}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0)

    problem += lpSum(distance_matrix[i][j] * x[i][j] for i in range(n) for j in range(n))
    for k in range(n):
        problem += lpSum(x[i][k] for i in range(n)) == 1          # un camino llega
        problem += lpSum(x[k][j] for j in range(n)) == 1          # un camino sale
    for k in range(1, n):                                          # cada ciudad se queda con una unidad
        problem += lpSum(f[i][k] for i in range(n)) - lpSum(f[k][j] for j in range(n)) == 1
    for i in range(n):                                             # flujo solo por caminos usados
        for j in range(n):
            if i != j:
                problem += f[i][j] &amp;lt;= (n - 1) * x[i][j]

    GLPK(options=[&apos;--mipgap&apos;, &apos;0.01&apos;, &apos;--tmlim&apos;, &apos;30&apos;]).solve(problem)
    return value(problem.objective)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La opción &lt;code&gt;--tmlim 30&lt;/code&gt; detiene GLPK a los 30 segundos, así que en instancias más grandes
el resultado es la mejor ruta encontrada en ese tiempo, no necesariamente la más corta.&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/tsp-solution.webp&quot; alt=&quot;Con las restricciones de flujo, la solución es una sola ruta que pasa por todas las ciudades&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Con las restricciones de flujo, la solución es una sola ruta que pasa por todas las ciudades&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;modelo-d-ruteo-de-vehículos&quot;&gt;Modelo D: ruteo de vehículos&lt;/h2&gt;
&lt;p&gt;El problema de ruteo de vehículos (en inglés, &lt;em&gt;vehicle routing problem&lt;/em&gt;) podría pensarse como una expansión del modelo anterior, donde tenemos más viajeros que solamente pueden llevar una carga máxima de productos.
Un depósito, el punto 0, atiende a &lt;span class=&quot;math&quot;&gt;\(n\)&lt;/span&gt; clientes, numerados del 1 al &lt;span class=&quot;math&quot;&gt;\(n\)&lt;/span&gt;. El cliente &lt;span class=&quot;math&quot;&gt;\(k\)&lt;/span&gt;
necesita &lt;span class=&quot;math&quot;&gt;\(q_k\)&lt;/span&gt; unidades de mercancía, y el depósito ninguna (&lt;span class=&quot;math&quot;&gt;\(q_0 = 0\)&lt;/span&gt;).
Hay tantos vehículos como haga falta, y cada uno lleva como máximo &lt;span class=&quot;math&quot;&gt;\(Q\)&lt;/span&gt; unidades. El objetivo es el conjunto de rutas más barato tal que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;toda ruta empieza y termina en el depósito,&lt;/li&gt;
&lt;li&gt;la demanda de cada cliente es atendida, y&lt;/li&gt;
&lt;li&gt;ninguna ruta lleva más de &lt;span class=&quot;math&quot;&gt;\(Q\)&lt;/span&gt; unidades en total.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/vrp.webp&quot; alt=&quot;Un depósito con  9 clientes&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Un depósito con  9 clientes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Podemos añadir algunas definiciones adicionales: &lt;span class=&quot;math&quot;&gt;\(c_{ij}\)&lt;/span&gt; como el costo de ir de &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; a &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, las variables son &lt;span class=&quot;math&quot;&gt;\(x_{ij} = 1\)&lt;/span&gt; si un vehículo va
de &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; a &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, y &lt;span class=&quot;math&quot;&gt;\(f_{ij} \ge 0\)&lt;/span&gt;, la cantidad de mercancía que se lleva de &lt;span class=&quot;math&quot;&gt;\(i\)&lt;/span&gt; a &lt;span class=&quot;math&quot;&gt;\(j\)&lt;/span&gt;, así que el modelo sería:&lt;/p&gt;
&lt;div class=&quot;math&quot;&gt;\[\begin{aligned}
\min\ &amp;amp; \sum_{i=0}^{n} \sum_{j=0}^{n} c_{ij}\, x_{ij} \\
\text{sujeto a}\ &amp;amp; \sum_{j=0}^{n} x_{kj} = 1, \quad \sum_{i=0}^{n} x_{ik} = 1, &amp;amp;&amp;amp; k = 1, \dots, n, \\
&amp;amp; \sum_{j=1}^{n} x_{0j} = \sum_{i=1}^{n} x_{i0}, \\
&amp;amp; \sum_{i=0}^{n} f_{ik} - \sum_{j=0}^{n} f_{kj} = q_k, &amp;amp;&amp;amp; k = 1, \dots, n, \\
&amp;amp; f_{ij} \le Q\, x_{ij}, &amp;amp;&amp;amp; i, j = 0, \dots, n,\ i \ne j.
\end{aligned}\]&lt;/div&gt;
&lt;p&gt;Línea por línea el modelo tiene una interpretación como la que dimos anteriomente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cada cliente sale un vehículo y a cada cliente llega uno&lt;/li&gt;
&lt;li&gt;Al depósito vuelven tantos vehículos como salen&lt;/li&gt;
&lt;li&gt;Cada cliente se queda exactamente con su demanda de la mercancía que pasa por él&lt;/li&gt;
&lt;li&gt;Y, finalmente, ningún camino lleva más de lo que cabe en un vehículo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Las restricciones de flujo cumplen el mismo papel que en el vendedor viajero: la mercancía
solo puede salir del depósito, así que toda ruta tiene que pasar por él.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def vehicle_routing_problem(distance_matrix, demand, vehicle_capacity):
    n = len(distance_matrix)
    problem = LpProblem(&amp;quot;vehicle_routing_problem&amp;quot;, LpMinimize)
    x = create_variable_matrix(n, n, cat=&apos;Integer&apos;, lowBound=0, upBound=1)
    f = create_variable_matrix(n, n, name=&amp;quot;f_{i}_{j}&amp;quot;, cat=&apos;Integer&apos;, lowBound=0)

    problem += lpSum(distance_matrix[i][j] * x[i][j] for i in range(n) for j in range(n))
    for k in range(1, n):
        problem += lpSum(x[k][j] for j in range(n)) == 1          # sale un vehículo
        problem += lpSum(x[i][k] for i in range(n)) == 1          # llega un vehículo
    problem += (lpSum(x[0][j] for j in range(1, n))                # depósito: salen tantos como llegan
                == lpSum(x[i][0] for i in range(1, n)))
    for k in range(1, n):                                          # cada cliente se queda con q_k
        problem += lpSum(f[i][k] for i in range(n)) - lpSum(f[k][j] for j in range(n)) == demand[k]
    for i in range(n):                                             # capacidad del vehículo
        for j in range(n):
            if i != j:
                problem += f[i][j] &amp;lt;= vehicle_capacity * x[i][j]

    return resolve_problem(problem)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En el ejemplo cada cliente necesita una unidad y un vehículo lleva cinco, así que los nueve
clientes necesitan al menos dos vehículos. El mejor plan usa exactamente dos, que podrían
hacer sus entregas al mismo tiempo.&lt;/p&gt;
&lt;figure class=&quot;figure&quot;&gt;
  &lt;img src=&quot;/es/blog/linear-programming/vrp-solution.webp&quot; alt=&quot;Ruta óptima: 2 vehículos cada uno en su propio ciclo desde el depósito&quot; width=&quot;1400&quot; height=&quot;700&quot; loading=&quot;lazy&quot;&gt;  &lt;figcaption&gt;Ruta óptima: 2 vehículos cada uno en su propio ciclo desde el depósito&lt;/figcaption&gt;
&lt;/figure&gt;
</content>
  </entry>
</feed>
