Post on 30-Jun-2022
BCP
Una biblioteca para el desarrollo de algoritmos
tipo Branch-and-Cut-and-Price
BCP: A library for Branch-and-Cut-and-Price algorithm development
Kenny Angelo Martín Rodríguez
Departamento de Ingeniería Informática y Sistemas
Escuela Técnica Superior de Ingeniería Informática
Trabajo de Fin de Grado
La Laguna, 8 de julio de 2014
III
D. Jorge Riera-Ledesma, con N.I.F. 43788061-V, profesor adscrito al Departamento
de Ingeniería Informática y Sistemas de la Universidad de La Laguna
C E R T I F I C A
Que la presente memoria titulada:
“BCP: una librería para el desarrollo de algoritmos tipo Branch-and-Cut-and-
Price.”
ha sido realizada bajo su dirección por D. Kenny Angelo Martín Rodríguez, con
N.I.F. 42418153-N.
Y para que así conste, en cumplimiento de la legislación vigente y a los efectos
oportunos, firman la presente en La Laguna a 10 de junio de 2014.
Fdo: D. Kenny Angelo Martín Rodríguez Fdo: D. Jorge Riera-Ledesma
V
Agradecimientos
Para empezar, me gustaría agradecer a mi familia por haberme apoyado tanto y confiar
ciegamente en mí, a pesar de todas las adversidades que han podido surgir. También
quería agradecer a mis amigos, tanto a los ya conocidos como a los que he ido
conociendo a lo largo de mi carrera, por estar ahí en los malos momentos y en los
peores, aún más.
Por otro lado, quisiera reconocer la educación de D. Jorge Riera-Ledesma con la que se
ha dirigido a mí en todo el periodo de la realización del Trabajo de Fin de Grado. Así
como su capacidad de ver todo desde un lado positivo y calmado.
Asimismo, me gustaría dar las gracias a todas las personas, tanto profesores como
alumnos de la Universidad de La Laguna, e incluso ajenos a la Universidad.
Independientemente de si hemos compartido una relación positiva, gracias a todos ellos,
porque eso me ha ayudado a crecer como persona, a volverme cada vez más fuerte y
afrontar todos los problemas que se han ido interponiendo en mi camino de forma
persistente.
Por último, quiero dedicar la realización de este proyecto a mi madre, una persona
guerrera con un espíritu de superación increíble, que ha sido mi referente cada día para
luchar y conseguir los objetivos en mi vida. Gracias a ella he conseguido todo lo que
tengo hasta ahora y, por ello, se merece estas palabras y mucho más.
VII
Resumen
Las bibliotecas (del inglés library) son un conjunto de funciones codificadas en un
lenguaje de programación determinado que ofrece una interfaz bien definida para su
uso.
El objetivo de este trabajo ha sido el crear una biblioteca de clases en C++ que
implementa un algoritmo de tipo Branch-and-Cut-and-Price. Este tipo de algoritmos
utiliza técnicas de descomposición – como la de Benders o Dantzig-Wolfe – para
mejorar los algoritmos y ofrecer soluciones a problemas de gran tamaño. Se pretende
que nuestra biblioteca se componga (esté compuesta por) de un conjunto de objetos
adaptables, de forma que los métodos de las clases puedan ser modificados o
enriquecidos para adaptarse a las especificaciones de cada problema.
Palabras clave
Biblioteca, Branch-and-Bound, Branch-and-Cut, Branch-and-Price, Branch-and-Cut-
and-Price, descomposición Dantzig-Wolfe, problema de optimización, Programación
Lineal Entera Mixta
IX
Abstract
A library is a collection of functions, written in terms of a programming language,
which has a well-defined interface so that we can use them.
The main goal of this project is to create a library in C++, which implements a Branch-
and-Cut-and-Price algorithm. This kind of algorithms makes use of decomposition
techniques – such as Benders or Dantzig-Wolfe – in order to improve algorithm
performance and offer solutions for large problems. We expect our library to consist of
a bunch of adaptable objects in a way that class methods could be modified or enriched
to fit the requirements of each problem.
Keywords
Library, Branch-and-Bound, Branch-and-Cut, Branch-and-Price, Branch-and-Cut-and-
Price, Dantzig-Wolfe decomposition, optimization problem, Mixed Integer Linear
Programming
XI
Índice general
Agradecimientos ........................................................................ V
Índice general ........................................................................... XI
Capítulo I. Introducción 15
1. Introducción .......................................................................... 16
1.1 Descripción del proyecto ............................................................................... 16
1.2 Antecedentes ................................................................................................. 16
1.3 Programación Lineal ..................................................................................... 17
1.3.1 Programación Lineal Entera ................................................................. 17
1.3.2 Programación Lineal Entera Mixta ....................................................... 18
1.4 Branch-and-Bound ........................................................................................ 18
1.4.1 Aplicación en problemas de Programación Lineal Entera Mixta ........... 18
1.4.2 Branch-and-Cut y su aplicación en problemas de MILP........................ 20
1.5 Branch-and-Price........................................................................................... 22
1.5.1 Branch-and-Cut-and-Price y su aplicación en problemas de MILP........ 22
1.6 Métodos de descomposición .......................................................................... 22
1.6.1 Descripción matemática ....................................................................... 23
1.6.2 Interpretación descriptiva ..................................................................... 25
Capítulo II. Descripción de la Biblioteca 27
2. Descripción de la Biblioteca ................................................. 28
2.1 Estructura ...................................................................................................... 28
2.2 Funcionamiento ............................................................................................. 28
2.2.1 Descriptor de variables y restricciones .................................................. 28
2.2.2 Árbol de búsqueda................................................................................ 30
2.2.3 Nodo .................................................................................................... 30
2.2.4 Separador ............................................................................................. 32
2.2.5 Pricer ................................................................................................... 32
XII
Capítulo III. Estudio Computacional 35
3. Estudio Computacional ........................................................ 36
3.1 Descripción del modelo ................................................................................. 36
3.1.1 Ejemplo gráfico del modelo .................................................................. 36
3.2 Formulaciones del modelo ............................................................................. 37
3.2.1 Notación matemática ............................................................................ 37
3.2.2 Primera formulación ............................................................................. 38
3.2.3 Segunda formulación ............................................................................ 39
3.2.4 Algoritmo Branch-and-Cut-and-Price ................................................... 40
3.3 Análisis global del modelo ............................................................................ 41
3.4 Análisis para una instancia del modelo .......................................................... 42
Capítulo IV. Conclusiones y Trabajos Futuros 45
4. Conclusiones y Trabajos Futuros ........................................ 46
4.1 Resumen ....................................................................................................... 46
4.2 Trabajos Futuros............................................................................................ 46
Capítulo V. Summary and Conclusions 49
5. Summary and Conclusions ................................................... 50
5.1 Summary and Conclusions ............................................................................ 50
5.2 Future Work .................................................................................................. 50
Apéndice A. Códigos ............................................................... 51
A. 1 Variables (Código fuente) ......................................................................... 51
A. 2 Restricciones (Código fuente) ................................................................... 51
A. 3 Árbol de ramificación (Archivo cabecera) ................................................. 53
A. 4 Árbol de ramificación (Código fuente) ...................................................... 53
A. 5 Nodo (Archivo cabecera) .......................................................................... 54
A. 6 Nodo (Código fuente) ............................................................................... 54
A. 7 Separador (Archivo cabecera) ................................................................... 55
A. 8 Separador (Código fuente) ........................................................................ 56
A. 9 Pricer (Archivo cabecera) ......................................................................... 56
A. 10 Pricer (Código fuente)............................................................................... 57
XIII
Bibliografía ................................................................................ 59
15
Capítulo I. Introducción
16
1. Introducción
1.1 Descripción del proyecto
Los problemas de Programación Lineal Entera complejos requieren en ocasiones de
una gran cantidad de recursos computacionales para su resolución. Por ello, suelen ser
abordados a través de transformaciones que permitan descomponer el problema en
subproblemas más pequeños. Aunque la resolución del problema principal implica
también la resolución de los subproblemas, estos últimos requieren menos recursos.
Una de estas transformaciones se conoce como descomposición de Dantzig-Wolfe. Esta
transformación elimina un conjunto de restricciones del programa lineal, y las
transforma en columnas o variables. Sin embargo, el número de columnas obtenidas
crece de forma exponencial con respecto al número de restricciones eliminadas. La
inclusión de todas las columnas haría la resolución del problema inabordable. Por ello,
las columnas se introducen de forma dinámica en el programa lineal a medida que éstas
son requeridas por el resolutor lineal. La generación lineal de columnas se conoce como
Pricing.
Cuando las variables originales del problema son enteras, la resolución del problema
demanda la utilización de un árbol de ramificación que divida el espacio de decisión de
acuerdo con los posibles valores de las variables. La división del espacio da lugar a un
árbol de decisión con un número exponencial de ramas con respecto al número de
variables, así que se deben establecer criterios para eliminar aquellas ramas que
conduzcan a soluciones no prometedoras. La utilización de un árbol de ramificación
para la resolución de un problema de Programación Lineal Entera se denomina Branch-
and-Bound.
La técnica que combina la generación dinámica de columnas con un árbol de
ramificación se conoce como Branch-and-Price. Si además de generar columnas, se
generan restricciones – también dinámicamente – dicha técnica se denomina Branch-
and-Cut-and-Price.
El manejo dinámico de variables, así como la gestión del árbol de ramificación, puede
llevarse a cabo de forma automatizada en un entorno de resolución de problemas de
Programación Lineal Entera. El objeto de este proyecto es la elaboración de una
biblioteca de clases, escrito en C++, que implemente un algoritmo de tipo Branch-and-
Cut-and-Price, facilitando la resolución de estos problemas de Programación Lineal
Entera Mixta mediante el método de descomposición de Dantzig-Wolfe.
1.2 Antecedentes
El desarrollo de bibliotecas para la resolución de problemas lineales mediante las
técnicas Branch-and-Cut, Branch-and-Price y Branch-and-Cut-and-Price ha sido el
objeto de trabajo de diversos proyectos. Entre ellos, cabe destacar el proyecto COIN-OR
[1], desarrollado por International Business Machines y que se basa en COIN-OR LP
Solver: un resolutor lineal; Open Solver Interface: una interfaz que independiza la
17
resolución de los problemas de su optimizador línea; y Branch-and-Cut-and-Price: una
biblioteca que reúne herramientas de programación para la resolución de problemas
mediante esta técnica.
Otra herramienta similar es Solving Contraint Integer Programs [2], desarrollada por el
Zuse Institute Berlin, que consiste en una biblioteca y un marco de trabajo para la
resolución de programas lineales enteros.
Estas dos bibliotecas han sido sometidas a diversas evaluaciones a través de programas
de ejemplo y han superado las expectativas de calidad. La herramienta que se pretende
desarrollar persigue un objetivo similar. Aunque, en lugar de intentar conseguir un
producto final, se pretende que se introduzcan las técnicas de resolución y se
comprendan a través de la implementación de esta biblioteca.
1.3 Programación Lineal
La Programación Lineal [3] es una técnica matemática surgida en el siglo XX. Consiste
en una serie de métodos y procedimientos que permiten modelar y resolver problemas
de optimización, cuya característica principal es maximizar o minimizar una función
lineal – llamada función objetivo – y cuyas variables se encuentran sujetas a una serie
de restricciones; dichas restricciones serán representadas por un sistema de inecuaciones
lineales.
1.3.1 Programación Lineal Entera
En algunos casos, se necesita que la solución óptima, es decir, aquella que maximice – o
minimice, según convenga – el valor objetivo, esté compuesta por algunas variables
restringidas a tomar valores enteros. Se habla entonces de Programación Lineal Entera
[4].
Estos problemas se formulan de la misma manera que los de Programación Lineal, pero
agregando la condición de que al menos una de las variables tomará valores enteros.
Analizando las posibles alternativas de que estas variables tomen un valor entero u otro
en un entorno, a partir de una solución obtenida considerando las variables reales,
podemos resolver este problema.
Los problemas de Programación Lineal Entera van a permitir modelar muchos más
casos que los de Programación Lineal, pero con la contrapartida de suponer un coste
computacional mucho mayor para su resolución.
La causa de este incremento de cómputo se debe a que desaparece la propiedad que
poseen los problemas de Programación Lineal de que al menos una solución óptima del
problema se encuentra en un punto extremo. En los problemas de Programación Lineal
Entera, los conjuntos pueden estar definidos a trozos y ser menos convexos, con lo que
la idea de punto extremo se descarta.
Por todo ello, se hace necesario usar algún método de diseño de algoritmos, como la
técnica Branch-and-Bound [5], que parte de añadir nuevas restricciones para cada
variable de forma que, cuando vuelva a ser evaluado, lleve al óptimo entero.
18
1.3.2 Programación Lineal Entera Mixta
Ya se ha visto que la Programación Lineal Entera define aquel modelo donde las
variables son números enteros no negativos. En situaciones reales, se puede hacer frente
a problemas de decisión (es decir, decisiones de sí o no), que pueden representarse con
variables denominadas binarias. Cuando sólo es necesario que algunas de las variables
sean enteras y el resto continuas, el modelo recibe el nombre de problema de
Programación Lineal Entera Mixta [6]; esta clasificación incluye también aquellos
modelos que se compongan tanto de variables enteras no negativas y continuas, como
de variables binarias.
1.4 Branch-and-Bound
El método de Branch-and-Bound es una variante de la técnica de Backtracking,
mejorado sustancialmente pare resolver problemas de optimización.
Se interpreta como un árbol de soluciones donde el recorrido no tiene por qué ser en
profundidad – como ocurría en backtracking – sino que se sigue una estrategia de
ramificación. Además, se utilizarán técnicas de poda para detectar en qué ramificación
ya no son óptimas las soluciones dadas (a través de estimaciones de cotas para cada
nodo). En caso de no ser óptima, dicha ramificación se podará para no malgastar
recursos y procesos en casos que se alejan de la solución óptima.
El algoritmo de Branch-and-Bound sigue, básicamente, el procedimiento siguiente:
Se introducen los hijos del nodo raíz en una Lista de Nodos Vivos (LNV), que
contendrá los nodos pendientes de expandir por el algoritmo. Según como estén
almacenados los nodos en la LNV, el recorrido se realizará de una forma u otra,
dando lugar a la estrategia de ramificación.
Se extrae uno nodo de la LNV.
Vemos si el nodo es factible (propone una solución válida) y óptimo (se compara con
la mejor solución encontrada de momento).
Si ese nodo es solución, se almacena; si no lo es, se introduce en la lista los hijos de
dicho nodo.
1.4.1 Aplicación en problemas de Programación Lineal
Entera Mixta
A continuación, se explicará el funcionamiento de la técnica Branch-and-Bound
enfocada a problemas MILP (Mixed Integer Linear Programs [6]), partiendo del
algoritmo básico y utilizando técnicas avanzadas para su resolución.
El algoritmo de Branch-and-Bound resuelve un MILP dividiendo el espacio de
búsqueda y generando una secuencia de subproblemas; este espacio de búsqueda se
puede representar por un árbol. Cada nodo del árbol corresponde a un subproblema que
deriva de los problemas previos a éste y que se encuentran en el camino que lleva del
19
nodo en cuestión a la raíz del árbol. El subproblema ( ) asociado a la raíz es
idéntico al problema original, al cual llamamos MILP.
La relajación lineal ( ) de puede ser definida como:
(1)
El algoritmo genera subproblemas a lo largo del árbol usando el siguiente esquema.
Considérese , la solución óptima de , que suele calcularse utilizando el algoritmo
simplex dual. Si es entero , entonces es una solución óptima para MILP.
Supóngase que , tal que no es entero. En tal caso, el algoritmo define dos
nuevos subproblemas ( y ), descendientes del nodo padre . El
subproblema es idéntico al padre, excepto por la restricción adicional
(2)
,
y el subproblema es idéntico también, exceptuando la restricción adicional
(3)
.
La notación representa el entero más grande que es menor o igual a y, y la notación , el entero más pequeño que es mayor o igual a y. Ambas restricciones pueden ser
manejadas modificando las cotas de la variable , en lugar de añadir explícitamente las
restricciones a la matriz. Los dos nuevos subproblemas no tienen a como solución
factible, pero la solución entera del problema MILP debe satisfacer una de las dos
restricciones (la de o ). Por tanto, ambos subproblemas se definen como
nodos activos del árbol de Branch-and-Bound y la variable se denomina variable de
ramificación.
En el siguiente paso, el algoritmo escoge uno de los nodos activos e intenta resolver la
relajación lineal asociada a dicho subproblema. La relajación podría ser no factible, en
cuyo caso el subproblema se descarta. En cambio, si puede ser resuelto y la solución es
factible entera (es decir, es entero ), entonces el valor objetivo proporciona la
cota superior para el problema de minimización MILP; si la solución no es factible
entera, nuevamente se definen dos nuevos subproblemas. Este proceso continúa iterando
hasta que ya no haya nodos activos. Llegados a este punto, la mejor solución entera
encontrada es la solución óptima para el problema MILP; si no se ha encontrado una
solución entera, entonces se dice que el problema MILP no tiene una solución entera
factible.
20
1.4.2 Branch-and-Cut y su aplicación en problemas de
MILP
En este apartado se hablará de la técnica de Branch-and-Cut [7], que se basa en el
algoritmo de Branch-and-Bound con la inclusión de planos de corte. Así, se describe
una técnica que nos permite obtener óptimas para problemas formulados mediante un
modelo de Programación Lineal Entera, usando algoritmos simplificados. Se puede
obtener una descripción detallada de las técnicas en libros de autores como Jünger y
Thienel [8], Padberg y Rinaldi [9] o Thienel [10].
La relajación lineal de un problema lineal entero (IP) es el problema lineal obtenido del
IP, eliminando las restricciones de integralidad. Por lo tanto, el valor óptimo de la
relajación en el caso de minimización es una cota inferior del valor óptimo del
problema lineal entero. O sea, .
La técnica Branch-and-Cut es una metodología que permite resolver problemas lineales
con un número exponencial o, al menos, muy grande de restricciones. Estas
restricciones generalmente vienen dadas de forma implícita. Véase la Figura 1 para una
ilustración de esta técnica.
Denotemos por ∏ un problema de optimización genérico. Este problema es una
estructura de datos que consta de una lista de restricciones activas , la lista de
variables activas , el vector de optimización , el vector solución de la relajación
lineal actual , el valor objetivo de la relajación lineal actual de acuerdo con y
el estado de la relajación lineal.
En un esquema básico de esta técnica para un problema de minimización, una lista
de subproblemas es inicializada con la relajación lineal del problema ∏. El valor de la
mejor solución encontrada hasta el momento es almacenado como cota superior global
. Cada iteración selecciona un subproblema de la lista de problemas pendientes. La
cota inferior es calculada para cada subproblema resolviendo la relajación lineal con el
conjunto actual de restricciones activas y variables activas. Por otro lado, el algoritmo
intenta mejorar la cota superior mediante una heurística basada en la solución obtenida
en la relajación lineal actual. El cálculo de esta cota ofrece una garantía de la distancia a
la optimalidad, es decir, para problemas de gran tamaño – y/o duros – el algoritmo de
Branch-and-Bound permite obtener una buena solución (posiblemente óptima) e indica
cuán lejos está la solución de llegar a ser óptima.
Un subproblema es podado de la lista si se cumple alguna de las siguientes condiciones:
se obtiene una solución factible entera a partir de la relajación lineal
la relajación lineal es no factible
la cota inferior local es mayor que la cota superior global.
Si la relajación lineal actual no fuera factible, entonces se lleva a cabo un intento por
generar nuevas desigualdades válidas violadas por la solución fraccional actual,
utilizando procedimientos de separación. Si se encuentra esa desigualdad, se añade al
programa lineal, esperando que nos lleve a una solución diferente menos fraccional.
Esto se repite hasta que se encuentra una solución entera o no se encuentran más planos
de corte.
21
Figura 1 Técnica de Branch-and-Cut
22
1.5 Branch-and-Price
La técnica de Branch-and-Price es un método híbrido entre Branch-and-Bound y el
método de Generación de Columnas [11]. Para cada nodo en el árbol de búsqueda, se
pueden añadir columnas a la relajación lineal. Al comienzo del algoritmo, se excluye un
conjunto de columnas de la relajación para reducir la complejidad computacional y los
requisitos de memoria; luego, las columnas se añaden a la relajación conforme se vayan
necesitando. Esto se hace posible ya que, en problemas de gran tamaño, la mayor parte
de las columnas tendrán un valor igual a cero en cualquier solución óptima. En
consecuencia, la inmensa mayoría de las columnas son irrelevantes y, por tanto,
descartables para la resolución del problema.
1.5.1 Branch-and-Cut-and-Price y su aplicación en
problemas de MILP
En este apartado se hablará de la técnica de Branch-and-Cut-and-Price para la
resolución de problemas de MILP. En muchos problemas MILP, suele haber algún tipo
de estructura asociada que pueda ser explotada normalmente por descomposición. Ya se
ha visto en apartados anteriores que cuando la relajación en cada nodo del árbol de
Branch-and-Bound es resuelta por generación de columnas, se habla de Branch-and-
Price. Opcionalmente, se pueden añadir métodos de planos de corte para reforzar la
relajación lineal; así surge la técnica de Branch-and-Cut-and-Price.
Se hace necesaria la reformulación del problema utilizando alguna técnica de
descomposición, en este caso la técnica de descomposición de Dantzig-Wolfe. La
relación entre el problema original y las reformulaciones permite entender cómo deben
ser formulados los planos de corte y cómo deben tomarse las decisiones de
ramificación, a la vez que mantenemos los problemas de generación de columnas
manejables.
La descomposición y reformulación de programas enteros mixtos [12] supone un
enfoque típico para obtener relajaciones más fuertes y reducir la simetría. Esto suele
conllevar la adición dinámica de variables (columnas) y/o restricciones (planos de corte)
al modelo. Por trivial que parezca esta técnica, hay que tener claro que tanto reforzar la
relajación a través del método de planos de corte como realizar una ramificación sobre
variables fraccionales interfieren en la generación de columnas y puede hacer
desaparecer las ventajas de la descomposición si no se procede con precaución.
1.6 Métodos de descomposición de Dantzig-Wolfe
Una vez descritos los diferentes algoritmos utilizados, procederemos a la descripción
del método de descomposición de Dantzig-Wolfe [13].
La motivación que lleva a utilizar métodos de descomposición surge cuando el tamaño
del problema, en término de número de variables y restricciones, hace su resolución
muy pesada. Para problemas pequeños o de tamaño medio, se pueden utilizar técnicas
convencionales de Programación Lineal; pero resulta imprescindible mejorar la técnica
utilizada en caso de que se quiera resolver problemas de grandes dimensiones. A
23
continuación, se describirá brevemente la técnica de la descomposición de Dantzig-
Wolfe.
1.6.1 Descripción matemática
A continuación, se procederá a la descripción matemática del Método de
Descomposición de Dantzig-Wolfe [13]. Se ha extraído del manual sobre Programación
Matemática [14]. Supóngase el problema lineal:
(4)
donde y son divisibles en la forma:
(5)
entonces el problema puede verse como:
(6)
Considérese el poliedro , y asúmase que son
sus puntos extremos y , sus direcciones extremas. Se asume también que
, ya que de lo contrario es no factible. Entonces, en virtud del teorema de
Caratheódory [14], cualquier punto (si existe alguno) es identificable con
valores y tales que
,
y , y . Por tal caracterización, el
problema es también reformulable como:
(7)
24
Esta reformulación del problema es conocida como problema maestro (completo).
Dado que los problemas y son equivalentes, ¿cuál es la ventaja de tal
reformulación? Al pasar desde hasta las filas se han sustituido por una
única (fila de convexidad), a costa de aumentar el número de columnas
hasta otro número extremadamente enorme. Debemos notar sin embargo que, usando el
simplex revisado para la resolución del problema maestro, no es necesario disponer de
todas las columnas en cada iteración. Véase ahora como proceder a la generación de
columnas (puntos/direcciones extremos de ).
Supóngase que se conocen algunas columnas de , es decir, que se dispone del
problema maestro relajado . Sin pérdida de generalidad, se puede asumir que
estos problemas maestros relajados son siempre factibles, añadiendo una variable
artificial con costo muy grande; si al final del proceso se observa que el valor objetivo
óptimo del último depende de este costo muy grande, entonces es no factible.
Si al resolver resulta no acotado, entonces el problema maestro completo (y en
consecuencia ) es no acotado (basta ampliar la dirección de descenso para con
ceros en las columnas en para crear una dirección de descenso para ). En otro caso,
sea un vector de variables duales óptimo para (las componentes del vector
están asociadas a las filas de
y a la fila de
convexidad). Entonces, usando las condiciones de holguras complementarias, el vector
será una solución dual óptima para cuando verifique:
(8)
esto es, cuando:
(9)
Para estudiar esto basta resolver:
(10)
Tras ello, hay que ver si tienen un valor objetivo óptimo no negativo. En tal caso, el
método del simplex revisado sobre ya puede detenerse porque tiene su solución
25
óptima; en caso contrario, se tiene una variable o que conviene que entre en la
base actual, y se prosigue haciendo iteraciones con el simplex revisado. Nótese
finalmente que la resolución de los dos problemas anteriores queda resuelta aplicando el
método del simplex al siguiente problema, conocido como subproblema de :
(11)
Esto conduce a un algoritmo iterativo en el que, iteración tras iteración, se va
enriqueciendo el problema máster con los cortes obtenidos de la resolución del
subproblema.
1.6.2 Interpretación descriptiva
De forma resumida, se podría presentar el funcionamiento de este método de
descomposición en una serie de pasos. Estos pasos no tienen por qué definirlo
completamente, ya que hay variaciones de la técnica.
1. Empezamos con una solución factible del problema maestro restringido,
formulando nuevas funciones objetivo para cada subproblema de forma que
éstas ofrezcan una mejora del valor objetivo actual del programa maestro.
2. Dadas estas nuevas funciones objetivo, los subproblemas se vuelven a resolver.
Por cada subproblema, el problema maestro recibe un valor óptimo.
3. El maestro incorpora una, o todas, las nuevas columnas generadas por las
soluciones de los subproblemas, basándose en la habilidad de cada columna de
mejorar el valor objetivo.
4. Se realizan tantas iteraciones del algoritmo simplex como nuevas columnas se
hayan añadido.
5. Si la función objetivo mejora, se vuelve a repetir todo el proceso.
6. Criterio de parada: ocurre cuando ninguna nueva columna de los subproblemas
mejora el valor objetivo del problema maestro.
27
Capítulo II. Descripción de la Biblioteca
28
2. Descripción de la Biblioteca
Ya se ha hablado de las nociones teóricas necesarias para entender el funcionamiento de
la biblioteca que se pretende realizar. Por tanto, el siguiente paso es describir el modelo
concreto de dicha biblioteca, incluyendo su estructura, así como el funcionamiento de
cada parte que la compone.
2.1 Estructura
Nuestra biblioteca se compone de cinco partes principales:
Descriptor de variables y restricciones, el cual definirá la familia de variables que
van a intervenir en el problema y, por ende, se encargará de establecer las
restricciones y el problema en general.
Árbol de búsqueda, que se encargará de gestionar los nodos.
Nodo, empezando por el nodo raíz y que, junto al resto, conforman el árbol de
búsqueda. Tendrá la configuración del problema de MILP asociado, específico para
cada nodo.
Separador, que se encargará de añadir dinámicamente, si fuera preciso, las filas
necesarias – ó también llamadas restricciones – al nodo que tiene como solución del
problema de MILP asociado, una solución fraccionaria.
Pricer, análogo al separador, introducirá columnas adicionales a través de un
algoritmo de generación dinámica de columnas.
2.2 Funcionamiento
2.2.1 Descriptor de variables y restricciones
Como se ha citado previamente, el descriptor de variables define el modelo del
problema que se va a abordar, estableciendo el conjunto de variables que se pretende
usar y las restricciones asociadas a éstas. Para poder explicar el funcionamiento de la
biblioteca que se ha diseñado, es imprescindible tener un modelo de ejemplo que, en
resumidas cuentas, será traducido en líneas de código. De esta manera, tenemos el
siguiente modelo:
(12)
(13)
29
(14)
(15)
(16)
(17)
(18)
(19)
(20)
En dicho modelo, hay dos familias de variables: las variables del problema Maestro y
las variables del subproblema. El conjunto de variables y , que corresponden a las
restricciones (18) y (19), pertenecen al subproblema; mientras que el resto es parte del
problema Maestro.
El fichero “variables.cc” (Véase Apéndice A.1) describe cómo introducir las variables
del modelo utilizando la biblioteca BCP. El procedimiento consiste simplemente en
rellenar los elementos de un array de objetos (que hace referencia a la
descripción de variables). Estas variables se introducen en un vector, insertando primero
el conjunto de variables , después el conjunto y, por último, el conjunto
En los campos del objeto se introduce los siguientes valores:
vt = tipo de variable, que puede ser binaria (BCP_template::_BINARY_) o continua
(BCP_template::_CONTINUOUS_)
st = indica si la variable es del problema Maestro
(BCP_template::_MASTER_PROBLEM_) o del subproblema
(BCP_template::_SUBPROBLEM_)
c = valor objetivo de la variable
lb = cota inferior de la variable
ub = cota superior de la variable
30
Por otro lado, el fichero “restricciones.cc” (Véase Apéndice A.2) corresponde con la
introducción de un conjunto de restricciones que conforma el problema. Se introduce
cinco familias de restricciones, separadas por comentarios. Cada una viene descrita por
un vector disperso compuesto de componentes , que hace referencia al índice de
la variable que forma parte de la restricción, y , que representa el coeficiente de
cada uno de estos índices. Una vez descrita, se crea la fila con la información asociada y
se añade al pool de restricciones.
2.2.2 Árbol de búsqueda
La clase BCP_branching_tree representa un árbol de ramificación. Dicho árbol,
optimiza cada nodo, introduce de forma dinámica restricciones y fija en cada uno de sus
nodos una variable entera a su límite superior o inferior. El árbol de ramificación
implementa recorridos en amplitud, profundidad, y recorrido Best Bound. El criterio de
selección de variable para realizar la ramificación se basa en aquella más próxima a 0.5
en el caso de variables binarias.
No obstante, a pesar de contar con todas estas estrategias de base, el usuario de la clase
puede redefinir cada procedimiento a fin de confeccionar las estrategias que mejor se
adapten al problema específico que se esté abordando.
Los archivos “BCP_branching_tree_t.hpp” (Véase Apéndice A.3) y
“BCP_branching_tree_t.cpp” (Véase Apéndice A.4) representan la clase gestora del
árbol de ramificación. Esta clase dispone de una lista enlazada de nodos que almacena
tanto los elementos por procesar ( ), como la lista de los ya procesados
(
El procedimiento process_tree extrae un elemento de la lista de nodos pendientes de ser
procesados e invoca al procedimiento optimize del nodo. Dicho procedimiento resuelve
el programa lineal e introduce las filas y columnas que sean necesarias.
Una vez hecho esto, se comprueba si la cota inferior obtenida es inferior a la mejor cota
superior guardada hasta el momento. Si es así y se encuentra alguna variable
fraccionaria, se invoca al procedimiento do_branch, que se encarga de seleccionar la
variable por la cual ramificar. Esta ramificación consiste en crear dos nodos a partir de
la variable fraccionaria e introducir ambos en . A continuación, se almacena el
nodo ya procesado en la lista de nodos procesados.
2.2.3 Nodo
Se trata de una de las clases más importantes, puesto que gestiona las operaciones del
nodo en el árbol de ramificación. La clase BCP_branching_node representa cada
subproblema asociado a un programa lineal entero mixto con alguna de estas variables
enteras fijadas. La descripción del programa lineal se encuentra dentro un atributo del
tipo BCP_node_solver.
La fijación de variables se consigue cuando, durante el proceso de ramificación, el
método do-branch de la clase BCP_branching_tree crea dos nodos, subdividiendo el
31
espacio de solución, e introduce una restricción sobre la variable fijada en cada uno de
los nodos resultantes.
La clase BCP_branching_node dispone de un método de activación. El propósito de este
método es reconstruir y activar un programa lineal a partir de la información guardada
de forma comprimida por el árbol de ramificación correspondiente al programa lineal de
su nodo padre. Una vez reconstruido el nodo, se añade la restricción que se ha
introducido para fijar la variable asociada a dicho nodo.
Finalizada la activación, la clase gestora del árbol invoca el método optimización. Este
método resuelve el programa lineal a través de un resolutor linear comercial; en esta
primera versión sólo es posible utilizar CPLEX. Una vez hecho esto, se invoca al
método de generación de filas (restricciones) y el de generación de columnas.
Se puede entender esta descripción con el archivo “BCP_branching_node_t.hpp” (Véase
Apéndice A.5) y el archivo “BCP_branching_node_t.cpp” (Véase Apéndice A.6). Entre
los atributos reseñables se encuentra:
Un puntero a la estructura dualSolution_t* dualSolution_, en la que se almacena la
solución del programa lineal, en otras palabras, almacena el valor de las variables
primales, duales y de los costos reducidos de las mismas.
El atributo nodeSolver_t* nodesolver_ almacena un puntero al resolutor lineal
(CPLEX, como antes se citaba).
El atributo branching_node_t** children_ es un array con los nodos hijos (que
suelen ser 2).
basic_colDataNodePool_t nodeCols_ es el conjunto de columnas utilizadas en este
nodo.
rowDataNodePool_t nodeRows_ es el conjunto de filas utilizadas en este nodo.
rowDataProblemPool_t& problemRows_ es el conjunto de filas generadas por todos
los nodos en este momento.
basic_colDataProblemPool_t& problemCols_ es el conjunto de columnas generadas
hasta el momento de forma global.
primal_heuristic_t& heuristic_ es una referencia al objeto que calcula las cotas
superiores a partir de heurísticas definidas por el usuario.
statistics_t& statistics_ almacena las estadísticas del problema.
problemInfo_t& problemInfo_ almacena información específica del problema.
problemSolution_t& solution_ guarda la mejor solución factible encontrada hasta el
momento.
pricer_t& pricer_ es un objeto generador de columnas.
separator_t& separator_ es un objeto generador de filas.
Por otro lado, en cuanto al procedimiento más importante que es optimize, se puede
hacer una descripción más profunda:
1. Se resuelve el programa lineal. A partir de este método se actualiza la
solución parcial obtenida hasta el momento.
32
2. Si el número de filas generadas es cero, entonces se invoca al generador de
columnas, que las añadirá al problema lineal.
3. Análogamente, si el número de columnas generadas es cero, entonces se
invoca el generador de filas; si se encuentras nuevas filas, se añaden al
problema lineal.
4. El procedimiento continúa iterando mientras se encuentren nuevas filas o
columnas que añadir.
2.2.4 Separador
En el transcurso de un proceso iterativo, se determina si es necesario introducir una
restricción dinámica, mediante el algoritmo de separación específico que hay definido el
usuario en la clase BCP_separator.
Este método lleva a cabo un algoritmo definido por el usuario de la biblioteca que, a
partir de una solución fraccionaria, determina si es necesario introducir una restricción.
Si así fuera, introduciría la restricción asociada a la solución parcial y resolvería
nuevamente el programa lineal.
Análogo a la explicación de la clase nodo, se hablará a continuación en referencia a los
archivos “BCP_branching_separator_t.hpp” (Véase Apéndice A.7) y
“BCP_branching_separator_t.cpp” (Véase Apéndice A.8), para poder entender de una
forma más clara la biblioteca.
Como se ha comentado, se trata de la clase generadora de restricciones, que deriva de la
clase separator_t. En ella se debe implementar dos métodos virtuales:
El método separate, que debe encontrar restricciones violadas por una solución
descrita por el vector e introducirlas en el pool de restricciones (row pool). El
contenido del método no es relevante en sí, pues depende enormemente del problema
que se quiera definir.
El procedimiento feasible analizará una solución y determinará si hay alguna
restricción violada; si no existiera ninguna violación de restricciones, entonces la
solución será factible.
2.2.5 Pricer
Un proceso similar es efectuado por el módulo de generación de columnas. Una vez que
se ha determinado que no es necesario introducir una nueva restricción se evalúa la
posibilidad de introducir columnas adicionales a través de un algoritmo de generación
de columnas o pricing. En tal caso se introducirían de forma dinámica, y, de forma
similar al proceso de separación de restricciones, se resolvería nuevamente el programa
lineal.
Este procedimiento iterativo de generación dinámica de filas y columnas se repite
iterativamente hasta que no se detecte nuevas restricciones o nuevas columnas. Es
entonces cuando se da por concluida la optimización del nodo, se actualizan las cotas
33
inferiores y superiores del mismo y se invoca al procedimiento de ramificación si
existiesen variables con valor fraccionario.
Aquellas soluciones enteras que no requieran la introducción de restricciones dinámicas
ni columnas adicionales, actualizarán la cota superior (global) del problema. Por otro
lado, el valor objetivo de la última optimización en el nodo dará lugar a la cota inferior
del mismo. Una vez actualizada la cota superior, se eliminarán aquellos nodos cuya cota
inferior supere o iguale la cota superior global.
Los ficheros que se adjuntan (Véase Apéndice A.9 y A.10) ilustran cómo construir un
generador de columnas a partir de la biblioteca BCP que se ha creado.
En primer lugar, se crea un objeto derivado de la clase pricer_t. Este objeto tiene un
procedimiento virtual, llamado generate_columns, que debe ser implementado en la
clase hija.
Dentro del constructor se establecen los valores de aquellos atributos específicos del
generador de columnas y, dentro del método generate_columns, se describe la llamada a
los algoritmos de generación dinámica de columnas (que en este caso se llama ). Una
vez generadas, se introducirán en el pool y se tomarán en cuenta en el programa
principal.
35
Capítulo III. Estudio Computacional
36
3. Estudio Computacional
A continuación, se hará un estudio computacional del proyecto que se quiere realizar.
Dado que se ha desarrollado una biblioteca de clases, se ha tenido que implementar un
problema específico para poder realizar este estudio.
3.1 Descripción del modelo
Esta sección ofrece una descripción informal del problema abordado para el estudio. Se
trata del Problema de Buses de Escuela, en el que dado un conjunto de localizaciones
conectadas entre sí por carreteras – que actuarán como paradas potenciales de bus–, hay
un conjunto de usuarios deseando llegar a un destino común; dichos usuarios pueden
llegar a alguna(s) de las paradas potenciales a pie. Se posee un depósito central que
opera una flota de buses con la misma capacidad, cuyo propósito es llevar a los usuarios
al destino. El problema considera dos tipos de coste: un coste de ruta, relacionado con la
longitud del camino que conecta dos paradas potenciales, y un coste de asignación,
asociado a la distancia caminada por cada usuario a cada parada potencial. Este
problema tiene como objetivo asignar a cada usuario una parada potencial y diseñar un
conjunto de rutas – que lleguen al destino – que visite todas las paradas con usuarios
asignados, de tal forma que el número total de usuarios asignado a las paradas que visita
cada ruta no exceda la capacidad del vehículo.
La suma de la longitud de las rutas y de los costes de asignación debe minimizarse. Hay
que resaltar que no todas las paradas deben ser visitadas, pero todos los usuarios sí
deben ser recogidos por los vehículos.
3.1.1 Ejemplo gráfico del modelo
En la Figura 1, se puede apreciar un ejemplo gráfico para una instancia específica del
problema con diferentes valores para la restricción de recursos.
Esta imagen muestra tres soluciones óptimas para una instancia con capacidad 10,
donde los puntos negros representan a los usuarios y los grises a las paradas potenciales.
La asignación de usuarios a paradas potenciales se representa por la línea de puntos,
mientras que cada línea solida indica una ruta de bus entre dos paradas. La Figura 1 (a)
muestra una solución donde la cota superior entre rutas viene dada por L=180, y la cota
inferior para la capacidad de los vehículos no está definida; esta solución recoge 25
usuarios con cuatro rutas. La Figura 1 (b) muestra la solución óptima para la instancia
con L=124, sin cota inferior definida; esta solución usa siete rutas, pero una de ellas
recoge solo un usuario. La Figura 1 (c) añade al ejemplo (b) una cota inferior para la
capacidad de los vehículos de ; este último utiliza seis rutas, en la que cada una
recoge al menos a 4 usuarios.
37
3.2 Formulaciones del modelo
Ya se ha hecho una descripción del modelo y ahora se procederá a introducir dos
formulaciones diferentes del modelo.
3.2.1 Notación matemática
Sea U el conjunto de usuarios y N el conjunto de paradas potenciales. Una flota
homogénea de vehículos, denotada por el conjunto K, está disponible en el depósito. La
capacidad de cada vehículo viene dada por Q. Por simplicidad, se asume que el depósito
y el destino se encuentran en la misma localización, representada por 0.
Para un conjunto dado , representa el conjunto de paradas potenciales
alcanzables por el usuario . De forma opuesta, para un conjunto dado, denota el conjunto de usuarios que es capaz de alcanzar una parada en .
Se modela el problema con un grafo directo , donde , es decir, V
es el conjunto de nodos que incluye las paradas potenciales y el depósito. El conjunto de
arcos del grafo , se define como
La variable representa el coste de ruta para cada arco Para cada par
que denota una asignación potencial de un usuario u a una parada i, ,
nos dice el coste de asignar u a i.
Se define y para
cualquier subconjunto de nodos . Se denota y , al
conjunto de arcos que van de cada nodo de S a cada nodo de T.
Figura 2 Tres soluciones óptimas para una instancia con capacidad 10: (a) , (b)
, (c) .
38
3.2.2 Primera formulación
Esta formulación se presenta en base a tres familias de variables discretas. Sea , para
cada arco y para cada vehículo , una variable binaria que tome el valor
1 si el vehículo k incluye en su ruta el arco y 0, en otro caso. Sea , para cada
parada potencial y , la variable binaria que indica si el vehículo k visita la
parada i. Para cada usuario , cada parada potencial , y cada vehículo
, se define como la variable binaria que toma valor 1 si y solo si el vehículo k
recoge al usuario u en la parada i. La siguiente formulación describe el problema:
(21)
(22)
(23)
(24)
(25)
(26)
(27)
(28)
(29)
(30)
39
(31)
La función objetivo (21) minimiza la suma de los costes de ruta más los de asignación.
La restricción (22) y cota (31) obligan a que exactamente un arco salga de cada parada
visitada y para cada vehículo k. La restricción (23), para cada y cada k en K,
fuerza que salgan el mismo número de arcos que entran. La restricción (24) es la
restricción de eliminación de subtour, que asegura que cada parada visitada en un
subconjunto , debe conectarse con el depósito a través de un camino. La
restricción (25) elimina la posibilidad de asignar a un usuario una parada no visitada. La
desigualdad (26) garantiza que cada usuario se recoja una sola vez. La desigualdad (27)
obliga a que cada parada sea visitada a lo sumo por un vehículo. La restricción (28)
excluye las rutas que supongan un coste mayor a unidades del recurso r.
3.2.3 Segunda formulación
Alternativamente, el problema puede modelarse como una formulación de partición de
conjunto, derivado de la descomposición de Dantzig-Wolfe [13] sobre la primera
formulación. Esta formulación hace una descripción del problema basándose en las
variables x, y y z, que representan la agregación de las variables y para todo
vehículo , respectivamente:
(32)
(33)
(34)
(35)
(36)
(37)
40
(38)
La restricción (33) y cota (34) realizan la misma función que (26) y (27),
respectivamente. Las restricciones (33) y (38) garantizan la convexidad de las variables
en esta formulación. Las igualdades (35), (36) y (37) definen el emparejamiento de las
variables x, y y z con , respectivamente.
3.2.4 Algoritmo Branch-and-Cut-and-Price
A continuación, se presenta una descripción del algoritmo basado en los modelos
previamente descritos. La siguiente figura muestra el procedimiento general de
acotación para un algoritmo Branch-and-Cut-and-Price (véase Figura 2)
Figura 3 Procedimiento general de acotación para un algoritmo BCP
41
3.3 Análisis global del modelo
Una vez descrito el problema abordado para el análisis de la biblioteca que se pretende
implementar, podemos realizar una comparación de resultados computacionales, donde:
Q = capacidad del vehículo
L = limitación en kilómetros de la ruta
(a), (b) y (c), son los diferentes escenarios
%LB = cota inferior (en porcentaje)
Nodes = número de nodos visitados
Cols = número de columnas añadidas
Cuts = número de cortes realizados
CPU = tiempo de ejecución
Tabla 1 Cómputo de resultados para diferentes escenarios
42
3.4 Análisis para una instancia del modelo
Como ya se ha hecho un análisis global del problema de Buses de Escuela, ahora
podemos hacer un análisis específico, que consiste en una tabla que muestra los
resultados de una ejecución para una entrada del problema. Las variables a considerar
son:
CPU = tiempo de ejecución
Node = identificador del nodo en ejecución
Left = número de nodos restantes
LP it = número de iteraciones en el nodo en cuestión
Mem = cantidad de memoria ocupada
Frac = número de variables con resultado fraccionario
P. Cols = número de columnas generadas hasta el momento
Cols = número de columnas generadas en ese nodo
Cuts = número de cortes generados en ese nodo
Cons = número de restricciones del modelo
Vars = número de variables del modelo
Dual bound = cota inferior global
Primal bound = cota superior global
Gap = ratio entre cotas inferior y superior.
Así, se pueden ver los resultados para las dos últimas iteraciones (Véase Tabla 2 y Tabla
3)
43
CPU node left LP it Mem frac P. cols cols cuts cons vars dual bound primal bound gap
1.0s 743 60 2 6096K 0 1640 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.0s 504 59 2 6096K 0 1642 18 1 14 56 1,38E+11 1,40E+11 1.23%
1.0s 505 58 1 6096K 3 1644 18 0 13 56 1,38E+11 1,40E+11 1.23%
1.0s 510 59 2 6096K 0 1646 12 1 11 56 1,38E+11 1,40E+11 1.23%
1.0s 511 58 1 6096K 3 1648 12 0 10 56 1,38E+11 1,40E+11 1.23%
1.0s 514 59 2 6096K 0 1650 12 1 11 56 1,38E+11 1,40E+11 1.23%
1.0s 515 58 1 6096K 6 1652 12 0 10 56 1,38E+11 1,40E+11 1.23%
1.0s 544 59 1 6096K 3 1654 16 0 12 56 1,38E+11 1,40E+11 1.23%
1.0s 545 60 2 6096K 0 1656 16 1 13 56 1,38E+11 1,40E+11 1.23%
1.0s 558 59 1 6096K 3 1658 18 0 13 56 1,38E+11 1,40E+11 1.23%
1.0s 559 60 1 6096K 6 1660 18 0 13 56 1,38E+11 1,40E+11 1.23%
1.0s 584 61 2 6096K 0 1662 24 1 17 56 1,38E+11 1,40E+11 1.23%
1.0s 585 60 2 6096K 0 1664 24 1 17 56 1,38E+11 1,40E+11 1.23%
1.0s 658 59 1 6096K 7 1666 28 0 20 56 1,38E+11 1,40E+11 1.23%
1.0s 659 60 1 6096K 10 1668 28 0 20 56 1,38E+11 1,40E+11 1.23%
1.0s 862 61 2 6132K 0 1670 34 1 23 56 1,38E+11 1,40E+11 1.23%
1.0s 863 60 2 6096K 0 1672 34 1 23 56 1,38E+11 1,40E+11 1.23%
1.1s 882 59 2 6096K 0 1674 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.1s 883 58 2 6096K 0 1676 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.1s 884 57 2 6096K 0 1678 14 1 12 56 1,38E+11 1,40E+11 1.23%
Tabla 1 Tabla para una entrada del problema (penúltima ejecución)
44
CPU node left LP it Mem frac P. cols cols cuts cons vars dual bound primal bound gap
1.1s 885 56 2 6096K 0 1680 14 1 12 56 1,38E+11 1,40E+11 1.23%
1.1s 886 55 1 6096K 3 1682 14 0 11 56 1,38E+11 1,40E+11 1.23%
1.1s 887 56 1 6096K 8 1684 14 0 11 56 1,38E+11 1,40E+11 1.23%
1.1s 888 57 2 6096K 0 1686 18 1 14 56 1,38E+11 1,40E+11 1.23%
1.1s 889 56 2 6096K 0 1688 18 1 14 56 1,38E+11 1,40E+11 1.23%
1.1s 890 55 1 6096K 3 1690 20 0 14 56 1,38E+11 1,40E+11 1.23%
1.1s 891 56 2 6096K 0 1692 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.1s 892 55 2 6100K 0 1694 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.1s 893 54 2 6100K 0 1696 20 1 15 56 1,38E+11 1,40E+11 1.23%
1.1s 894 53 1 6100K 0 1698 30 0 21 56 1,38E+11 1,38E+11 0.00%
Tabla 2 Tabla para una entrada del problema (última ejecución)
45
Capítulo IV. Conclusiones y Trabajos Futuros
46
4. Conclusiones y Trabajos Futuros
4.1 Resumen
Durante el desarrollo de este proyecto hemos estudiado las técnicas de ramificación y
poda para la resolución de Programas Lineales Enteros Mixtos, con el fin de desarrollar
un marco de trabajo, concretado en una biblioteca de clases. Se pretende que esta
biblioteca facilite la implementación de aplicaciones para resolver problemas de
optimización.
El desarrollo de esta biblioteca incluye clases para gestionar el árbol de ramificación
asociado a la técnica de ramificación y poda. Además, hemos incluido dentro de cada
uno de los nodos del árbol la posibilidad de enriquecer el modelo dinámicamente con
filas y columnas. Así, el desarrollo final de trabajo ha sido un marco para resolver
problemas mediante la técnica Branch-and-Cut-and-Price.
Esta biblioteca ha sido probada sobre dos problemas de optimización en rutas,
mostrando un comportamiento robusto y eficiente. La experiencia computacional nos ha
mostrado también que este entorno es capaz de resolver problemas de cardinalidad
realista.
Los cocimientos necesarios para este proyecto han sido obtenidos de las asignaturas
Algoritmos y Estructuras de Datos, Algoritmos y Estructuras de Datos Avanzadas,
Optimización, Análisis y Diseños de Algoritmos y Complejidad Computacional.
4.2 Trabajos Futuros
El trabajo que hemos llevado a cabo durante los plazos previstos para su desarrollo han
alcanzado las expectativas. El objetivo ha sido el desarrollo de un prototipo que permita
resolver problemas generales descritos mediante Programación Lineal Entera, y que
requieran la generación de filas y columnas.
El sentido de este proyecto no era tanto el desarrollo de un producto final, sino de un
prototipo que fuera una herramienta pedagógica para la comprensión de algoritmos
como Branch-and-Cut-and-Price en el ámbito académico. Sin duda, la brevedad del
tiempo de ejecución de trabajo ha dejado flecos por resolver, aunque las directrices ya
están claramente establecidas.
Entre estos flecos, hemos observados que se debe mejorar la gestión de filas y
columnas, de manera que se puedan eliminar aquellas que no sean necesarias en
determinado punto de la ejecución del algoritmo. En otras palabras, se debe mejorar la
aplicación de manera que se pueda detectar aquellas restricciones que no intervengan en
la resolución del programa lineal y aquellas columnas con costo reducido positivo.
Por otro lado, un futuro proyecto debería incluir la generación de soluciones factibles,
es decir, lograr el cálculo de cotas superiores de forma general, ya que hasta el momento
sólo se están tomando como cotas superiores aquellas soluciones factibles que se
encuentran durante el desarrollo del árbol de ramificación.
47
Finalmente, observamos que nuestra propuesta sólo puede utilizar un resolutor lineal
comercial (CPLEX). Sería una buena práctica modificar la biblioteca de manera que la
resolución del programa lineal fuera independiente del resolutor.
49
Capítulo V. Summary and Conclusions
50
5. Summary and Conclusions
5.1 Summary and Conclusions
Throughout this project, we have studied variations of the Branch and Bound technique,
used for solving mixed integer linear programming problems, with the purpose of creating a
development framework. To be more specific, we are talking about a library which allows
the implementation of computer applications for solving optimization problems.
The library mentioned above, provides with data structures to manage the branching tree
related to the Branch-and-Bound technique. Moreover, we have included the possibility of
enriching the model by adding rows and columns dynamically. Therefore, we have finally
developed a framework for solving optimization problems by means of the Branch-and-Cut-
and-Price technique.
We have tested this work on two optimization routing problems, proving a robust and
efficient performance. The computational experience has also proven that this framework is
able to solve problems with a realistic cardinality.
The background required for this project has been drawn from the modules Algorithms and
Data Structures, Advanced Algorithms and Data Structures, Optimization, Algorithms
Analysis and Design and Computational Complexity.
5.2 Future Work
The work we have been carrying out for the whole semester to develop the project has
reached our expectations. The aim has been the development of a template for solving
general problems able to be defined as Integer Linear Programming Problems, in addition to
both, a row and column generation requirement.
The main goal of this project was not the development of a final product, but a prototype
which could be used as a pedagogical tool to help us understanding Branch-and-Cut-and-
Price algorithms in academia. There is no doubt that, because of the tight time window in
our planning, this work has left some loose ends to solve out; however, the guideline has
been clearly established.
Among the possible improvements, we have observed that row and column management
should be upgraded, in a way that rows and columns are eliminated when they are no longer
needed in some point of the execution of the algorithm. In other words, the application
should be improved so that constraints which do not interfere with the linear program
solving, are detected, so the columns with positive reduced cost.
On the other hand, a future work of the project could entail feasible solution generation, it
means, accomplishing the upper bound calculation in a general form, since feasible solutions
found during the execution of the branching tree are the only ones that have been taken into
account so far. Finally, it is also shown that our approach is just able to use a commercial-
aimed linear solver (CPLEX). It might be a good practice to modify the library and make the
linear program solving an independent module, out of the solver scope.
51
Apéndice A. Códigos
A. 1 Variables (Código fuente)
// variables.cpp
const int n_x = n_arc * m ; // Numero de variables x
const int n_e_k = n_v * m; // Numero de variables e
const int n_s = n_v; // Numero de variables s
const int n_var = n_x + n_e_k + n_v; // Numero total de variables
varDes_.init(n_var);
const int base_x = 0;
const int base_e = n_x;
const int base_s = base_e + n_e_k;
for (int i = base_x; i < n_x; i++){
varDes_.vt_[i] = BCP_template::_CONTINUOUS_;
varDes_.st_[i] = BCP_template::_SUBPROBLEM_;
varDes_.c_[i] = 0.0;
varDes_.lb_[i] = 0.0;
varDes_.ub_[i] = 1.0;
}
for (int i = base_e; i < base_e + n_e_k; i++){
varDes_.vt_[i] = BCP_template::_CONTINUOUS_;
varDes_.st_[i] = BCP_template::_SUBPROBLEM_;
varDes_.c_[i] = 0.0;
varDes_.lb_[i] = 0.0;
varDes_.ub_[i] = 1E100;
}
for (int i = base_s; i < base_s + n_s; i++){
varDes_.vt_[i] = BCP_template::_CONTINUOUS_;
varDes_.st_[i] = BCP_template::_MASTER_PROBLEM_;
varDes_.c_[i] = 0.0;
varDes_.lb_[i] = 0.0;
varDes_.ub_[i] = 1E100;
}
A. 2 Restricciones (Código fuente)
// restricciones.cpp
for (int j = 0; j < n_v; j++) {
BCP_template::value_t* values = new BCP_template::value_t [1];
BCP_template::long_size_t* columns = new BCP_template::long_size_t [1];
columns[0] = base_s + j;
values[0] = 1.0;
BCP_template::rowData_t* row = new BCP_template::rowData_t(1, columns, values,
-1E100, 0, n_row_);
n_row_++;
rowPool_.addRow(row);
}
//////////////////////////////////////////////////////////////////////////////
for (int k = 0; k < m; k++)
52
for (int j = 0; j < n_v; j++) {
BCP_template::value_t* values = new BCP_template::value_t [2];
BCP_template::long_size_t* columns = new BCP_template::long_size_t [2];
columns[0] = base_e + k * n_v + j;
values[0] = 1.0;
columns[1] = base_s + j;
values[1] = -1.0;
BCP_template::rowData_t* row = new BCP_template::rowData_t(2, columns,
values, -1E100, 0, n_row_);
n_row_++;
rowPool_.addRow(row);
}
//////////////////////////////////////////////////////////////////////////////
for (int j = 1; j < n_v; j ++) {
BCP_template::value_t* values = new BCP_template::value_t [(n_v - 1) * m];
BCP_template::long_size_t* columns = new BCP_template::long_size_t [(n_v - 1)
* m];
int ll = 0;
for (int k = 0; k < m; k ++)
for (int i = 0; i < n_v; i++)
if (i != j) {
columns[ll] = k * n_arc + i * n_v + j;
values[ll] = 1.0;
ll++;
}
BCP_template::rowData_t* row = new BCP_template::rowData_t(ll, columns,
values, I_.q_[j], I_.q_[j], n_row_);
n_row_++;
rowPool_.addRow(row);
}
//////////////////////////////////////////////////////////////////////////////
for (int k = 0; k < m; k++) {
BCP_template::value_t* values = new BCP_template::value_t [n_v];
BCP_template::long_size_t* columns = new BCP_template::long_size_t [n_v];
int ll = 0;
for (int i = 1; i < n_v; i++) {
columns[ll] = k * n_arc + 0 * n_v + i;
values[ll] = 1.0;
ll++;
}
BCP_template::rowData_t* row = new BCP_template::rowData_t(ll, columns,
values, 1, 1, n_row_);
n_row_++;
rowPool_.addRow(row);
}
//////////////////////////////////////////////////////////////////////////////
for (int k = 0; k < m; k++)
53
for (int j = 1; j < n_v; j++) {
BCP_template::value_t* values = new BCP_template::value_t [n_v];
BCP_template::long_size_t* columns = new BCP_template::long_size_t [n_v];
int ll = 0;
for (int i = 0; i < n_v; i++)
if (i != j) {
columns[ll] = k * n_arc + j * n_v + i;
values[ll] = 1.0;
ll++;
}
BCP_template::rowData_t* row = new BCP_template::rowData_t(ll, columns,
values, -1E100, 1, n_row_);
n_row_++;
rowPool_.addRow(row);
}
A. 3 Árbol de ramificación (Archivo cabecera)
// BCP_branching_tree_t.hpp
class branching_tree_t {
public:
statistics_t& statistics_;
sll tree_;
sll already_processed_;
public:
branching_tree_t(statistics_t& statistics, branching_node_t* node);
virtual ~branching_tree_t(void);
virtual void process_tree(void);
};
A. 4 Árbol de ramificación (Código fuente)
// BCP_branching_tree_t.cpp
branching_tree_t::branching_tree_t(statistics_t& statistics, branching_node_t*
node) :
tree_(),
node_counter_(1) {
push(node);
}
branching_tree_t::~branching_tree_t(void) {
tree_.clean();
already_processed_.clean();
}
void branching_tree_t::process_tree(void) {¡
while (!tree_.empty()) {
branching_node_t* current_node = pop();
current_node->activate();
current_node->optimize();
54
if (current_node->dualSolution_->lB_ + integer_tolerance_ > current_node-
>solution_.uB_) {
current_node->status_ = PRUNED;
do_branch(current_node);
store_node(current_node);
current_node = NULL;
}
}
tree_.clean();
already_processed_.clean();
}
A. 5 Nodo (Archivo cabecera)
// BCP_branching_node_t.hpp
class branching_node_t {
public:
statistics_t& statistics_;
problemInfo_t& problemInfo_;
problemSolution_t& solution_;
pricer_t& pricer_;
separator_t& separator_;
primal_heuristic_t& heuristic_;
basic_colDataProblemPool_t& problemCols_; // Todas las columnas generadas
rowDataProblemPool_t& problemRows_; // Todas las filas generadas
basic_colDataNodePool_t nodeCols_;
// Todas las columnas heredadas y las generadas
rowDataNodePool_t nodeRows_;
// Todas las filas heredadas y las generadas
dualSolution_t* dualSolution_; // Solución en cada iteración
nodeSolver_t* nodeSolver_; // Optimizador del nodo
branching_node_t** children_;
int n_children_;
public:
branching_node_t(master_problem_t*, obj_inx_t node_id, double slack_var_cost =
SLACK_VAR_COST);
virtual void optimize(void);
};
A. 6 Nodo (Código fuente)
// BCP_branching_node_t.cpp
branching_node_t::branching_node_t(master_problem_t* mp, obj_inx_t node_id, double
slack_var_cost) :
statistics_(mp->statistics_),
problemInfo_(mp->problemInfo_),
solution_(mp->solution_),
pricer_(mp->pricer_),
separator_(mp->separator_),
heuristic_(mp->heuristic_),
55
problemCols_(mp->problemCols_),
problemRows_(mp->problemRows_),
nodeCols_(mp->problemInfo_.n_problem_var_),
nodeRows_(),
dualSolution_(NULL),
nodeSolver_(NULL),
children_(NULL),
n_children_(0),
nodelB_(-BD_INFINITY)
{}
void branching_node_t::optimize(void) {
do {
const int nRow = nodeRows_.nRow();
const int nCol = nodeCols_.nCol();
new_cols = new_rows = 0;
//--------------------------------------------------------------------
// Solving
//--------------------------------------------------------------------
nodeSolver_->solve(nRow, nCol, dualSolution_->lB_, dualSolution_->x_s_,
dualSolution_->x_, lambda, rc, dualSolution_->pi_, dualSolution_->slack_,
dualSolution_->lpFeasible_);
if (dualSolution_->lpFeasible_) {
//----------------------------------------------------------------
// Pricing
//----------------------------------------------------------------
if (new_rows == 0) {
pricer_.pricing(nodeRows_, colPool, nodeInfo_->fixed_to_zero_,
nodeInfo_->fixed_to_one_, dualSolution_->pi_);
dualSolution_->n_found_col_ = colPool.nCol();
new_cols = colPool.nCol();
problemCols_.moveFrom(colPool);
}
//----------------------------------------------------------------
// Separating
//----------------------------------------------------------------
if (new_cols == 0) {
separator_.separate(dualSolution_->x_, rowPool);
new_rows = rowPool.nRow();
problemRows_.moveFrom(rowPool);
}
}
} while (new_rows + new_cols != 0);
}
A. 7 Separador (Archivo cabecera)
// BCP_separator_t.hpp
class benders_cut_generator_t: public BCP_template::separator_t {
public:
benders_cut_generator_t(IAV_Instance& I);
virtual ~benders_cut_generator_t(void);
virtual void separate(double* x, double lb, BCP_template::nodeInfo_t* ni
,BCP_template::rowDataPreliminarPool_t& pool);
56
virtual bool feasible(double* x, double lb, BCP_template::nodeInfo_t* ni );
};
A. 8 Separador (Código fuente)
// BCP_separator_t.cpp
benders_cut_generator_t::benders_cut_generator_t(IAV_Instance& I):
BCP_template::separator_t()
{}
benders_cut_generator_t::~benders_cut_generator_t(void)
{}
void benders_cut_generator_t::separate(double* x, double lb,
BCP_template::nodeInfo_t* ni ,BCP_template::rowDataPreliminarPool_t& pool) {
// Encontrar una desigualdad violada
}
bool benders_cut_generator_t::feasible(double* x, double lb,
BCP_template::nodeInfo_t* ni) {
return true;
}
A. 9 Pricer (Archivo cabecera)
// BCP_pricer_t.hpp
class iav_pricer_t : public BCP_template::pricer_t {
public:
IAV_Instance& I_;
double Cap_;
DP_2_cyc_CAP_shortest_path_CMT SP_;
public:
iav_pricer_t(int nVar, double* c, IAV_Instance& I);
iav_pricer_t(IAV_Instance& I);
virtual ~iav_pricer_t(void);
virtual void generateColumns(int nvar, double* rc,int nrow, double* pi,
bit_set& fixed_to_zero, bit_set& fixed_to_one,
BCP_template::basic_colDataPreliminarPool_t& p_coolPool);
};
57
A. 10 Pricer (Código fuente)
// BCP_pricer_t.cpp
iav_pricer_t::iav_pricer_t(int nVar, double* c, IAV_Instance& I):
pricer_t(nVar,c),
I_(I),
SP_(I.n_, I.m_, 100000.0, I.r_, I.m_t_, I.t_)
{}
iav_pricer_t::iav_pricer_t(IAV_Instance& I):
pricer_t(),
I_(I),
SP_(I.n_, I.m_, 100000.0, I.r_, I.m_t_, I.t_)
{}
iav_pricer_t::~iav_pricer_t(void)
{}
void iav_pricer_t::generateColumns(int nvar, double* rc, int nrow, double* pi,
bit_set& fixed_to_zero, bit_set& fixed_to_one,
BCP_template::basic_colDataPreliminarPool_t& p_colPool) {
const int m = I_.m_;
const int n_v = I_.n_ + 1;
const int n_arc = n_v * n_v;
double sg[nvar];
for(int i = 0; i < nvar; i++)
if ((fixed_to_zero.contains(i)))
sg[i] = MAX_DP_COST;
else
sg[i] = rc[i];
double cost[n_arc + n_v];
for(int k = 0; k < m; k++) {
std::vector<path_t*> path_list;
update_red_cost(sg, k, cost);
SP_.solve(cost, k, path_list);
const int pl_sz = path_list.size();
path_t* aux;
for(int i=0;i<pl_sz;i++) {
58
aux = path_list[i];
if (aux->redcost_< -1E-2) {
addCol(p_colPool, aux->nit_, aux->inx_, aux->val_);
delete aux;
}
else {
aux->clean();
delete aux;
}
}
}
}
59
Bibliografía
[1] «Computational Infrastructure for Operations Research,» COIN-OR Foundation, 22
Enero 2012. [En línea]. Available: http://www.coin-or.org/. [Último acceso: 25 Abril
2014].
[2] T. Achterberg, T. Berthold, T. Koch y K. Wolter, «Solving Constraint Integer
Programs,» 23 Marzo 2014. [En línea]. Available: http://scip.zib.de/. [Último acceso:
25 Abril 2014].
[3] R. Sala Garrido, Programación Lineal: Metodología y Problemas, Tebar, 1993.
[4] D. de la Fuente García y P. Priore Moreno, «Programación Lineal Entera y
Programación no Lineal,» Oviedo, España, Servicio de Publicaciones de la Universidad
de Oviedo, 1996, pp. 1-13.
[5] R. Neapolitan y K. Naimipour, «Foundations of Algorithms Using C++ Pseudocode
(3rd Edition),» pp. 233-264.
[6] V. Quimey, «Capitulo 4: Programación Lineal Entera,» 19 Diciembre 2013. [En línea].
Available: http://mate.dm.uba.ar/~qvivas/operativa/archivos/cap4_05.pdf. [Último
acceso: 3 Mayo 2014].
[7] A. Caprara y M. Fischetti, «Branch and Cut Algorithms,» Annotated bibliographies in
combinatorial optimization, 1997, p. Chapter 4.
[8] M. Jünger y S. Thienel, «Introduction to ABACUS – a Branch-and-Cut System,»
Operations Research Letters, 1992, p. 22.
[9] M. Padberg y G. Rinaldi, «A branch and cut algorithm for the resolution of large-scale
symmetric traveling salesman problems,» SIAM Review,, 1991, p. 33.
[10] S. Thienel, «ABACUS: A Branch and Cut System,» Universität zu Köln, 1995.
[11] C. Barnhart, E. Johnson, G. Nemhauser, M. Savelbergh y P. Vance, «Branch-and-Price:
Column Generation for Solving Huge Integer Programs,» Braun-Brumfield, 1994, pp.
186-207.
[12] F. Vanderbeck y L. Wolsey, Reformulation and Decomposition of Integer Programs,
Springer, Berlin: 50 Years of Integer Programming, 2010.
[13] G.-. G.-. Dantzig y P. Wolfe, Decomposition principle for linear programs, Oper. Res.,
60
1960.
[14] J. J. Salazar-González, Programación Matemática, Ediciones Días de Santos, 2001.
[15] E. Balas, S. Ceria, G. Cornuéjols y N. Natraj, «Gomory Cuts Revisited,» Operations
Research Letters, 1996, p. 19.
[16] P. Toth y D. Vigo, The Vehicle Routing Problem, S.I.A.M., 2002.
[17] T. B. T. K. K. W. Tobias Achterberg, «Solving Constraint Integer Programs,» 23
Marzo 2014. [En línea]. Available: http://scip.zib.de/. [Último acceso: 25 Abril 2014].
[18] R. S. Garrido, Programación Lineal: Metodología y Problemas, Tebar, 1993.
[19] P. P. M. David de la Fuente García, «Programación Lineal Entera y Programación no
Lineal,» Oviedo, España, Servicio de Publicaciones de la Universidad de Oviedo, 1996,
pp. 1-13.
[20] Q. Vivas, «Capitulo 4: Programación Lineal Entera,» 19 Diciembre 2013. [En línea].
Available: http://mate.dm.uba.ar/~qvivas/operativa/archivos/cap4_05.pdf. [Último
acceso: 3 Mayo 2014].
[21] K. N. Richard E. Neapolitan, «Foundations of Algorithms Using C++ Pseudocode (3rd
Edition),» pp. 233-264.
[22] M. F. A. Caprara, «Branch and Cut Algorithms,» Annotated bibliographies in
combinatorial optimization, 1997, p. Chapter 4.