7 Apéndice 7.1 Apéndice A: Implementación de la Heurística de Takahashi y Matsuyama En este apartado se encuentra el código que implementa la heurística de Takahashi y Matsuyama en código C. 7.1.1 Función Principal Esta función recibe el nombre del fichero que contiene el enunciado del problema (por ejemplo steind5.txt) y va llamando al resto de funciones auxiliares, para procesar el fichero, reservar memoria, ejecutar el algoritmo, imprimir los resultados, y liberar la memoria. En caso de fallo de reserva de memoria o apertura del descriptor de fichero lo indica con un mensaje enviado a la salida estándar (pantalla). La función ejecuta el algoritmo de Takahashi Matsuyama implementado en la función Soladmisible por cada nodo terminal. Ya se comentó anteriormente, que la solución depende del nodo terminal por el cual se comience a buscar una solución. /********************************************************************/ /* NOMBRE: Función Principal */ /* Proyecto Fin de Carrera */ /* Asunto: Resolución del Problema de Steiner */ /********************************************************************/ # include <stdio.h> # include <string.h> # include <stdlib.h> # include "prototipo.h" # include "constantes.h" Capítulo 7–70 int main(int argc,char **argv) { FILE *fich_grafo=NULL; /* Descriptor de fichero */ float **grafo=NULL; /* Grafo enunciado float ***subgrafo=NULL; /* Grafo Sol Takahashi en */ [0] y en [1] valor flujo */ int **lista_steiners=(int**)malloc(sizeof(int*)); int **lista_terminales=(int**)malloc(sizeof(int*)); int *num_nodos=malloc(sizeof(int)); int *num_terminales=malloc(sizeof(int)); int nodoorigen; int iteracion; int i,j; float resul=0; if (argc==2) { fich_grafo=fopen(argv[1],"r"); if (fich_grafo!=NULL) { grafo=(float **)lee_archivo(fich_grafo,num_nodos, num_terminales,lista_steiners,lista_terminales); float *dist=(float*)malloc(*num_nodos * sizeof(float)); int *pred=(int*)malloc(*num_nodos * sizeof(int)); /* Para cada nodo terminal como origen */ for(i=0;i<(*num_terminales);i++) { nodoorigen=(*lista_terminales)[i]; subgrafo=(float ***)soladmisible(grafo,*num_nodos, lista_terminales,*num_terminales,nodoorigen,pred,dist); Capítulo 7–71 } liberamem(grafo,lista_steiners,lista_terminales,num_nodos, num_terminales,subgrafo,pred,dist); } else { fprintf(stderr,"error en archivo\n"); fclose(fich_grafo); } } else { fprintf(stderr, "error en argumentos del programa\n"); } system("PAUSE"); return(0); } 7.1.2 Funciones Auxiliares En este apartado se describen las funciones llamadas desde la función principal. Entre ellas se encuentran: • Lee_archivo. Procesa el fichero de entrada que contiene el enunciado, obteniendo la tabla que describe el grafo con sus costes, el número de nodos total, terminales y la lista de nodos Steiners y terminales. • Liberamem. Liberar la memoria reservada para almacenar las listas, valores y matrices que se pasan por referencia a lo largo del código. • Soladmisible. Implementa la heurística del algoritmo de Takahashi y Matsuyama propiamente. Imprimiendo por pantalla Capítulo 7–72 los resultados obtenidos partiendo desde todos los nodos terminales. • Rutamin. Calcula la distancia entre un nodo dado (origen) y el resto de los nodos del grafo que se le pasa como parámetro de entrada. /********************************************************************/ /* NOMBRE: Funciones auxiliares */ /* Proyecto Fin de Carrera */ /* Asunto: Resolución del Problema de Steiner */ /********************************************************************/ # include <stdio.h> # include <stdlib.h> # include <ctype.h> # include <string.h> # include <math.h> # include "constantes.h" # include "prototipo.h" /********************************************************************/ /* Procesa el fichero de entrada que contiene el enunciado, */ /* obteniendo la tabla que describe el grafo con sus costes, el */ /* número de nodos total, terminales y la lista de nodos */ /* Steiners y terminales.Lee el fichero de entrada y lo traduce a */ /* una matriz que representa el grafo */ /********************************************************************/ float **lee_archivo(FILE *fichero,int *num_nodos, int *num_terminales, int **lista_steiners, int **lista_terminales) { char linea[MAXLINEA]; char aux[TAMNODOS]; float **matriz=NULL; int i=0; int j=0; int k=0; int encontrado=0; Capítulo 7–73 int num_arcos; int arco; int nodoorigen; int nododestino; int peso; fgets(linea,MAXLINEA,fichero); while (!isspace((int)linea[i+1])) /* Leo nº nodos */ { aux[i]=linea[i+1]; i++; } aux[i]='\0'; *num_nodos=atoll(aux); /*devuelvo por ref el tamaño del grafo*/ while (isspace((int)linea[i+1])) { i++; } while (!isspace((int)linea[i+1])) /* Leo nº arcos */ { aux[j]=linea[i+1]; i++; j++; } aux[j]='\0'; num_arcos=atoll(aux); /* Reserva matriz num_nodos*num_nodos */ matriz=(float**)malloc((*num_nodos)* sizeof(float *)); for (i=0;i<(*num_nodos);i++) { matriz[i]=(float*)malloc((*num_nodos)* sizeof(float)); } /* Inicializando matriz que representa el grafo*/ for (i=0;i<*num_nodos;i++) { for (j=0;j<(*num_nodos);j++) Capítulo 7–74 { matriz[i][j]=MAXLONG; } } arco=0; while(arco<num_arcos) /* Rellena la matriz con los pesos de los arcos*/ { fgets(linea,MAXLINEA,fichero); i=0; j=0; while (!isspace((int)linea[i+1])) /* Leo nodo origen */ { aux[i]=linea[i+1]; i++; } aux[i]='\0'; nodoorigen=atoll(aux); while (isspace((int)linea[i+1])) /* paso los espacios */ { i++; } j=0; while (!isspace((int)linea[i+1])) /* Leo nodo destino */ { aux[j]=linea[i+1]; i++; j++; } aux[j]='\0'; nododestino=atoll(aux); while (isspace((int)linea[i+1])) /*paso los espacios*/ { i++; } Capítulo 7–75 j=0; while (!isspace((int)linea[i+1])) /* Leo el peso */ { aux[j]=linea[i+1]; i++; j++; } aux[j]='\0'; peso=atoll(aux); /* Relleno la matriz que representa el grafo quitando al indice 1 para que empiece en 0 */ matriz[nodoorigen-1][nododestino-1]=matriz[nododestino1][nodoorigen-1]=peso; arco++; } fgets(linea,MAXLINEA,fichero); /* Leo nº nodos terminales */ i=0; while (!isspace((int)linea[i+1])) { aux[i]=linea[i+1]; i++; } aux[i]='\0'; *num_terminales=atoll(aux); *lista_terminales=(int*)malloc((*num_terminales)* sizeof(int)); fgets(linea,MAXLINEA,fichero); /* Almaceno los terminales*/ i=0; for(k=0;k< *num_terminales;k++) { j=0; while (!isspace((int)linea[i+1])) /* Tomo un número */ { aux[j]=linea[i+1]; i++; j++; } Capítulo 7–76 aux[j]='\0'; (*lista_terminales)[k]=atoll(aux)-1; if (isspace((int)linea[i+1])) /* Paso los espacios */ { i++; } if (linea[i+1]=='\n') { fgets(linea,MAXLINEA,fichero); i=0; } } *lista_steiners=(int*)malloc(((*num_nodos)-(*num_terminales))* sizeof(int)); /*los que no son terminales son steiners*/ j=0; for(k=0;k<*num_nodos;k++) { encontrado=0; for(i=0;i<*num_terminales;i++) { if(((*lista_terminales)[i])==k) { encontrado=1; } } if(!encontrado) { (*lista_steiners)[j]=k; j++; } } return(matriz); } /********************************************************************/ /* Dado una matriz que representa al grafo y su dimension, */ /* proporciona el arbol minimo que une un nodo origen con el resto, */ Capítulo 7–77 /* y los predecesores que guian hasta la raiz del árbol (origen) */ /********************************************************************/ void rutamin(float **grafo, int num_nodos,int origen,float *dist,int *pred) { int i=0; int j=0; int bucleneg; int actualizo; int iteraciones=0; int aux; /* para recorrer los predecesores en busca de bucles negativos */ float suma; float MAX; MAX=MAXLONG; for(i=0;i<num_nodos;i++) { dist[i]=MAX; min, los /*para comenzar la búsqueda de dist inicializo al máximo para que vayan actualizándose */ pred[i]=MAX /* Por poner un valor para detectar que aun no existe predecesor */ } dist[origen]=0; pred[origen]=origen; do { actualizo=0; for(i=0;i<num_nodos;i++) { for(j=0;j<num_nodos;j++) { suma=dist[i]+grafo[i][j]; if( (dist[j] > suma) && (grafo[i][j]!=MAX) &&(pred[i]!=MAX)) /* si es de un nodo a el mismo (caso i,j con i=j) no entra */ { Capítulo 7–78 /* Compruebo que no paso por un predecesor => bucle negativo */ aux=i; bucleneg=0; /* printf("origen=%d dist a %d=%f, grafo%d-%d = %f\n",origen,j, dist[j],i,j,grafo[i][j]);*/ do { /* printf("j=%d aux=%d pred[%d]=%d\n",j,aux,aux,pred[aux]);*/ if(j==aux) { /* existe bucle negativo, no actualizo, obvio ese arco */ bucleneg=1; /* printf("detectado bucle neg %d %d\n",aux,origen);*/ } else { if((pred[aux]!=MAX)&&(pred[aux]!=aux)) /* si existe el pred y no es el mismo (para el caso origen)*/ { aux=pred[aux]; /*printf("new aux=%d\n",aux) ;*/ } } } while((aux!=origen)&&(!bucleneg)&&(pred[aux]!=MAX)); /* como es un árbol debería llegar al origen si no existe bucle (A MENOS QUE SEA UN PESO NEGATIVO)*/ /* printf("DIst %d,%d=%f\n",i,j,dist[j]); */ if((bucleneg!=1)&&(j!=origen)) /* ultima cond por si esta el bucle con el nodo origen*/ { dist[j]=(dist[i]+grafo[i][j]); pred[j]=i; actualizo=1; /* printf("conectado %d\ta %d\tcon %f\n",i,j,dist[j]);*/ } } } } /* iteraciones++;printf("vez %d\n",iteraciones);*/ } Capítulo 7–79 while(actualizo==1); /* Para imprimir las distancias del árbol, NO las mínimas de la sol admisible */ /* for (i=0;i<num_nodos;i++) { printf("dist a %d=%f\n",i,dist[i]); } printf("%d Iteraciones\n",iteraciones);*/ } /********************************************************************/ /* Esta función implementa el algoritmo de Takahashi y Matsuyama */ /* grafo es una tabla que contiene los pesos de los arcos, que */ /* indica cómo es el grafo. */ /********************************************************************/ float ***soladmisible(float **grafo,int num_nodos, int **lista_terminales,int num_terminales,int nodoorigen,int *pred,float *dist) { int k; /* Desde que nodo pisado parto a buscar la ruta min */ int i; /* Hasta que terminal estoy llegando */ int l; /* Recorro lista terminales para comprobar que no estuviera ya en la lista de terminales pisados */ int j; /* Recorre para añadir, la lista de nodos pisados */ int m; /* Para copiar la lista de los predecesores del terminal cercano encontrado */ float mindist=MAXLONG; int terminalcercano; int num_steiners=num_nodos-num_terminales; int *desdepred=(int*)malloc(num_nodos * sizeof(int)); float ***subgrafo=(float***)malloc(num_nodos * sizeof(float **)); /* 0 es la sol adminisible y 1 los flujos */ int *nodos=(int*)malloc(num_nodos * sizeof(int)); /* lista de nodos que pertenecen al subgrafo */ int *terminales=(int*)malloc(num_terminales * sizeof(int)); int sig; int ant; Capítulo 7–80 int encontrado; int termpisados; int desdeorigen; for (i=0;i<num_nodos;i++) { subgrafo[i]=(float**)malloc((num_nodos)* sizeof(float*)); for(j=0;j<num_nodos;j++) { subgrafo[i][j]=(float*)malloc(2 * sizeof(float)); } } /*Inicializar submatriz y nodos */ for (i=0;i<num_nodos;i++) { nodos[i]=-1; for (j=0;j<num_nodos;j++) { subgrafo[i][j][0]=MAXLONG; /* Peso del arco */ subgrafo[i][j][1]=0; /* Flujo árbol */ } } /*Inicializar lista terminales encontrados */ for (i=0;i<(num_terminales);i++) { terminales[i]=-1; } nodos[0]=terminales[0]=nodoorigen; /*inicialmente dispongo del nodo origen elegido en la lista de terminales y nodos pisados */ termpisados=1; /* He añadido el origen que es terminal.Resto 1 xq queda un terminal menos */ j=1; /* Avanzo la lista de nodos pisados al añadir el origen */ do /* printf("Iteracion\n"); */ { mindist=MAXLONG; Capítulo 7–81 /* busco desde los for(k=0;nodos[k]!=-1;k++) nodos que ya tengo de subgrafo el terminal mas cercano*/ { rutamin(grafo,num_nodos,nodos[k],dist,pred); /* printf("Rutamin %d\n",nodos[k]);*/ /* hacia los terminales */ for(i=0;i<num_terminales;i++) { encontrado=0; /*printf("de %d a %d dist %f \n",nodos[k],dist[i]);*/ for(l=0;l<num_terminales;l++) /* que no estuviera ya ese terminal */ { if(terminales[l]==(*lista_terminales)[i]) { encontrado=1; } } if((dist[(*lista_terminales)[i]]<mindist)&&(!encontrado)) { mindist=dist[(*lista_terminales)[i]]; terminalcercano=(*lista_terminales)[i]; desdeorigen=nodos[k]; /* Guardo desde que nodo del subgrafo nuevo diponible he encontrado el terminal más cercano */ for(m=0;m<num_nodos;m++) /* Guardo los predecesores desde ese origen, al terminal mas cercano encontrado */ { desdepred[m]=pred[m]; } } /* printf("dist=%f, nodo=%d\n",mindist,terminalcercano);*/ } } /*printf(" mas cercano a %d = %d con peso %f\n",desdeorigen, terminalcercano,mindist); */ sig=terminalcercano; /* He encontrado un terminal cercano a Capítulo 7–82 lo que ya tenia distinto de los que había */ terminales[termpisados]=sig;/* Añado el terminal que he encontrado a la lista */ /* Una vez encontrado, guardo su ruta while(sig!=desdeorigen) en nodos y grafo */ /* printf("sig=%d desdeorigen=%d\n",sig,desdeorigen); */ { encontrado=0; for(i=0;i<num_nodos;i++) /* No añadir uno que ya existía en la lista de nodos y arcos pisados */ { if(nodos[i]==sig) { encontrado=1; } } if(!encontrado) { nodos[j]=sig; /*printf("meto %d\n",sig);*/ ant=sig; j++; sig=desdepred[ant]; subgrafo[ant][sig][0]=grafo[ant][sig]; /* no guardo el simetrico para que el grafo sea dirigido y poder sacar el flujo (subgrafo[sig][ant]= */ } } /*printf("%d\n",termpisados); /* en realidad +1 porque empieza desde 0 y comprueba hasta < */ termpisados++; } while(termpisados<num_terminales); /* Obtener flujos */ Capítulo 7–83 /* Para cada terminal recorre for(i=0;i<num_terminales;i++) el camino al origen y añade su flujo */ /* printf("terminal %dº=%d\n",i,terminales[i]); */ { if(terminales[i]!=nodoorigen) { sig=terminales[i]; do { for(j=0;j<num_nodos;j++) { if(subgrafo[sig][j][0]!=MAXLONG) { subgrafo[sig][j][1]=subgrafo[sig][j][1]+1; /* Añado una unidad de flujo */ /* printf("arco %d-%d flujo %f\n",sig,j,subgrafo[sig][j][1]);*/ sig=j; } } /* printf("sig=%d nodoori=%d\n",sig,nodoorigen); */ } while(sig!=nodoorigen); } } /* Imprime de la sol admisible de Takahashi printf("Matriz del árbol resultante y flujo sol admisible\n"); */ float resultado=0; for (i=0;i<num_nodos;i++) { for (j=0;j<num_nodos;j++) { /*if(subgrafo[i][j][0]!=MAXLONG)printf("[%d][%d]=%f",i,j, subgrafo[i][j][0]); if(subgrafo[i][j][1]!=0) printf("flujo %f \n",subgrafo[i][j][1]);*/ if (subgrafo[i][j][0]!=MAXLONG) { resultado=resultado+subgrafo[i][j][0]; } } } Capítulo 7–84 printf("\nOrigen %d Resultado Sol. admisible Takahashi %f\n",nodoorigen,resultado); free (terminales); free (nodos); free (desdepred); return(subgrafo); } /********************************************************************/ /* Esta función libera todos los rangos de memoria reservados para */ /* la ejecución del programa. Requiere conocer el número de nodos */ /* y número de nodos terminales para poder liberar las tablas y */ /* listas apuntadas por la etiqueta correspondiente. */ /********************************************************************/ void liberamem(float **grafo, int **lista_steiners, int **lista_terminales,int *num_nodos,int *num_terminales, float ***subgrafo,int *pred, float *dist) { int i; int j; for (i=0;i<*num_nodos;i++) { free(grafo[i]); for (j=0;j<*num_nodos;j++) { free(subgrafo[i][j]); } free(subgrafo[i]); } free(grafo); free(subgrafo); free(num_nodos); free(*lista_steiners); free(lista_steiners); Capítulo 7–85 free(num_terminales); free(*lista_terminales); free(lista_terminales); free(dist); free(pred); } 7.1.3 Prototipo de Funciones A continuación de describen los prototipos de las funciones empleadas. /********************************************************************/ /* NOMBRE: Prototipo de funciones */ /* Proyecto Fin de Carrera */ /* Asunto: Resolución del Problema de Steiner */ /********************************************************************/ float **lee_archivo(FILE *fichero,int *num_nodos, int *num_terminales, int **lista_steiners, int **lista_terminales); void liberamem(float **grafo, int **lista_steiners, int **lista_terminales, int *num_nodos,int *num_terminales,float ***subgrafo,int *pred, float *dist); float ***soladmisible(float **grafo,int num_nodos, int **lista_terminales,int num_terminales, int nodoorigen, int *pred,float *dist); void rutamin(float **grafo, int num_nodos,int origen,float *dist, int *pred); 7.1.4 Constantes En este apartado se describen las constantes empleadas. # define MAXLINEA 600 /* El fichero de entrada lista todos los steiners en una línea */ Capítulo 7–86 # define MAXLONG 9999 # define TAMNODOS 4 /* Acota el peso de los arcos */ /* Num de digitos maximo de un nodo */ Capítulo 7–87 7.2 Anexo B: Implementación en Matlab 7.2.1 Lectura del Fichero de Entrada y Solución Inicial. La siguiente función procesa dos ficheros de texto que han de pasarse como parámetros de entrada a la función: • Fichero con el enunciado del problema (por ejemplo steinb.txt) con el formato indicado en la Tabla 4 Formato del Fichero de Entrada. • Fichero exportado del algoritmo de Takahashi y Matsuyama con los valores de los flujos que circulan por cada arco del grafo para la solución calculada. Como parámetros de salida ofrece el número de nodos total, el número de nodos terminales, una tabla con el grafo y sus costes, la lista de terminales, y la solución inicial proporcionada por la heurística de Takahashi y Matsuyama en el vector x0. function[num_nodo,num_terminales,grafo,lista_terminales,x0]=LoadInf2(s,s2) fid=fopen(s,'r'); if fid==-1 disp('Error, the file could not be opened') else line=fgets(fid); index=findstr(line,' '); num_nodo=str2num(line(index(1)+1:index(2)-1)); grafo=zeros(num_nodo^2,1); num_arc=str2num(line(index(2)+1:(length(line)))); for i=1:num_arc line=fgets(fid); Capítulo 7–88 index=findstr(line,' '); nodo_origen=str2num(line(index(1)+1:index(2)-1)); nodo_destino=str2num(line(index(2)+1:index(3)-1)); peso=str2num(line(index(3)+1:(length(line)))); grafo((nodo_origen-1)*num_nodo+nodo_destino,1)=peso; grafo((nodo_destino-1)*num_nodo+nodo_origen,1)=peso; %grafo end line=fgets(fid); index=findstr(line,' '); num_terminales=str2num(line); lista_terminales=zeros(1,num_terminales); leidos=0; while (leidos~=num_terminales) line=fgets(fid); index=findstr(line,' '); items=length(index); %items mas 1, por el espacio que hay al ppio de linea for(h=1:items-1) if(h~=items-1) lista_terminales(h)=str2num(line(index(h)+1:index(h+1)-1)); else lista_terminales(h)=str2num(line(index(h)+1:(length(line)))); end end leidos=leidos+items-1; end if (fclose(fid)==-1) disp('Error, the file could not be closed') end end % Leer segundo fichero de flujos exportado del codigo C Capítulo 7–89 fid=fopen(s2,'r'); if fid==-1 disp('Error, the file could not be opened') else tam=num_nodo^2; x0=zeros(1,tam); for i=1:tam index=findstr(line,'.'); x0(i)=str2num(line(1:(index(1)-1))); end if (fclose(fid)==-1) disp('Error, the file could not be closed') end end x0=transpose(x0); 7.2.2 Código Restricción Lineal. Las dos líneas siguientes, implementan la restricción lineal: xij ≥ 0, → ∀(i, j ) ∈ A A=diag(-ones(1,dim)); b=zeros(dim,1); 7.2.3 Código Restricción no Lineal El siguiente código implementa la restricción no lineal: 1 → si j ∈ T X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N − | T | +1 → si j = e Los códigos que implementan la parte derecha e izquierda de la restricción se encuentran separados para mayor claridad: Capítulo 7–90 • Implementación del lado izquierdo (Aeq): function Aeq= CreaAeq(num_nodo); dim_sq=num_nodo; Aeq=zeros(dim_sq); for i=1:dim_sq; for j=1:dim_sq; for h=1:dim_sq; if(i==j) if(j~=h) Aeq(i,(j-1)*dim_sq+h)=1; end end if (i~=j) if(h==i) Aeq(i,(j-1)*dim_sq+h)=-1; end end end end end Aeq=[Aeq zeros(dim_sq,1)]; • Implementación del lado derecho (beq): function [restriccion]=conservacion(lista_terminales,nodo_end,num_nodo) for(nodo=1:num_nodo) if(nodo==nodo_end) restriccion(nodo,1)=1-length(lista_terminales); elseif(findstr(lista_terminales,nodo)) restriccion(nodo,1)=1; else restriccion(nodo,1)=0; end end Capítulo 7–91 7.2.4 Función Principal. A continuación se muestra el código de la función principal que resuelve el problema: % Resolución del Pb de Optimización. function [x,fval]= Problema (enunciado,flujos) [num_nodo,num_terminales,grafo,lista_terminales,x0]=LoadInf2(enunciado,fluj os); nodo_end=lista_terminales(1); dim=length(x0); A=diag(-ones(1,dim)); b=zeros(dim,1); Aeq=CreaAeq(num_nodo); beq=conservacion(lista_terminales,nodo_end,num_nodo); X=zeros(dim,1); fval=zeros(1,1); [X,fval]=fmincon(inline('-sum(-grafo.*[((x-).^2)./((x.^2)+1)])'),x0,A,b,Aeq,beq); En esta última línea es donde figura la función de integridad, que en este caso se trata de la primera. 7.3 Apéndice B: Implementación Empleando las Condiciones de Kunh Tucker En este capítulo, se documenta cómo se implementó una heurística basada en las funciones de integridad y el empleo de las condiciones de Kunh Tucker. Capítulo 7–92 7.3.1 Planteamiento del Problema Tal y como vimos en el apartado 5.3.2, llegamos a que podíamos transformar la función objetivo a: max ∑ c · f (x ij ij ) ( i , j )∈A st: 1 → sij ∈ T X ( j , N ) − X ( N , j ) = 0 → sij ∉ T , j ≠ e, ∀j ∈ N − | T | +1 → sij = e xij ≥ 0, → ∀(i, j ) ∈ A Sin embargo, el modelo así expresado resulta ser no acotado, pues las restricciones del modelo no obligan a que si xij > 0 entonces xji = 0, o viceversa. De esta forma, al no evaluarse la función objetivo sobre las anterior variables binarias sino sobre las variables continuas xij directamente, podría darse la situación (para la función de integridad nº 1) de que el valor óptimo se estableciera en xij = ∞ y xij = ∞ -k, lo cual provoca un flujo neto sobre el arco no dirigido de k unidades en la dirección i→j. Con el objeto de evitar el efecto descrito, se elige como función objetivo g(xij)=f((xij- xji)2 ) en lugar de f(xij) con lo cual se evalúa directamente el flujo neto sobre el arco no dirigido, es decir, las k unidades del caso anterior. Hay que destacar que el valor de la función “g”, g(xij)=g(xji)= f((xij- xji)2 ), es el mismo se evalúe sobre el arco dirigido i→j , que sobre el arco dirigido j→i. Recogiendo el efecto de la variable binaria (que identifica el arco no dirigdo {i,j}). Con esto, el modelo queda como sigue: max Z = ∑c ( i , j )∈ A ij · g ( x ij ) = ∑c ij · f (( x ij − x ji ) 2 ) ( i , j )∈ A Capítulo 7–93 st: 1 → sij ∈ T X ( j , N ) − X ( N , j ) = 0 → sij ∉ T , j ≠ e, ∀j ∈ N − | T | +1 → sij = e xij ≥ 0, → ∀(i, j ) ∈ A A partir de ahora, y a diferencia del modelo que se propuso en el apartado 5.3, se define la función gij(xij)= cij · f((xij- xji)2 ). Las condiciones de Kunh Tucker quedan de la siguiente forma: µ i − µ j + vij = −∇g ij ( x ij ) , ∀(i, j ) ∈ A vij .xij = 0 , ∀(i, j ) ∈ A 1 → si j ∈ T X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N − | T | +1 → si j = e vij ≥ 0, → ∀(i, j ) ∈ A xij ≥ 0, → ∀(i, j ) ∈ A µ i libre Donde la variable vij actúa como holgura de la restricción. Cuando la restricción µ i − µ j + vij = −∇g ij ( x ij ) se cumple con signo de igualdad, la holgura toma el valor 0, circulando flujo sobre el arco (i,j). Esto es, el arco (i,j) perteneciente al árbol de Steiner. Si la holgura toma valor distinto de cero, no circula flujo sobre el arco (i,j), y por tanto dicho arco no pertenece al árbol de Steiner. En resumen, se pueden dar dos situaciones: Caso 1.1.- Capítulo 7–94 Si vij >0 y vji >0. Entonces µ i − µ j < −∇g ij ( x ij ) y µ j − µ i < −∇g ji ( x ji ) siendo por tanto, xij = 0 y xji = 0 respectivamente. En cuyo caso el arco no dirigido (i-j) no está incluido en el árbol de Steiner. Esto es, y ij = 0. 2.Caso 2. Si vij =0 o bien vji bien µ j − µ i = −∇g ji ( x ji ) siendo =0. por Entonces tanto, xij > µ i − µ j = −∇g ij ( x ij ) 0 o bien xji o > 0 respectivamente. En cuyo caso el arco no dirigido (i-j) sí está incluido en el árbol de Steiner. Esto es, y ij > 0. 7.3.2 Método Heurístico de la Solución 7.3.2.1 Identificación de una Solución Admisible Para comenzar a iterar necesitamos una solución admisible inicial. Por ejemplo tomada del algoritmo de Takahashi y Matsuyama que se ha implementado. 7.3.2.2 Modelo Lineal Asociado a una Solución Admisible La función gij(xij), definida en el apartado 7.3.1, es claramente no lineal. Sin embargo, consideramos su aproximación lineal en el entorno de cada una de las soluciones admisibles del problema (por ejemplo la antes señalada). En estas condiciones son formulables los problemas primal y dual del nuevo problema lineal asociado. Problema Primal: Capítulo 7–95 max Z = ∑ ∇g ij ( x ij0 )· x ij ≡ − min ( i , j )∈ A ∑ − ∇g ij ( x ij0 )·x ij ( i , j )∈ A st: 1 → si j ∈ T X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N − | T | +1 → si j = e xij ≥ 0, → ∀(i, j ) ∈ A Donde N representa el conjunto de nodos del grafo y A el conjunto de arcos dirigidos (nótese que existirá un arco (i,j) y un arco (j,i) por cada arco no-dirigido {i,j}. Problema dual Se puede escribir de dos formas: min ∑ µ i i∈T st: µ i − µ j ≥ ∇g ij ( xij0 ) , ∀(i, j ) ∈ A µ i libre o bien: − max ∑ µ i i∈T st: µ i − µ j ≤ ∇g ij ( x ij0 ) , ∀(i, j ) ∈ A µ i libre Donde la variable libre µe se fija arbitrariamente al valor de 0, en base al rango de la matriz de incidencia del problema de distribución original (primal), que dispone de una fila redundante. Capítulo 7–96 0 Cuando todos los valores de − ∇g ij ( xij ) son positivos, y si se obvia el signo “menos” que precede a la función objetivo y el cual no condiciona el proceso de optimización, el problema linealizado, en el entorno de la solución admisible {x0}, puede ser resuelto mediante el algoritmo de la ruta mínima para grafos con bucles. Esto es correcto, puesto que el mínimo de la suma es siempre una cota superior de la suma de los mínimos, de acuerdo con el principio de optimalidad de Bellman. Esto es: Min ∑ µ i ≥ ∑ ( Min( µ i )) i∈T i∈T Así, si µi0 es la ruta mínima (en términos de distancia mínima acumulada) desde el nodo e hasta el terminal i, entonces la solución {µ0} compuesta por todas las rutas mínimas desde cada terminal i ∈T es la solución óptima del problema dual ya que todos estos valores corresponden a rutas mínimas admisibles. Y la suma de los mínimos es siempre menor o igual que el mínimo de la suma. 7.3.2.3 Discusión sobre la Admisibilidad del Problema Lineal El argumento anterior es siempre válido que el problema primal sea acotado (y por consiguiente su dual no incompatible). Esto siempre sucede cuando los costes del problema de la ruta mínima (en nuestro caso 0 − ∇g ij ( xij ) ) sean todos positivos. Cuando existen costes positivos y negativos, el problema de la ruta mínima no pude incluir ningún bucle en el que la suma total de la distancia sea negativa. En tal caso el problema primal sería no acotado y el dual Capítulo 7–97 incompatible. Así por ejemplo, cualquier grafo que incluya como subgrafo alguno como el descrito en la Figura 20 Bucle Negativo, constituye un problema no acotado para el cálculo de la ruta mínima. Nótese que siempre se 0 0 verifica que cij = − ∇g ij ( xij ) = − ∇g ji ( x ji ) = cji. Figura 20 Bucle Negativo Las restricciones asociadas al problema dual para el arco {1,2} de la Figura 20 Bucle Negativo, serían las siguientes: µ1 - µ2 ≥ +2 µ2 - µ1 ≥ +2 Resultando que es incompatible. Por ello hay que analizar los valores de 0 para los que − ∇g ij ( xij ) pueda ser negativo. Esto sólo sucede para valores de Xij>1, donde la pendiente de las funciones de integridad es positiva y por 0 tanto el valor de − ∇g ij ( xij ) negativo. 0 Los valores de Xij ≤ 1 no ofrecen problemas ya que − ∇g ij ( xij ) ≥ 0 (tomando el valor de 0 en el caso de Xij=1). Así pues, los arcos que pueden generar subgrafos no válidos son aquellos sobre los que circula flujo positivo mayor que 1. Capítulo 7–98 Hay que garantizar que ningún bucle que incluya dichos arcos sume un valor total neto negativo. En otro caso se produce un ciclo de realimentación negativo y el problema (primal) es no-acotado. Para construir un grafo sobre el cual el problema linealizado tenga solución acotada y para los casos en que se verifique la propiedad anterior (bucle de coste total negativo) se procede a eliminar aquel arco del bucle que transporte una cantidad de flujo menor positiva (idealmente un arco que transporte una unidad de flujo, en caso de que exista empate, se seleccionará aleatoriamente). Al conjunto de bucles de coste total negativo le denominaremos B, y se dice que un bucle b es de coste total neto negativo si b ∈ B. Así, se define el grafo G*, de la forma G*(N,A\E). Tomadas estas consideraciones para la no-formación de bucles de coste total negativo, y una vez procedido a ala generación del nuevo grafo G*, el problema es resoluble aplicando el siguiente procedimiento iterativo: 1. Se parte de una solución admisible del primal {x0}. 0 2. Se evalúan los valores de − ∇g ij ( xij ) , para todo (i,j) ∈ A tal que Xij0 ∈(x0). Se construye el grafo G*. 3. Se resuelve el problema linealizado haciendo uso del algoritmo de la ruta mínima para grafos con bucles. En el libro de D. Cieslik se encuentran varias formas de conseguirlo. 4. La solución obtenida en el paso 3, {µ0, vij0}, implica una nueva solución admisible en flujo, xN, a partir de las condiciones de optimalidad de Kunh-Tucker. 5. Se vuelve al paso 2. Capítulo 7–99 El algoritmo de la ruta mínima para grafos con bucles proporcionala solución óptima (mínima distancia y camino) desde el nodo e hasta todos los nodos del gafo (en este caso también el conjunto de nodos terminales) . Esta solución determina los valores {µ0} del problema dual y los arcos pertenecientes a la ruta mínima. La determinación de la solución del problema primal, {x0}, se establece contabilizando una unidad de flujo por cada arco situado en cada una de las rutas mínimas que parten de cada terminal, i. Con ello se dispone de un nuevo patrón de flujo {x0}, y se puede volver a aplicar el procedimiento iterativo. La solución así calculada, incluye, siempre, un conjunto de arcos que son variables básicas no-degeneradas (x=1), y que determinan un camino entre el nodo e y el nodo seleccionado como destino final, así como otro conjunto de arcos que son variables básicas-degeneradas (x=0). La consideración de todas las variables básicas (ya sean degeneradas o no) determina un árbol. 7.3.2.4 Selección del nodo e Si bien en el modelo lineal de programación mixta, la selección del nodo e no afectaba a la calidad de la solución final, la selección del nodo e en el modelo no lineal sí afecta a la solución de dicho problema. Es decir, el nuevo problema que se genera al asociar un nodo e a nodo terminal concreto de la red, son todos distintos, ya que los flujos Xij lo son. En efecto, la elección del nodo afecta tanto al cálculo del árbol resultado de la aplicación del algoritmo de la ruta mínima, como a la determinación de los flujos. Capítulo 7–100 Es por ello que hay que considerar todos los posibles candidatos a ser nodo e, y comprobar aquella selección que provee el mejor valor del problema de Steiner. El conjunto de candidatos lo definen todos los nodos terminales del grafo. De esta forma se habrá de ejecutar |T| veces el algoritmo de cálculo de la ruta mínima para grafos con bucles. 7.3.2.5 Pseudocódigo Para implementarlo, se programó un código que realiza lo siguiente: 1. Se lee del fichero de entrada el enunciado del problema, para crear la matriz que contiene el grafo y los pesos, y la lista de nodos terminales y Steiners. 2. Se elige un nodo terminal como e. 3. Se calcula el gradiente de la función de integridad evaluado en el punto de la solución calculada {x0}. 4. Como en la función que se creó para calcular la ruta mínima, ya se integró la detección de bucles negativos (simplemente, se descartaba ese arco), no era necesario detectarlos y eliminarlos. Resolver el problema linealizado mediante aplicación del algoritmo de la ruta mínima en grafos con bucles, tomando como pesos, el “menos” el gradiente de la función. 5. Determinar la nueva solución Xadm que proporcionan las condiciones de Kunh Tucker asociadas a la solución. Si Vij=0, entonces el arco {i,j} pertenece al árbol de Steiner y sus flujos se calculan por evaluación directa. 6. Volver al paso 3 mientras converja la solución y queden nodos terminales por considerar como e. Capítulo 7–101 Capítulo 7–102