Optimiza la secuenciación de trabajos con heurísticas y Ortools en Python.
Minimiza la tardanza total combinando heurísticas clásicas con un modelo de programación lineal paso a paso.
Fuente: Algocademy
La secuenciación de trabajos es un problema clásico de optimización en el que se busca determinar el orden óptimo para procesar tareas en una máquina, minimizando la tardanza total. En este artículo, combinamos heurísticas clásicas (SPT, EDD, etc.) con un modelo de programación lineal usando ortools para garantizar la solución óptima.
Trabajaremos con 10 trabajos generados aleatoriamente, aplicaremos 7 heurísticas y un modelo de optimización.
Generación de Datos
Generamos 10 trabajos con duraciones y fechas de entrega aleatorias.
import random num_trabajos = 10 trabajos = [f'J{i+1}' for i in range(num_trabajos)] duraciones = [random.randint(1, 10) for _ in range(num_trabajos)] fechas_entrega = [random.randint(5, 30) for _ in range(num_trabajos)] datos_trabajos = list(zip(trabajos, duraciones, fechas_entrega)) print("Datos generados:") for t in datos_trabajos: print(t)
Aplicación de Heurísticas
Calculamos la tardanza total con 7 heurísticas:
- SPT: Shortest Processing Time
- EDD: Earliest Due Date
- LPT: Longest Processing Time
- FCFS: First Come First Served
- LCFS: Last Come First Served
- S/O: Slack per Operation
- CR: Critical Ratio
def calcular_tardanza(secuencia, duraciones, fechas_entrega): n = len(secuencia) tiempo_actual = 0 tardanza_total = 0 for i in secuencia: tiempo_actual += duraciones[i] tardanza = max(0, tiempo_actual - fechas_entrega[i]) tardanza_total += tardanza return tardanza_total def heuristica_spt(duraciones): return sorted(range(len(duraciones)), key=lambda i: duraciones[i]) def heuristica_edd(fechas_entrega): return sorted(range(len(fechas_entrega)), key=lambda i: fechas_entrega[i]) def heuristica_slack(duraciones, fechas_entrega): slack = [fechas_entrega[i] - duraciones[i] for i in range(len(duraciones))] return sorted(range(len(slack)), key=lambda i: slack[i]) # Implementación de las heurísticas adicionales def heuristica_lpt(duraciones): return sorted(range(len(duraciones)), key=lambda i: duraciones[i], reverse=True) def heuristica_ldd(fechas_entrega): return sorted(range(len(fechas_entrega)), key=lambda i: fechas_entrega[i], reverse=True) def heuristica_cr(duraciones, fechas_entrega): # Critical Ratio (Fecha de Entrega / Duración) - menor CR primero cr = [fechas_entrega[i] / duraciones[i] if duraciones[i] > 0 else float('inf') for i in range(len(duraciones))] return sorted(range(len(cr)), key=lambda i: cr[i]) # Ejecutar todas las heurísticas n = len(trabajos) indices = list(range(n)) heurísticas = { 'FCFS': indices, 'SPT': heuristica_spt(duraciones), 'LPT': heuristica_lpt(duraciones), 'EDD': heuristica_edd(fechas_entrega), 'LDD': heuristica_ldd(fechas_entrega), 'Slack': heuristica_slack(duraciones, fechas_entrega), 'CR': heuristica_cr(duraciones, fechas_entrega) } resultados = {} secuencias_resultados = {} for nombre, sec_indices in heurísticas.items(): tardanza = calcular_tardanza(sec_indices, duraciones, fechas_entrega) resultados[nombre] = tardanza secuencias_resultados[nombre] = ' → '.join(trabajos[i] for i in sec_indices) mejor_tardanza = min(resultados.values()) peor_tardanza = max(resultados.values()) print(f" Mejor Tardanza Total (Heurísticas): {mejor_tardanza}") print(f"Peor Tardanza Total (Heurísticas): {peor_tardanza}")
Optimización con ortools
Resolvemos el problema de forma exacta usando el solver CP-SAT de Google OR-Tools.
import time from ortools.sat.python import cp_model start_time = time.time() # Crear el modelo CP-SAT model = cp_model.CpModel() n = len(trabajos) # Variables: intervalo para cada trabajo en la máquina única job_intervals = [] starts = [] ends = [] tardiness = [] for i in range(n): max_end_time = sum(duraciones) start_var = model.NewIntVar(0, max_end_time, f'start_{i}') end_var = model.NewIntVar(0, max_end_time, f'end_{i}') interval_var = model.NewIntervalVar(start_var, duraciones[i], end_var, f'interval_{i}') starts.append(start_var) ends.append(end_var) job_intervals.append(interval_var) # Tardanza: max(0, end_var - due_date) tardy_var = model.NewIntVar(0, max_end_time, f'tardy_{i}') model.AddMaxEquality(tardy_var, [end_var - fechas_entrega[i], 0]) tardiness.append(tardy_var) # Restricción de no solapamiento en la máquina única model.AddNoOverlap(job_intervals) # Objetivo: Minimizar la tardanza total model.Minimize(sum(tardiness)) # Crear el solucionador solver = cp_model.CpSolver() solver.parameters.max_time_in_seconds = 300.0 status = solver.Solve(model) tiempo_opt = time.time() - start_time # Extraer resultados secuencia = [] tardanza_optima = None if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: tardanza_optima = solver.ObjectiveValue() start_times_with_indices = [(solver.Value(starts[i]), i) for i in range(n)] start_times_with_indices.sort() secuencia = [index for start_time, index in start_times_with_indices] print(f"✅ Solución encontrada en {tiempo_opt:.2f} segundos") print(f"📊 Tardanza total: {tardanza_optima:.1f} días") print(f"🔄 Orden: {' → '.join(trabajos[i] for i in secuencia)}") else: print(f"❌ No se encontró solución. Estado: {solver.StatusName(status)}")
Resultados
| Heurística | Tardanza (días) | Ranking |
|---|---|---|
| EDD | 6 | 🥇 Óptimo |
| Óptimo (ortools) | 6 | 🥇 Óptimo |
| Slack | 12 | 🥈 Cercano |
| FCFS | 43 | Medio |
| SPT | 8,830 | Malo |
| Critical Ratio (CR) | 13,135 | Malo |
| LPT | 24,717 | Muy malo |
| LDD | 25,370 | Peor |
EDD + ortools
LDD (Least Due Date)
Insight clave: El modelo de ortools obtiene el mismo resultado óptimo que la heurística EDD. Esto no siempre ocurre — en instancias más grandes, las heurísticas pueden quedarse en óptimos locales mientras el solver encuentra el global.
Consejos Pro
- Escala a más trabajos con Gurobi o CPLEX cuando ortools se vuelva lento (más de 100 trabajos).
- Visualiza con Gantt en Plotly para presentar resultados a equipos no técnicos.
- Valida siempre contra una heurística rápida (EDD o Slack) como benchmark antes de lanzar el solver.
- Considera setup times si los trabajos requieren cambio de configuración entre uno y otro.
¿Quieres aplicarlo HOY en tu empresa?
Descarga el Jupyter Notebook completo con gráficos Gantt interactivos.
🚀 ¿Cómo aplicarlo HOY?
- Excel: Copia tus datos (columna A: ID, B: Duración, C: Entrega)
- Código: Pega en líneas 6-8 del notebook
- Ejecuta: Obtén el orden óptimo en 3 segundos
- Implementa: Envía al jefe → "Esto ahorra días por semana"
Referencias
[1] OR-Tools Documentation — Google Optimization
[2] Principles of Sequencing and Scheduling — Kenneth R. Baker, Dan Trietsch