Programación entera y problemas de redes

"Cuando una variable es '¿asignar o no?' la matemática se vuelve mucho más expresiva — y mucho más costosa de resolver."

Qué vas a aprender en este capítulo

PL continua resuelve cuando "media unidad" tiene sentido (kg de harina, litros de pintura). Pero en muchos problemas reales las variables son discretas:

Eso es Programación Entera (PE). En este capítulo:

  1. Formulación con variables enteras y binarias.
  2. Branch & Bound (B&B) — el algoritmo central de PE.
  3. Cuatro problemas de redes clásicos (transporte, asignación, MST, ruta crítica).
  4. Modelar restricciones lógicas con binarias.
  5. Resolución práctica.

4.1 Variables enteras y binarias

📐 Fundamento

Una variable entera: xZ,x0x \in \mathbb{Z}, x \geq 0.

Una variable binaria: x{0,1}x \in {0, 1}. Caso especial pero brutalmente útil:

  • xj=1x_j = 1 si se elige la opción jj, 00 si no.
  • yi=1y_i = 1 si se abre la sucursal ii.
  • zij=1z_{ij} = 1 si el camión ii visita el cliente jj.

Mixed Integer Program (MIP): mezcla variables continuas y enteras.

El gran problema: complejidad

PL continua: polinomial (O(n3.5)O(n^{3.5}) con interior point). PE/MIP: NP-difícil. El espacio de soluciones es discreto, no podés moverte continuamente.

En la práctica: solvers modernos (Gurobi, CPLEX, HiGHS) resuelven MIPs con millones de variables en minutos gracias a B&B + heurísticas. Pero un MIP "pequeño" mal modelado puede tardar horas.

4.2 Branch & Bound

💡 Intuición

Tenés el problema entero PP. Lo resolvés ignorando la integralidad — esto se llama relajación LP. Da una solución que probablemente tiene valores fraccionarios.

Tomás una variable fraccional xj=3.7x_j = 3.7 y te ramificás en dos subproblemas:

  • P1P_1: P+restriccioˊxj3P + \text{restricción } x_j \leq 3.
  • P2P_2: P+restriccioˊxj4P + \text{restricción } x_j \geq 4.

Resolvés ambos. Si uno da solución entera, la guardás como mejor candidato. Si no, ramificás de nuevo.

Clave: si la relajación LP de un subproblema da un valor peor que el mejor candidato entero conocido, podés podarlo (no seguir ramificando). De ahí "bound".

El proceso es una búsqueda en árbol: combina enumeración inteligente con poda agresiva.

📝 Ejemplo trabajado — B&B chico

maxZ=5x1+4x2,  6x1+4x224,  x1+2x26,  xjZ0\max Z = 5 x_1 + 4 x_2,\; 6 x_1 + 4 x_2 \leq 24,\; x_1 + 2 x_2 \leq 6,\; x_j \in \mathbb{Z}_{\geq 0}

Paso 1: relajación LP. Resolvés sin la restricción de enteros. Solución: x1=3,x2=1.5x_1 = 3, x_2 = 1.5, Z=21Z = 21. (x2x_2 fraccional.)

Paso 2: ramificar en x2x_2.

  • Rama A: x21x_2 \leq 1. LP da x1=3.33,x2=1x_1 = 3.33, x_2 = 1, Z=20.67Z = 20.67. Sigue fraccional. Ramificás en x1x_1:
    • x13x_1 \leq 3: LP da (3,1)(3, 1), Z=19Z = 19. Entero. Candidato.
    • x14x_1 \geq 4: LP da (4,0)(4, 0), Z=20Z = 20. Entero. Mejor candidato.
  • Rama B: x22x_2 \geq 2. LP da x1=2.67,x2=2x_1 = 2.67, x_2 = 2, Z=21.33Z = 21.33. Sigue fraccional. Ramificás en x1x_1:
    • x12x_1 \leq 2: LP da (2,2)(2, 2), Z=18Z = 18. Entero, pero peor que 20. Descartar.
    • x13x_1 \geq 3: LP da (3,1.5)(3, 1.5) — pero x22x_2 \geq 2, infactible. Descartar.

Óptimo entero: (4,0)(4, 0) con Z=20Z = 20.

