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:
- ¿Construir una sucursal en este sitio? Sí/No ( o ).
- ¿Cuántos buses asignar a una ruta? Entero positivo.
- ¿Cuál pedido va a qué camión? Asignación discreta.
Eso es Programación Entera (PE). En este capítulo:
- Formulación con variables enteras y binarias.
- Branch & Bound (B&B) — el algoritmo central de PE.
- Cuatro problemas de redes clásicos (transporte, asignación, MST, ruta crítica).
- Modelar restricciones lógicas con binarias.
- Resolución práctica.
4.1 Variables enteras y binarias
📐 Fundamento
Una variable entera: .
Una variable binaria: . Caso especial pero brutalmente útil:
- si se elige la opción , si no.
- si se abre la sucursal .
- si el camión visita el cliente .
Mixed Integer Program (MIP): mezcla variables continuas y enteras.
El gran problema: complejidad
PL continua: polinomial ( 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 . 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 y te ramificás en dos subproblemas:
- : .
- : .
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
Paso 1: relajación LP. Resolvés sin la restricción de enteros. Solución: , . ( fraccional.)
Paso 2: ramificar en .
- Rama A: . LP da , . Sigue fraccional. Ramificás en :
- : LP da , . Entero. Candidato.
- : LP da , . Entero. Mejor candidato.
- Rama B: . LP da , . Sigue fraccional. Ramificás en :
- : LP da , . Entero, pero peor que 20. Descartar.
- : LP da — pero , infactible. Descartar.
Óptimo entero: con .
Notá que la solución entera NO es "redondear" la LP → . Redondear ingenuamente cuesta , peor que el verdadero óptimo .
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: orígenes con oferta , destinos con demanda . Costo de enviar 1 unidad desde origen a destino . Minimizar costo total.
Variables: = unidades enviadas desde a .
Casos:
- Si (balanceado): las restricciones de oferta y demanda son igualdades.
- Si oferta > demanda: agregás un destino ficticio que absorba el excedente, costo .
- 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 (), 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: tareas, trabajadores. Costo de asignar trabajador a tarea . Asignar exactamente uno a uno, minimizando costo total.
Variables binarias: , si va a tarea .
Algoritmo dedicado: método húngaro (). 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 (): ordenar aristas por peso, agregar la más barata que no forme ciclo.
- Prim (): empezar de un nodo, expandir incluyendo siempre la arista más barata hacia un nodo no incluido.
Aplicaciones:
- Cablear 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):
- Arista más barata: CHL-SJO (25). Acepta.
- SM-LU (30). Acepta.
- SJO-PE (35). Acepta.
- 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:
- Para cada tarea calculás ES (Earliest Start) y EF (Earliest Finish).
- Para cada tarea calculás LS y LF (Latest Start/Finish) hacia atrás.
- Holgura = . Tareas con holgura están en la ruta crítica.
- 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 , máximo unidades")
donde es "abro fábrica ". Si , debe ser . Si , . es un número grande (big-M).
Al menos de opciones
Exactamente uno
Implicación ( ⇒ )
Disyunción ("restricción A o B")
Difícil en PL puro; con binaria :
Si : A activa, B "se relaja". Si : B activa, A "se relaja". El solver elige ó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 "por seguridad" en una restricción tipo . La relajación LP queda muy débil: puede ser casi 0 y casi . 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 , usá . 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 y redondeás a . Pero 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
- Una empresa debe decidir abrir 2 de 5 sucursales (binario). Cada una tiene costo de apertura y atiende demanda del área. Formulá el modelo.
- Resolvé el problema de transporte del cap. 4.3 con NetworkX o PuLP. Verificá que la solución es entera.
- Aplicá el algoritmo húngaro a un problema de asignación 4×4 con la matriz que vos elijas.
- Construí el grafo de carreteras entre 6 ciudades de El Salvador (datos aproximados). Calculá Dijkstra entre dos extremos y MST.
- Modelá una restricción "exactamente 2 de 5 alternativas". Implementala.
- 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
- Wolsey, Integer Programming — el libro de referencia académico.
- Williams, Model Building in Mathematical Programming — formulación creativa, muchos ejemplos.
- NetworkX docs (networkx.org) — implementaciones listas para grafos.
- OR-Tools (Google) — solvers + interfaces para Python, Java, C++. Gratuito y muy potente.
- Siguiente capítulo: Teoría de decisiones.
4.13 Mini-proyecto integrador
🏗️ Avance del proyecto — Agregar restricciones discretas
Entregables (cap. 4):
- Identificá 3 restricciones lógicas o decisiones binarias en tu caso original.
- Reformulá el modelo con esas variables.
- Resolvé con PuLP. Compará la solución entera con la solución continua del cap. 2.
- Reportá la degradación del objetivo al imponer integralidad (si la hay).
- 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.