Академический Документы
Профессиональный Документы
Культура Документы
Facultad de Ingeniera
Departamento de Modelacin y Gestin Industrial
Programa de Magster en Gestin de Operaciones
Abril, 2010.
Tabla de contenidos
Programacin Matemtica: Conceptos Bsicos ............................................................... 5
Programacin Lineal: Funcin Objetivo y Restricciones ........................................................... 5
Programacin Entera: Funcin Objetivo y Restricciones .......................................................... 6
Solvers para Programacin Lineal y Entera ............................................................................... 7
Anexos ............................................................................................................................ 46
Anexo 1: Cdigo implementacin de Algoritmo Planos Cortantes para TSP en OPL.............. 46
Anexo 2: Cdigo implementacin Algoritmo Planos Cortantes para TSP usando Tecnologa
Concierto. (esto puede mostrarse de otra forma??) .............................................................. 48
Anexo 2.1: Cdigo clase TSPSolver.cpp ............................................................................... 48
Anexo 2.2: Cdigo clase TSPTour.cpp ................................................................................. 51
3
As, el vector
corresponden a las variables de decisin del problema, la funcin
es la funcin objetivo, mientras las funciones
son las funciones de
restricciones, y las constantes
son los lmites o disponibilidad de recursos para las
restricciones. Un vector corresponde a una solucin factible, si satisface las restricciones, y un vector
corresponde a una solucin ptima, si posee el menor valor de la funcin objetivo entre todos los
vectores que satisfacen las restricciones del problema. Mayores detalles se pueden consultar en un gran
[1]
2
nmero de libros del rea de autores como W. Winston y M. Griva .
Generalmente se consideran familias o clases de problemas de optimizacin, caracterizados por formas
particulares de la funcin objetivo y las funciones asociadas a las restricciones. As, hay problemas de
programacin lineal, no-lineal, programacin entera, etc.
. Otro
elemento fundamental es la naturaleza de las variables de decisin, en que se asume que son variables
reales continuas no-negativas.
[1]
[2]
Problema Ilustrativo: La compaa Wyndor Glass produce productos de vidrio de alta calidad, incluidos
ventanas y puertas de vidrio. La compaa posee tres plantas, la planta 1 fabrica los marcos de aluminio,
la planta 2 los marcos de madera, y la planta 3 fabrica el vidrio y ensambla ventanas y puertas. Debido a
la disminucin en las ventas, la alta direccin ha decidido descontinuar ciertos productos no rentables
por lo que se dispone de capacidad de produccin para lanzar dos nuevos productos: Una puerta de
cristal de 8 pies con marco de aluminio, y una ventana colgante de doble marco de madera de 4x6 pies.
El producto 1 necesita capacidad de produccin en las plantas 1 y 3, mientras el producto 2 requiere
produccin en las plantas 2 y 3. Las instalaciones de produccin en la planta 1 estarn disponibles 4
horas/semana, mientras la planta 2 dispone de 12 horas/semana y la planta 3 de 18 horas semanales.
La capacidad productiva de cada planta usada por cada lote de productos depende de su tasa de
produccin. Se estima que cada lote de puertas requerir una hora de tiempo de produccin en la
planta 1 y de tres horas en la planta 3. Mientras para cada lote de ventanas, se requerirn alrededor de
dos horas en la planta 2 y de dos horas en la planta 3. Analizando los datos de los costos y la decisin de
los precios, el departamento de contabilidad estima las ganancias de los dos productos (por lotes),
siendo de $300 para las puertas y $500 para las ventanas. La administracin quiere conocer Cul debe
ser la cantidad de puertas y de ventanas a producir por semana tal de maximizar las ganancias?
Para establecer el modelo matemtico, definimos las variables de decisin en relacin a la cantidad de
puertas y ventanas a producir, as definimos:
Y llamaremos por
al beneficio total por semana. Adems cada planta posee
limitaciones en su capacidad de produccin, as cada lote del producto 1 requiere una hora de
produccin en la planta 1, teniendo la planta disponible slo 4 horas/semana, lo cual se traduce en la
expresin matemtica que
. Situacin similar ocurre en la planta 2, por lo que
.
Mientras en la planta 3, el tiempo consumido por ambos productos es
y se dispone de slo
18 horas, lo cual se traduce en la restriccin que
.
Para resumir, el modelo de programacin lineal consiste en encontrar la tasa de produccin de ambos
productos, tal de:
De esta manera, el problema queda completamente formulado, por lo cual en este punto se podra
ocupar algn algoritmo u otro mtodo de resolucin para resolver el problema.
Ahora, si algunas variables pueden tomar valores en los nmeros reales y otras estn restringidas a ser
enteras, corresponde a un problema de Programacin Entera Mixta. Otro caso de gran importancia son
los problemas de Programacin Combinatorial, en que las variables estn restringidas a tomar valores
iguales a 0 1.
restricciones limitado, cuya importancia principal radica en el hecho de la cotidianidad y gran nmero de
usuarios que posee Excel, an cuando no posee una gran calidad.
XPRESS: es un solver de alto rendimiento de programacin lineal y programacin lineal entera mixta.
Tambin puede ser usado para resolver problemas de programacin cuadrtica, y funciona solamente
en conjunto con el sistema de modelamiento GAMS.
GLPK: es un software libre que intenta resolver problemas de gran tamao de programacin lineal, y
programacin entera mixta, entre otros. Conformado por una serie de rutinas para ser utilizadas como
libreras de programacin.
CPLEX: es una herramienta para resolver problemas de programacin lineal, problemas de redes, de
programacin entera mixta, cuadrtica, cuadrtica restringida, y programacin por restricciones.
Corresponde al solver comercial ms completo y eficiente que ha dominado el mercado en los ltimos
aos.
Puede ser usando en diferentes formas para satisfacer a las diversas necesidades de los usuarios:
Optimizador Interactivo (CPLEX Interactive Optimizer): corresponde a un programa ejecutable, que
puede leer interactivamente un problema o a travs de archivos en formatos especficos y desplegar los
resultados en los formatos que se desee.
Tecnologa Concierto (Concert Technology): es un conjunto de libreras de clases de C++, Java, .NET y
Python, que ofrecen una interfaz de programacin y aplicaciones que otorgan facilidades de
programacin que permiten utilizar el solver de CPLEX en aplicaciones realizadas en los respectivos
lenguajes de programacin mencionados. Las libreras de la tecnologa concierto hacen uso de la librera
llamable (CPLEX Callable Library).
Librara llamable de CPLEX: corresponden a una librera en C que permiten a los programadores insertar
los optimizadores de CPLEX en aplicaciones escritas en C, Visual Basic, FORTRAN, o cualquier otro
programa que puede ocupar funciones de C.
OPL Development Studio: se describi anteriormente, se menciona nuevamente slo para enfatizarla
como otra modalidad de uso.
Es importante mencionar que posee conexin y compatibilidad con otros softwares ampliamente
usados en ingeniera o de uso comn, como lo son Matlab y Excel.
GUROBI: es un solver para resolver problemas de gran tamao de programacin lineal, y programacin
entera mixta. Lanzado recientemente al mercado, y entre sus desarrolladores se encuentran fundadores
y desarrolladores de CPLEX, es totalmente comparable en eficiencia con los mejores solvers actuales
como CPLEX, slo que fue diseado desde cero para aprovechar el procesamiento paralelo y multi-core.
Debido a la gran relevancia y predominio que ha tenido durante los ltimos aos es que este manual
est completamente enfocado al uso en sus diferentes modalidades de OPL/CPLEX.
Una vez que el proyecto ha sido creado, entramos directamente en el ambiente de OPL, en
donde efectivamente nuestro proyecto, i.e. el problema que queremos resolver, tomar forma para ser
ejecutado; esto se muestra en la Ilustracin 2. A la izquierda se muestra la ventana de administracin de
proyectos, en este caso slo existe el proyecto ejemplo1_OPL, a la derecha se muestra el archivo .mod,
vaco hasta ahora, y las pestaas de los archivos .dat y .mod.
Ilustracin 2: Ambiente OPL para la edicin de los archivos .mod, .dat y .ops
Es importante que el uso de OPL y del solver CPLEX, supone que se cuenta con un modelo
vlido, es decir, que representa la situacin que se quiere resolver ya sea para fines prcticos o
acadmicos.
sujeto a
Este es un problema muy bsico que resolveremos a travs de OPL. Dada la estructura del problema,
podemos representar todos los datos de ste en forma explcita en el archivo .mod como se muestra en
la Ilustracin 3. En la ilustracin se muestra la declaracin (o definicin) de las variables de decisin
, por eso el nombre dvar, como variables reales positivas (float+); posteriormente se
define la funcin objetivo indicando el sentido de sta (minimize) y finalmente se declaran las tres
restricciones dentro del bloque subject to.
Ilustracin 3: Generacin del modelo del problema en el archivo .mod (parmetros explcitos)
Es claro que el modelo est completamente definido y no es necesario utilizar el archivo .dat
puesto que todos los datos del problema estn explcitos en el modelo. Para resolver el problema es
necesario ejecutarlo haciendo click en el botn
en la barra de herramientas. Al ejecutar el programa,
OPL internamente pasa el modelo al motor CPLEX y ste lo resuelve, desplegando la solucin en la
pestaa soluciones en la parte inferior del ambiente del programa. En este caso particular la solucin se
muestra en la Ilustracin 4; el valor ptimo y la solucin ptima son entonces
y
respectivamente.
Ilustracin 4: Solucin entregada por el solver CPLEX al problema.
10
Como se ha sealado, en este caso no fue necesario enlazar el archivo .mod, que contiene el
modelo, con el archivo .dat que contendra los datos o parmetros, puesto que fue posible definir todo
el modelo del problema en el .mod. Consideraremos ahora el caso en que generemos un archivo .mod
en que sea necesario considerar datos contenidos en el archivo .dat. En la Ilustracin 5 se muestra la
definicin del modelo considerando que los datos sern obtenidos desde el archivo .dat. Un rol
importante lo juega la definicin de rangos (ranges) que representan las dimensiones de arreglos o
matrices de variables o parmetros. En este caso se deben definir dos rangos uno indica la dimensin
del vector de variables de decisin (n) y el otro el nmero de restricciones (m), es claro adems que la
dimensin de la matriz de restricciones es m n, la dimensin del vector de coeficientes de la funcin
objetivo es n y la dimensin del vector de coeficientes del lado derecho de las restricciones es m.
Ilustracin 5: Generacin del modelo del problema en el archivo .mod (parmetros implcitos)
Una vez que se ha definido el archivo .mod donde se contiene el modelo, es necesario escribir
el archivo .dat donde se contienen los datos del modelo, en este caso los datos son los coeficientes de
las variables de decisin en la funcin objetivo, los coeficientes de la matriz de restricciones y los rhs de
las restricciones. En la . Al ejecutar el programa se puede comprobar que valor ptimo y la solucin
ptima son entonces
y
respectivamente, lo que reafirma la validez
del modelo ahora compuesto por el archivo .datIlustracin 6 se muestra la definicin de los valores de estos parmetros en el archivo .dat. Al
ejecutar el programa se puede comprobar que valor ptimo y la solucin ptima son entonces
y
respectivamente, lo que reafirma la validez del modelo ahora compuesto por
el archivo .datIlustracin 6: Definicin de los parmetros del modelo en el archivo .dat
11
En el modelo recin presentado era necesario indicar explcitamente el tamao del problema
(nmero de variables y nmero de restricciones) en el archivo .mod, y escribir en funcin de estos
tamaos tanto la funcin objetivo como las restricciones. Una forma ms adecuada y general es
representar tanto la funcin objetivo como las restricciones en trminos de sumatorias, es decir a travs
de un modelo del tipo:
Esto es posible, y es la forma en la que se resuelven los problemas de verdadero inters para
nuestros propsitos. Sin embargo aumenta la complejidad de la escritura del modelo, puesto que se
deben incluir ciclos y, por ejemplo, el operador de sumatoria.
Antes de comenzar a escribir el modelo es necesario redefinir la tercera restriccin del
problema en estudio puesto que est como una restriccin del tipo , por lo que se reescribe como
.
La forma de expresar la funcin objetivo en OPL es utilizando el operador sumatoria (sum)
indicando que esta sumatoria se realiza sobre un cierto rango. En el caso del problema, el rango de la
sumatoria est dado por el nmero de variables, por lo que la funcin objetivo se define como:
minimize sum(i in n) coeficientesFO[i]*x[i]
Ya definida la funcin objetivo en trminos del operador sumatoria, definiremos ahora las
restricciones en forma similar. Si consideramos la segunda formulacin presentada, podemos escribir la
j-sima restriccin como:
sum(i in n)matrizRestricciones[i][j]*x[i] <= rhs[j],
el rango del ndice j est dado por el nmero de restricciones, en este caso hay tres restricciones, por lo
que j = 1,2,3, un ciclo forall se debe utilizar para recorrer el las restricciones haciendo que el ndice
de este ciclo recorra el rango de j. El conjunto de restricciones entonces se modela a travs del cdigo
que se muestra en la
Ilustracin 7.
Ilustracin 7: Definicin de las restricciones en lenguaje algebraico
El cdigo completo del archivo .mod se muestra en la Ilustracin 8, en sta se puede notar que
la nueva implementacin es mucho ms genrica y podra representar a casi cualquier modelo de
programacin lineal, salvo por el hecho que se especifica el tanto el nmero de variables (int
numVariables=3) como el nmero de restricciones (int numRestricciones=3), sin embargo
ambos parmetros podra perfectamente ser definidos en el archivo .dat generalizando as el modelo
presentado.
12
Ilustracin 8: Generacin del modelo del problema en el archivo .mod (operadores sum y forall)
Al ejecutar el programa se puede comprobar que valor ptimo y la solucin ptima son
entonces
y
respectivamente, lo que reafirma la validez del modelo
ahora compuesto por el archivo .dat.
de esta forma OPL sabe que se enlazar con el archivo nombreArchivo que debiese estar contenido en
la carpeta del proyecto. En este caso, el archivo que utilizaremos ser datos_ejemplo1_OPL.xls.
Los datos que leeremos desde el archivo ser el nmero de variables, el nmero de
restricciones, los coeficientes de la funcin objetivo, los coeficientes de la matriz de restricciones y el los
valores de los rhss. Para ello, es necesario conocer la ubicacin de stos parmetros en la planilla Excel
en trminos de sus coordenadas (columna y fila). En la Ilustracin 9 se muestran las celdas conteniendo
los datos del problema.
Ilustracin 9: Planilla .xls conteniendo los datos del problema
De la Ilustracin 9 podemos ver que el nmero de variables est ubicado en la celda C2, por lo
que se debe indicar esto en el archivo .dat, la forma de hacerlo se muestra a continuacin:
numVariables from SheetRead(sheet, "Hoja1!C2")
13
El resto de los datos se leen en forma similar como se muestra en la Ilustracin 10, es importante notar
que en el caso de los coeficientes de la matriz de restricciones (matrizRestricciones), estos
deben ser ledos desde una matriz de datos por lo que en el comando de lectura debe sealarse la celda
superior izquierda (C7) y la celda inferior derecha (E9) desde donde se extraern los datos.
Ilustracin 10: Archivo .dat con la lectura de parmetros desde excel
14
Con el modelo ya presentado, podemos proceder a generar el proyecto OPL para resolver sta
problema. El proyecto (construido en el proyecto ejemplo_transporte_OPL adjunto a este documento),
tendr dos variantes mostrando diferentes posibles definiciones de estructuras de datos con tal de
mostrar cmo se pueden utilizar definiciones de variables que mejoren la eficiencia del cdigo.
Una vez estructurados los parmetros sealando su naturaleza, se debe proceder a escribir el
cdigo del archivo .dat donde se contendr los valores para stos. Los parmetros Ciudades,
Productos, Capacidad, Oferta y Demanda son simples de definir, puesto que son todos
vectores unidimensionales o bidimensionales. En la Ilustracin 12 se muestran los valores de estos
parmetros definidos a partir de vectores y matrices. Es claro que estos puede ser fcilmente enlazada a
una planilla de clculo .xls (ver Ilustracin 10 para ms detalles).
15
Conocida ya la estructura de los datos que contendrn los valores de los costos, es necesario encontrar
ahora una representacin que permita ser ingresada al archivo .dat; en primer lugar, podemos
representar la forma general de la matriz de costos como un vector columna de tres filas, como se
muestra en la Ilustracin 13, en donde se muestra que en cada uno de los componentes (hasta ahora
sin definir, lo que simboliza con ) se deben cargar las matrices de costos para los productos X, Y y Z
respectivamente. Por ejemplo, en la primera componente debe definirse una matriz de dimensin
10 10, conteniendo los costos de transporte entre las 10 ciudades del producto X, es decir, sus
componentes son del tipo
donde {A,,J} y
{A,,J}.
Ilustracin 13: Esquema de la definicin de valores de parmetros Costos en el archivo .dat (variante I)
En la Ilustracin 14 se muestra la definicin de los costos de transporte para el producto X, con la ayuda
de texto comentado (/* */) se hace ms fcil entender cmo debe ser llenada la matriz. Es claro que
un valor cero en una posicin ij indica que no existe un camino entre ambas ciudades.
16
Ilustracin 14 Definicin de valores del parmetro Costo (para producto X) en el archivo .dat (variante I)
Una vez definidos ya todos los valores de los parmetros a utilizar por el modelo, se puede
definir el modelo, es decir, funcin objetivo y restricciones. De acuerdo al modelo presentado en la
Ecuacin 1, la funcin objetivo es slo una triple sumatoria definida sobre el conjunto de productos y
sobre pares de ciudades que expresa el costo total de transporte dado por las cantidades a transportar y
el producto de estas cantidades por los respectivos costos unitarios de transporte. Una forma de
representar esto es utilizando tres sumatorias en forma consecutiva como se muestra a continuacin:
minimize sum(p in Productos)
sum(o in Ciudades)
sum(d in Ciudades)
Costo[p][o][d]*Trans[p][o][d];
aunque una forma mucho ms simple de hacerlo, es simplemente utilizar un nico operador sumatoria y
en ste considerar los tres conjuntos, es decir:
minimize
sum(p in Productos, o, d in Ciudades)Costo[p][o][d]*Trans[p][o][d];
esto indica que todo la cantidad total transportada del producto p desde las otras ciudades o hacia la
ciudad d debe ser igual a la demanda del producto p que tiene la ciudad d. De la misma forma, es
necesario adems que, por balance, sea utilizada enviada toda la oferta del producto p que tiene la
ciudad o, lo que puede representarse como
forall(p in Productos, o in Ciudades)
sum(d in Ciudades) Trans[p][o][d] == Oferta[p][o];
restriccin que indica que todo lo transportado del producto p desde la ciudad o hacia todas las
ciudades d debe ser igual a la oferta del producto p que tiene la ciudad o.
Finalmente, es necesario agregar una restriccin asociada a la capacidad del vehculo que
realiza el transporte; dicha restriccin debe sealar que el total transportado debe ser menor a la
capacidad del vehculo, es decir
forall(o, d in Ciudades)
sum(p in Productos) Trans[p][o][d] <= Capacidad.
CPLEX y ste lo resuelve, desplegando la solucin en la pestaa soluciones en la parte inferior del
ambiente del programa. En este caso particular la solucin se muestra a continuacin:
Trans =
[[
[0
[0
[0
[0
[0
[0
[0
[0
[0
[0
[
[0
[0
[0
[0
[0
[0
[0
[0
[0
[0
[0
[
[0
[0
[0
[0
[0
[0
[0
[0
[0
[0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
225
75
0
0
0
0
0
0
0
0
0
500
0
0
0
0
0
0
0
0
0
50
50
0
0
0
0
0
0
0
0
0
300
0
0
0
0
0
0
0
0
525
225
0
0
0
0
0
0
0
0
0
100
0
0
0
0
0
0
0
0
0
0
100
0
0
0
0
0
0
0
0
400
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
75
0
0
0
0
0
0
0
0
250
0
0
0
0
0
0
0
0
0
0
50
0
0
0
0
0
0
0
0
400
250
0
0
0
0
0
0
0
0
25
300
625
0
0
0
0
0
0
0
0
200
0
0
0
0
0
0
0
0
0
0
0
225
0
0
0
0
0
0
0
625
125
100
0
0
0
0
0
0
0
0
0
100
0
0
0
0
0
0
0
0
0
225
25
0
0
0
0
0
0
0
150
0
350
0
0
0
0
0
0
0
0
0
0
250
0
0
0
0
0
0
0
]
]
]
]
]
]
]
]
]
] ]
]
]
]
]
]
]
]
]
]
]
] ]
]
]
]
]
]
]
]
]
]
] ]];
El valor 250, resaltado en rojo, indica que desde la ciudad B hasta la ciudad H se envan 250 unidades del
producto X; el valor 225, resaltado en anaranjado, indica que desde la ciudad C hasta la ciudad E se
envan 225 unidades del producto Y; y, finalmente, el valor 100, resaltado en verde, indica que desde la
B hasta la ciudad E se envan 100 unidades del producto Z. Se puede ver cada ninguno de los envos
sobrepasa las 625 unidades establecidas como valores lmite de capacidad. El costo ptimo corresponde
a 199500 unidades monetarias.
18
contenga tres elementos producto, ciudad de origen y ciudad de destino, como se muestra a
continuacin
tuple ruta {
string p;
string o;
string d;}
Con esto hemos definido un metadato que contiene informacin respecto a una ruta determinada, el
conjunto de todas las rutas ser entonces un arreglo de estos metadatos. Este arreglo ser ledo desde
el archivo .mod donde cargaremos la informacin de las rutas existentes. La definicin del arreglo de
estas tuplas en el archivo .mod se presenta ms abajo
{ruta} Rutas = ...;
El conjunto de estas rutas se deben explicar en el archivo .dat, y se pueden obtener de un anlisis de la
matriz Costo presentada anteriormente (ver Ilustracin 14), considerando aquellas componentes
diferentes a cero, lo que significa que consideraremos slo una parte de los datos originales; de los 300
registros originales (3x10x10) ahora slo tenemos 63 registros, que corresponden a las 63 rutas
existentes como se muestra a continuacin.
Rutas = {
<X
<X
<X
<X
A
A
B
C
D>,
J>,
I>,
H>,
<X
<X
<X
<X
A
B
B
C
E>,
D>,
J>,
I>,
<X
<X
<X
<X
A
B
C
C
<Y
<Y
<Y
<Y
A
A
B
C
D>,
J>,
I>,
H>,
<Y
<Y
<Y
<Y
A
B
B
C
E>,
D>,
J>,
I>,
<Y
<Y
<Y
<Y
A
B
C
C
<Z
<Z
<Z
<Z
A
A
B
C
D>,
J>,
I>,
H>,
<Z
<Z
<Z
<Z
A
B
B
C
E>,
D>,
J>,
I>,
<Z
<Z
<Z
<Z
A
B
C
C
};
Los datos de la oferta tambin pueden definirse utilizando tuples, parece natural generar una tupla
inicial que contenga dos elementos, el producto y la ciudad que lo oferta. De los datos sabemos que
existen slo tres ciudades ofertantes, A, B y C, cada una ofertando diferentes cantidades de los
productos X, Y y Z. De esta forma, se define una tupla oferta como se muestra a continuacin
tuple oferta {
string p;
string o;}
En esta tupla, p corresponde al producto y o a la ciudad de origen de dicho producto; sin embargo no
est explicitada la informacin de cunto oferta cada ciudad de cada producto. Para ingresar esta
informacin es necesario primero determinar qu ciudades son en realidad ofertantes; esto se puede
realizar con un procedimiento similar al realizado para definir las rutas, utilizando un arreglo de tuplas
que llamaremos CiudadesOferta en el archivo .mod de la siguiente forma:
{oferta} CiudadesOferta = ...;
Estos datos deben entregarse en el archivo .dat y deben contener qu ciudades ofrecen qu productos,
informacin que se presenta en la Ilustracin 12, y que en esta nueva definicin de datos debe
entregarse como sigue,
CiudadesOferta={
<X A>, <Y A>, <Z A>, <X B>, <Y B>, <Z B>, <X C>, <Y C>, <Z C>,};
Ya definidas las ciudades de oferta y los productos que stas ofrecen, es necesario indicar ahora cunto
ofrece cada una de esas ciudades de cada uno de los productos, para ellos generamos un arreglo de
19
datos reales llamado Oferta el que debe contener un dato para cada par <p,o>, es decir, debemos
crear un arreglo como se muestra a continuacin,
float Oferta[CiudadesOferta] = ...;
Los datos son ledos desde el archivo .dat en donde se deben presentar de la siguiente forma:
Oferta = #[
<X
<Y
<Z
<X
<Y
<Z
<X
<Y
<Z
A>:
A>:
A>:
B>:
B>:
B>:
C>:
C>:
C>:
400
800
200
700
1600
300
800
1800
300
]#;
Esta notacin para entregar los datos permite asociar un nico dato (cantidad ofertada en este caso)
para cada par <p,o>, cada elemento <p,o>:Q de este arreglo indica que la ciudad o ofrece Q unidades
del producto p. Esto permite que los datos puedan ingresarse en forma desordenada y an as se
asignara la cantidad correcta a cada par <p,o>.
Similar a lo presentado para los datos de oferta, para la demanda es necesario definir variables
similares. Definiremos primero una tupla caracterizando la demanda, dada por un par <p,d>, donde p
indica el producto y d indica la ciudad:
tuple demanda {
string p;
string d; }
Una vez definida la tupla demanda podemos definir cules son las ciudades que demandan y cules son
los productos que ests requieren, generando un arreglo de tuplas demanda al que llamaremos
CiudadesDemanda y cuyos datos leeremos desde el archivo .dat, y son lo presentados debajo.
CiudadesDemanda={
<X D>, <Y D>, <Z D>, <X E>, <Y E>, <Z E>, <X F>, <Y F>,
<Z F>, <X G>, <Y G>, <Z G>, <X H>, <Y H>, <Z H>, <X I>,
<Y I>, <Z I>, <X J>, <Y J>, <Z J>,
};
Una vez definidas las demandas en trminos de ciudades y productos, podemos ahora asignar a cada
par <p,d> las correspondientes cantidades demandadas D, utilizando la misma notacin que en el caso
de la oferta, es decir, elementos del tipo <p,d>:D en un arreglo compuesto de elementos reales. La
declaracin de este arreglo en el archivo .mod conteniendo esta informacin de demandas as como los
datos a ingresar en el archivo .dat se muestran debajo.
float Demanda[CiudadesDemanda] = ...;
Demanda = #[ <X
<Z
<Y
<X
<Z
D>:
E>:
G>:
I>:
J>:
300, <Y
100, <X
250, <Z
225, <Y
250 ]#;
D>:
F>:
G>:
I>:
500, <Z D>: 100, <X E>: 300, <Y E>: 750,
100, <Y F>: 400, <Z F>: 0, <X G>: 75,
50, <X H>: 650, <Y H>: 950, <Z H>: 200,
850, <Z I>: 100, <X J>: 250, <Y J>: 500
Los datos que se deben definir ahora, corresponden a los costos de transporte. Sabemos que cada ruta
tiene un costo asociado dependiendo del producto transportado y las ciudades entre cada ruta. Dado
que ya hemos definido un arreglo conteniendo cada ruta, podemos ahora definir un arreglo de valores
reales con tantos elementos como rutas; podemos utilizar el mismo formato usado anteriormente
definiendo un elemento del tipo <p o d>:C indicando que la ruta entre la ciudad o y la ciudad p tiene
un costo C si se transporta el producto p. Pero presentaremos otra forma ms sencilla, en la que
directamente sealaremos los costos de transporte, asumiendo que el orden es exactamente el mismo
que el que tienen los datos de las rutas. Los costos estarn contenidos en la siguiente variable
float Costo[Rutas] = ...;
20
30,10,8,10,11,71,6,22,7,10,7,21,82,13,19,11,12,10,25,83,15,39,14,
11,14,16,82,8,27,9,12,9,26,95,17,24,14,17,13,28,99,20,41,15,12,
16,17,86,8,29,9,13,9,28,99,18,26,14,17,13,31,104,20];
Esta misma forma podra haberse utilizado en el caso de Oferta y Demanda, simplificando an ms el
ingreso de datos.
En muchos problemas de flujo en redes como ste, es til definir separar los nodos entre
aquellos que slo son de oferta, aquellos que slo son de demanda y aquellos que son de transbordo. En
este caso, tenemos nodos de oferta y de demanda exclusivamente. Definiremos dos conjuntos de
nodos: Orig y Dest, para indicar nodos de oferta y demanda respectivamente. Podemos determinar
estos conjuntos sin la necesidad de ingresarlos como datos, sino que simplemente con la informacin ya
contenida en los parmetros; por ejemplo, si existe una ruta <p o d>, sabemos que o ser una ciudad
de oferta u origen y d ser una ciudad de demanda o destino, es claro que los conjuntos Orig y Dest
son en realidad vectores o arreglos de datos de naturaleza string puesto que hemos declarado las
ciudades de tal forma. El cdigo en lenguaje OPL para la creacin de ambos conjuntos es el que se
muestra ms abajo.
{string} Orig[p in Productos] = {o | <p,o,d> in Rutas};
{string} Dest[p in Productos] = {d | <p,o,d> in Rutas};
Se puede interpretar este cdigo de la siguiente forma para el caso del vector Orig: escoger para cada
producto p y de entre todas las rutas, aquellas ciudades de origen de estas rutas. Similar definicin se
puede dar para el caso del vector Dest.
A este punto ya hemos definido todos los parmetros del modelo, por lo que resta todava
definir las variables de decisin, funcin objetivo y restricciones.
Respecto a la variables de decisin, y puesto que el modelo problema a resolver es el mismo
que el tratado en la primera variante, entonces la variable de decisin es la misma, es decir, la cantidad
transportada de cada producto desde una ciudad de oferta a una de destino. En la primera variante
presentada para este problema, se defini una variable de decisin del tipo Trans[p][o][d], donde el
primer subndice, p, indicaba el producto transportado y los subndices o y d indicaban la ruta de
transporte. En esta segunda variante no tiene sentido definir una variable de esa forma puesto que el
parmetro Rutas implcitamente contiene ya los subndices p, o y d, definiendo completamente el
dominio de la variable de decisin Trans. De esta forma, la variable de decisin se debe definir de la
siguiente manera:
dvar float+ Trans[Rutas];
En esta definicin de variable, se especifica que para cada ruta, definida por un producto, una ciudad de
origen y una ciudad de destino, existe una cantidad real positiva asociada (por eso se declara la variable
como float+) que representa la cantidad transportada de aquel producto entre aquellas ciudades.
La funcin objetivo ser, por cierto, similar a la definida en la variante I mostrada
anteriormente, pero presentar diferencias puesto que tanto los coeficientes como las variables de
decisin son de una naturaleza diferente. En la variante anterior, la funcin objetivo estaba compuesta
por trminos del tipo Costo[p][o][d]*Trans[p][o][d], sin embargo ahora podemos reemplazar
los subndices [p][o][d] por un nico ndice asociado a cada ruta, es decir podemos definir la funcin
objetivo como sigue:
Minimize sum(l in Rutas) Costo[l] * Trans[l];
21
donde el ndice l contiene la informacin tanto de los productos como de las ciudades entre los que
este producto se transporta.
Las restricciones sern tambin esencialmente iguales variando apenas debido a la definicin
de los parmetros, al igual que en el caso de la funcin objetivo. Las restricciones de Oferta, Demanda y
Capacidad se muestran abajo.
forall(p in Productos , o in Orig[p])
{
ctOferta:
sum(d in Dest[p])Trans[< p,o,d >] == Oferta[<p,o>];
}
forall(p in Productos, d in Dest[p])
{
ctDemanda:
sum(o in Orig[p])Trans[< p,o,d >] == Demanda[<p,d>];
}
ctCapacidad:
forall(o , d in Ciudades)
sum(<p,o,d> in Rutas) Trans[<p,o,d>] <= Capacidad;
Es importante notar el rol que juegan los vectores Orig y Dest en estas restricciones, puesto slo se
generan restricciones de oferta (ctOferta) para las ciudades de origen y restricciones de demanda
(ctDemanda) para las ciudades de destino.
Una vez definido el modelo completo y resuelto y se puede corroborar que el valor ptimo de
la funcin objetivo es 199500, igual valor obtenido al obtenido en la primera variante desarrollada.
Respecto a la solucin ptima, es decir, los valores de las variables del vector Trans, estos de presentan
a continuacin:
Trans = [0
250
0
0
0
0
0
0
0
500
0
0
0
225
0
225
50
0
0
75
25
0
100
0
400
300
625
0
0
250];
0
0
100
75
150
0
625 100 350
50
0
225
0
0
225
525 400 250
0
0
100
0
0
25
300
0
50
0
0
125
0 200
0
Podemos ver que la 12va ruta, que corresponde a la ruta <X,B,H> tiene un valor 250, que es el mismo
valor de la solucin encontrada en el primero modelo desarrollado, lo que corrobora la validez de esta
nueva formulacin.
Si bien esta segunda variante eleva el nivel de complejidad de las variables definidas, mejora el
manejo interno de los parmetros y variables en el motor de resolucin de OPL, reduciendo tanto las
iteraciones internas en la generacin del modelo como en la resolucin de ste. Se pueden realizar
mejoras a la segunda variante, una de ellas es bastante intuitiva y corresponde a agregar a cada ruta su
respectivo costo definiendo tuplas del tipo <p,o,d,c>, donde c contiene el costo de dicha ruta.
22
y
enteros
+ restricciones de eliminacin de subtour
En esta formulacin,
(2)
(3)
(4)
(5)
son las variables de decisin y que toman dos valores posibles (restriccin (4)),
tal que
(6)
Una formulacin basada en este tipo de restricciones es sumamente ineficiente si se pretende resolver
directamente utilizando un solver como CPLEX, sin embargo son sumamente eficientes si se utilizan en
estrategias algortmicas ms sofisticadas como algoritmos de separacin de restricciones como
subrutina de algoritmos de planos de corte o algoritmos branch and cut especialmente diseados para
TSP u otro problema que considere este mismo tipo de restricciones. En esta seccin, propondremos
una estrategia algortmica ms simple, por lo es necesario utilizar una formulacin alternativa a la
[4]
presentada en (6). Miller, Tucker y Zemlin presentan otra formulacin basada en el mismo modelo de
asignacin pero utilizando
restricciones de eliminacin de subtour usando
variables
continuas adicionales. Estas restricciones son
para todo
(7)
[3]
G.B. Dantzig, D.R. Fulkerson and S.M. Johnson, "Solutions of large scale travelling salesman problem", Operations Research 2,
393-410 (1954).
[4]
C.E. Miller, A.W. Tucker and R.A. Zemlin, "Integer programming formulation of travelling salesman problems", J. ACM 7, 326-329
(1960).
23
,y
para todo
(8)
En esta definicin, n corresponde al nmero de nodos y N representa el conjunto de estos n nodos dado
por los enteros entre 1 y n. Es claro que puesto que existen conexiones entre cada par de nodos, el
vector de costos c tiene un dominio en el espacio NxN. En el archivo .dat, se presenta la lectura de estos
datos desde una planilla Excel, por lo que de acuerdo a lo presentado la primera seccin, la estructura
est dada por:
SheetConnection sheet("datos.xls");
n from SheetRead( sheet, "Hoja1!D2" );
c from SheetRead( sheet, "Hoja1!C5:R20" );
Utilizaremos a modo de ejemplo una instancia de 16 nodos, cuya matriz de costos se muestra en la
Tabla 1. En la tabla se puede ver que los elementos en la diagonal, es decir los valores de
, tienen
valor cero, hecho que debe considerarse en la generacin del modelo.
Tabla 1: Matriz de costos de la instancia considerada para TSP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
0
6
5
3
11
8
7
1
12
8
26
5
5
5
7
1
2
6
0
1
4
17
14
13
6
17
13
32
11
11
11
13
7
3
5
1
0
5
16
13
12
6
16
12
31
10
10
11
12
6
4
3
4
5
0
13
11
10
3
15
11
28
8
8
8
9
5
5
11
17
16
13
0
4
6
11
8
8
16
7
7
6
4
11
6
8
14
13
11
4
0
1
8
5
4
17
3
3
3
3
8
7
7
13
12
10
6
1
0
7
5
3
18
2
2
2
3
7
8
1
6
6
3
11
8
7
0
12
8
26
5
5
5
6
2
9
12
17
16
15
8
5
5
12
0
4
15
7
7
7
8
11
10
8
13
12
11
8
4
3
8
4
0
19
3
4
4
6
7
11
26
32
31
28
16
17
18
26
15
19
0
20
21
20
20
25
12
5
11
10
8
7
3
2
5
7
3
20
0
0
1
3
5
13
5
11
10
8
7
3
2
5
7
4
21
0
0
1
3
4
14
5
11
11
8
6
3
2
5
7
4
20
1
1
0
2
5
15
7
13
12
9
4
3
3
6
8
6
20
3
3
2
0
7
16
1
7
6
5
11
8
7
2
11
7
25
5
4
5
7
0
asociadas a
que las variables continuas son del tipo float+, as entonces definimos las variables de decisin como
sigue:
dvar boolean x[N][N];
dvar float+ u[N];
Las restricciones de asignacin, que se muestran en (2) y (3), son muy simples de definir, y se presentan
abajo.
forall (j in N)
sum(i in N : i!=j)x[i][j] ==1;
forall (i in N)
sum(j in N : i!=j)x[i][j] ==1;
Las restricciones de MTZ, (7) y (8), tampoco suponen alguna complejidad, y se definen como se muestra
en el siguiente cdigo;
forall (i in N)
{
forall(j in N : j!=1 && j!=i)
{
u[i]-u[j]+1 <= n*(1-x[i][j]);
}
}
u[1]==1;
forall (i in N: i!=1)
{
u[i]>=2;
u[i]<=n;
}
A este punto ya hemos definido completamente tanto el archivo .mod como el .dat para
resolver el la instancia de TSP definida por los datos en Tabla 1. Sin embargo, agregaremos un nuevo
elemento; utilizaremos una funcin de OPL que permite guardar el valor ptimo de algunas o todas las
variables de decisin definidas en el modelo en una planilla Excel, que podra ser una planilla diferente a
a aquella que contiene los datos del problema. En el modelo utilizado, las variables de decisin de real
importancia son las variables
, contenidas en el arreglo x[N][N], por lo que estas son las variables
que quisiramos registrar. A continuacin se muestra el cdigo para realizar eso:
SheetConnection sheet2("solucion.xls");
x to SheetWrite(sheet2, "Hoja1!C5:R20");
Como se aprecia en el cdigo, es necesario abrir o generar conexin con otra planilla, en este caso la
planilla solucin.xls, la que debe existir antes de resolver el problema. Posteriormente se indica que la
variable que queremos escribir en el archivo s la variable x y luego indicamos en qu posiciones
queremos que se registre la matriz de valores de la solucin (Hoja1!C5:R20). Una vez que ejecutamos
el programa, la matriz x[N][N] se escribe en la planilla sealada y el resultado se muestra en la
25
Ilustracin 15. En color rojo hemos destacados las variables que toman valor 1, y por ende, las aristas
que pertenecen al tour ptimo; vemos entonces que el tour ptimo est dado por la secuencia
1-3-2-4-10-9-8-5-12-13-11-14-15-6-7-16-1,
y el costo de dicho tour es 71.
Ilustracin 15: Detalle de la planilla solucin.xls en el que se muestra los valores ptimos de las variables
26
y un conjunto
de
,
tal que
donde
A diferencia del problema de Mnimo rbol de Cobertura (ACM) en el que se busca conectar
todos los nodos en
a mnimo costo, el StTP busca conectar los nodos en
llamados terminales
utilizando, si es necesario, algunos nodos adicionales en
, estos nodos adicionales son llamados
nodos steiner. En la segunda figura de la Ilustracin 16 se muestra una solucin factible del StTP para la
instancia de la primera figura; en rojo se muestran los nodos terminales y en verde los nodos steiner
necesarios para conectar los nodos terminales. En la tercera figura se muestra una solucin factible al
problema de rbol de Cobertura en el cual es necesario cubrir todos los nodos del grafo.
Ilustracin 16: De izquierda a derecha: Instancia
ACM.
[5]
Cheng, X. and Du, D. E. (eds): 2002, Steiner Trees in Industry Series (Series on Combinatorial Optimization Vol. 11), 1st edn,
Kluwer Academic Publishers, Dordrecht, The Netherlands.
[6]
Michel X. Goemans, The Steiner tree polytope and related polyhedral, Mathematical Programming 63 (1994) ~5 7-182
[7]
Maculan, N., Plateau, G. and Lisser, A.: 2003, Integer linear models with a polynomial number of variables and constraints for
some classical combinatorial optimization problems, Pesquisa Operacional 23(1), 161168.
27
Sea
contrario,
es parte
y valor 0 en caso
el flujo desde el nodo al nodo que pasa a travs del nodo . El StTP puede formularse a travs del
siguiente modelo de programacin lineal entero mixto.
Sujeto a
Esta formulacin necesita de un nodo raz, que sin prdida de generalidad ser el nodo 1 el que adems
forma parte de . El conjunto
contiene los nodos de destino las aristas del tipo
, mientras que
el conjunto
contiene los nodos de origen de las aristas del tipo
.
A diferencia de la formulacin dada para TSP, en esta caso ocuparemos las estructuras utilizadas en la
segunda variante del problema de transporte tratado en la Seccin 2, es decir, usaremos tuplas para dar
estructura tanto a la informacin de aristas como nodos. Una arista se define por nodo de origen y nodo
de destino, y esta informacin es clave para identificar una arista de otra; con esto, introduciremos un
concepto til en el uso de tuplas, el concepto de key. Tal como hemos sealado, el nodo de origen y
nodo de destino son los elementos que definen una arista, por lo que crearemos una estructura edge
conteniendo las claves origen y destino, como se muestra abajo.
28
tuple edge
{
key int o;
key int d;
}
Una vez generada la estructura edge, generaremos otra tupla conteniendo la informacin de cada
arista. La informacin de cada arista es el nodo de origen, el nodo destino y el costo o distancia entre
estos nodos. Tanto el nodo de origen como el nodo de destino se representan por valores enteros
(1,2,,n) mientras que el costo es, en general, un valor real positivo, sin embargo, y sin prdida de
generalidad, asumiremos que ser entero. A continuacin se muestra el cdigo para declarar la tupla
conteniendo los datos de las aristas (dataEdge)
tuple dataEdge
{
int o;
int d;
int cost;
}
Respecto a los nodos y puesto que slo tienen un valor clave (1,2, n) podemos generar slo
una tupla conteniendo su identificador y la informacin asociada a l, es decir, si es nodo terminal o no,
incluyendo adems un identificador root (o raz) para el nodo i=1. Abajo se muestra el cdigo para
definir la tupla dataNode.
tuple dataNode
{
key int num;
string type; //Optional, Terminal & Root
}
Ya creadas las estructuras que contendrn la informacin de aristas y nodos, podemos generar
los arreglos de tuplas dataEdge y dataNode y leer la informacin desde el archivo .dat. La declaracin
de los arreglos se muestra a continuacin:
{dataEdge} dataEdges = ...;
{dataNode} dataNodes = ...;
Como se puede ver, se est sealando que la informacin est contenida en el archivo .mod; los datos
de la instancia considerada en esta seccin en el formato de lectura son los siguientes:
dataEdges={
<1
<3
<5
<6
2
4
6
9
4>,
5>,
5>,
2>,
<1
<3
<5
<7
3
5
8
9
2>,
7>,
8>,
3>,
<2
<4
<5
<8
3
5
9
9
3>,
1>,
9>,
1>,};
<2 4 6>,
<4 6 4>,
<6 7 5>,
<2 7 7>,
<4 7 6>,
De acuerdo a lo que se muestra, existen 3 nodos terminales: 1, 4 y 8. El resto de los nodos son nodos
opcionales y algunos de ellos podran pasar a ser nodos steiner.
En este punto los arreglos dataEdges y dataNodes contienen toda la informacin de la
instancia. Necesitamos ahora extraer desde estos arreglos informacin aislada para utilizar luego en el
modelo de programacin entera, especficamente en la generacin de variables de decisin, vector de
costos. Esta informacin aislada son las aristas, los costos de las aristas, los nodos terminales, los nodos
29
opcionales, el nodo raz, los conjuntos de nodos de origen de las aristas y los conjuntos de nodos de
destino de las aristas; toda esta informacin ser contenida en estructuras independientes cuya
naturaleza (tupla, entero, arreglo o valor nico) depender de la informacin que contendr. A
continuacin se muestra el cdigo para extraer esta informacin:
// extraccin de la informacin slo de aristas
{edge} edges = { <o,d> | <o,d,c> in dataEdges};
// extraccin de la informacin de los costos de aristas en un vector
int Cost[edges] = [<t.o , t.d>:t.cost | t in dataEdges ];
// extraccin de la informacin de los nodos de origen de las aristas
{int} getOut[node in N] = {d | <node,d> in edges};
// extraccin de la informacin de los nodos de destino de las aristas
{int} getIn[node in N] = {o | <o,node> in edges};
// declaracin del nodo raz
{int} rootNode = {node | <node, c> in dataNodes : c == "Root"};
// extraccin de los datos opcionales
{int} optionalNodes = {nodes | <nodes,c> in dataNodes : c == "Optional"};
// extraccin de los datos terminales
{int} terminalNodes ={nodes | <nodes,c> in dataNodes : c == "Terminal"};
Como se puede ver en el modelo de programacin entera, existen cuatro variables de decisin:
y , la declaracin de estas se muestra abajo.
dvar
dvar
dvar
dvar
boolean y[edges];
float+ z[N][N][terminalNodes];
boolean x[N];
int+ p;
En este punto ya tenemos todos los elementos para comenzar a formular el modelo en el archivo .mod.
En primer lugar definiremos la funcin objetivo, la que est dada por minimizacin de la suma de los
costos de las aristas incluidas en el rbol, es decir
minimize sum(<i,j> in edges)Cost[<i,j>]*y[<i,j>];
la restriccin
, se escribe como,
la desigualdad
, se declara como
y la restriccin
se escribe como
forall(k in terminalNodes)
{
sum(j in getOut[k])z[k][j][k] - sum(j in getIn[k])z[j][k][k] == -1;
}
El resto de las restricciones no necesitan mayor detalle dada su simpleza y se presentan abajo.
forall(<i,j> in edges, k in terminalNodes)
{
z[i][j][k] <= y[<i,j>];
z[j][i][k] <= y[<i,j>];
30
}
sum(i in N)x[i] == p;
sum(<i,j> in edges) y[<i,j>] == p - 1;
p <= n;
forall(<i,j> in edges)
{
y[<i,j>] <= x[i];
y[<i,j>] <= x[j];
}
forall(i in terminalNodes)
x[i] == 1;
Definido ya el modelo completo, podemos resolverlo obteniendo un valor ptimo 16, y valores de
variables de decisin binarias dados por
,
y el
resto de las variables con valor cero. Es decir, se conectan los nodos 1,3,4,5 y 8, siendo 3 y 5 nodos
steiner, necesarios para conectar los nodos terminales (1, 4 y 8).
31
Entonces resolvemos la relajacin lineal del ILP anterior, en el cual no se consideran las restricciones de
integralidad. Es decir, se resuelve:
En el caso general es de esperar que la solucin factible obtenida no sea entera, sin embargo, si es
entera, la solucin encontrada a travs de la relajacin lineal tambin ser la solucin ptima del
problema de programacin lineal entera.
La idea principal del algoritmo de planos de corte consiste en que si agregamos una restriccin al ILP que
no excluya soluciones factibles enteras, entonces la solucin no cambia. Por lo que si agregamos
restricciones lineales (cutting planes), una por una, al ILP hasta que la solucin de su relajacin lineal sea
entera, obtendremos la solucin ptima del ILP.
Ahora, para resolver el TSP mediante planos de corte, usaremos la formulacin de Dantzig, Fulkerson y
[3]
Johnson que se mostr en secciones previas.
As, dado un conjunto de ciudades
arco
pertenece al tour,
si es que el
El modelo es similar al descrito por las ecuaciones (1)-(4) y (6), salvo que en esta oportunidad se
considera una pequea variacin en que se agrupan los dos conjuntos de restricciones de asignacin, y
se usan el conjunto de restricciones de eliminacin de subtours de Dantzig, Fulkerson y Johnson. La
resolucin de este modelo es imprctica, debido al conjunto de restricciones de eliminacin de
subtours, pues corresponden a un nmero exponencial de restricciones al considerar todos los
subconjuntos de ciudades.
La idea detrs del algoritmo de planos de corte para resolver el TSP consiste en resolver la relajacin del
modelo anterior (RTSP), en que no se considera ninguna restriccin de eliminacin de subtour, e
iterativamente ir agregando restricciones a medida que en la solucin de la relajacin lineal aparezcan
32
subtours. Obviamente el algoritmo termina cuando no existan subtours. Claramente ser necesario un
procedimiento para, dada una solucin, encuentre los subtours existentes.
Un esquema del algoritmo de planos de corte se presenta a continuacin:
Cuando se usa la programacin en OPL, se evita tener que unir y compilar la aplicacin, slo debes
agregar instrucciones de programacin al archivo del modelo.
Existen dos expresiones de programacin de alto nivel:
Es importante mencionar que el orden de declaracin y orden de las instrucciones, siguen una secuencia
lgica. Vale decir, en un programa que posee un modelo matemtico, instrucciones de preprocesamiento, post-procesamiento y un control de flujo, el orden y secuencia de las instrucciones de
cada seccin es la siguiente:
33
Ahora, en base a las explicaciones anteriores es posible enunciar la implementacin de planos cortantes
para el TSP, para un mejor entendimiento se explicara identificando cada una de las secciones y el
modelo completo se muestra en el anexo 2.
Declaracin de datos y variables de decisin: adems del tamao del problema
, como es un TSP
clsico, los nicos datos que hacen falta son las distancias entre ciudades, y como es simtrico, basta
con la matriz diagonal superior sin la diagonal, pues consta de puros ceros. Las instrucciones necesarias
son:
int n = ...;
range Cities = 1..n;
tuple edge {int i; int j;}
setof(edge) Edges = {<i,j> | ordered i,j in Cities};
int dist[Edges] = ...;
As, se define una estructura de datos (tuple) para las aristas, y se crea un conjunto ordenado de aristas
llamado Edges. Al ser ordenado se establece que el elemento es menor al elemento en la arista
, por lo que en la instruccin int dist[Edges] = ... lo que se realiza es tomar de un archivo de
datos la informacin relacionada a la diagonal superior de matriz de distancias.
Las nicas variables de decisin necesarias en el problema corresponden a si una determinada arista
pertenece o no a la solucin (tour).
dvar boolean x[Edges];
Como en el algoritmo se desea ingresar los cortes relacionados a los subtours encontrados, es necesaria
una estructura que, por ejemplo, guarde el tamao del subtour encontrado y en un arreglo el subtour:
tuple Subtour { int size; int subtour[Cities]; }
{Subtour} subtours = ...;
La ltima instruccin permite leer de un archivo de datos los subtours, sin embargo, como se resuelve el
problema relajado, se comienza sin ningn subtour y a medida que se resuelvan relajaciones se irn
agregando subtours a los datos con el formato establecido.
Definicin del modelo matemtico de Dantzig, Fulkerson, y Johnson:
minimize sum (<i,j> in Edges) dist[<i,j>]*x[<i,j>];
subject to
{
forall (j in Cities)
// Restricciones de asignacin
sum (<i,j> in Edges) x[<i,j>] + sum (<j,k> in Edges) x[<j,k>] == 2;
forall (s in subtours)
// Restricciones de eliminacin de subtours
sum (i in Cities : s.subtour[i] != 0)
x[<minl(i, s.subtour[i]), maxl(i, s.subtour[i])>] <= s.size-1;
};
Notar en la restricciones de eliminacin de subtour que las restricciones se aaden para cada subtour
existente en la seccin de datos subtours, por lo que en la primera iteracin no se agregara ninguna
restriccin de dicho tipo. Adems la sumatoria se realiza sobre cada elemento distinto de cero (por lo
que se espera que sea inicializado con valores iguales a cero) sobre un arreglo de dimensin del nmero
de ciudades, en que la posicin indica la ciudad y el valor de dicha posicin, la siguiente ciudad que se
34
visita. Un ejemplo en que el nmero de ciudades sea 10 y exista un subtour de 3 ciudades podra estar
representado por la siguiente figura:
4
10
Notar adems que el uso de las funciones minl y maxl tiene relacin slo a que el problema es
simtrico.
Instrucciones de post-procesamiento: es necesario para que dada una solucin, identificar el subtour
tour asociado a la solucin. El esquema general presenta la siguiente estructura:
execute
{
/*
* Funcin findSubtour
*/
}
Debido a que el procedimiento no tiene gran complejidad, el procedimiento completo se muestra con
comentarios explicativos en el anexo 1, mientras los archivos se encuentran en la carpeta del proyecto
de OPL TSP-DFJ.
Instrucciones de control de flujo: a travs de la expresin main se especifica el comienzo de las
instrucciones de control de flujo, que otorga gran flexibilidad al resolver el problema, permitiendo usar
diferentes modelos con diferentes datos, resolver multiples modelos, modificar los datos del modelo
entre una resolucin y otra. Es particularmente til cuando se quiere resolver un modelo con diferentes
datos, como en nuestro caso. Resaltar que cuando se ejecute las instrucciones se ejecutaran de acuerdo
al main. La sintaxis de las expresiones relacionadas al lenguaje de programacin IBM ILOG Script for
OPL son muy similares a la sintaxis de C y C++, incluidas expresiones de asignacin, llamadas a
funciones, propiedades de acceso, entre otros.
Las estructuras necesarias para manipular el modelo y los datos se muestran en la siguiente tabla:
IloOplModelDefinition
IloCplex
IloOplDataSource
IloOplDataElements
IloOplModel
Cuando se ejecuta el bloque main, se crea por defecto una variable llamada thisOplModel que
contiene la instancia de IloOplModel disponible, adems de una variable cplex que corresponde
a la instancia ya creada del algoritmo CPLEX. Una vez creada la instancia de IloOplModel se puede
resolver a travs del algoritmo cplex.
Las variables en el lenguaje de programacin de OPL se definen a travs de la palabra var, distinta a la
usada para las variables de decisin dvar de los modelos.
La implementacin del bloque main es la siguiente:
35
main
{
var opl = thisOplModel
var mod = opl.modelDefinition;
var dat = opl.dataElements;
while (1)
{
cplex.clearModel();
opl = new IloOplModel(mod,cplex);
opl.addDataSource(dat);
opl.generate();
if (!cplex.solve())
{
writeln("ERROR");
opl.end();
break;
}
opl.postProcess();
if (opl.newSubtourSize == opl.n)
{
writeln("Tour final ",opl.thisSubtour);
opl.end();
break;
}
dat.subtours.add(opl.newSubtourSize, opl.newSubtour);
opl.end();
}
}
Inicialmente se asigna la instancia de la clase IloOplModel a una variable llamada opl. As, esta variable
contendr la definicin del modelo y sus datos actuales. Por lo que usando el mtodo
opl.modelDefinition se extraer el modelo a la variable mod, y con opl.dataElements se extraen
los datos a una variable llamada dat. Mediante un ciclo while que siempre es verdadero se asegura
que siempre se realice el ciclo para iterativamente ir resolviendo el modelo relajado y agregando los
cortes. Al inicio de cada iteracin se limpia el algoritmo cplex, se crea una instancia de
IloOplModel uniendo el modelo modificado (mod) con el algoritmo cplex, se le agregan los datos
(modificados), y se genera el modelo completo para ser nuevamente resuelto a travs del comando
cplex.solve(). Si es que no se puede resolver, se despliega un mensaje de error, y si es que se
resuelve, se realiza el post-procesamiento para encontrar un subtour. Si es que el tamao del subtour es
igual al nmero de ciudades, significa que se ha encontrado la solucin, en caso contrario, se agrega a la
seccin de los subtour en los datos, el nuevo tamao del subtour, y el nuevo subtour para que sea
agregado al modelo como restriccin (cut). Al finalizar cada iteracin se elimina la instancia de opl para
el eficiente uso de la memoria, y de esta forma termina la implementacin del TSP usando planos
cortantes.
http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/
36
tal forma que sean ledos de acuerdo al formato de lectura de la implementacin realizada, que en el
caso se utiliza la matriz diagonal superior de distancias entre ciudades.
El tiempo aproximado que demor OPL en resolver la instancia corresponde a 0,12 segundos. Vale decir
que si comparamos con la instancia resuelta mediante el modelo de Miller, Tucker y Zemlin que tena 16
ciudades y demor del orden de 20 segundos, se aprecia claramente la mayor rapidez del mtodo de
planos de corte y la solucin se muestra en la Ilustracin 17. Adems que la principal diferencia se
presenta en instancias de mayor tamao, pues si bien se observa un aumento en los tiempos de
resolucin a mayor nmero de ciudades, es posible resolver instancias de mucho mayor tamao con el
algoritmo de planos de corte que con el modelo de Miller, Tucker y Zemlin.Ilustracin 1
Ilustracin 17: Solucin de problema gr17.tsp
Una de las deficiencias de la implementacin del algoritmo de planos cortantes en OPL es la flexibilidad
intermedia que otorga el lenguaje de programacin de OPL, por lo que si se desea mayor flexibilidad y
eficiencia es recomendable el uso directo de las libreras otorgadas por la tecnologa concierto en
aplicaciones desarrolladas en diversos lenguajes como C, C++, Java, Python, etc.
37
Objetos de
Modelamiento de la
Tecnologa Concierto
Objetos IloCplex
-------------------------Caractersticas
internas de CPLEX
38
Mencionar que no se explicarn en detalles los elementos propios de programacin, como la creacin
de clases, declaracin de mtodos, variables, lectura de datos, entre otros. Se concentrar la atencin
en explicar los mtodos que tienen estrecha relacin con la creacin del modelo y el algoritmo para
resolver el problema, los cuales se encuentran exclusivamente en la clase TSPSolver. Para ver el
programa completo se puede ver el anexo 2 o la carpeta del proyecto desarrollado en Netbeans.
La definicin de la clase TSPSolver posee la siguiente estructura:
class TSPSolver
{
public:
IloEnv env
IloInt nCities;
IloInt **d;
TSPSolver( char *nombre );
TSPTour solverTSP();
private:
IloBoolVar **createDVars();
IloModel buildModel( IloBoolVar **x );
IloBool **getTourInfo(IloBoolVar **x, IloCplex cplex);
vector<TSPTour> buildTours( IloBool **tourInfo );
bool addConstraints(IloCplex cplex,IloBoolVar **x,vector<TSPTour> &tours);
};
[9]
http://ie.tamu.edu/INEN689-602/Papers/ILOG%20CPLEX%209_0%20README.htm
39
En la declaracin del constructor, se recibe como parmetro un puntero a un arreglo de caracteres que
contiene el nombre del archivo de datos del problema que se desea resolver.
La clase contiene un nico mtodo pblico llamado solverTSP(), encargado de implementar el
algoritmo de planos cortantes, y retorna un objeto de tipo TSPTour, que corresponde a un arreglo de
enteros.
Otros mtodos privados de la clase, necesarios para la implementacin del algoritmo de planos
cortantes son:
En una aplicacin desarrollada en C++ usando Concierto, tpicamente el primer objeto creado
corresponde al ambiente, que corresponde a una instancia de la clase IloEnv. As, como miembro
pblico de la clase, se cre un objeto que contiene el ambiente, a travs de la instruccin:
IloEnv env;
El ambiente es de central importancia y necesita estar disponible para los constructores de todas las
otras clases de la Tecnologa Concierto, pues, entre otras cosas, provee del manejo optimizado de la
memoria para los objetos de las clases de la Tecnologa Concierto (mejora el manejo de la memoria
realizado por el sistema operativo). IloEnv, as como la mayora de las clases de Concierto, es una
clase de manejo (Handle Class), es decir, es un puntero a un objeto de implementacin.
Tambin se crearon otros miembros pblicos:
IloInt nCities;
IloInt **d;
La variable nCites es del tipo IloInt, que corresponde a los tipos de variables enteras en la
Tecnologa Concierto. Esta variable almacena el nmero de ciudades del problema a resolver. Mientras
la variable d, corresponde a un doble puntero que apunta a variables de tipo IloInt. El uso de d es
para representar la matriz de distancias entre ciudades.
Mencionamos que la primera instruccin es crear el ambiente, la siguiente es la sentencia try/catch
que permite el manejo de errores de dos tipos, el primero cubre errores simples de programacin, y el
segundo tipo no puede ser evitado slo con una correcta programacin, como por ejemplo el
agotamiento de la memoria. As, tiene la siguiente estructura:
try
{
// ...
}
catch (IloException& e)
{
cerr << "Concert Exception: " << e << endl;
}
catch (...)
{
cerr << "Other Exception" << endl;
}
env.end();
40
Despus de la sentencia try, en la parte denotada por los puntos suspensivos (...), se escribe la
totalidad del cdigo. Las sentencia catch, captura los errores capturados por la Tecnologa Concierto
algn otro error desconocido. Al final de toda implementacin, es necesario destruir el ambiente
construido a travs de la instruccin env.end().
El cdigo completo del algoritmo se muestra a continuacin:
TSPTour TSPSolver::solverTSP()
{
try
{
IloBoolVar **x = this->createDVars();
IloModel model = this->buildModel(x);
IloCplex cplex(this->env);
cplex.extract(model);
while (cplex.solve())
{
IloBool **tourInfo = this->getTourInfo(x, cplex);
vector<TSPTour> tours = this->buildTours(tourInfo);
if( !this->addConstraints(cplex,x,tours) )
{
return tours[0];
}
}
}
catch (IloException & e)
{
cerr << "Concert Exception: " << e << endl;
}
catch (...)
{
cerr << "Other Exception..." << endl;
}
env.end();
}
Lo primero que se realiza, es crear las variables de decisin mediante el mtodo createDVars()(ver
anexo 2.1), que corresponden a variables binarias (boleanas)
, que toman un valor igual a 1 0, en
caso a si se va de la ciudad a la ciudad , respectivamente.
En el procedimiento, se crea un puntero a puntero x de tipo IloBoolVar y se asigna memoria al
arreglo bidimensional de dimensin
. Tambin se le da nombre, y se inicializan todas
las variables mediante un constructor adecuado, en que se especifica el ambiente en que sern
utilizadas y el nombre asignado. El mtodo retorna un puntero a puntero que se asigna a un puntero a
puntero del mismo tipo tambin llamado x.
El siguiente paso consiste en crear un objeto de modelamiento para construir el modelo matemtico del
problema a resolver. Los objetos de modelamiento tambin son conocidos como extrables, porque
cada objeto es extrado uno por uno cuando se extrae un modelo de optimizacin a un objeto
IloCplex. La clase extrable ms fundamental es la IloModel, pues objetos de esta clase son usados
para definir modelos de optimizacin completos que son posteriormente extrados a un objeto
IloCplex.
Una vez que un objeto IloModel se ha construido, con el propsito de definir el modelo de
optimizacin, es poblado con otros objetos extrables, tales como:
, donde
es una
41
As, se crea un objeto de tipo IloModel llamado model, al cual se le asigna el objeto de retorno del
mtodo buildModel (ver anexo 2.1), que corresponde a un objeto IloModel que contiene el modelo
del TSP relajado. En las instrucciones del mtodo buidModel, que recibe como parmetro las variables
de decisin, se construye el modelo.
El primer paso para construir un modelo es su construccin:
IloModel model(this->env);
En que se especifica el ambiente en que se construir, el cual es pasado como argumento al constructor.
En este punto se est en condiciones de poblar el modelo mediante otros objetos extrables. Si
queremos agregar al modelo la funcin objetivo es necesario crear una expresin numrica, pasando
como argumento el ambiente al cual pertenecer la expresin, lo siguiente es crear la expresin:
IloNumExpr objective(this->env);
for (i = 0; i < nCities; i++)
{
for (j = 0; j < nCities; j++)
{
if (i != j)
{
objective += x[i][j] * d[i][j];
}
}
}
, ahora es
necesario agregarla al modelo mediante el mtodo add y la funcin IloMinimize para especificar
que se desea minimizar la funcin objetivo:
model.add(IloMinimize(this->env, objective));
Ahora, para crear las restricciones es necesario el uso de objetos del tipo IloRange. Es posible crear
un rango mediante los constructores de la clase IloRange, a travs de operadores aritmticos sobre
las variables (IloNumVar) mediante expresiones (instancias de IloExpr y sus subclases). En
nuestro caso se construirn los rangos mediante expresiones.
El conjunto de restricciones (de asignacin) que definen que para cada ciudad, solamente se puede salir
hacia una ciudad corresponde a:
Notar que efectivamente se crean nCities restricciones al declararse la espresin expr (instancia de
la clase IloNumExpr) afuera del segundo ciclo for. As, lo primero es declarar la expresin y decirle
42
en qu ambiente hacerlo. Una vez creada la expresin, es necesario el uso de un objeto IloRange
para crear la restriccin, usando el operador aritmtico de igualdad (==) entre el valor numrico y la
expresin. Ahora, es necesario agregar la restriccin (constraint) al modelo mediante el mtodo
add, de caso contrario la restriccin no es tomada en cuenta.
La forma para agregar el segundo conjunto de restricciones en que se asegura que exactamente una vez
se entre a cada cuidad.
Una vez que el modelo se ha realizado, es posible crear una instancia de IloCplex pasndole como
argumento el ambiente, mediante la instruccin:
IloCplex cplex(this->env);
De esta manera, el objeto cplex puede ser usado para extraer el modelo a ser resuelto. Una manera
de extraer el modelo es mediante el mtodo:
cplex.extract(model);
Este mtodo requiere que se haya creado el objeto de tipo IloCplex anteriormente. As, existe otro
mtodo que permite realizar los dos pasos anteriores en uno:
IloCplex cplex(model);
Extrado el modelo por un objeto de la clase IloCplex, es posible resolver el problema mediante la
instruccin:
cplex.solve();
El mtodo solve devuelde un valor de tipo IloBool, donde IloTrue indica que cplex encontr
exitosamente una solucin factible (no necesariamente ptima), e IloFalse significa que no fue
encontrada una solucin.
Por tal motivo, se usa un ciclo while(cplex.solve()) cuya condicin permite que se resuelva
iterativamente el problema mientras se van agregando los cortes relacionados a los subtours de las
soluciones.
Una vez resuelto el problema mediante cplex, es necesario extraer la solucin. La anterior es posible
mediante el mtodo getValue, perteneciente a la clase IloCplex. Para extraer el valor de todas las
variables de decisin, se cre el mtodo getTourInfo, descrito a continuacin:
IloBool **TSPSolver::getTourInfo(IloBoolVar **x, IloCplex cplex)
{
IloBool **values = (IloBool **)malloc(nCities*sizeof(IloBool *));
int i, j;
for (i = 0; i < this->nCities; i++)
{
values[i] = (IloBool *) malloc((nCities)*sizeof (IloBool));
for (j = 0; j < this->nCities; j++)
{
if (i != j)
values[i][j] = cplex.getValue(x[i][j]);
else
values[i][j] = IloFalse;
}
}
return values;
}
Ya que el mtodo retorna un puntero a puntero de datos de tipo IloBool, se crea una variable local
que es donde se guardarn los valores que se desean extraer, para posteriormente retornarla. En el
procedimiento se asigna memoria dinmicamente para almacenar la matriz de valores IloBool, los
43
1
2
3
4
5
1
0
0
0
1
0
2
0
0
0
0
1
3
0
1
0
0
0
4
1
0
0
0
0
5
0
0
1
0
0
1
2
3
4
5
1
0
0
0
0
1
2
0
0
0
1
0
3
0
1
0
0
0
4
1
0
0
0
0
5
0
0
1
0
0
La primera matriz define los subtours 1-4-1 y 2-3-5-2, y la segunda matriz define el tour 1-4-2-3-5-1. As,
mediante el procedimiento buildTours, que recibe como parmetro a la matriz tourInfo, se
construyen los tours subtours. Su funcin es bastante clara, sin embargo escapa del uso de la
Tecnologa Concierto y CPLEX, por lo que slo se muestra en el anexo XXX.
Lo siguiente consiste en agregar, en lo posible, una restriccin de eliminacin de subtours mediante el
mtodo addConstraints. Dicho mtodo retorna un valor booleano en caso de agregar o no una
restriccin. El procedimiento consiste en agregar el subtour de dimensin menor, parte del mtodo se
muestra:
if ( minimum.size() >= 2 && minimum.size() <= nCities )
{
IloNumExpr lhs(this->env);
for ( j = 0 ; j < minimum.size() - 1 ; j++ )
{
for ( k = 0 ; k < minimum.size() - 1 ; k++ )
{
if ( minimum.get(j) != minimum.get(k) )
{
lhs += x[minimum.get(j)][minimum.get(k)];
}
}
}
IloRange constraint = ( lhs <= minimum.size() -2 );
cplex.addCut(constraint);
}
Si el tamao del subtour es mayor o igual a 2 menor o igual a nCities, entonces es posible agregar
una restriccin de eliminacin de subtours para las ciudades pertenecientes al subtours actual. La forma
de crear la restriccin es similar a como se explic para las otras restricciones del modelo, creando una
expresin y posteriormente creando la restriccin mediante un objeto IloRange. Para que la
restriccin (corte) sea tomada en cuenta, es necesario agregarla al solver mediante el mtodo addcut
de la clase IloCplex.
Finalmente, cuando no se haya agregado una restriccin de eliminacin de subtour, el algoritmo
termina y se retorna el tour ptimo encontrado.
44
En la figura se aprecia un mejor rendimiento de esta ltima implementacin por sobre las anteriores.
Adems cabe destacar la mayor flexibilidad de programacin que se posee al resolver los problemas
mediante tecnologa concierto. En la implementacin realizada, la lectura de datos se realiz de tal
forma de leer directamente sobre los archivos disponibles de la TSPLIB, por lo que el programa se puede
utilizar para resolver cualquier instancia sin siquiera modificar o adaptar los archivos de datos, como en
las otras implementaciones realizadas.
Es cierto que el uso de la Tecnologa Concierto requiere de mayor conocimiento de programacin, pero
sin duda posee innumerables ventajas en la resolucin de problemas, adems que luego de un tiempo
no tan excesivo se logra un avance considerable en la curva de aprendizaje. No obstante siempre est en
el usuario la decisin final de seleccin de acuerdo a las necesidades e intereses personales.
45
Anexos
Anexo 1: Cdigo implementacin de Algoritmo Planos Cortantes para TSP en OPL
/*****************************************************************************
* Seccion de datos
*****************************************************************************/
// Ciudades
int
n
range
Cities
// Conjunto
tuple
setof(edge)
int
= ...;
= 1..n;
de aristas
edge
{int i; int j;}
Edges
= {<i,j> | ordered i,j in Cities};
dist[Edges] = ...;
// Variables de decisin
dvar boolean x[Edges];
tuple Subtour { int size; int subtour[Cities]; }
{Subtour} subtours = ...;
/*****************************************************************************
* Construccin de modelo (TSP relajado)
*****************************************************************************/
// Objective
minimize sum (<i,j> in Edges) dist[<i,j>]*x[<i,j>];
subject to {
// Cada ciudad esta unida a otras dos ciudades
forall (j in Cities)
sum (<i,j> in Edges) x[<i,j>] + sum (<j,k> in Edges) x[<j,k>] == 2;
// Restricciones de eliminacin de subtours
forall (s in subtours)
sum (i in Cities : s.subtour[i] != 0)
x[<minl(i, s.subtour[i]), maxl(i, s.subtour[i])>] <= s.size-1;
};
// Post-procesamiento para encontrar los subtours
// Informacion de la solucion
int thisSubtour[Cities];
int newSubtourSize;
int newSubtour[Cities];
// Informacin auxiliar
int visited[i in Cities] = 0;
// Arreglo que contiene la informacion de las ciuades adjuntas a j
setof(int) adj[j in Cities] = {i | <i,j> in Edges : x[<i,j>] == 1} union
{k | <j,k> in Edges : x[<j,k>] == 1};
execute
{
newSubtourSize = n;
// Encontrar una ciudad que no ha sido visitada
for (var i in Cities)
{
//Si la ciudad i ya fue visitada, seguimos a la siguiente ciudad
if (visited[i]==1)
{
continue;
}
//La ciudad actual es el inicio del tour
46
var start = i;
//La variable nodo toma el valor de la ciudad inicial (actual)
var node = i;
//Se inicializa tamao de subtour a cero
var thisSubtourSize = 0;
//Se inicializa el arreglo que contendr el subtour
for (var j in Cities)
{
thisSubtour[j] = 0;
}
/* Mientras no haya completado el subtour (el fin del subtour es
* el nodo inicial) o thisSubtourSize==0 asegura que la condicin
* sea valida al inicio */
while (node!=start || thisSubtourSize==0)
{
//Marcamos a la ciudad actual como visitada
visited[node] = 1;
//La ciudad sucesora la marcamos como la ciudad inicial
var succ = start;
//Recorro las ciudades adjuntas a la ciudad actual
for (i in adj[node])
{
//Si la ciudad i no ha sido visitada, la visitar
if (visited[i] == 0)
{
succ = i;
break;
}
}
//Agrego al subtour la ciudad recien encontrada
thisSubtour[node] = succ;
//La ciudad actual pasa a ser succ
node = succ;
//Aumento en 1 la cantidad de ciudades del tour
++thisSubtourSize;
}
//Acabamos de encontrar un subtour vlido
//Guarda siempre el subtour ms pequeo
if (thisSubtourSize < newSubtourSize)
{
//Copio las ciudades del subtour actual al nuevo subtour
for (i in Cities)
{
newSubtour[i] = thisSubtour[i];
}
newSubtourSize = thisSubtourSize;
}
}
}
/*****************************************************************************
* Programacin del programa principal (control de flujo)
*****************************************************************************/
main
{
var opl = thisOplModel
var mod = opl.modelDefinition;
var dat = opl.dataElements;
while (1)
{
cplex.clearModel();
opl = new IloOplModel(mod,cplex);
47
opl.addDataSource(dat);
opl.generate();
if (!cplex.solve())
{
writeln("ERROR: could not solve");
opl.end();
break;
}
opl.postProcess();
if (opl.newSubtourSize == opl.n) {
writeln("Tour Final ",opl.thisSubtour);
opl.end();
break;
}
dat.subtours.add(opl.newSubtourSize, opl.newSubtour);
opl.end();
}
}
"TSPSolver.h"
"TSPUtil.h"
<stdlib.h>
<stdio.h>
=
=
=
=
fgets(aux,
fgets(aux,
fgets(aux,
fgets(aux,
MAX_LINE,
MAX_LINE,
MAX_LINE,
MAX_LINE,
file);
file);
file);
file);
48
{
try
{
char nombre[100];
IloBoolVar **x = this->createDVars();
IloModel model = this->buildModel(x);
IloCplex cplex(this->env);
cplex.extract(model);
cplex.exportModel("modelo.lp");
int iteration = 0;
while (cplex.solve())
{
IloBool **tourInfo = this->getTourInfo(x, cplex);
vector<TSPTour> tours = this->buildTours(tourInfo);
if( !this->addConstraints(cplex,x,tours) )
{
return tours[0];
}
iteration++;
sprintf( nombre, "modelo%d.lp" , iteration );
cplex.exportModel( nombre );
}
} catch (IloException & e)
{
cerr << "### EXCEPTION: " << e << endl);
} catch (...)
{
cout << "### UNEXPECTED ERROR ..." << endl;
}
}
IloBoolVar ** TSPSolver::createDVars()
{
IloBoolVar **x = (IloBoolVar **) malloc(nCities * sizeof (IloBoolVar *));
int i, j;
char *nombre = (char *) malloc(100 * sizeof (char));
for (i = 0; i < this->nCities; i++)
{
x[i] = (IloBoolVar *) malloc((this->nCities) * sizeof (IloBoolVar));
for (j = 0; j < this->nCities; j++)
{
if (i != j)
{
sprintf(nombre, "x(%d,%d)", i, j);
x[i][j] = IloBoolVar(this->env, nombre);
}
}
}
return x;
}
IloModel TSPSolver::buildModel(IloBoolVar** x)
{
IloModel model(this->env);
int i, j;
//creo y agrego la funcion objetivo (1)
IloNumExpr objective(this->env);
for (i = 0; i < nCities; i++)
{
for (j = 0; j < nCities; j++)
{
if (i != j)
{
objective += x[i][j] * d[i][j];
}
}
}
model.add(IloMinimize(this->env, objective));
49
//restricciones
for (j = 0; j < nCities; j++)
{
IloNumExpr expr(env);
for (i = 0; i < nCities; i++)
{
if (i != j)
{
expr += x[i][j];
}
}
IloRange constraint = (expr == 1);
model.add(constraint);
}
//restricciones
for (i = 0; i < nCities; i++)
{
IloNumExpr expr(env);
for (j = 0; j < nCities; j++)
{
if (i != j)
{
expr += x[i][j];
}
}
IloRange constraint = (expr == 1);
model.add(constraint);
//cplex.add(constraint);
}
return model;
}
IloBool **TSPSolver::getTourInfo(IloBoolVar **x, IloCplex cplex)
{
IloBool **values = (IloBool **) malloc(nCities * sizeof (IloBool *));
int i, j;
for (i = 0; i < this->nCities; i++)
{
values[i] = (IloBool *) malloc((this->nCities) * sizeof (IloBool));
for (j = 0; j < this->nCities; j++)
{
if (i != j)
values[i][j] = cplex.getValue(x[i][j]);
else
values[i][j] = IloFalse;
}
}
return values;
}
vector<TSPTour> TSPSolver::buildTours(IloBool **tourInfo)
{
vector<TSPTour> tours;
int **tourInfoAux = (int **) malloc(this->nCities * sizeof (int *));
int i, j, start;
for (i = 0; i < this->nCities; i++)
{
tourInfoAux[i] = (int *) malloc((this->nCities) * sizeof (int));
for (j = 0; j < this->nCities; j++)
{
if( tourInfo[i][j] == IloTrue )
//tourInfoAux[i][j] = 1;
*(*(tourInfoAux+i)+j) = 1;
else
//tourInfoAux[i][j] = 0;
*(*(tourInfoAux+i)+j) = 0;
50
}
}
for (start = 0; start < this->nCities; start++)
{
while( TSPUtil::fullyUsed(tourInfoAux,this->nCities,start) == false )
{
int current = start;
int subtourSize = 0;
TSPTour tour;
tour.add(start);
while (start != current || subtourSize == 0)
{
subtourSize++;
int next;
for (next = 0; next < this->nCities; next++)
{
if (next != current && tourInfoAux[current][next] == 1 )
{
tourInfoAux[current][next] = 0;
//tourInfoAux[next][current] = 0;
current = next;
tour.add(current);
break;
}
}
}
tours.push_back(tour);
}
}
return tours;
}
bool TSPSolver::addConstraints(IloCplex
&tours )
{
int i, j, k;
bool constraintsAdded = false;
cplex,IloBoolVar
**x,vector<TSPTour>
51
"TSPUtil.h"
<stdio.h>
<stdlib.h>
<iostream>
52
53