Notá que la solución entera NO es "redondear" la LP (3,1.5)(3, 1.5)(3,1)(3, 1). Redondear ingenuamente cuesta Z=19Z = 19, peor que el verdadero óptimo (4,0)=20(4, 0) = 20.

Esta es la razón clave por la que PE requiere algoritmos, no atajos: redondeo es a menudo subóptimo y a veces incluso infactible.

4.3 Problema de Transporte

📐 Fundamento

Setup: mm orígenes con oferta sis_i, nn destinos con demanda djd_j. Costo cijc_{ij} de enviar 1 unidad desde origen ii a destino jj. Minimizar costo total.

Variables: xijx_{ij} = unidades enviadas desde ii a jj.

minZ=i,jcijxij\min Z = \sum_{i,j} c_{ij} x_{ij}
jxijsii(oferta)\sum_j x_{ij} \leq s_i \quad \forall i \quad (\text{oferta})
ixijdjj(demanda)\sum_i x_{ij} \geq d_j \quad \forall j \quad (\text{demanda})
xij0x_{ij} \geq 0

Casos:

  • Si si=dj\sum s_i = \sum d_j (balanceado): las restricciones de oferta y demanda son igualdades.
  • Si oferta > demanda: agregás un destino ficticio que absorba el excedente, costo 00.
  • Si demanda > oferta: infactible en la forma básica. Tenés que decidir qué demanda no satisfacer.

Hecho asombroso: aunque parece PE (cantidades de cajas son enteras), la matriz de restricciones es totalmente unimodular, lo que garantiza que la solución LP siempre es entera. Resolvés con Simplex normal y nunca tenés que ramificar.

Ejemplo:

Costo $/u Tienda1 Tienda2 Tienda3 Oferta
Fábrica A 4 6 8 100
Fábrica B 5 3 7 150
Demanda 80 120 50

Modelo: 6 variables (xA1,,xB3x_{A1}, \ldots, x_{B3}), 5 restricciones (2 oferta + 3 demanda). Resolvés con scipy.optimize.linprog, networkx.min_cost_flow, o cualquier solver MIP.

4.4 Problema de Asignación

📐 Fundamento

Setup: nn tareas, nn trabajadores. Costo cijc_{ij} de asignar trabajador ii a tarea jj. Asignar exactamente uno a uno, minimizando costo total.

Variables binarias: xij{0,1}x_{ij} \in {0, 1}, =1= 1 si ii va a tarea jj.

minZ=ijcijxij\min Z = \sum_{ij} c_{ij} x_{ij}
jxij=1i(cada trabajador a una tarea)\sum_j x_{ij} = 1 \quad \forall i \quad (\text{cada trabajador a una tarea})
ixij=1j(cada tarea a un trabajador)\sum_i x_{ij} = 1 \quad \forall j \quad (\text{cada tarea a un trabajador})
xij{0,1}x_{ij} \in \{0, 1\}

Algoritmo dedicado: método húngaro (O(n3)O(n^3)). Más rápido que B&B para este caso particular.

Aplicaciones:

  • Asignar pilotos a vuelos.
  • Asignar cirugías a quirófanos.
  • Asignar repartidores a zonas en una ciudad.
  • Matching en sitios de citas (LP relajado).

4.5 Árbol generador mínimo (MST)

📐 Fundamento

Setup: Un grafo conexo no dirigido con pesos en las aristas. Encontrar un subgrafo que conecte todos los nodos con peso total mínimo, sin ciclos.

Algoritmos clásicos:

  • Kruskal (O(ElogE)O(E \log E)): ordenar aristas por peso, agregar la más barata que no forme ciclo.
  • Prim (O(ElogV)O(E \log V)): empezar de un nodo, expandir incluyendo siempre la arista más barata hacia un nodo no incluido.

Aplicaciones:

  • Cablear nn pueblos con fibra óptica mínimo costo total (proyecto INTERNET RURAL en SV).
  • Diseño de circuitos integrados (rutas de cobre mínimo).
  • Clustering jerárquico (eliminar la arista más cara da clusters).

Ejemplo: 5 pueblos en SV, costos de conexión:

SM LU CHL SJO PE
SM - 30 45 60 80
LU 30 - 50 70 90
CHL 45 50 - 25 65
SJO 60 70 25 - 35
PE 80 90 65 35 -

