Universidad de TalcaFacultad de Ingeniería Departamento de Modelación y Gestión Industrial Programa de Magíster en Gestión de Operaciones IBM ILOG CPLEX, OPL y Concert Technology: Manual práctico Julio Mardones S. – Eduardo Álvarez M. Abril, 2010. 2 Tabla de contenidos Programación Matemática: Conceptos Básicos ............................................................... 5 Programación Lineal: Función Objetivo y Restricciones ........................................................... 5 Programación Entera: Función Objetivo y Restricciones .......................................................... 6 Solvers para Programación Lineal y Entera ............................................................................... 7 Uso de OPL para resolución de Problemas de Programación Matemática ..................... 9 Descripción General: Generación de un proyecto y de los archivos .mod, .dat y .ops ............. 9 Resolución de un problema básico en OPL ............................................................................. 10 Enlace de una planilla Excel para la lectura de datos.......................................................... 13 Resolución de un problema de mediana complejidad en OPL ............................................... 14 Problema de Transporte: Variante I .................................................................................... 14 Problema de Transporte: Variante II ................................................................................... 18 Resolución del TSP utilizando OPL (Parte I) .................................................................... 23 TSP: Formulación del problema como un PLEM ..................................................................... 23 TSP: Formulación del problema en OPL .................................................................................. 24 Discusión de resultados: Tiempos de Resolución y alcance de la formulación ...................... 26 Resolución del Steiner Tree Problem en grafos utilizando OPL ..................................... 27 StT Problem: Formulación del problema como un PLEM ....................................................... 27 StT Problem: Formulación del problema en OPL .................................................................... 28 Discusión del modelo presentado ........................................................................................... 31 Resolución del TSP Problem utilizando OPL (Parte II) .................................................... 32 Descripción del Algoritmo de Planos de Corte (PC) ................................................................ 32 Descripción de la Implementación en OPL del Algoritmo PC ................................................. 33 Discusión de resultados: Tiempos de Resolución y alcance de la formulación ...................... 36 Resolución del TSP Problem utilizando OPL (Parte III) ................................................... 38 Descripción de la Implementación utilizando Tecnología Concierto del Algoritmo PC .......... 38 Discusión de resultados: Tiempos de Resolución, Configuración de Parámetros .................. 44 Anexos ............................................................................................................................ 46 Anexo 1: Código implementación de Algoritmo Planos Cortantes para TSP en OPL.............. 46 Anexo 2: Código implementación Algoritmo Planos Cortantes para TSP usando Tecnología Concierto. (esto puede mostrarse de otra forma??) .............................................................. 48 Anexo 2.1: Código clase TSPSolver.cpp ............................................................................... 48 Anexo 2.2: Código clase TSPTour.cpp ................................................................................. 51 3 Anexo 2.3: Código clase TSPUtil.cpp ................................................................................... 52 4 Programación Matemática: Conceptos Básicos La Programación Matemática es un área de la Matemática Aplicada que trata de resolver problemas de decisión en los que se deben determinar acciones que optimicen un determinado objetivo, pero satisfaciendo ciertas limitaciones en los recursos disponibles. En el enfoque científico de toma de decisiones, es fundamental el concepto de modelo matemático, que corresponde a una representación matemática de la situación real que se quiere representar para tomar las mejores decisiones. Dicho de otra forma, los modelos de optimización tratan de expresar matemáticamente el propósito de resolver un problema de la mejor forma posible. Un problema de programación matemática tiene la siguiente forma: Así, el vector corresponden a las variables de decisión del problema, la función es la función objetivo, mientras las funciones son las funciones de restricciones, y las constantes son los límites o disponibilidad de recursos para las restricciones. Un vector corresponde a una solución factible, si satisface las restricciones, y un vector corresponde a una solución óptima, si posee el menor valor de la función objetivo entre todos los vectores que satisfacen las restricciones del problema. Mayores detalles se pueden consultar en un gran [1] 2 número de libros del área de autores como W. Winston y M. Griva . Generalmente se consideran familias o clases de problemas de optimización, caracterizados por formas particulares de la función objetivo y las funciones asociadas a las restricciones. Así, hay problemas de programación lineal, no-lineal, programación entera, etc. Programación Lineal: Función Objetivo y Restricciones Un problema de programación lineal se refiere a la optimización (minimización ó maximización) de una función lineal, sujeta a restricciones lineales de igualdad y/ó desigualdad, así, tanto la función objetivo como las restricciones están representadas por funciones lineales en las variables de decisión. Un problema de programación lineal puede ser enunciado de manera general de la siguiente forma: O de manera más compacta a través de notación matricial: Donde los coeficientes están representados por la matriz , y . Otro elemento fundamental es la naturaleza de las variables de decisión, en que se asume que son variables reales continuas no-negativas. [1] [2] W.Winston, Investigación de Operaciones, 4°Edición, Thomson (2004). I. Griva, S. Nash, A. Sofer, Linear and Nonlinear Optimization, 2°Edición, SIAM (2009). 5 mientras la planta 2 dispone de 12 horas/semana y la planta 3 de 18 horas semanales. lo cual se traduce en la restricción que . y la planta 3 fabrica el vidrio y ensambla ventanas y puertas. incluidos ventanas y puertas de vidrio. definimos las variables de decisión en relación a la cantidad de puertas y ventanas a producir. Las instalaciones de producción en la planta 1 estarán disponibles 4 horas/semana. El producto 1 necesita capacidad de producción en las plantas 1 y 3. la planta 2 los marcos de madera. y una ventana colgante de doble marco de madera de 4x6 pies. Además cada planta posee limitaciones en su capacidad de producción. el problema queda completamente formulado. se traduce en el siguiente problema de Programación Lineal Entera. Mientras en la planta 3. La compañía posee tres plantas. mientras el producto 2 requiere producción en las plantas 2 y 3. Analizando los datos de los costos y la decisión de los precios. teniendo la planta disponible sólo 4 horas/semana. Se estima que cada lote de puertas requerirá una hora de tiempo de producción en la planta 1 y de tres horas en la planta 3. siendo de $300 para las puertas y $500 para las ventanas. la planta 1 fabrica los marcos de aluminio. Programación Entera: Función Objetivo y Restricciones Supongamos que tenemos un problema de programación lineal como el estudiado anteriormente. Situación similar ocurre en la planta 2. por lo que . así definimos: Y llamaremos por al beneficio total por semana. Mientras para cada lote de ventanas. así cada lote del producto 1 requiere una hora de producción en la planta 1. Para resumir. Debido a la disminución en las ventas. el departamento de contabilidad estima las ganancias de los dos productos (por lotes). se requerirán alrededor de dos horas en la planta 2 y de dos horas en la planta 3. La capacidad productiva de cada planta usada por cada lote de productos depende de su tasa de producción. el tiempo consumido por ambos productos es y se dispone de sólo 18 horas. y si además ponemos la restricción adicional de que las variables de decisión deben tomar valor enteros. lo cual se traduce en la expresión matemática que . por lo cual en este punto se podría ocupar algún algoritmo u otro método de resolución para resolver el problema. el modelo de programación lineal consiste en encontrar la tasa de producción de ambos productos. la alta dirección ha decidido descontinuar ciertos productos no rentables por lo que se dispone de capacidad de producción para lanzar dos nuevos productos: Una puerta de cristal de 8 pies con marco de aluminio. La administración quiere conocer ¿Cuál debe ser la cantidad de puertas y de ventanas a producir por semana tal de maximizar las ganancias? Para establecer el modelo matemático. 6 . tal de: De esta manera.Problema Ilustrativo: La compañía Wyndor Glass produce productos de vidrio de alta calidad. si algunas variables pueden tomar valores en los números reales y otras están restringidas a ser enteras. métodos. Puede ser usado con diversos solvers de optimización obteniendo gran flexibilidad y rendimiento. mientras los segundos. puede ser utilizado con una gran cantidad de otros solvers.Ahora. ya sea para variables continuas y/o discretas. Como fruto del trabajo de cientos de investigadores que durante décadas han desarrollado vastas teorías. han hecho propicio el desarrollo de paquetes computacionales orientados a la resolución de problemas de programación matemática. que resuelven un problema matemático. un solver corresponde a una serie de rutinas que forman parte de un software matemático. una herramienta de línea de comandos (oplrun) para ejecutar modelos desde la línea de comandos y una interfaz de programación y aplicaciones (API). Los primeros corresponden a un ambiente que otorga una interfaz y un lenguaje para confeccionar los modelos matemáticos a resolver. Más formalmente. como ya se dijo. y fundamentalmente de programación lineal y entera. corresponde a un problema de Programación Entera Mixta. lineal entera y no lineal. Mientras los solvers más conocidos son: EXCEL: el famoso programa de ofimática de Windows posee dentro de sus herramientas un solver capaz de resolver problemas de programación lineal. Especialmente diseñado para resolver aplicaciones complejas con modelos de gran tamaño. con un número de variables y 7 . Otro caso de gran importancia son los problemas de Programación Combinatorial. brindan las rutinas y algoritmos para la resolución de los modelos. para incluir los modelos en aplicaciones independientes. inclusive los más importantes. y consiste de un lenguaje propio de programación con un solver de alto nivel. Existen solvers para resolver diversos tipos de problemas. algoritmos en el área. y no-lineal. Solvers para Programación Lineal y Entera En el ámbito de la programación matemático es fundamental el conocimiento y desarrollo de los Solvers matemáticos. en que las variables están restringidas a tomar valores iguales a 0 ó 1. entera. capaz de integrarse con otras aplicaciones. OPL Development Studio: corresponde a un poderoso sistema de modelamiento para el desarrollo de aplicaciones de programación. Entre los ambientes de programación matemática más famosos tenemos: LINGO/LINDO: corresponde a un ambiente integrado de optimización para la construcción y resolución de modelos de programación lineal. y los solvers. sumado al desarrollo en las tecnologías de la computación y a la necesidad de resolver problemas y aplicaciones cada vez de mayor complejidad y tamaño. sin embargo nos enfocaremos principalmente a los solvers de optimización lineal. Un solver toma la descripción de un problema de una forma genérica y lo resuelve. Se debe realizar la distinción existente entre los sistemas de modelamiento. que puede funcionar como un programa independiente o como librerías de programación. AMPL: (A Mathematical Programming Language) es un poderoso e integral lenguaje para problemas de optimización lineal y no lineal. lineal entera y no lineal. de una manera fácil y con un lenguaje de programación propio. un ambiente de desarrollo integrado (IDE) para ejecutar y probar los modelos de optimización. GAMS: (General Algebraic Modeling System) es un sistema de modelamiento de alto nivel. Proporciona un intérprete para los modelos escritos en OPL (Optimization Programming Language). mientras usa CPLEX como solver. cuadrática restringida. Tecnología Concierto (Concert Technology): es un conjunto de librerías de clases de C++. Java. XPRESS: es un solver de alto rendimiento de programación lineal y programación lineal entera mixta. . es totalmente comparable en eficiencia con los mejores solver’s actuales como CPLEX. y programación por restricciones. que ofrecen una interfaz de programación y aplicaciones que otorgan facilidades de programación que permiten utilizar el solver de CPLEX en aplicaciones realizadas en los respectivos lenguajes de programación mencionados. FORTRAN. y programación entera mixta. y funciona solamente en conjunto con el sistema de modelamiento GAMS. entre otros. Lanzado recientemente al mercado. se menciona nuevamente sólo para enfatizarla como otra modalidad de uso. y programación entera mixta. Debido a la gran relevancia y predominio que ha tenido durante los últimos años es que este manual está completamente enfocado al uso en sus diferentes modalidades de OPL/CPLEX. y entre sus desarrolladores se encuentran fundadores y desarrolladores de CPLEX. como lo son Matlab y Excel. Las librerías de la tecnología concierto hacen uso de la librería llamable (CPLEX Callable Library). Libraría llamable de CPLEX: corresponden a una librería en C que permiten a los programadores insertar los optimizadores de CPLEX en aplicaciones escritas en C. cuadrática. Visual Basic. cuya importancia principal radica en el hecho de la cotidianidad y gran número de usuarios que posee Excel. Es importante mencionar que posee conexión y compatibilidad con otros software’s ampliamente usados en ingeniería o de uso común. También puede ser usado para resolver problemas de programación cuadrática. GUROBI: es un solver para resolver problemas de gran tamaño de programación lineal. sólo que fue diseñado desde cero para aprovechar el procesamiento paralelo y multi-core. OPL Development Studio: se describió anteriormente. 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. problemas de redes.NET y Python. aún cuando no posee una gran calidad. Corresponde al solver comercial más completo y eficiente que ha dominado el mercado en los últimos años.restricciones limitado. CPLEX: es una herramienta para resolver problemas de programación lineal. GLPK: es un software libre que intenta resolver problemas de gran tamaño de programación lineal. o cualquier otro programa que puede ocupar funciones de C. 8 . que puede leer interactivamente un problema o a través de archivos en formatos específicos y desplegar los resultados en los formatos que se desee. de programación entera mixta. Conformado por una serie de rutinas para ser utilizadas como librerías de programación. modelar un determinado problema que luego se resuelve utilizando el solver ILOG CPLEX.ops El software IBM ILOG OPL permite desarrollar y desplegar de forma rápida modelos de optimización mediante depuración.ops descritos anteriormente. A la izquierda se muestra la ventana de administración de proyectos. . sólo nos concentraremos en los archivos . coeficientes de la función objetivo.mod). Ilustración 1: Creación de un proyecto en el ambiente OPL Una vez que el proyecto ha sido creado. vacío hasta ahora. Ilustración 2: Ambiente OPL para la edición de los archivos .mod. el problema que queremos resolver. pruebas. Dados los alcances de este manual.dat y .dat. a través de un lenguaje adecuado para modelos de programación matemática. La creación de un proyecto en el ambiente OPL se muestra en la Ilustración 1.mod.mod. la creación supone asignar un nombre a éste y crear los archivos . coeficiente de las restricciones y valores del lado derecho de éstas (archivo . en este caso sólo existe el proyecto ejemplo1_OPL. el que está compuesto por tres archivos: uno contiene el modelo. esto se muestra en la Ilustración 2.mod y .dat y .mod. función objetivo y restricciones (archivo . i. tomará forma para ser ejecutado. entramos directamente en el ambiente de OPL.dat y .Uso de OPL para resolución de Problemas de Programación Matemática Descripción General: Generación de un proyecto y de los archivos . para resolver un determinado problema es necesario generar un proyecto de OPL.e. ajuste y generación de aplicaciones. definición de variables. a la derecha se muestra el archivo .mod. en donde efectivamente nuestro proyecto. . y un archivo que contiene la configuración que el decisor define para el funcionamiento del solver (archivo . En términos generales.ops).mod). y las pestañas de los archivos . es decir.ops 9 .dat y . A través de una interfaz permite. . uno que contiene los datos o parámetros del modelo. Ilustración 4: Solución entregada por el solver CPLEX al problema. posteriormente se define la función objetivo indicando el sentido de ésta (minimize) y finalmente se declaran las tres restricciones dentro del bloque subject to. Ilustración 3: Generación del modelo del problema en el archivo .mod (parámetros explícitos) Es claro que el modelo está completamente definido y no es necesario utilizar el archivo . Resolución de un problema básico en OPL Consideremos el siguiente problema de programación lineal sujeto a Este es un problema muy básico que resolveremos a través de OPL. En este caso particular la solución se muestra en la Ilustración 4. que representa la situación que se quiere resolver ya sea para fines prácticos o académicos. En la ilustración se muestra la declaración (o definición) de las variables de decisión y . Dada la estructura del problema.mod como se muestra en la Ilustración 3. Al ejecutar el programa. 10 . Para resolver el problema es necesario ejecutarlo haciendo click en el botón en la barra de herramientas.dat puesto que todos los datos del problema están explícitos en el modelo. es decir. como variables reales positivas (float+). desplegando la solución en la pestaña soluciones en la parte inferior del ambiente del programa. el valor óptimo y la solución óptima son entonces y respectivamente. por eso el nombre dvar. OPL internamente pasa el modelo al motor CPLEX y éste lo resuelve.Es importante que el uso de OPL y del solver CPLEX. supone que se cuenta con un modelo válido. podemos representar todos los datos de éste en forma explícita en el archivo . En la Ilustración 5 se muestra la definición del modelo considerando que los datos serán obtenidos desde el archivo . Al ejecutar el programa se puede comprobar que valor óptimo y la solución óptima son entonces y respectivamente. lo que reafirma la validez del modelo ahora compuesto por el archivo . que contiene el modelo. puesto que fue posible definir todo el modelo del problema en el .mod. en este caso los datos son los coeficientes de las variables de decisión en la función objetivo.Como se ha señalado. la dimensión del vector de coeficientes de la función objetivo es n y la dimensión del vector de coeficientes del lado derecho de las restricciones es m.mod en que sea necesario considerar datos contenidos en el archivo .mod (parámetros implícitos) Una vez que se ha definido el archivo . es necesario escribir el archivo .dat.dat que contendría los datos o parámetros. lo que reafirma la validez del modelo ahora compuesto por el archivo .dat 11 . en este caso no fue necesario enlazar el archivo .datIlustración 6 se muestra la definición de los valores de estos parámetros en el archivo . Al ejecutar el programa se puede comprobar que valor óptimo y la solución óptima son entonces y respectivamente. Ilustración 5: Generación del modelo del problema en el archivo . los coeficientes de la matriz de restricciones y los rhs de las restricciones.dat.mod. con el archivo .dat donde se contienen los datos del modelo. Un rol importante lo juega la definición de rangos (ranges) que representan las dimensiones de arreglos o matrices de variables o parámetros. Consideraremos ahora el caso en que generemos un archivo . En la . es claro además que la dimensión de la matriz de restricciones es m n.datIlustración 6: Definición de los parámetros del modelo en el archivo . En este caso se deben definir dos rangos uno indica la dimensión del vector de variables de decisión (n) y el otro el número de restricciones (m).mod donde se contiene el modelo.dat. salvo por el hecho que se especifica el tanto el número de variables (int numVariables=3) como el número de restricciones (int numRestricciones=3). por lo que se reescribe como .mod. por ejemplo. Antes de comenzar a escribir el modelo es necesario redefinir la tercera restricción del problema en estudio puesto que está como una restricción del tipo .3. podemos escribir la j-ésima restricción como: sum(i in n)matrizRestricciones[i][j]*x[i] <= rhs[j]. puesto que se deben incluir ciclos y. es decir a través de un modelo del tipo: Esto es posible. un ciclo forall se debe utilizar para recorrer el las restricciones haciendo que el índice de este ciclo recorra el rango de j. y escribir en función de estos tamaños tanto la función objetivo como las restricciones. en este caso hay tres restricciones. por lo que j = 1. Sin embargo aumenta la complejidad de la escritura del modelo. sin embargo ambos parámetros podría perfectamente ser definidos en el archivo .2. el rango de la sumatoria está dado por el número de variables. definiremos ahora las restricciones en forma similar. el rango del índice j está dado por el número de restricciones. el operador de sumatoria. La forma de expresar la función objetivo en OPL es utilizando el operador sumatoria (sum) indicando que esta sumatoria se realiza sobre un cierto rango.En el modelo recién presentado era necesario indicar explícitamente el tamaño del problema (número de variables y número de restricciones) en el archivo .mod se muestra en la Ilustración 8. En el caso del problema. por lo que la función objetivo se define como: minimize sum(i in n) coeficientesFO[i]*x[i] Ya definida la función objetivo en términos del operador sumatoria. 12 . El conjunto de restricciones entonces se modela a través del código que se muestra en la Ilustración 7. y es la forma en la que se resuelven los problemas de verdadero interés para nuestros propósitos. Ilustración 7: Definición de las restricciones en lenguaje algebraico El código completo del archivo .dat generalizando así el modelo presentado. Una forma más adecuada y general es representar tanto la función objetivo como las restricciones en términos de sumatorias. en ésta se puede notar que la nueva implementación es mucho más genérica y podría representar a casi cualquier modelo de programación lineal. Si consideramos la segunda formulación presentada. Para ello. la forma de hacerlo se muestra a continuación: numVariables from SheetRead(sheet. Ilustración 9: Planilla . los coeficientes de la matriz de restricciones y el los valores de los rhs’s.dat. Enlace de una planilla Excel para la lectura de datos Una forma mucho más sofisticada para la lectura de datos es el uso del método de lectura de archivos de extensión . En este caso.Ilustración 8: Generación del modelo del problema en el archivo .xls conteniendo los datos del problema De la Ilustración 9 podemos ver que el número de variables está ubicado en la celda C2.xls (excel) que cuenta OPL. En primer lugar es necesario declarar el siguiente comando: SheetConnection sheet("nombreArchivo.xls").dat. por lo que se debe indicar esto en el archivo . es necesario conocer la ubicación de éstos parámetros en la planilla Excel en términos de sus coordenadas (columna y fila).dat en el que los datos se puedan leer desde una planilla de cálculo. En la Ilustración 9 se muestran las celdas conteniendo los datos del problema. a través de un esquema de comandos es posible definir un archivo . "Hoja1!C2") 13 . el archivo que utilizaremos será datos_ejemplo1_OPL. el número de restricciones. de esta forma OPL sabe que se enlazará con el archivo “nombreArchivo” que debiese estar contenido en la carpeta del proyecto. Los datos que leeremos desde el archivo será el número de variables. lo que reafirma la validez del modelo ahora compuesto por el archivo .mod (operadores sum y forall) Al ejecutar el programa se puede comprobar que valor óptimo y la solución óptima son entonces y respectivamente. los coeficientes de la función objetivo.xls. : demanda de la ciudad j del producto p. H. Esto se puede expresar a través del siguiente modelo de programación lineal: 14 . es diferente el costo de transportar una cierta carga del producto Z entre las ciudades B y F que transportar la misma carga (en kilogramos por ejemplo) de producto Y entre las mismas dos ciudades. y existe una red de transporte conectando estos clientes entre sí. Resolución de un problema de mediana complejidad en OPL Consideremos una situación logística simplificada en la que tenemos un grupo de centros de distribución que ofertan y demandan productos. consideremos la siguiente definición de variables y parámetros: : cantidad a transportar del producto p entre la ciudad i y la ciudad j. es importante notar que en el caso de los coeficientes de la matriz de restricciones (matrizRestricciones). En particular hay 10 centros de distribución ubicados en las ciudades A. F. cuantificándose sus volúmenes en kilogramos. además se sabe que el vehículo que transporta tiene una capacidad de 625 kilogramos. : costo de transportar el producto p entre la ciudad i y la ciudad j. B. G. C. Y y Z. estos deben ser leídos desde una matriz de datos por lo que en el comando de lectura debe señalarse la celda superior izquierda (C7) y la celda inferior derecha (E9) desde donde se extraerán los datos. : capacidad del vehículo. se sabe a demás que existe un costo de transporte que depende tanto de la ciudad de origen y ciudad de destino como del producto a ser transportado. E. Para representar el problema a través de un modelo de programación lineal.dat con la lectura de parámetros desde excel Nuevamente. Ilustración 10: Archivo . I y J. cada una con una oferta y una demanda de los productos en estudio.El resto de los datos se leen en forma similar como se muestra en la Ilustración 10. al ejecutar el programa se puede comprobar que valor óptimo y la solución óptima son entonces y respectivamente. El objetivo es buscar una planificación de transporte tal que se minimicen los costos de traslado satisfaciéndose la demanda de cada centro de distribución y respetando la capacidad del vehículo. D. los productos demandados y ofertados son X. es decir. : oferta de la ciudad j del producto p. ops. Es claro que estos puede ser fácilmente enlazada a una planilla de cálculo .dat donde se contendrá los valores para éstos. Algo interesante de notar es que tanto ciudades como productos se definen como arreglos (vectores) de variables tipo string. Oferta y Demanda son simples de definir.dat.mod (variante I) Una vez estructurados los parámetros señalando su naturaleza. pero nos concentraremos en la configuración de los dos primeros. Los parámetros Ciudades. Productos.Ecuación 1: Modelo de programación lineal del Problema de Transporte Con el modelo ya presentado. puesto que son todos vectores unidimensionales o bidimensionales. En la Ilustración 12 se muestran los valores de estos parámetros definidos a partir de vectores y matrices. tendrá dos variantes mostrando diferentes posibles definiciones de estructuras de datos con tal de mostrar cómo se pueden utilizar definiciones de variables que mejoren la eficiencia del código. 15 . {A.mod se muestra en la Ilustración 11.dat y .xls (ver Ilustración 10 para más detalles). se debe proceder a escribir el código del archivo .Y. podemos proceder a generar el proyecto OPL para resolver ésta problema. la generación de un proyecto de OPL supone la creación de archivos .…. Capacidad. El proyecto (construido en el proyecto ejemplo_transporte_OPL adjunto a este documento).Z} respectivamente. Esto muestra la flexibilidad de lenguaje respecto a la definición de parámetros. Ilustración 11: Definición de parámetros y variables del problema (1) en el archivo . . Problema de Transporte: Variante I Tal como se mostró en el ejemplo considerado en la primera sección. Al observar la formulación del problema en la Ecuación 1 y la definición de variables y parámetros.J} y {X. puesto que no toman valores numéricos sino que valores alfanuméricos. donde se muestra que los valores de los parámetros serán obtenidos desde el archivo . una forma natural de presentar esta información en el archivo .mod. Oferta y Demanda en el archivo . con la ayuda de texto comentado (/* … */) se hace más fácil entender cómo debe ser llenada la matriz.Ilustración 12 Definición de valores de parámetros Ciudades.dat (variante I) En la Ilustración 14 se muestra la definición de los costos de transporte para el producto X. lo que simboliza con “…”) se deben cargar las matrices de costos para los productos X. como se muestra en la Ilustración 13.J}. 16 . es decir. esquemáticamente. Y y Z respectivamente. Es claro que un valor cero en una posición ij indica que no existe un camino entre ambas ciudades.…. sus componentes son del tipo donde {A. los costos de transporte están dado por un arreglo de tres dimensiones (productos ciudades ciudades) por lo que su definición no es tan directa como en el caso de la Oferta o la Demanda.dat. podemos representar la forma general de la matriz de costos como un vector columna de tres filas. pero cuyos componentes también lo sean. el costo de transportar el producto p desde la ciudad i a la ciudad j.…. es necesario encontrar ahora una representación que permita ser ingresada al archivo . Ilustración 13: Esquema de la definición de valores de parámetros Costos en el archivo . en primer lugar. en donde se muestra que en cada uno de los componentes (hasta ahora sin definir. Capacidad.J} y {A. lo que necesitamos definir está dado por: Conocida ya la estructura de los datos que contendrán los valores de los costos. En este caso es necesario definir también una matriz. conteniendo los costos de transporte entre las 10 ciudades del producto X.dat (variante I) De acuerdo a la definición de parámetros que se muestra en la Ilustración 11. es decir. Por ejemplo. Productos. ya que es preciso que los elementos de este conjunto de datos permitan representar parámetros del tipo . en la primera componente debe definirse una matriz de dimensión 10 10. aunque una forma mucho más simple de hacerlo. se puede definir el modelo. la función objetivo es sólo 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. o in Ciudades) sum(d in Ciudades) Trans[p][o][d] == Oferta[p][o]. lo que puede representarse como forall(p in Productos. Esta restricción puede escribirse como forall(p in Productos. Se indica en el modelo de programación lineal. OPL internamente pasa el modelo al motor 17 . restricción 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. o. d in Ciudades)Costo[p][o][d]*Trans[p][o][d]. dicha restricción debe señalar que el total transportado debe ser menor a la capacidad del vehículo. De la misma forma. es simplemente utilizar un único operador sumatoria y en éste considerar los tres conjuntos. De acuerdo al modelo presentado en la Ecuación 1. es decir. formulación mucho más natural y fácil de entender. es necesario agregar una restricción asociada a la capacidad del vehículo que realiza el transporte. 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. d in Ciudades) sum(p in Productos) Trans[p][o][d] <= Capacidad. es necesario además que. es decir forall(o. sea utilizada enviada toda la oferta del producto p que tiene la ciudad o. puesto que se debe satisfacer la demanda para cada cliente y para cada producto. podemos proceder a resolver el problema haciendo click en el botón en la barra de herramientas. Generado ya el modelo. es decir: minimize sum(p in Productos. función objetivo y restricciones. Al ejecutar el programa. Finalmente.dat (variante I) Una vez definidos ya todos los valores de los parámetros a utilizar por el modelo. por balance. Como se puede ver en la primera restricción del modelo dado en Ecuación 1.Ilustración 14 Definición de valores del parámetro Costo (para producto X) en el archivo . que hay tantas restricciones como productos y ciudades. es necesario establecer una restricción que asegure que cada ciudad satisfaga su demanda de los distintos productos. Una forma de representar esto es utilizando tres sumatorias en forma consecutiva como se muestra a continuación: minimize sum(p in Productos) sum(o in Ciudades) sum(d in Ciudades) Costo[p][o][d]*Trans[p][o][d]. d in Ciudades) sum(o in Ciudades) Trans[p][o][d] == Demanda[p][d]. En este caso particular la solución se muestra a continuación: 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 ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]]. desplegando la solución en la pestaña soluciones en la parte inferior del ambiente del programa. y muchos de éstos estarán ocupados por un valor cero.mod una tupla que 18 . De esta forma. en una forma más eficiente que a través de matrices. la que contiene muchos datos iguales a cero. resaltado en anaranjado. era necesario definir matrices de datos (oferta. Estos datos permiten además poder definir arreglos de tuples. indica que desde la ciudad C hasta la ciudad E se envían 225 unidades del producto Y. resaltado en verde. La definición de una estructura de datos que contenga las rutas existentes parece ser útil para evitar la definición de una matriz excesivamente esparza como la matriz costo del modelo anterior. al comprender un primer ejemplo se hace simple su posterior aplicación. es decir. demanda y costo) que contenían varias filas o columnas sólo con valores cero. finalmente. se hace también difícil su uso. hace que esa estructura de datos sea poco adecuada al tratar de resolver problemas de mayor tamaño ya que se necesitan grandes espacios de memoria. el valor 225. indica que desde la B hasta la ciudad E se envían 100 unidades del producto Z. por ende. resaltado en rojo. el valor 100.CPLEX y éste lo resuelve. Problema de Transporte: Variante II En el modelo trabajado en la sección anterior. Se puede ver cada ninguno de los envíos sobrepasa las 625 unidades establecidas como valores límite de capacidad. El costo óptimo corresponde a 199500 unidades monetarias. definiremos en el archivo . indica que desde la ciudad B hasta la ciudad H se envían 250 unidades del producto X. y. En el lenguaje OPL existe un modelo de datos llamado tuple el que permite almacenar arreglos de arreglos. lo que se traduce en una notación confusa y además. El valor 250. Si bien inicialmente se hace difícil comprender las estructuras de estos datos y. definir arreglos de arreglos de arreglos. } Con esto hemos definido un metadato que contiene información respecto a una ruta determinada. <X B G>.. <X A G>. <X B F>. Estos datos deben entregarse en el archivo . J>. <X A H>. <Z C F>. <Z C>. <Y B>. <Y C E>.. A. <Z B F>. H>. <Y <Y <Y <Y A B C C F>. para ellos generamos un arreglo de 19 . De esta forma. información que se presenta en la Ilustración 12. H>. E>. <X C F>. utilizando un arreglo de tuplas que llamaremos CiudadesOferta en el archivo . B y C. como se muestra a continuación tuple ruta { string p.contenga tres elementos producto. I>. Y y Z. string o.. <Y A I>. <X A I>. <X C E>. <Y <Y <Y <Y A B B C E>. D>. considerando aquellas componentes diferentes a cero. de los 300 registros originales (3x10x10) ahora sólo tenemos 63 registros. parece natural generar una tupla inicial que contenga dos elementos. el producto y la ciudad que lo oferta. <Z B H>. E>. esto se puede realizar con un procedimiento similar al realizado para definir las rutas. y se pueden obtener de un análisis de la matriz Costo presentada anteriormente (ver Ilustración 14). J>. J>. <Y A>. <X <X <X <X A B C C F>.. D>. J>.dat. I>. E>. <Y A G>. <Y C G>. Rutas = { <X <X <X <X A A B C D>. p corresponde al producto y o a la ciudad de origen de dicho producto. I>.} En esta tupla. La definición del arreglo de estas tuplas en el archivo . D>. <Z <Z <Z <Z A A B C D>. <Y C F>.mod se presenta más abajo {ruta} Rutas = . <Y A H>. <Z B G>. J>. string o. D>. Este arreglo será leído desde el archivo . <Z <Z <Z <Z A B B C E>. Los datos de la oferta también pueden definirse utilizando tuples. I>...dat y deben contener qué ciudades ofrecen qué productos. <Z A G>. <Z A H>. lo que significa que consideraremos sólo una parte de los datos originales. sin embargo no está explicitada la información de cuánto oferta cada ciudad de cada producto. <X C>. ciudad de origen y ciudad de destino. CiudadesOferta={ <X A>. es necesario indicar ahora cuánto ofrece cada una de esas ciudades de cada uno de los productos. <Z A I>. <Z C G>. <Z A>. <Z <Z <Z <Z A B C C F>. I>. Ya definidas las ciudades de oferta y los productos que éstas ofrecen. <X B H>. De los datos sabemos que existen sólo tres ciudades ofertantes. cada una ofertando diferentes cantidades de los productos X.mod de la siguiente forma: {oferta} CiudadesOferta = . <Y B H>. el conjunto de todas las rutas será entonces un arreglo de estos metadatos. J>. D>. Para ingresar esta información es necesario primero determinar qué ciudades son en realidad ofertantes. J>. <Y C>. <Y <Y <Y <Y A A B C D>. J>. D>. string d. H>. y que en esta nueva definición de datos debe entregarse como sigue. <Y B F>. <Z C E>. <X B>. <X C G>. <X <X <X <X A B B C E>.}. <Z B>. El conjunto de estas rutas se deben explicar en el archivo .mod donde cargaremos la información de las rutas existentes. se define una tupla oferta como se muestra a continuación tuple oferta { string p. I>. J> }. <Y B G>. que corresponden a las 63 rutas existentes como se muestra a continuación. 50. Sabemos que cada ruta tiene un costo asociado dependiendo del producto transportado y las ciudades entre cada ruta. en la que directamente señalaremos los costos de transporte. Los datos son leídos desde el archivo .. <Y G>. podemos ahora asignar a cada par <p. } Una vez definida la tupla demanda podemos definir cuáles son las ciudades que demandan y cuáles son los productos que estás requieren.d>. <Y E>: 750. string d.dat. Dado que ya hemos definido un arreglo conteniendo cada ruta. dada por un par <p. <Z D>. Los costos estarán contenidos en la siguiente variable float Costo[Rutas] = . <X H>. <Z I>: 100. <Z J>. generando un arreglo de tuplas demanda al que llamaremos CiudadesDemanda y cuyos datos leeremos 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 ]#. 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. <Z F>. <X G>. <Y F>: 400. <Y J>. <Z D>: 100. para la demanda es necesario definir variables similares. Una vez definidas las demandas en términos de ciudades y productos. Definiremos primero una tupla caracterizando la demanda.datos reales llamado Oferta el que debe contener un dato para cada par <p. <Y H>: 950. <Y H>.o>:Q de este arreglo indica que la ciudad o ofrece Q unidades del producto p.. <X F>. 850. <Y 100. <Z H>: 200. Esta notación para entregar los datos permite asociar un único dato (cantidad ofertada en este caso) para cada par <p. <Y I>.d> las correspondientes cantidades demandadas D. <X G>: 75. Pero presentaremos otra forma más sencilla.o>. asumiendo que el orden es exactamente el mismo que el que tienen los datos de las rutas. es decir. float Demanda[CiudadesDemanda] = . debemos crear un arreglo como se muestra a continuación. float Oferta[CiudadesOferta] = . <Z F>: 0. D>: F>: G>: I>: 500. elementos del tipo <p. <Z E>. <Y E>. <X E>: 300. Similar a lo presentado para los datos de oferta.... <Y F>. utilizando la misma notación que en el caso de la oferta.d>:D en un arreglo compuesto de elementos reales. <X I>. <Z G>. podemos ahora definir un arreglo de valores reales con tantos elementos como rutas.dat se muestran debajo. corresponden a los costos de transporte. Demanda = #[ <X <Z <Y <X <Z D>: E>: G>: I>: J>: 300. 20 . <Z 225. y son lo presentados debajo. <Y 250 ]#. <X E>.o>. <Y D>. }. Esto permite que los datos puedan ingresarse en forma desordenada y aún así se asignaría la cantidad correcta a cada par <p. <X J>: 250. <X H>: 650.. <Z I>... <Y J>: 500 Los datos que se deben definir ahora. <Z H>. donde p indica el producto y d indica la ciudad: tuple demanda { string p.. es decir.mod conteniendo esta información de demandas así como los datos a ingresar en el archivo . CiudadesDemanda={ <X D>. La declaración de este arreglo en el archivo .o>. <X J>. 100. cada elemento <p. <X 250. 20.d> in Rutas}.o.28.24.17.17. tenemos nodos de oferta y de demanda exclusivamente.11. sabemos que o será una ciudad de oferta u origen y d será una ciudad de demanda o destino. es decir.13. similar a la definida en la variante I mostrada anteriormente. y puesto que el modelo problema a resolver es el mismo que el tratado en la primera variante.83.y sus datos son los que siguen. o y d. se definió una variable de decisión del tipo Trans[p][o][d].12. la cantidad transportada de cada producto desde una ciudad de oferta a una de destino. Podemos determinar estos conjuntos sin la necesidad de ingresarlos como datos.104.19.9. La función objetivo será. la variable de decisión se debe definir de la siguiente manera: dvar float+ Trans[Rutas].7.14. si existe una ruta <p o d>. la función objetivo estaba compuesta por términos del tipo Costo[p][o][d]*Trans[p][o][d].41. es útil definir separar los nodos entre aquellos que sólo son de oferta. p.13. 21 .26. En este caso.99.39.12. 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. por ejemplo.10. aquellos que sólo son de demanda y aquellos que son de transbordo.99. Esta misma forma podría haberse utilizado en el caso de Oferta y Demanda.o.29.9.14.86.8. pero presentará diferencias puesto que tanto los coeficientes como las variables de decisión son de una naturaleza diferente.31. definiendo completamente el dominio de la variable de decisión Trans.6.14.10. En la primera variante presentada para este problema.21. De esta forma.9.15.16. {string} Orig[p in Productos] = {o | <p. Definiremos dos conjuntos de nodos: Orig y Dest. indicaba el producto transportado y los subíndices o y d indicaban la ruta de transporte. entonces la variable de decisión es la misma. sino que simplemente con la información ya contenida en los parámetros.71.10. función objetivo y restricciones. En esta segunda variante no tiene sentido definir una variable de esa forma puesto que el parámetro Rutas implícitamente contiene ya los subíndices p.10.8.20]. 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.11.82.17. En la variante anterior. 11. donde el primer subíndice. por cierto. {string} Dest[p in Productos] = {d | <p. para indicar nodos de oferta y demanda respectivamente. 16. por lo que resta todavía definir las variables de decisión.8. Se puede interpretar este código de la siguiente forma para el caso del vector Orig: escoger para cada producto p y de entre todas las rutas.14.26.25. simplificando aún más el ingreso de datos. es decir podemos definir la función objetivo como sigue: Minimize sum(l in Rutas) Costo[l] * Trans[l].95. sin embargo ahora podemos reemplazar los subíndices [p][o][d] por un único índice asociado a cada ruta.18.27.d> in Rutas}. En muchos problemas de flujo en redes como éste.82. definida por un producto.22.17. En esta definición de variable. una ciudad de origen y una ciudad de destino.13. se especifica que para cada ruta.15. Costo = [ 30. Respecto a la variables de decisión. A este punto ya hemos definido todos los parámetros del modelo.12.7.9.13. Similar definición se puede dar para el caso del vector Dest. aquellas ciudades de origen de estas rutas.28. El código en lenguaje OPL para la creación de ambos conjuntos es el que se muestra más abajo. o. Demanda y Capacidad se muestran abajo. mejora el manejo interno de los parámetros y variables en el motor de resolución de OPL. lo que corrobora la validez de esta nueva formulación.d>]. al igual que en el caso de la función objetivo. o in Orig[p]) { ctOferta: sum(d in Dest[p])Trans[< p. 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. 22 . Respecto a la solución óptima. forall(p in Productos . Las restricciones serán también esencialmente iguales variando apenas debido a la definición de los parámetros. que corresponde a la ruta <X.donde el índice l contiene la información tanto de los productos como de las ciudades entre los que este producto se transporta. Es importante notar el rol que juegan los vectores Orig y Dest en estas restricciones. d in Ciudades) sum(<p. los valores de las variables del vector Trans. estos de presentan a continuación: 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].d>] <= Capacidad. Una vez definido el modelo completo y resuelto y se puede corroborar que el valor óptimo de la función objetivo es 199500.o. Si bien esta segunda variante eleva el nivel de complejidad de las variables definidas. donde c contiene el costo de dicha ruta.d >] == Oferta[<p. igual valor obtenido al obtenido en la primera variante desarrollada. que es el mismo valor de la solución encontrada en el primero modelo desarrollado. Se pueden realizar mejoras a la segunda variante.o. es decir.d> in Rutas) Trans[<p.o.o. puesto sólo se generan restricciones de oferta (ctOferta) para las ciudades de origen y restricciones de demanda (ctDemanda) para las ciudades de destino. Las restricciones de Oferta.H> tiene un valor 250.B. } forall(p in Productos. reduciendo tanto las iteraciones internas en la generación del modelo como en la resolución de éste.c>. una de ellas es bastante intuitiva y corresponde a agregar a cada ruta su respectivo costo definiendo tuplas del tipo <p. d in Dest[p]) { ctDemanda: sum(o in Orig[p])Trans[< p. } ctCapacidad: forall(o .d >] == Demanda[<p.d.o>]. Miller. propondremos una estrategia algorítmica más simple.R. (7) [3] G. sin embargo son sumamente eficientes si se utilizan en estrategias algorítmicas más sofisticadas como algoritmos de separación de restricciones como subrutina de algoritmos de planos de corte o algoritmos branch and cut especialmente diseñados para TSP u otro problema que considere este mismo tipo de restricciones. Una formulación general para el problema se puede definir en las ecuaciones (1-5) presentada abajo. Estas restricciones son para todo y .Resolución del TSP utilizando OPL (Parte I) En esta sección estudiaremos un problema clásico de optimización combinatorial y que tiene amplias áreas de aplicación en diferentes disciplinas de ingeniería (telecomunicaciones. 326-329 (1960).B.E. conformando un conjunto N y conexiones directas entre cada par de éstos con un peso asociado correspondiente a distancia.. Presentaremos una formulación para este problema que permite la resolución de instancias del problema utilizando los conocimientos hasta ahora presentados del uso del software OPL y el motor de solución CPLEX.A. "Integer programming formulation of travelling salesman problems". Dantzig. Fulkerson and S. Operations Research 2.. Johnson. el TSP consiste en encontrar un tour que recorre todos estos puntos con la mínima suma de los pesos de las conexiones que componen dicho tour. puntos. Dantzig. D. manufactura y otras). servidores. "Solutions of large scale travelling salesman problem". etc. Tucker y Zemlin presentan otra formulación basada en el mismo modelo de asignación pero utilizando restricciones de eliminación de subtour usando variables continuas adicionales. En esta sección. (2) (3) (4) (5) son las variables de decisión y que toman dos valores posibles (restricción (4)). El coeficiente representa la ponderación o peso asociado al elemento . 1 y 0. costo de construcción. por lo es necesario utilizar una formulación alternativa a la [4] presentada en (6). TSP: Formulación del problema como un PLEM Dada una red compuesta de n ciudades. Miller. (1) Sujeto a: y enteros + restricciones de eliminación de subtour En esta formulación. camino o link es parte del tour óptimo y 0 indica lo contrario. 1 indica que la conexión. J. Tucker and R. [3] Fulkerson y Johnson usan un modelo con variables y restricciones de eliminación de subtour del tipo: para todo tal que (6) Una formulación basada en este tipo de restricciones es sumamente ineficiente si se pretende resolver directamente utilizando un solver como CPLEX. salvo que en la implementación se añadan restricciones adicionales que impidan la aparición de variables . A. el problema del Vendedor Viajero o TSP (de su nombre en inglés Traveling Salesman Problem). Zemlin.W. ACM 7. etc. [4] C. logística. 23 . Las ecuaciones (2) y (3) forman las restricciones de asignación y aseguran que cada nodo es visitado sólo una vez.M. 393-410 (1954). por simplicidad y para asegurar factibilidad hacemos simplemente para todo . tiempo de viaje. Los parámetros del modelo son el número de nodos y los costos o distancias de las aristas. consideraremos la formulación dada por: El modelo se presenta en el archivo ejemplo_TSP_OPL en el directorio que contiene de este documento. 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 La definición de las variables de decisión es bastante intuitiva. Si consideramos las definiciones entregadas en la primera sección respecto de conjuntos y rangos. n corresponde al número de nodos y N representa el conjunto de estos n nodos dado por los enteros entre 1 y n.. variables binarias asociadas a cada arista y variables continuas asociadas a cada nodo... Utilizaremos a modo de ejemplo una instancia de 16 nodos. cuya matriz de costos se muestra en la Tabla 1. y el dominio de estas variables está dado por el espacio NxN. En esta definición. n from SheetRead( sheet. el vector de costos c tiene un dominio en el espacio NxN. por lo que de acuerdo a lo presentado la primera sección.OPL y el solver CPLEX.mod: int n=.. int c[N][N]=. TSP: Formulación del problema en OPL Con tal de resolver el TSP utilizando ILOG . hecho que debe considerarse en la generación del modelo.dat. mientras 24 .n. la estructura está dada por: SheetConnection sheet("datos. parece intuitivo definir estos parámetros de la siguiente forma en el archivo . en la continuación de esta sección los elementos en N serán llamados nodos y las conexiones entre éstos serán llamadas aristas. se presenta la lectura de estos datos desde una planilla Excel.xls").. Las variables binarias en OPL se declaran como variables del tipo boolean.. c from SheetRead( sheet...y para todo . range N=1. Por simplicidad. tienen valor cero. (8) y las denominaremos como restricciones MTZ. Es claro que puesto que existen conexiones entre cada par de nodos. es decir los valores de . "Hoja1!D2" ). En el archivo . "Hoja1!C5:R20" ). En la tabla se puede ver que los elementos en la diagonal. dat para resolver el la instancia de TSP definida por los datos en Tabla 1. forall (i in N) { forall(j in N : j!=1 && j!=i) { u[i]-u[j]+1 <= n*(1-x[i][j]). agregaremos un nuevo elemento. por lo que estas son las variables que quisiéramos registrar. En primer lugar. Una definición intuitiva está dada por minimize sum(i in N)(sum(j in N)(c[i][j]*x[i][j])). (7) y (8). "Hoja1!C5:R20"). sin embargo. la que debe existir antes de resolver el problema. Como se aprecia en el código. dvar float+ u[N]. la matriz x[N][N] se escribe en la planilla señalada y el resultado se muestra en la 25 . podemos comenzar a estructurar el modelo. contenidas en el arreglo x[N][N]. y se presentan abajo. Una vez que ejecutamos el programa. que podría ser una planilla diferente a a aquella que contiene los datos del problema. En el modelo utilizado. } } u[1]==1. utilizaremos una función de OPL que permite guardar el valor óptimo de algunas o todas las variables de decisión definidas en el modelo en una planilla Excel. Las restricciones de asignación. u[i]<=n.xls. que se muestran en (2) y (3). las variables de decisión de real importancia son las variables . definiremos la función objetivo. y se definen como se muestra en el siguiente código. en este caso la planilla solución. } A este punto ya hemos definido completamente tanto el archivo . lo que no tiene sentido en términos del problema.xls"). Por ello. las variables x[i][i] tenderían a tomar valor 1. son muy simples de definir. es necesario añadir una restricción a la segunda sumatoria con tal de considerar sólo aquellas aristas que existen en la instancia. Definidas ya las variables de decisión. Sin embargo. forall (i in N: i!=1) { u[i]>=2. 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 solución (Hoja1!C5:R20). es necesario “abrir” o generar “conexión” con otra planilla.mod como el . tampoco suponen alguna complejidad. x to SheetWrite(sheet2. forall (j in N) sum(i in N : i!=j)x[i][j] ==1. A continuación se muestra el código para realizar eso: SheetConnection sheet2("solucion. la función objetivo correcta se muestra a continuación: minimize sum(i in N)(sum(j in N : i!=j)(c[i][j]*x[i][j])). forall (i in N) sum(j in N : i!=j)x[i][j] ==1. puesto que las componentes tienen valor cero.que las variables continuas son del tipo float+. Las restricciones de MTZ. así entonces definimos las variables de decisión como sigue: dvar boolean x[N][N]. Por ello. las aristas que pertenecen al tour óptimo. Generación de Columnas. CPLEX y Tecnología Concierto (Concert Technology) capaces de resolver en tiempo razonable instancias de mayor tamaño y por ende de mayor importancia real. Branch & Cut. una instancia de 25 nodos podría tomar varios minutos y una de 100 nodos podría tomar muchas horas o incluso días utilizando esta metodología. instancias reales se componen por cientos. y el costo de dicho tour es 71. Sin embargo. Discusión de resultados: Tiempos de Resolución y alcance de la formulación La instancia considerada en este ejemplo tiene 16 nodos y puede considerarse como una instancia didáctica apenas. 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. etc. En color rojo hemos destacados las variables que toman valor 1.0 GHz. sin embargo puede ser útil en estrategias algorítmicas más sofisticadas en las que se resuelvan subproblemas usando esta formulación en algoritmos como Relajación Lagrangeana. Esto impacta fuertemente respecto de la real utilidad de la formulación y la estrategia presentada para resolver el TSP. esta formulación no pasa de ser meramente didáctica. esta pequeña instancia toma. 26 . RAM 2 GB). entre 20 y 25 segundos en un notebook regular (procesador 2. para resolver el TSP usando herramientas OPL. y por ende.xls en el que se muestra los valores óptimos de las variables . pero más eficientes. En secciones posteriores se mostrarán metologías más sofisticadas. en promedio.Ilustración 15. Ilustración 15: Detalle de la planilla solución. salvo que se requiera resolver instancias muy pequeñas. miles o centenas de miles de nodos y un número aún superior de aristas. 1st edn. Dordrecht. Ilustración 16: De izquierda a derecha: Instancia . si es necesario. [6] Michel X. X. Mathematical Programming 63 (1994) ~5 7-182 [7] Maculan. Dado un grafo no dirigido llamados nodos terminales. a partir de formulaciones basadas en desigualdades de flujo multibien no simultáneo. A diferencia del problema de Mínimo Árbol de Cobertura (ACM) en el que se busca conectar todos los nodos en a mínimo costo. Maculan. tal que y se minimice la siguiente función objetivo donde es en valor real asociado a cada arista . Steiner Trees in Industry Series (Series on Combinatorial Optimization Vol. D. and Du. el StTP busca conectar los nodos en llamados terminales utilizando. El StTP se define a continuación. es. en rojo se muestran los nodos terminales y en verde los nodos steiner necesarios para conectar los nodos terminales. [6] específicamente programación entera mixta. [5] Cheng. StTP y otros. and Lisser.. En la tercera figura se muestra una solución factible al problema de Árbol de Cobertura en el cual es necesario cubrir todos los nodos del grafo.: 2003. Solución factible del StTP. al igual que el TSP. un problema clásico de Optimización Combinatorial estudiado intensamente durante los últimos 50 años dada la amplia gama [5] de aplicaciones en diferentes áreas de la ingeniería . Plateau y Lisser [REF]. (eds): 2002. 161–168.Resolución del Steiner Tree Problem en grafos utilizando OPL StT Problem: Formulación del problema como un PLEM El Steiner Tree Problem (StTP) en grafos. E. A. o redes. The Steiner tree polytope and related polyhedral. estos nodos adicionales son llamados nodos steiner. para el StTP. 27 . Plateau. N. Al igual que para el TSP existen diversas formulaciones de programación matemática. 11). ACM. el StTP consiste en encontrar un árbol y un conjunto de . En esta sección. Goemmans resume las principales [7] formulaciones desarrolladas para el StTP hasta 1991 y en 2003. The Netherlands. muestran una serie de modelos enteros lineales útiles para conjunto importante de problemas en redes como TSP. Goemans. Pesquisa Operacional 23(1). Integer linear models with a polynomial number of variables and constraints for some classical combinatorial optimization problems. algunos nodos adicionales en . G. En la segunda figura de la Ilustración 16 se muestra una solución factible del StTP para la instancia de la primera figura. Kluwer Academic Publishers. utilizaremos los modelos presentados en este último artículo adaptándolos al StTP. Solución factible del ACM. . el concepto de key. Tal como hemos señalado. usaremos tuplas para dar estructura tanto a la información de aristas como nodos. mientras que el conjunto contiene los nodos de origen de las aristas del tipo . con esto. StT Problem: Formulación del problema en OPL Al igual que en el caso del TSP.. range N = 1. hay información respecto a los nodos. el nodo de origen y nodo de destino son los elementos que definen una arista. Tanto n como el conjunto N de estos nodos se definen como sigue: int n = .. en el StT consideramos la información de una red compuesta por nodos y aristas. El StTP puede formularse a través del siguiente modelo de programación lineal entero mixto.Sea contrario. 28 . puesto que además se utilizará el rango o conjunto de nodos dado por n en la definición tanto de parámetros como variables de decisión. por lo que crearemos una estructura edge conteniendo las claves origen y destino. estas últimas con pesos asociados que representan distancias.n.. en esta caso ocuparemos las estructuras utilizadas en la segunda variante del problema de transporte tratado en la Sección 2. A diferencia de la formulación dada para TSP. como se muestra abajo. una variable binaria con valor 1 si la arista una variable binaria con valor 1 si el nodo es parte es parte y valor 0 en caso y valor 0 en caso contrario y el flujo desde el nodo al nodo que pasa a través del nodo . es decir. Sujeto a Esta formulación necesita de un nodo raíz. y esta información es clave para identificar una arista de otra. Una arista se define por nodo de origen y nodo de destino. Los primeros parámetros a definir están relacionados con el número de nodos. El conjunto contiene los nodos de destino las aristas del tipo . introduciremos un concepto útil en el uso de tuplas. Adicionalmente. tiempos o costos de transporte entre nodos. que sin pérdida de generalidad será el nodo 1 el que además forma parte de . es necesario indicar cuáles son los nodos terminales. n. <2 4 6>. A continuación se muestra el código para declarar la tupla conteniendo los datos de las aristas (dataEdge) tuple dataEdge { int o. } Respecto a los nodos y puesto que sólo tienen un valor clave (1. 5>. existen 3 nodos terminales: 1. string type. 2>.2. Terminal & Root } Ya creadas las estructuras que contendrán la información de aristas y nodos. <3 Optional>. El resto de los nodos son nodos opcionales y algunos de ellos podrían pasar a ser nodos steiner.. La declaración de los arreglos se muestra a continuación: {dataEdge} dataEdges = . int d. <4 Terminal>. <5 Optional>. <4 7 6>. Tanto el nodo de origen como el nodo de destino se representan por valores enteros (1.}. {dataNode} dataNodes = . <2 7 7>. sin embargo. <6 7 5>. el nodo destino y el costo o distancia entre estos nodos. los costos de las aristas. tuple dataNode { key int num. en general.dat. dataNodes ={<1 Root>. 7>. key int d. <6 Optional>. 4 y 8. 9>. 5>. incluyendo además un identificador “root” (o raíz) para el nodo i=1. vector de costos. si es nodo terminal o no.. es decir. <9 Optional>.mod. asumiremos que será entero. <2 <4 <5 <8 3 5 9 9 3>. los nodos 29 . generaremos otra tupla conteniendo la información de cada arista. De acuerdo a lo que se muestra. <8 Terminal>. } Una vez generada la estructura edge.… ó n) podemos generar sólo una tupla conteniendo su identificador y la información asociada a él. 1>.}. los nodos terminales. //Optional. Necesitamos ahora extraer desde estos arreglos información aislada para utilizar luego en el modelo de programación entera. En este punto los arreglos dataEdges y dataNodes contienen toda la información de la instancia.…. <2 Optional>. La información de cada arista es el nodo de origen.. Abajo se muestra el código para definir la tupla dataNode. se está señalando que la información está contenida en el archivo . podemos generar los arreglos de tuplas dataEdge y dataNode y leer la información desde el archivo .tuple edge { key int o. y sin pérdida de generalidad.2. específicamente en la generación de variables de decisión.n) mientras que el costo es.. un valor real positivo. 3>. int cost. Esta información aislada son las aristas. 1>... 8>. <1 <3 <5 <7 3 5 8 9 2>. los datos de la instancia considerada en esta sección en el formato de lectura son los siguientes: dataEdges={ <1 <3 <5 <6 2 4 6 9 4>. <4 6 4>. <7 Optional>. Como se puede ver. j>].c> in dataEdges}. } El resto de las restricciones no necesitan mayor detalle dada su simpleza y se presentan abajo.j> in edges)Cost[<i.c> in dataNodes : c == "Optional"}.d>:t. // extracción de la información de los costos de aristas en un vector int Cost[edges] = [<t.cost | t in dataEdges ]. Como se puede ver en el modelo de programación entera. int+ p. boolean x[N].sum(j in getIn[i])z[j][i][k] == 0.sum(j in getIn[k])z[j][k][k] == -1.c> in dataNodes : c == "Terminal"}. // extracción de la información de los nodos de destino de las aristas {int} getIn[node in N] = {o | <o. los conjuntos de nodos de origen de las aristas y los conjuntos de nodos de destino de las aristas. En primer lugar definiremos la función objetivo. c> in dataNodes : c == "Root"}. dvar dvar dvar dvar boolean y[edges]. float+ z[N][N][terminalNodes]. // extracción de los datos opcionales {int} optionalNodes = {nodes | <nodes.d. 30 .j> in edges.sum(j in getIn[i])z[j][i][k] == 1.j>]. la declaración de estas se muestra abajo. // declaración del nodo raíz {int} rootNode = {node | <node.d> in edges}. toda esta información será contenida en estructuras independientes cuya naturaleza (tupla. forall(k in terminalNodes. t. la restricción . arreglo o valor único) dependerá de la información que contendrá. existen cuatro variables de decisión: y . entero. se escribe como. A continuación se muestra el código para extraer esta información: // extracción de la información sólo de aristas {edge} edges = { <o. se declara como forall(i in optionalNodes. es decir minimize sum(<i.mod.d> | <o. el nodo raíz.j>]*y[<i. forall(<i. // extracción de los datos terminales {int} terminalNodes ={nodes | <nodes. } la desigualdad . k in terminalNodes) { z[i][j][k] <= y[<i. } y la restricción se escribe como forall(k in terminalNodes) { sum(j in getOut[k])z[k][j][k] .j>]. // extracción de la información de los nodos de origen de las aristas {int} getOut[node in N] = {d | <node.o .opcionales. z[j][i][k] <= y[<i. En este punto ya tenemos todos los elementos para comenzar a formular el modelo en el archivo . k in terminalNodes) { sum(j in getOut[i])z[i][j][k] . la que está dada por minimización de la suma de los costos de las aristas incluidas en el árbol. i in rootNode) { sum(j in getOut[i])z[i][j][k] .node> in edges}. j>] <= x[j]. podemos resolverlo obteniendo un valor óptimo 16.3. siendo 3 y 5 nodos steiner. salvo que. sum(<i. al igual que en el caso del TSP. } forall(i in terminalNodes) x[i] == 1. y[<i. y valores de variables de decisión binarias dados por . p <= n. se utilice en un diseño algorítmico más elaborado en los que sea necesario resolver sub problemas de menor tamaño en forma iterativa y se utilice esta formulación para ello. forall(<i. Es decir. necesarios para conectar los nodos terminales (1.j>] <= x[i]. Discusión del modelo presentado Al igual que en el caso del TSP.5 y 8.j>] == p .4. la formulación presentada para el StTP es por un lado simple de implementar pero ineficiente si lo que se busca es resolver instancias de gran tamaño.j> in edges) { y[<i. Definido ya el modelo completo.1. se conectan los nodos 1. y el resto de las variables con valor cero.j> in edges) y[<i. 4 y 8).} sum(i in N)x[i] == p. 31 . en el cual no se consideran las restricciones de integralidad. Es decir. para resolver el TSP mediante planos de corte. e iterativamente ir agregando restricciones a medida que en la solución de la relajación lineal aparezcan 32 . en que no se considera ninguna restricción de eliminación de subtour. La idea principal del algoritmo de planos de corte consiste en que si agregamos una restricción al ILP que no excluya soluciones factibles enteras. Así. dado un conjunto de ciudades arco pertenece al tour. salvo que en esta oportunidad se considera una pequeña variación en que se agrupan los dos conjuntos de restricciones de asignación. debido al conjunto de restricciones de eliminación de subtours. una por una. La resolución de este modelo es impráctica. el modelo es el siguiente: El modelo es similar al descrito por las ecuaciones (1)-(4) y (6). y se usan el conjunto de restricciones de eliminación de subtours de Dantzig. Así. Por lo que si agregamos restricciones lineales (cutting planes). sin embargo. Fulkerson y Johnson. si es entera. se resuelve: En el caso general es de esperar que la solución factible obtenida no sea entera. obtendremos la solución óptima del ILP. entonces la solución no cambia. útil para problemas de mediano tamaño pero muy interesante desde un punto de vista teórico. usaremos la formulación de Dantzig. Fulkerson y [3] Johnson que se mostró en secciones previas. La distancia o el costo de ir de la ciudad a la .Resolución del TSP Problem utilizando OPL (Parte II) Descripción del Algoritmo de Planos de Corte (PC) El método de planos de corte es un procedimiento para encontrar soluciones a problemas de programación lineal entera. al ILP hasta que la solución de su relajación lineal sea entera. La idea detrás del algoritmo de planos de corte para resolver el TSP consiste en resolver la relajación del modelo anterior (RTSP). Se considera el siguiente problema de programación lineal entera (ILP) en forma estándar: Entonces resolvemos la relajación lineal del ILP anterior. ó ciudad es definido como . pues corresponden a un número exponencial de restricciones al considerar todos los subconjuntos de ciudades. la solución encontrada a través de la relajación lineal también será la solución óptima del problema de programación lineal entera. Ahora. y las variables de decisión serán si es que el en caso contrario. Control de flujo mientras el modelo es resuelto. y realizando procedimientos adicionales ajenos al modelo mismo. Existen dos expresiones de programación de alto nivel: La expresión main para las instrucciones de la sección de control de flujo. post-procesamiento y un control de flujo.subtours. se resuelven iterativamente relajaciones del problema. El lenguaje de programación es llamado ‘IBM ILOG Script for OPL’. Es importante mencionar que el orden de declaración y orden de las instrucciones. La expresión execute para instrucciones de las secciones de pre-procesamiento y postprocesamiento. y dar formato a los datos. Un esquema del algoritmo de planos de corte se presenta a continuación: Descripción de la Implementación en OPL del Algoritmo PC Hasta el momento se han realizado implementaciones en que se dispone de un modelo matemático a resolver. transformar. Resolver repetidamente instancias de un mismo modelo. por lo que se requerirá utilizar herramientas de programación que posee OPL IDE. Vale decir. el cual es una implementación de JavaScript que soporta elementos distintos a los de modelación de OPL. Crear soluciones algorítmicas donde la salida de un modelo es usada como la entrada de otro modelo. siguen una secuencia lógica. para enviarlos a otra aplicación. en un programa que posee un modelo matemático. dada una solución. Obviamente el algoritmo termina cuando no existan subtours. o a través de un archivo independiente. instrucciones de preprocesamiento. aumentando un poco la complejidad de la implementación. mientras para resolver el TSP mediante el algoritmo de planos de corte presentado anteriormente. sólo debes agregar instrucciones de programación al archivo del modelo. el orden y secuencia de las instrucciones de cada sección es la siguiente: 33 . y durante el transcurso del algoritmo se van agregando cortes (restricciones al modelo). y los datos necesarios se han entregado en el mismo archivo del modelo. Cuando se usa la programación en OPL. se evita tener que unir y compilar la aplicación. Agregar instrucciones de post-procesamiento para agregar. En todos los casos se ha dispuesto de toda la información necesaria para la resolución del problema con antelación. Entre las cosas que es posible realizar tenemos: Agregar instrucciones de pre-procesamiento para preparar los datos del modelo. Controlar los parámetros de CPLEX y/u OPL. encuentre los subtours existentes. Claramente será necesario un procedimiento para. lo que se realiza es tomar de un archivo de datos la información relacionada a la diagonal superior de matriz de distancias. la siguiente ciudad que se 34 . Ahora. Notar en la restricciones de eliminación de subtour que las restricciones se añaden para cada subtour existente en la sección de datos ‘subtours’. int dist[Edges] = .. Declaración de datos y variables de decisión: además del tamaño del problema .. como se resuelve el problema relajado.j in Cities}. subject to { forall (j in Cities) // Restricciones de asignación sum (<i..k> in Edges) x[<j. Declaración de datos y variables de decisión. Las únicas variables de decisión necesarias en el problema corresponden a si una determinada arista pertenece o no a la solución (tour). s. Fulkerson. Al ser ordenado se establece que el elemento es menor al elemento en la arista . dvar boolean x[Edges].j> | ordered i. en base a las explicaciones anteriores es posible enunciar la implementación de planos cortantes para el TSP. maxl(i. sin embargo. para un mejor entendimiento se explicara identificando cada una de las secciones y el modelo completo se muestra en el anexo 2. y se crea un conjunto ‘ordenado’ de aristas llamado ‘Edges’.. Además 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 dimensión del número de ciudades. Así.n. int j. por lo que en la instrucción int dist[Edges] = . basta con la matriz diagonal superior sin la diagonal.subtour[i] != 0) x[<minl(i. por lo que en la primera iteración no se agregara ninguna restricción de dicho tipo. tuple edge {int i. forall (s in subtours) // Restricciones de eliminación de subtours sum (i in Cities : s. Como en el algoritmo se desea ingresar los cortes relacionados a los subtours encontrados. es necesaria una estructura que. } {Subtour} subtours = . range Cities = 1.subtour[i]). }... y Johnson: minimize sum (<i.k>] == 2.. La última instrucción permite leer de un archivo de datos los subtours.j>] + sum (<j.} setof(edge) Edges = {<i...size-1. guarde el tamaño del subtour encontrado y en un arreglo el subtour: tuple Subtour { int size. Instrucciones de post-procesamiento. se comienza sin ningún subtour y a medida que se resuelvan relajaciones se irán agregando subtours a los datos con el formato establecido. Instrucciones de control de flujo.. Instrucciones de pre-procesamiento..j> in Edges) dist[<i. pues consta de puros ceros.j> in Edges) x[<i. Definición del modelo matemático de Dantzig.j>]*x[<i.subtour[i])>] <= s. y como es simétrico. Las instrucciones necesarias son: int n = . en que la posición indica la ciudad y el valor de dicha posición.j>]. por ejemplo. se define una estructura de datos (tuple) para las aristas. int subtour[Cities].. los únicos datos que hacen falta son las distancias entre ciudades. como es un TSP clásico. Definición del modelo matemático. s. pero puede modificarse. Instrucciones de control de flujo: a través de la expresión main se especifica el comienzo de las instrucciones de control de flujo. Las estructuras necesarias para manipular el modelo y los datos se muestran en la siguiente tabla: IloOplModelDefinition Une al archivo . Instrucciones de post-procesamiento: es necesario para que dada una solución. distinta a la usada para las variables de decisión dvar de los modelos. identificar el subtour ó tour asociado a la solución. no es modificable. llamadas a funciones. resolver multiples modelos. La sintaxis de las expresiones relacionadas al lenguaje de programación ‘IBM ILOG Script for OPL’ son muy similares a la sintaxis de C y C++. además 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 través del algoritmo cplex. permitiendo usar diferentes modelos con diferentes datos.dat la representación de los datos. IloOplDataElements Una fuente de datos.visita. modificar los datos del modelo entre una resolución y otra. mientras los archivos se encuentran en la carpeta del proyecto de OPL “TSP-DFJ”. IloOplModel Estructura que une una definición de un modelo con una o varias fuentes de datos. Las variables en el lenguaje de programación de OPL se definen a través de la palabra var. se crea por defecto una variable llamada ‘thisOplModel’ que contiene la instancia de IloOplModel disponible. La implementación del bloque main es la siguiente: 35 . Un ejemplo en que el número de ciudades sea 10 y exista un subtour de 3 ciudades podría estar representado por la siguiente figura: 4 0 0 9 0 0 0 0 1 0 1 2 3 4 5 6 7 8 9 10 Notar además que el uso de las funciones minl y maxl tiene relación sólo a que el problema es simétrico. Resaltar que cuando se ejecute las instrucciones se ejecutaran de acuerdo al main. como en nuestro caso. Es particularmente útil cuando se quiere resolver un modelo con diferentes datos. Cuando se ejecuta el bloque main. incluidas expresiones de asignación.mod la representación del modelo IloCplex Una instancia del algoritmo CPLEX IloOplDataSource Une al archivo . entre otros. propiedades de acceso. El esquema general presenta la siguiente estructura: execute { /* * Función findSubtour */ } Debido a que el procedimiento no tiene gran complejidad. que otorga gran flexibilidad al resolver el problema. el procedimiento completo se muestra con comentarios explicativos en el anexo 1. opl = new IloOplModel(mod. opl.postProcess().tsp”. y de esta forma termina la implementación del TSP usando planos cortantes. Si es que no se puede resolver. librería que posee una gran cantidad de información sobre el problema del vendedor viajero y datos de prueba comúnmente utilizados por la comunidad científica internacional.thisSubtour). } } Inicialmente se asigna la instancia de la clase IloOplModel a una variable llamada opl.end(). } dat. } opl. opl. break. y si es que se resuelve. 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.newSubtourSize.end(). y se genera el modelo completo para ser nuevamente resuelto a través del comando cplex.ifi. if (!cplex. se crea una instancia de IloOplModel uniendo el modelo modificado (mod) con el algoritmo cplex.uni-heidelberg.clearModel(). opl. Al inicio de cada iteración se limpia el algoritmo cplex. opl. var dat = opl. se realiza el post-procesamiento para encontrar un subtour.dataElements se extraen los datos a una variable llamada dat.generate().cplex).newSubtourSize == opl.add(opl.opl.end().solve()) { writeln("ERROR").de/software/TSPLIB95/ 36 .subtours. while (1) { cplex.modelDefinition se extraerá el modelo a la variable mod. el cual es una instancia de la [8] librería TSPLIB .newSubtour). se le agregan los datos (modificados).dataElements. esta variable contendrá la definición del modelo y sus datos actuales. se despliega un mensaje de error.n) { writeln("Tour final ". el nuevo tamaño del subtour. opl.solve(). significa que se ha encontrado la solución.modelDefinition.addDataSource(dat). y el nuevo subtour para que sea agregado al modelo como restricción (cut). break. en caso contrario. y con opl. Así. Discusión de resultados: Tiempos de Resolución y alcance de la formulación La prueba del modelo se realizo con los datos del problema “gr17. Si es que el tamaño del subtour es igual al número de ciudades. if (opl. Por lo que usando el método opl. se agrega a la sección de los subtour en los datos. La dimensión del problema es de 17 ciudades. Al finalizar cada iteración se elimina la instancia de opl para el eficiente uso de la memoria.main { var opl = thisOplModel var mod = opl. Es necesario mencionar que los datos fueron adaptados de 8 http://comopt. opl. Ilustración 1 Ilustración 17: Solución de problema gr17. El tiempo aproximado que demoró OPL en resolver la instancia corresponde a 0. etc. es posible resolver instancias de mucho mayor tamaño con el algoritmo de planos de corte que con el modelo de Miller. C++. que en el caso se utiliza la matriz diagonal superior de distancias entre ciudades.12 segundos. se aprecia claramente la mayor rapidez del método de planos de corte y la solución se muestra en la Ilustración 17. Python.tsp Una de las deficiencias de la implementación del algoritmo de planos cortantes en OPL es la flexibilidad intermedia que otorga el lenguaje de programación de OPL.tal forma que sean leídos de acuerdo al formato de lectura de la implementación realizada. por lo que si se desea mayor flexibilidad y eficiencia es recomendable el uso directo de las librerías otorgadas por la tecnología concierto en aplicaciones desarrolladas en diversos lenguajes como C. Además que la principal diferencia se presenta en instancias de mayor tamaño. Tucker y Zemlin que tenía 16 ciudades y demoró del orden de 20 segundos. Tucker y Zemlin. Vale decir que si comparamos con la instancia resuelta mediante el modelo de Miller. Java. pues si bien se observa un aumento en los tiempos de resolución a mayor número de ciudades. 37 . instancias y funciones miembro. Así. Los nombres de las clases se escriben concatenadamente. Mencionar además que CPLEX se encuentra disponible en Windows. y la conexión a bases de datos) ya que puede compartir los mismos objetos. sistemas UNIX. Esos objetos son agrupados en un objeto de tipo IloModel que representa el objeto de optimización completo. Una instancia de un objeto IloCplex lee un modelo y extrae sus datos a un optimizador apropiado de CPLEX. ilustración 18: Vista de una aplicación utilizando Tecnología Concierto Aplicación del Usuario Objetos de Modelamiento de la Tecnología Concierto Objetos IloCplex -------------------------Características internas de CPLEX 38 . se puede preguntar por su solución. clases y funciones definidos en la librería comienzan con Ilo. Resaltar que la Tecnología Concierto no es un lenguaje nuevo. Después de resuelto un modelo. Mac OS. sino que te deja usar estructuras de datos y de control proveídas por C++. Estos objetos pueden ser divididos en dos categorías: Objetos de Modelamiento: son usados para definir el problema de optimización. Se comienza con letras minúsculas la primera palabra en nombres de argumentos. Así la parte de una aplicación asociada a la Tecnología Concierto puede ser perfectamente integrada con el resto de la aplicación (por ejemplo. La interfaz de programación funciona de la misma manera y se proveen de las mismas funciones en todas las plataformas. con palabras que comienzan con mayúsculas. Las partes de optimización en las aplicaciones realizadas son capturadas en un conjunto de objetos de C++ que interactúan entre sí (ver ilustración 18).Resolución del TSP Problem utilizando OPL (Parte III) Descripción de la Implementación utilizando Tecnología Concierto del Algoritmo PC En la sección de ‘Solvers para Programación Lineal y Entera’ se realizó una breve descripción de lo que es la Tecnología Concierto. Ofrece una librería de clases y funciones para C++ que permiten diseñar modelos de problemas de programación matemática y programación por restricciones. Objetos de resolución: son una instancia del objeto IloCplex que son usados para resolver los modelos creados a través de los objetos de modelamiento. Modificadores comienzan con la palabra set. los nombres de tipos. Funciones de acceso comienza con la palabra clave get seguido por el nombre del miembro del dato. Existe cierta notación y convenio con los nombres definidos en la Tecnología Concierto. En una aplicación generalmente se crean muchos objetos de modelamiento para especificar los problemas de optimización. Entonces el objeto IloCplex se encuentra listo para resolver el modelo extraído. por ejemplo: IloNumVar. Por ejemplo: IloNumVar::setBounds. Funciones de acceso para miembros booleanos comienzan con is. la interfaz gráfica. y en particular el algoritmo de planos cortantes para el TSP. TSPSolver: alberga el algoritmo de planos cortantes. TSPUtil: clase para crear métodos estáticos útiles para todas las clases. por lo que se puede usar cualquier programa de desarrollo siguiendo las normas de complilación establecidas por los makefiles.Introducidos conceptos básicos en relación a la Tecnología Concierto y CPLEX. como la creación de clases. declaración de métodos.04.edu/INEN689-602/Papers/ILOG%20CPLEX%209_0%20README. usando NetBeans IDE 6. Su configuración requiere de una serie de pasos de relativa complejidad. En un sistema operativo Linux es necesario el uso de makefiles.tamu. vector<TSPTour> buildTours( IloBool **tourInfo ). Para compilar las aplicaciones en C++ usando CPLEX con la Tecnología Concierto. variables. De igual forma al caso de Windows. IloBool **getTourInfo(IloBoolVar **x.26Ghz y 3GB de memoria RAM.1. se crearon las siguientes clases: TSPTour: clase para representar la estructura de datos de un tour. TSPSolver( char *nombre ). los proyectos de CPLEX se pueden desarrollar principalmente en computadores con sistema operativo Windows ó Linux. los cuales se encuentran exclusivamente en la clase TSPSolver. lectura de datos. Ya que se utilizó un lenguaje de programación orientado a objetos (C++). IloCplex cplex). Para ver el programa completo se puede ver el anexo 2 o la carpeta del proyecto desarrollado en Netbeans.7. se está en condiciones de comenzar a describir elementos propios necesarios para realizar una implementación en C++. IloModel buildModel( IloBoolVar **x ). private: IloBoolVar **createDVars(). entre otros. Para ver detalles del proceso de compilación de los ejemplos ó proyectos nuevos es de gran utilidad el uso de archivo “readme. TSPTour solverTSP(). Como se mencionó. que poseen los archivos de encabezado (headers). procesador Intel Core2 Duo P8400 de 2. La implementación se llevo a cabo en un notebook con sistema operativo Ubuntu 9. Mencionar que no se explicarán en detalles los elementos propios de programación. es necesario decirle al ‘compilador’ donde encontrar el directorio ‘include’ de CPLEX y Concierto. Para desarrollarlos en Windows es necesario el uso del software de Microsoft Visual Studio en sus versiones de 2005 ó 2008. }. También es necesario decirle al ‘linkeador’ donde encontrar las librerías de CPLEX y Concierto. entre otros métodos necesarios para resolver el problema. [9] http://ie. La definición de la clase TSPSolver posee la siguiente estructura: class TSPSolver { public: IloEnv env IloInt nCities. Se concentrará la atención en explicar los métodos que tienen estrecha relación con la creación del modelo y el algoritmo para resolver el problema.vector<TSPTour> &tours).html” disponible en el directorio de instalación de 9 CPLEX ó se puede encontrar en internet una versión anterior del archivo . IloInt **d.IloBoolVar **x. por lo que es de gran utilidad el uso de los ejemplos disponibles y replicar su configuración paso a paso. bool addConstraints(IloCplex cplex. es de gran utilidad el seguimiento de los ejemplos otorgados por ILOG CPLEX para la configuración de los proyectos propios.htm 39 . . El ambiente es de central importancia y necesita estar disponible para los constructores de todas las otras clases de la Tecnología Concierto.En la declaración del constructor. Mientras la variable d.. addConstraints: agrega las restricciones (cortes) relacionados a los subtours encontrados. La clase contiene un único método público llamado solverTSP(). Otros métodos privados de la clase. el primero cubre errores simples de programación. buildTours: construye los subtours ó Tour en base a la solución encontrada. getTourInfo: permite extraer la información de la solución entregada por CPLEX. que corresponde a los tipos de variables enteras en la Tecnología Concierto. así como la mayoría de las clases de Concierto. provee del manejo optimizado de la memoria para los objetos de las clases de la Tecnología Concierto (mejora el manejo de la memoria realizado por el sistema operativo). En una aplicación desarrollada en C++ usando Concierto. encargado de implementar el algoritmo de planos cortantes. } env. como por ejemplo el agotamiento de la memoria.. es decir. típicamente el primer objeto creado corresponde al ambiente. es un puntero a un objeto de implementación.. es una clase de manejo (Handle Class). IloInt **d. IloEnv.) { cerr << "Other Exception" << endl. Mencionamos que la primera instrucción es crear el ambiente. necesarios para la implementación del algoritmo de planos cortantes son: createDVars: se crean e inicializan las variables de decisión del problema. Así. que corresponde a un arreglo de enteros. buildModel: construye el modelo del TSP relajado. Esta variable almacena el número de ciudades del problema a resolver. pues. y el segundo tipo no puede ser evitado sólo con una correcta programación. si es que existen.end(). la siguiente es la sentencia try/catch que permite el manejo de errores de dos tipos. como miembro público de la clase. entre otras cosas. corresponde a un doble puntero que apunta a variables de tipo IloInt. que corresponde a una instancia de la clase IloEnv. Así. se creó un objeto que contiene el ambiente. La variable nCites es del tipo IloInt. También se crearon otros miembros públicos: IloInt nCities. tiene la siguiente estructura: try { // . } catch (IloException& e) { cerr << "Concert Exception: " << e << endl. } catch (. 40 . se recibe como parámetro un puntero a un arreglo de caracteres que contiene el nombre del archivo de datos del problema que se desea resolver. y retorna un objeto de tipo TSPTour. El uso de d es para representar la matriz de distancias entre ciudades. a través de la instrucción: IloEnv env. if( !this->addConstraints(cplex. Los objetos de modelamiento también son conocidos como extraíbles.1). En el procedimiento. cplex).. El código completo del algoritmo se muestra a continuación: TSPTour TSPSolver::solverTSP() { try { IloBoolVar **x = this->createDVars().. .tours) ) { return tours[0]. que corresponden a variables binarias (boleanas) .. } catch (. y se inicializan todas las variables mediante un constructor adecuado.. en que se especifica el ambiente en que serán utilizadas y el nombre asignado. es poblado con otros objetos extraíbles." << endl. cplex.solve()) { IloBool **tourInfo = this->getTourInfo(x. se crea un puntero a puntero x de tipo IloBoolVar y se asigna memoria al arreglo bidimensional de dimensión . Una vez que un objeto IloModel se ha construido. IloRange: definen restricciones de la forma expresión lineal.end(). que toman un valor igual a 1 ó 0. es crear las variables de decisión mediante el método createDVars()(ver anexo 2. Al final de toda implementación. IloObjective: representa una función objetivo. captura los errores capturados por la Tecnología Concierto ó algún otro error desconocido. Las sentencia catch. También se le da nombre. } env. en la parte denotada por los puntos suspensivos (. while (cplex. donde es una 41 ...extract(model). El método retorna un puntero a puntero que se asigna a un puntero a puntero del mismo tipo también llamado x. porque cada objeto es extraído uno por uno cuando se extrae un modelo de optimización a un objeto IloCplex. en caso a si se va de la ciudad a la ciudad . El siguiente paso consiste en crear un objeto de modelamiento para construir el modelo matemático del problema a resolver. pues objetos de esta clase son usados para definir modelos de optimización completos que son posteriormente extraídos a un objeto IloCplex. se escribe la totalidad del código.).x. IloModel model = this->buildModel(x). } } } catch (IloException & e) { cerr << "Concert Exception: " << e << endl.) { cerr << "Other Exception. La clase extraíble más fundamental es la IloModel. tales como: IloNumVar: representan las variables de decisión del modelo. es necesario destruir el ambiente construido a través de la instrucción env. respectivamente.Después de la sentencia try.end(). IloCplex cplex(this->env). } Lo primero que se realiza. vector<TSPTour> tours = this->buildTours(tourInfo). con el propósito de definir el modelo de optimización. for (i = 0. se construye el modelo. } } } Podemos ver que la expresión objective. el cual es pasado como argumento al constructor. para crear las restricciones es necesario el uso de objetos del tipo IloRange.1). En nuestro caso se construirán los rangos mediante expresiones. lo primero es declarar la expresión y decirle 42 . j < nCities. al cual se le asigna el objeto de retorno del método buildModel (ver anexo 2.add(constraint). El conjunto de restricciones (de asignación) que definen que para cada ciudad. En las instrucciones del método buidModel. lo siguiente es crear la expresión: IloNumExpr objective(this->env). i < nCities. Si queremos agregar al modelo la función objetivo es necesario crear una expresión numérica. Es posible crear un rango mediante los constructores de la clase IloRange. Ahora. i++) { for (j = 0. En que se especifica el ambiente en que se construirá. pasando como argumento el ambiente al cual pertenecerá la expresión. j++) { IloNumExpr expr(env). objective)). se crea un objeto de tipo IloModel llamado model.add(IloMinimize(this->env. efectivamente corresponde al término . i++) { if (i != j) { expr += x[i][j]. que corresponde a un objeto IloModel que contiene el modelo del TSP relajado. i < nCities. El primer paso para construir un modelo es su construcción: IloModel model(this->env). } } IloRange constraint = (expr == 1). En este punto se está en condiciones de poblar el modelo mediante otros objetos extraíbles. que recibe como parámetro las variables de decisión. Así. model. } Notar que efectivamente se crean nCities restricciones al declararse la espresión expr (instancia de la clase IloNumExpr) afuera del segundo ciclo for. j++) { if (i != j) { objective += x[i][j] * d[i][j]. j < nCities. solamente se puede salir hacia una ciudad corresponde a: El código necesario para construir el conjunto de restricciones anterior es: for (j = 0. ahora es necesario agregarla al modelo mediante el método add y la función IloMinimize para especificar que se desea minimizar la función objetivo: model. a través de operadores aritméticos sobre las variables (IloNumVar) ó mediante expresiones (instancias de IloExpr y sus subclases).Así. for (i = 0. se crea una variable local que es donde se guardarán los valores que se desean extraer. El método solve devuelde un valor de tipo IloBool.extract(model). Una manera de extraer el modelo es mediante el método: cplex. La forma para agregar el segundo conjunto de restricciones en que se asegura que exactamente una vez se entre a cada cuidad. para posteriormente retornarla. mediante la instrucción: IloCplex cplex(this->env). i < this->nCities. } } return values. se usa un ciclo while(cplex. Ahora. es necesario extraer la solución. usando el operador aritmético de igualdad (==) entre el valor numérico y la expresión. Así. perteneciente a la clase IloCplex. los 43 . Este método requiere que se haya creado el objeto de tipo IloCplex anteriormente. En el procedimiento se asigna memoria dinámicamente para almacenar la matriz de valores IloBool. Una vez creada la expresión. } Ya que el método retorna un puntero a puntero de datos de tipo IloBool. existe otro método que permite realizar los dos pasos anteriores en uno: IloCplex cplex(model). int i. e IloFalse significa que no fue encontrada una solución. i++) { values[i] = (IloBool *) malloc((nCities)*sizeof (IloBool)). donde IloTrue indica que cplex encontró exitosamente una solución factible (no necesariamente óptima). es necesario agregar la restricción (constraint) al modelo mediante el método add. for (i = 0.solve(). j++) { if (i != j) values[i][j] = cplex. de caso contrario la restricción no es tomada en cuenta.solve()) cuya condición permite que se resuelva iterativamente el problema mientras se van agregando los cortes relacionados a los subtours de las soluciones. La anterior es posible mediante el método getValue. else values[i][j] = IloFalse. for (j = 0. j. es posible resolver el problema mediante la instrucción: cplex. Para extraer el valor de todas las variables de decisión. IloCplex cplex) { IloBool **values = (IloBool **)malloc(nCities*sizeof(IloBool *)). es posible crear una instancia de IloCplex pasándole como argumento el ambiente. descrito a continuación: IloBool **TSPSolver::getTourInfo(IloBoolVar **x. j < this->nCities.getValue(x[i][j]).en qué ambiente hacerlo. De esta manera. el objeto cplex puede ser usado para extraer el modelo a ser resuelto. Una vez que el modelo se ha realizado. se creó el método getTourInfo. es necesario el uso de un objeto IloRange para crear la restricción. Extraído el modelo por un objeto de la clase IloCplex. Una vez resuelto el problema mediante cplex. Por tal motivo. Así. Lo siguiente consiste en agregar.size() <= nCities ) { IloNumExpr lhs(this->env).get(k)]. Para que la restricción (corte) sea tomada en cuenta. j++ ) { for ( k = 0 .cuales se extraen a través de la instrucción cplex. el algoritmo termina y se retorna el tour óptimo encontrado. por lo que haciendo un seguimiento de los valores uno puede construir un tour ó subtours asociados a la solución. Configuración de Parámetros 44 . k < minimum.size() >= 2 && minimum. for ( j = 0 . 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.1 . parte del método se muestra: if ( minimum. Su función es bastante clara. cuando no se haya agregado una restricción de eliminación de subtour. que no es más que una matriz de dimensión de 0 y 1 que contienen los valores de las variables de decisión. deben tener solamente un único 1 por fila y por columna. cplex. La forma de crear la restricción es similar a como se explicó para las otras restricciones del modelo. creando una expresión y posteriormente creando la restricción mediante un objeto IloRange.get(j)][minimum.get(k) ) { lhs += x[minimum. } } } IloRange constraint = ( lhs <= minimum. Finalmente. entonces es posible agregar una restricción de eliminación de subtours para las ciudades pertenecientes al subtours actual.addCut(constraint). sin embargo escapa del uso de la Tecnología Concierto y CPLEX.getValue (x[i][j]). El valor de retorno se almacena en tourInfo. Así.get(j) != minimum. y la segunda matriz define el tour 1-4-2-3-5-1.size() .1 . j < minimum. que recibe como parámetro a la matriz tourInfo. Dicho método retorna un valor booleano en caso de agregar o no una restricción.size() . en que de acuerdo a las restricciones. por lo que sólo se muestra en el anexo XXX. k++ ) { if ( minimum. El procedimiento consiste en agregar el subtour de dimensión menor. Discusión de resultados: Tiempos de Resolución. es necesario agregarla al solver mediante el método addcut de la clase IloCplex.size() -2 ). } Si el tamaño del subtour es mayor o igual a 2 ó menor o igual a nCities. se construyen los tours ó subtours. una restricción de eliminación de subtours mediante el método addConstraints. en lo posible. mediante el procedimiento buildTours. en la ilustración 19 ilustración 19tenemos: ilustración 19: Representación matricial de variables de decisión. 45 . Por tal motivo con el propósito de comparación. Además cabe destacar la mayor flexibilidad de programación que se posee al resolver los problemas mediante tecnología concierto.A través de las pruebas realizadas anteriormente se vio la superioridad de la implementación realizada en OPL para resolver el TSP mediante planos de corte para la instancia "gr17. En la figura se aprecia un mejor rendimiento de esta última implementación por sobre las anteriores.tsp" perteneciente a la librería de datos TSPLIB. ilustración 20: solución gr17. como en las otras implementaciones realizadas.tsp mediante Tecnología Concierto. 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. Es cierto que el uso de la Tecnología Concierto requiere de mayor conocimiento de programación. En la implementación realizada. además que luego de un tiempo no tan excesivo se logra un avance considerable en la curva de aprendizaje. pero sin duda posee innumerables ventajas en la resolución de problemas. No obstante siempre está en el usuario la decisión final de selección de acuerdo a las necesidades e intereses personales. también se resolvió el mismo problema con la implementación realizada del mismo algoritmo pero con tecnología concierto usando el lenguaje de programación C++. k>] == 2.n.size-1..subtour[i]). // Post-procesamiento para encontrar los subtours // Informacion de la solucion int thisSubtour[Cities]. // Encontrar una ciudad que no ha sido visitada for (var i in Cities) { //Si la ciudad i ya fue visitada.Anexos Anexo 1: Código implementación de Algoritmo Planos Cortantes para TSP en OPL /***************************************************************************** * Seccion de datos *****************************************************************************/ // Ciudades int n range Cities // Conjunto tuple setof(edge) int = . /***************************************************************************** * Construcción de modelo (TSP relajado) *****************************************************************************/ // Objective minimize sum (<i. tuple Subtour { int size. int newSubtourSize.j>]*x[<i.j> in Edges) dist[<i. } {Subtour} subtours = . int subtour[Cities]. int newSubtour[Cities]. // Información auxiliar int visited[i in Cities] = 0..k>] == 1}.} Edges = {<i. // Variables de decisión dvar boolean x[Edges].. s. de aristas edge {int i. int j. }.j in Cities}.. subject to { // Cada ciudad esta unida a otras dos ciudades forall (j in Cities) sum (<i.. } //La ciudad actual es el inicio del tour 46 .j> | ordered i.k> in Edges : x[<j.j>] == 1} union {k | <j... seguimos a la siguiente ciudad if (visited[i]==1) { continue..j>]. s. maxl(i. = 1... execute { newSubtourSize = n. // Arreglo que contiene la informacion de las ciuades adjuntas a j setof(int) adj[j in Cities] = {i | <i. dist[Edges] = .j> in Edges : x[<i.k> in Edges) x[<j.j> in Edges) x[<i.subtour[i])>] <= s.j>] + sum (<j.subtour[i] != 0) x[<minl(i. // Restricciones de eliminación de subtours forall (s in subtours) sum (i in Cities : s. } } } /***************************************************************************** * Programación del programa principal (control de flujo) *****************************************************************************/ main { var opl = thisOplModel var mod = opl. } newSubtourSize = thisSubtourSize.var start = i.dataElements. la visitaré if (visited[i] == 0) { succ = i. opl = new IloOplModel(mod. } /* Mientras no haya completado el subtour (el fin del subtour es * el nodo inicial) o thisSubtourSize==0 asegura que la condición * sea valida al inicio */ while (node!=start || thisSubtourSize==0) { //Marcamos a la ciudad actual como visitada visited[node] = 1. 47 .clearModel(). } } //Agrego al subtour la ciudad recien encontrada thisSubtour[node] = succ. var dat = opl. //La ciudad sucesora la marcamos como la ciudad inicial var succ = start. //Aumento en 1 la cantidad de ciudades del tour ++thisSubtourSize. } //Acabamos de encontrar un subtour válido //Guarda siempre el subtour más pequeño if (thisSubtourSize < newSubtourSize) { //Copio las ciudades del subtour actual al nuevo subtour for (i in Cities) { newSubtour[i] = thisSubtour[i]. //Se inicializa tamaño de subtour a cero var thisSubtourSize = 0. //Recorro las ciudades adjuntas a la ciudad actual for (i in adj[node]) { //Si la ciudad i no ha sido visitada. //Se inicializa el arreglo que contendrá el subtour for (var j in Cities) { thisSubtour[j] = 0. while (1) { cplex. //La ciudad actual pasa a ser succ node = succ.cplex).modelDefinition. //La variable nodo toma el valor de la ciudad inicial (actual) var node = i. break. i. *aux3. (esto puede mostrarse de otra forma??) Anexo 2. fgets(aux. for (j = 0. MAX_LINE. " %d". i++) { this->d[i] = (IloInt *) malloc(this->nCities * sizeof (IloInt)). this->nCities = n.subtours. fgets(aux. MAX_LINE.opl.n) { writeln("Tour Final ". file = fopen(nombre. i < nCities. file). aux3 aux3 aux3 aux3 = = = = fgets(aux. opl. opl. &aux2). aux3 = fgets(aux. } opl. "DIMENSION: %d". MAX_LINE.cpp #include #include #include #include "TSPSolver. aux3 = fgets(aux.newSubtourSize.newSubtour). file). this->d = (IloInt **) malloc(this->nCities * sizeof (IloInt *)). aux2. } } Anexo 2: Código implementación Algoritmo Planos Cortantes para TSP usando Tecnología Concierto. if (!cplex. MAX_LINE.h> using namespace std. int n. j.generate(). } dat. } } fclose(file). for (i = 0. MAX_LINE.thisSubtour). j++) { aux4 = fscanf(file.end().end(). opl.opl. &n). "r"). file).h" "TSPUtil. aux4. } TSPTour TSPSolver::solverWithBranchAndCut() 48 . break.add(opl. d[i][j] = d[j][i] = aux2.h" <stdlib.h> <stdio. fgets(aux. MAX_LINE. if (opl. opl. break. file). file).solve()) { writeln("ERROR: could not solve").addDataSource(dat).end(). char aux[MAX_LINE]. opl.1: Código clase TSPSolver. j <= i. TSPSolver::TSPSolver(char* nombre) { FILE *file. MAX_LINE. aux3 = fgets(aux.newSubtourSize == opl.postProcess(). file). file). aux4 = fscanf(file. cplex. "x(%d. i++) { x[i] = (IloBoolVar *) malloc((this->nCities) * sizeof (IloBoolVar)). } IloModel TSPSolver::buildModel(IloBoolVar** x) { IloModel model(this->env). j). sprintf( nombre. j < this->nCities.) { cout << "### UNEXPECTED ERROR . while (cplex..solve()) { IloBool **tourInfo = this->getTourInfo(x. IloModel model = this->buildModel(x).tours) ) { return tours[0]. int i. cplex.x. for (i = 0. } catch (..lp" . j++) { if (i != j) { sprintf(nombre. } } catch (IloException & e) { cerr << "### EXCEPTION: " << e << endl). iteration ). i++) { for (j = 0. 49 . vector<TSPTour> tours = this->buildTours(tourInfo). i < nCities. int i.{ try { char nombre[100]. for (i = 0.. char *nombre = (char *) malloc(100 * sizeof (char)). j. cplex. } } } return x. j.extract(model). i. i < this->nCities. "modelo%d. j++) { if (i != j) { objective += x[i][j] * d[i][j]." << endl. } } } model. cplex).add(IloMinimize(this->env. nombre). int iteration = 0. for (j = 0. IloCplex cplex(this->env).exportModel( nombre ). if( !this->addConstraints(cplex. } } IloBoolVar ** TSPSolver::createDVars() { IloBoolVar **x = (IloBoolVar **) malloc(nCities * sizeof (IloBoolVar *)).lp"). //creo y agrego la funcion objetivo (1) IloNumExpr objective(this->env).%d)". objective)).exportModel("modelo.. IloBoolVar **x = this->createDVars(). j < nCities. } iteration++. x[i][j] = IloBoolVar(this->env. i++) { if (i != j) { expr += x[i][j]. model.add(constraint). j++) { if (i != j) { expr += x[i][j]. j. j < this->nCities. *(*(tourInfoAux+i)+j) = 0. i++) { IloNumExpr expr(env). i++) { tourInfoAux[i] = (int *) malloc((this->nCities) * sizeof (int)). } } IloRange constraint = (expr == 1).add(constraint). model. 50 . for (j = 0. for (j = 0. } return model. for (i = 0. } vector<TSPTour> TSPSolver::buildTours(IloBool **tourInfo) { vector<TSPTour> tours. j++) { if (i != j) values[i][j] = cplex. j++) { if( tourInfo[i][j] == IloTrue ) //tourInfoAux[i][j] = 1. i < nCities. j. //cplex. IloCplex cplex) { IloBool **values = (IloBool **) malloc(nCities * sizeof (IloBool *)). i < nCities. j++) { IloNumExpr expr(env). i++) { values[i] = (IloBool *) malloc((this->nCities) * sizeof (IloBool)). int i. else //tourInfoAux[i][j] = 0. for (i = 0. int **tourInfoAux = (int **) malloc(this->nCities * sizeof (int *)). start. j < this->nCities.getValue(x[i][j]). else values[i][j] = IloFalse. } IloBool **TSPSolver::getTourInfo(IloBoolVar **x. for (j = 0. for (i = 0.add(constraint). *(*(tourInfoAux+i)+j) = 1. i < this->nCities. } } IloRange constraint = (expr == 1). i < this->nCities.//restricciones for (j = 0. } //restricciones for (i = 0. j < nCities. j < nCities. } } return values. int i. } 51 .vector<TSPTour> TSPTour minimum = TSPUtil::getMinimumTour(tours). j++ ) { for ( k = 0 . //tourInfoAux[next][current] = 0.get(k) ) { lhs += x[minimum. k < minimum. } } return tours.size() . start < this->nCities.this->nCities.size() >= 2 && minimum. if ( minimum.get(j)][minimum.2: Código clase TSPTour.IloBoolVar **x. int subtourSize = 0. } } } IloRange constraint = ( lhs <= minimum.addCut(constraint).h" TSPTour::TSPTour() { this->data = new vector<int>().add(start). current = next. k++ ) { if ( minimum.cpp #include "TSPTour.1 . break. for ( j = 0 .start) == false ) { int current = start.add(current).} } for (start = 0. TSPTour tour.size() -2 ). next++) { if (next != current && tourInfoAux[current][next] == 1 ) { tourInfoAux[current][next] = 0. cplex. int next. next < this->nCities.1 .size() . start++) { while( TSPUtil::fullyUsed(tourInfoAux. } } } tours. while (start != current || subtourSize == 0) { subtourSize++. tour.get(j) != minimum. } bool TSPSolver::addConstraints(IloCplex &tours ) { int i. for (next = 0. k. bool constraintsAdded = false. } return constraintsAdded.size() <= nCities ) { IloNumExpr lhs(this->env).push_back(tour). tour.get(k)]. constraintsAdded = true. j. cplex. } Anexo 2. j < minimum. 3: Código clase TSPUtil.h" <stdio.cpp #include #include #include #include "TSPUtil. bool TSPUtil::fullyUsed( int **tourInfo. for ( j = 0. } Anexo 2.h> <stdlib. } int TSPTour::get(int index) { return (* this->data)[index]. j < size. return 0. } 52 . } TSPTour TSPUtil::getMinimumTour(vector<TSPTour> tours) { TSPTour minimumTour = tours[0].size() > tours[i]. j++) { if (tourInfo[row][j] == 1) { return false. i < tours. int value) { (* this->data)[index] = value.size() ) { minimumTour = tours[i]. int size.void TSPTour::set(int index. int i. } int TSPTour::size() { return this->data->size(). i++ ) { if ( minimumTour.h> <iostream> using namespace std. } } return minimumTour. int row ) { int j. for ( i = 0.size() . } } return true. //? } void TSPTour::add(int value) { this->data->push_back(value). 53 .