1. Programación Lineal Generalizada y Generación de columnas Suponemos un problema de Programación Lineal en la forma estandar (P) Minimizar sujeto a z = cx Az = b x≥0 donde n = número de variables m = número de restricciones A = matriz m × n de coeficientes aij c = (c1 , ..., cn ) vector fila de costos b = (b1 , ..., bm )T vector columna del lado derecho z = función objetivo Aunque el tamaño de los problemas que pueden resolverse hoy dı́a es muy elevado (miles de variables y restricciones en un simple PC), existen numerosos problemas prácticos de grandes dimensiones que no pueden ser ni siquiera formulados adecuadamente debido al excesivo número de variables y la cantidad de memoria exigida para almacenarlas. Estos Problemas de Gran Escala pueden a veces ser resueltos mediante técnicas de descomposición que reducen la solución de un problema grande a una sucesión de problemas de menor dimensión. Los principales métodos de descomposición, como el de Dantzig-Wolfe y el de Benders, están basados en técnicas de generación de columnas y generación de restricciones y se aplican a multitud de problemas de Programación Lineal y también de Programación Entera. Es esta sección vamos a ver la filosofı́a general de los métodos de generación de columnas, que se engloba generalmente en el término de Programación Lineal Generalizada. Los métodos de Generación de restricciones, que es la versión dual de generación de columnas, se aplican fundamentalmente en los modelos de Programación Entera, y serán vistos en la parte 2. Suponemos aquı́ que el problema (P) tiene un número de variables n mucho mayor que el número de restricciones m, es decir n À m, de forma que, aunque es imposible almacenar en la memoria del ordenador todas las columnas de la matriz A, conocemos las columnas sólo implı́citamente. Por ejemplo, cada columna puede corresponder a un vértice o punto extremo de un poliedro, o en otros casos a un camino entre dos nodos en cierto grafo. En cada ejemplo especı́fico el conjunto de columnas puede tener una forma diferente, y aquı́ supondremos que S ⊂ Rm es el conjunto de todas las columnas, y para cada a ∈ S se tiene asociado el costo c(a). La formulación del problema completo en la forma de columnas es: (P) Minimizar z= sujeto a n P n P cj xj j=1 aj x j = b j=1 1 xj ≥ 0, j = 1, ..., n donde los vectores columna aj y b pertenecen a Rm . Como el número de columnas puede ser elevadı́simo se trata se no intentar enumerarlas todas explı́citamente, sino generar sólo las columnas que se necesiten. Más concretamente, suponemos que se han generado un conjunto de columnas con ı́ndices G ⊂ {1, ..., n} y se considera el problema restringido siguiente: (PR) Minimizar sujeto a P cj xj z= P j∈G aj x j = b xj ≥ 0, ∀j ∈ G j∈G Mientras que el problema original (P) no puede ser resuelto explı́citamente, porque el número de columnas o variables es excesivo, el problema restringido (PR) debe poder resolverse completamente. Después de aplicar el método simplex se tendrá una SBF óptima x0 correspondiente a una base B, y un vector de multiplicadores simplex o varibles duales óptimo ω = cB B −1 . Toda solución factible para (PR) (xj , j ∈ G) es factible para el problema (P), pues basta extender las varibles con xj = 0, ∀j ∈ / G. Entonces, para saber si la solución actual x0 , que es óptima para (PR), es óptima para (P) o, en caso contrario, determinar la columna que va a entrar enla base, habrı́a que calcular el coste reducido zj −cj = cB B −1 aj −cj = ωaj −cj para toda columna no básica aj o ı́ndice j ∈ R, para después tomar el coste reducido máximo. Si tenemos definidas las columnas implı́citamente como a ∈ S, calcular el coste reducido máximo máx {zj − cj } es equivalente a resolver el siguiente subproblema: j=1,...,n (Pω ) máx {ωa − c(a)} a∈S Si a ∈ S es una solución óptima del subproblema (Pω ) y ωa − c(a) ≤ 0 entonces la solución actual es óptima, y si ωa − c(a) > 0 entonces añadimos la columna a a la formulación actual del problema (PR) y se reoptimiza mediante el método simplex . Esta aproximación es llamada Genaración de Columnas porque normalmente sólo se generan explı́citamente una fracción muy pequeña de todas las columnas de S. Dependiendo de la estructura de S y de la forma del costo c(a), el subproblema (Pω ) puede ser un problema sencillo de PL , un problema de Programación Dinámica, un problema de redes o de flujo máximo, o un problema sencillo de Programación Entera como por ejemplo un problema de tipo mochila. Los algoritmos de generación de columnas están basados en la interacción entre el problema de Programación Lineal (PR), que se resuelve explı́citamente, y el subproblema (Pω ), que genera las columnas, como se ve en la siguiente Figura: 2 vector w Subproblema ( P w ) Problema (PR) columna a Esta aproximación se ha aplicado a multitud de situaciones y problemas, entre los que se pueden destacar: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. El problema Cutting Stock. Problemas con estructuras especiales, y el principio de descomposición de Dantzig-Wolfe. Flujo de costo mı́nimo en una red. Flujo en redes multiproducto. Reformulación por caminos. Problemas con estructura de bloques angulares. Programación multi-item. Resolución del dual Lagrangeano por generación de columnas. Maximización de funciones cóncavas y lineales a trozos. Programación de tripulaciones (Crew Scheduling). Descomposición de problemas de Programación Entera. Branchand-Price. 3 2. El problema de Cutting Stock El problema de corte de materiales o Cutting Stock fué uno de los primeros problemas resueltos mediante un algoritmo de generación de columnas (por Gilmore y Gomory hacia 1961), y hoy dı́a constituye uno de los problemas clásicos de la Investigación Operativa. Aquı́ vamos a ver la versión de dimensión 1, pero ideas análogas se pueden aplicar a dimensión 2 (corte de tableros y otras superficies) y dimensión 3 (empaquetado de objetos en contenedores). La cantidad de problemas de corte y empaquetado es tan variada que constituye ya un área propia dentro de los problemas de producción (ver por ejemplo H. Dyckhoff y U. Finke Cutting and Packing in Production and Distribution, Physica-Verlag 1992). En la siguiente página Web CaPaD Cutting and Packing at Dresden University of Technology, http://www.math.tu-dresden.de/ capad/, se mantiene actualizada mucha información sobre estos tipos de problemas. El problema surge en industrias dedicadas al papel, madera, hierro, textiles, plásticos, etc., y el de dimensión 1 se formula de la forma siguiente: se tiene en stock una cantidad muy grande de barras o tiras de longitud L, y se tiene que servir un pedido de deferentes piezas de menores longitudes. Supongamos por ejemplo que hay m tipos de piezas demandadas, y se requieren bi piezas de longitud li , i = 1, ..., m. Supondremos que li ≤ L, i = 1, ..., m, pues si no el problema no tiene solución. El problema es cómo cortar el material del stock para servir todas las piezas demandadas y de forma que la cantidad desperdiciada sea mı́nima. Para modelizar el problema Gilmore y Gomory introdujeron el concepto de patrón de corte. Un patrón es es una forma particular de cortar una barra del stock produciendo cierto número de las piezas demandadas. Entonces un patrón viene determinado por el vector a = (a1 , a2 , ..., am ), donde ai es el número de piezas de longitud li que produce ese patrón. Notar que este vector verifica las condiciones: m P li a i ≤ L i=1 ai ≥ 0 entero i = 1, ..., m Supongamos que se pudieran enumerar todos los patrones posibles, con ı́ndices j = 1, ..., n, y sea aij = el número de piezas de longitud li que produce el patrón j xj = el número de veces que se usa el patrón j Entonces el problema de minimizar el número de barras del stock para servir las piezas demandadas se puede formular como el siguiente problema de Programación Entera: Minimizar z= n P xj j=1 sujeto a n P aij xj ≥ bi i = 1, ..., m j=1 xj ≥ 0, entero 4 j = 1, ..., n Este problema de Programación Entera suele ser difı́cil de resolver fundamentalmente por dos razones: una es la presencia de variables enteras, y otra es que el número n de columnas o variables puede ser muy elevado. Por ejemplo, para una longitud L = 200 y demandas de m = 40 longitudes diferentes en el intervalo [20, 80] el número de patrones fácilmente está entre 10 y 100 millones. Sin embargo, el número de restricciones del sistema anterior es fijo, y es = m. Basandose en este hecho, Gilmore y Gomory dieron una solución a este problema, resolviendo, mediante un algoritmo de generación de columnas, la Relajación Lineal Minimizar z= n P xj j=1 n P sujeto a aij xj ≥ bi i = 1, ..., m j=1 xj ≥ 0 j = 1, ..., n Una vez resuelta la relajación lineal, es fácil obtener soluciones factibles (enteras). La forma más sencilla es redondear hacia arriba la solución, aunque hay otras formas de obtener soluciones factibles ligeramente mejores como se verá a continuación. Indicando por z el valor óptimo entero (desconocido), por zLP el valor óptimo de la relajación lineal y por z 0 el valor de una solución factible entera, se tienen las desigualdades básicas en Programación Entera para un problema de minimización: zLP ≤ z ≤ z 0 La diferencia z 0 − zLP es conocida como el hueco de dualidad (duality gap) y cuando es pequeña indica que la solución factible actual de valor z 0 está cerca del óptimo. Ejemplo 1 Suponemos un stock con longitud L = 100 y demandas de las siguientes piezas: 97 610 395 211 piezas piezas piezas piezas de de de de longitud longitud longitud longitud 45 36 31 14 En este ejemplo es fácil enumerar todos los patrones posibles, resultando n = 37 y la siguiente matriz A de patrones: j a1j a2j a3j a4j 1 2 0 0 0 2 1 1 0 1 3 1 1 0 0 4 1 0 1 1 5 1 0 1 0 6 1 0 0 3 7 1 0 0 2 8 1 0 0 1 5 9 1 0 0 0 10 0 2 0 2 11 0 2 0 1 12 0 2 0 0 13 0 1 2 0 14 0 1 1 2 j a1j a2j a3j a4j 15 0 1 1 1 16 0 1 1 0 17 0 1 0 4 18 0 1 0 3 19 0 1 0 2 20 0 1 0 1 21 0 1 0 0 22 0 0 3 0 23 0 0 2 2 24 0 0 2 1 25 0 0 2 0 j a1j a2j a3j a4j 27 0 0 1 3 28 0 0 1 2 29 0 0 1 1 30 0 0 1 0 31 0 0 0 7 32 0 0 0 6 33 0 0 0 5 34 0 0 0 4 35 0 0 0 3 36 0 0 0 2 37 0 0 0 1 26 0 0 1 4 Si resolvemos la relajación lineal del problema anterior para este ejemplo, eliminando las restricciones de xj enteros, se obtiene la siguiente solución óptima: x1 = 48,5; x∗10 = 105,5; x∗12 = 100,75; x∗13 = 197,5, y el resto 0, con zLP = 452,25. ¿Cómo obtener a partir de esta solución fraccionaria una solución entera?. Una solución factible (entera) se obtiene siempre redondeando hacia arriba, 0 0 0 0 dando en este caso la solución: x1 = 49; x10 = 106; x12 = 101; x13 = 198, con z 0 = 454. Se tienen entonces las desigualdades 452,25 ≤ z ≤ 454, y como z tiene que ser un entero, las desigualdades mejoradas 453 ≤ z ≤ 454. La solución heurı́stica se puede mejorar ligeramente de diferentes formas: redondeamos primero los x∗ hacia abajo obteniendo una solución que usa 450 barras del stock y produciendo 96 piezas de 45 cm. , 607 piezas de 36 cm., 394 piezas de 31 cm. y 210 piezas de 14 cm, y las piezas que faltan se pueden obtener fácilmente de otras 3 barras, ası́ que tendrı́amos servidas todas las piezas con 453 barras del stock. Como la relajación lineal tiene un valor de zLP = 452,25, esta solución de valor z = 453 es necesarianente óptima. En general se pueden aplicar esquemas de redondeo del tipo anterior para obtener soluciones óptimas o casi óptimas, salvando ası́ la dificultad de las variables enteras. Otra posibilidad es resolver el problema entero con las columnas que han sido generadas. 6 3. Algoritmo de generación de columnas para el problema Cutting Stock En esta sección vamos a resolver por generación de columnas la relajación lineal del problema, es decir el problema (PM) Minimizar z= n P xj j=1 n P sujeto a aj x j ≥ b j=1 xj ≥ 0 j = 1, ..., n donde aj , b son vectores columna de dimensión m. Este problema es llamado a veces el problema maestro (PM). Como el número n de variables xj (o columnas aj ) puede ser muy elevado, normalmente no podemos tener definido explı́citamente el problema maestro completo. Supongamos que se han generado un conjunto de columnas con ı́ndices G ⊆ {1, 2, ..., n} y considermos el Problema Maestro Restringido: P (PMR) Minimizar z = xj P j∈G sujeto a aj x j ≥ b j∈G xj ≥ 0 j∈G y se supone que este problema ha sido resuelto explı́citamente y que es factible. Sea x una solución óptima del problema anterior, y sea ω solución dual óptima, o vector de multiplicadores asociado a la base óptima. Extendiendo la solución con x = 0 si j ∈ / G se puede considerar x solución fatible para (PM). Para saber si x es óptima deberı́amos calcular los costes reducidos zj − cj = ωaj − cj = ωaj − 1 para todo j = 1, ..., n. En vez de esto bastarı́a calcular el coste reducido máximo, y esto llevarı́a al Subproblema: máx {ωaj } − 1 j=1,...,n Eliminando el término constante -1 y aplicando la caracterización de patrones vista anteriormente, el Subproblema se pone de la siguiente forma (donde ahora las variables son a1 , a2 , ..., am ): (Pω ) Maximizar sujeto a m P i=1 m P ωi ai li a i ≤ L i=1 ai ≥ 0 entero 7 i = 1, ..., m Este subproblema es un problema de tipo mochila (una sola restricción con variables enteras) que se puede resolver relativamente bien. Sea a una solución óptima del subproblema. Si ωa ≤ 1 (en la práctica ωa ≤ 1 + tol, donde tol es cierta tolerancia) entonces lasolución actual es óptima, mientras que si ωa > 1 la columna a generada deberá entrar en el Problema Maestro Restringido, y se reoptimiza mediante el método simplex revisado. Como conjunto inicial pueden tomarse los siguientes m patrones: para cada i = 1, ..., m el patrón i corta bL/li c piezas de tipo i , lo que darı́a como matriz de patrones inicial la matriz diagonal: bL/li c 0 0 0 0 bL/li c 0 0 B= ... ... ... ... 0 0 0 bL/li c Se tendrı́a entonces el siguiente procedimiento de generación de columnas para resolver la relajación lineal del problema de Cutting Stock: 1. Se selecciona un conjunto inicial de patrones, como el anterior. 2. Se resuelve el problema de Programación Lineal con los patrones actuales (Problema Maestro Restringido). 3. Usando los precios duales ω se resuelve el Subproblema, que es un problema de tipo mochila, con solución óptima a. 4. Si ωa > 1 entonces se añade la columna a generada al Problema Maestro Restringido, y se vuelve a la etapa 2. 5. Si ωa ≤ 1 ya tenemos la solución óptima del problema . El procedimiento está basado en la interacción entre el Problema Maestro Restringido (que genera un conjunto de precios ω), y el problema de la mochila (que genera una columna a), como se muestra en la Figura 1. A partir de la solución óptima del problema (PMR) final se obtiene una solución factible para el problema entero original mediante algún procedimiento de redondeo, o bien resolviendo el problema entero con las columnas generadas, produciendo una solución entera muy cercana a la solución óptima. 4. Propiedades de redondeo Un problema de Programación Entera con valor óptimo z y valor óptimo de la relajación lineal zLP se dice que tiene la propiedad de integralidad si z = zLP . Se dice que tiene la propiedad de redondeo hacia arriba (IRUP: Integer RoundUp Property) si se verifica que z = dzLP e. Y se dice que tiene la propiedad modificada de redondeo hacia arriba (MIRUP)si se verifica que z ≤ dzLP e + 1. Todos los ejemplos del problema de Cutting Stock de dimensión uno que se han resuelto verifican la propiedad MIRUP, pero este hecho no ha podido aún demostrado matemáticamente. Este es el motivo por el que en 1997 Scheithauer y Terno propusieran esta conjetura. 8 5. Programa AMPL para el problema Cutting Stock A continuación están los ficheros con el modelo, los datos de un ejemplo y los comandos para resolver un problema de Cutting Stock de dimensión 1. Modelo # ---- # MODELO CUTTING STOCK USANDO PATRONES # -------------param n integer >= 0, default 0; param m integer >=0; Longitud; #Número de patrones #Número de piezas distintas param #Longitud del stock param iter default 0; param tol; param n_usados default 0; set PATRONES := 1..n; set PIEZAS := 1..m; param param param param pedidos {PIEZAS} > 0; obtenidas{PIEZAS}; longitud{PIEZAS} > 0; a {PIEZAS,PATRONES} integer >= 0; param xheur{PATRONES}integer>=0; check {j in PATRONES}: sum {i in PIEZAS} longitud[i] * a[i,j] <= Longitud; param zLP; #------- # PROBLEMA MAESTRO ENTERO #--------------------- var X {PATRONES} integer >= 0; minimize Numero: sum {j in PATRONES} X[j]; subj to Demanda {i in PIEZAS}: sum {j in PATRONES} a[i,j] * X[j] >= pedidos[i]; # ----------------# SUBPROBLEMA MOCHILA #-------------------- 9 param precio {PIEZAS}; var patron {PIEZAS} integer >= 0; maximize Precio_total: sum {i in PIEZAS} precio[i] * patron[i]; subj to Long_Max: sum {i in PIEZAS} longitud[i] * patron[i] <= Longitud; Datos Datos para un ejemplo de 4 piezas: param tol:=1.e-5; param Longitud := 100; param m:=4; param: longitud pedidos := 1 45 97 2 36 610 3 31 395 4 14 211; Comandos En este script se resuelve primero la relajación lineal, obteniéndose ası́ la cota inferior, y después se obtienen dos soluciones heurı́sticas (enteras), una por redondeo entero directo, y otra resolviendo el problema entero con la matriz de patrones que se han ido generando. # # # # ---------------------------------------METODO DE GENERACION DE COLUMNAS DE GILMORE-GOMORY PARA EL CUTTING STOCK PROBLEM ---------------------------------------- # usar include c:\jsaez\prog_ampl\cutstock\cortar1.run; reset; model c:\jsaez\prog_ampl\cutstock\cortar1.mod; data c:\jsaez\prog_ampl\cutstock\cortar2.dat; option solver cplex; option solution_round 6; 10 option solver_msg 0; problem Maestro: X, Numero, Demanda; option relax_integrality 1; problem Mochila: patron,Precio_total,Long_Max; option relax_integrality 0; # Generación de m patrones iniciales for {i let let let }; in PIEZAS} { n:= n + 1; a[i,n] := floor (Longitud/longitud[i]); {i2 in PIEZAS: i2 <> i} a[i2,n] := 0; # Bucle principal repeat { solve Maestro; let iter:=iter+1; # display iter,Numero; # display X ; let {i in PIEZAS} precio[i] := Demanda[i].dual; solve Mochila; # display precio,patron; if Precio_total <= 1+tol then break; else { let n := n + 1; let {i in PIEZAS} a[i,n] := patron[i]; }; }; printf"\n**Solucion optima del problema Maestro relajado\n" ; printf"\nNumero de patrones generados = %d",n; printf"\nValor de la relajacion lineal = %.2f",Numero ; let zLP:=Numero; option display_transpose -10; printf"\nMatriz de patrones \n" ; display a; 11 display X ; printf"\n**Heuristica de redondeo\n "; let{j in PATRONES}xheur[j]:=ceil(X[j]); display xheur; printf"\nNumero de barras usadas = %d\n", sum{j in PATRONES}xheur[j]; printf "Desperdicio = %5.2f%%\n\n", 100 * (1 - (sum {i in PIEZAS} longitud[i] * pedidos[i]) / (Longitud * sum{j in PATRONES}xheur[j])) ; printf"\n**Segunda solucion entera heuristica\n\n"; option Maestro.relax_integrality 0; solve Maestro; display X; printf"\nNumero de barras usadas = %d\n",Numero ; printf "Desperdicio = %5.2f%%\n\n", 100 * (1 - (sum {i in PIEZAS} longitud[i] * pedidos[i]) / (Longitud * Numero)) ; printf"\nNumero de piezas obtenidas "; display{i in PIEZAS}sum{j in PATRONES}a[i,j]*X[j]; printf"\n**Cotas finales sobre el optimo\n\nCota inferior = %d", ceil(zl); printf"\tCota superior = %d\n\n",Numero; 12