MST (Kruskal):

  1. Arista más barata: CHL-SJO (25). Acepta.
  2. SM-LU (30). Acepta.
  3. SJO-PE (35). Acepta.
  4. SM-CHL (45). Acepta (4 aristas, conecta 5 nodos — listo).

Costo MST: 25 + 30 + 35 + 45 = 135.

4.6 Camino más corto y ruta crítica

📐 Fundamento

Camino más corto: ya lo vimos en Estructuras de Datos (Dijkstra). En IO se usa para:

  • GPS / ruteo logístico.
  • Planificación de paquetes en redes.

Ruta crítica (CPM): caso especial sobre un DAG de tareas con duraciones. Para un proyecto con tareas y precedencias:

  1. Para cada tarea calculás ES (Earliest Start) y EF (Earliest Finish).
  2. Para cada tarea calculás LS y LF (Latest Start/Finish) hacia atrás.
  3. Holgura = LSESLS - ES. Tareas con holgura =0= 0 están en la ruta crítica.
  4. La duración del proyecto = EF del nodo final = LF del nodo final.

Aplicación: gestión de proyectos. La ruta crítica te dice qué tareas no pueden atrasarse sin atrasar todo el proyecto. Se cubre en detalle en Gestión de Proyectos de TI.

Ejemplo (5 tareas):

Tarea Duración Predecesoras
A 3
B 5 A
C 4 A
D 2 B, C
E 3 D

ES/EF hacia adelante:

  • A: ES=0, EF=3.
  • B: ES=3, EF=8. C: ES=3, EF=7.
  • D: ES=max(8,7)=8, EF=10.
  • E: ES=10, EF=13.

Duración proyecto: 13 días. Ruta crítica: A → B → D → E (todas con holgura 0). C tiene holgura 1.

4.7 Restricciones lógicas con binarias

🛠️ Aplicado

Las binarias permiten modelar lógica combinatoria dentro de PL. Algunas plantillas útiles:

If-then ("si abro fábrica ii, máximo MM unidades")

xiMyix_i \leq M y_i

donde yi{0,1}y_i \in {0, 1} es "abro fábrica ii". Si yi=0y_i = 0, xix_i debe ser 00. Si yi=1y_i = 1, xiMx_i \leq M. MM es un número grande (big-M).

Al menos kk de nn opciones

iyik\sum_i y_i \geq k

Exactamente uno

iyi=1\sum_i y_i = 1

Implicación (y1=1y_1 = 1y2=1y_2 = 1)

y2y1y_2 \geq y_1

Disyunción ("restricción A o B")

Difícil en PL puro; con binaria zz:

Restriccioˊn A: aTxbA+M(1z)\text{Restricción A: } a^T x \leq b_A + M(1-z)
Restriccioˊn B: aTxbB+Mz\text{Restricción B: } a^T x \leq b_B + Mz

Si z=1z = 1: A activa, B "se relaja". Si z=0z = 0: B activa, A "se relaja". El solver elige zz óptimamente.

Cuidado con big-M: valores muy grandes hacen la relajación LP débil y B&B lento. Usá el mínimo M que aún funcione.

4.8 Resolución práctica

🛠️ Aplicado

PuLP — programación entera

import pulp

m = pulp.LpProblem("plant_location", pulp.LpMinimize)

# Variables: y_i abrir fábrica, x_ij unidades enviadas
y = {i: pulp.LpVariable(f"y_{i}", cat='Binary') for i in range(3)}
x = {(i, j): pulp.LpVariable(f"x_{i}_{j}", lowBound=0)
     for i in range(3) for j in range(5)}

# Objetivo: costo apertura + costo envío
m += pulp.lpSum(F[i] * y[i] for i in range(3)) + \
     pulp.lpSum(c[i][j] * x[i, j] for i in range(3) for j in range(5))

# Restricciones
for j in range(5):
    m += pulp.lpSum(x[i, j] for i in range(3)) >= d[j]      # demanda
for i in range(3):
    m += pulp.lpSum(x[i, j] for j in range(5)) <= K[i] * y[i]  # capacidad si abierta

m.solve()

NetworkX — redes

import networkx as nx

G = nx.Graph()
G.add_weighted_edges_from([('SM','LU',30), ('SM','CHL',45), ('CHL','SJO',25), ...])

mst = nx.minimum_spanning_tree(G)         # MST
print(sorted(mst.edges(data=True)))

camino = nx.shortest_path(G, 'SM', 'PE', weight='weight')   # Dijkstra

NetworkX no es el más rápido pero es el más fácil para empezar.

4.9 Errores comunes

⚠️ Trampa común

Big-M demasiado grande. Usás M=109M = 10^9 "por seguridad" en una restricción tipo xMyx \leq My. La relajación LP queda muy débil: yy puede ser casi 0 y xx casi 10910^9. El árbol B&B explota explorando ramas inútiles.

Tip: elegí el M más chico que aún sea válido. Si en el peor caso x100x \leq 100, usá M=100M = 100. Lo difícil es saber el peor caso — sale del análisis del problema.

⚠️ Trampa común

Asumir que MIP es como LP "con un poco más de tiempo". El tiempo de resolución de MIPs es muy sensible a la formulación. Un mismo problema con dos formulaciones puede tardar 1 s o 1 día. La formulación "natural" no siempre es la mejor.

Tip: si tu MIP no resuelve en tiempo razonable, reformulá antes de pedir más hardware. Técnicas: agregación de restricciones, cortes válidos, ordenamiento de variables.

⚠️ Trampa común

Confundir "óptimo de la relajación LP" con "óptimo entero". Tras correr LP, ves x=3.5x = 3.5 y redondeás a 44. Pero 44 puede ser infactible (otra restricción se viola) o subóptimo (otra combinación entera es mejor).

Tip: el redondeo solo es seguro si todas las restricciones se satisfacen. Si no, usá B&B (cualquier solver MIP lo hace por vos).

4.10 Resumen visual

                  PL continua (cap. 2-3)
                          │
                          ▼  agregás x ∈ ℤ
                  Programación Entera
                          │
                          ├── Branch & Bound
                          │     │
                          │     ├── relajación LP
                          │     ├── ramificar en x_j fraccional
                          │     └── podar con bound
                          │
                          ├── Casos especiales:
                          │     ├── transporte (matriz unimodular → LP da entero)
                          │     ├── asignación (algoritmo húngaro)
                          │     ├── MST (Kruskal/Prim)
                          │     ├── camino más corto (Dijkstra)
                          │     └── ruta crítica (CPM)
                          │
                          └── Lógica con binarias:
                                ├── if-then (big-M)
                                ├── k de n
                                ├── implicaciones
                                └── disyunciones

4.11 Ejercicios

  1. Una empresa debe decidir abrir 2 de 5 sucursales (binario). Cada una tiene costo de apertura FiF_i y atiende demanda dd del área. Formulá el modelo.
  2. Resolvé el problema de transporte del cap. 4.3 con NetworkX o PuLP. Verificá que la solución es entera.
  3. Aplicá el algoritmo húngaro a un problema de asignación 4×4 con la matriz que vos elijas.
  4. Construí el grafo de carreteras entre 6 ciudades de El Salvador (datos aproximados). Calculá Dijkstra entre dos extremos y MST.
  5. Modelá una restricción "exactamente 2 de 5 alternativas". Implementala.
  6. Para tu pénsum: modelá el orden de cursos como DAG, calculá la ruta crítica (camino más largo). ¿Cuántos ciclos mínimo necesitás?

4.12 Para profundizar

4.13 Mini-proyecto integrador

🏗️ Avance del proyecto — Agregar restricciones discretas

Entregables (cap. 4):

  1. Identificá 3 restricciones lógicas o decisiones binarias en tu caso original.
  2. Reformulá el modelo con esas variables.
  3. Resolvé con PuLP. Compará la solución entera con la solución continua del cap. 2.
  4. Reportá la degradación del objetivo al imponer integralidad (si la hay).
  5. Identificá un problema de redes auxiliar en tu caso (transporte, asignación, ruta) y resolvelo.

Definiciones nuevas: programación entera, programación binaria, MIP (Mixed Integer Program), Branch & Bound, relajación LP, bound, problema de transporte, balanceado, totalmente unimodular, problema de asignación, método húngaro, MST (Minimum Spanning Tree), Kruskal, Prim, camino más corto, CPM (Critical Path Method), holgura del proyecto, big-M.